Troubleshooting¶
Common issues and solutions.
Why was my action denied?¶
Every Adjudication includes a reason field explaining which policy triggered the denial:
from sondera import Decision
result = await harness.adjudicate(stage, role, content)
if result.decision == Decision.DENY:
print(result.reason)
# e.g., "forbid-dangerous-bash: command matches forbidden pattern"
Use the TUI to see decisions in real-time with full context:
Platform only
The TUI requires Sondera Platform (SonderaRemoteHarness). The local CedarPolicyHarness doesn't persist trajectory data.
My policy isn't being evaluated¶
Check: Action name mismatch¶
Cedar actions are derived from tool names. Spaces and hyphens become underscores:
| Tool Name | Cedar Action |
|---|---|
File Write |
My_Agent::Action::"File_Write" |
send-email |
My_Agent::Action::"send_email" |
Fix: Ensure your policy action name matches the transformed tool name.
Check: Stage mismatch¶
A PRE_TOOL policy won't fire on POST_MODEL adjudications.
Fix: Verify your policy targets the stage where you're calling adjudicate().
Check: Missing context existence check¶
If your policy uses context.parameters.X, Cedar errors if the field doesn't exist.
Fix: Always include existence checks:
forbid(principal, action, resource)
when {
context has parameters &&
context.parameters has command &&
context.parameters.command like "*dangerous*"
};
How do I debug my Cedar policies?¶
1. Validate syntax before loading:
from sondera.harness.cedar import validate_policy
errors = validate_policy(my_policy_string)
if errors:
print("Policy errors:", errors)
2. Use the TUI to step through adjudications and see which policies matched:
Platform only
The TUI requires SonderaRemoteHarness. It won't show data from CedarPolicyHarness since the local harness doesn't persist trajectories.
3. Check your schema matches your agent's tools. Mismatched action names silently skip policies.
4. Test in isolation with unit tests:
from sondera import Decision
@pytest.mark.asyncio
async def test_my_policy():
harness = CedarPolicyHarness(policy_set=policy, schema=schema)
await harness.initialize(agent=agent)
result = await harness.adjudicate(
Stage.PRE_TOOL, Role.MODEL,
ToolRequestContent(tool_id="Bash", args={"command": "rm -rf /"})
)
assert result.decision == Decision.DENY
print(f"Reason: {result.reason}")
Connection issues with Sondera Platform¶
Common errors:
Connection refused— Endpoint unreachableSSL certificate problem— TLS configuration issueUnauthorized— Invalid or expired API token
Debug steps:
# 1. Verify endpoint is reachable
curl -v https://harness.sondera.ai:443
# 2. Check environment variables
echo $SONDERA_HARNESS_ENDPOINT
echo $SONDERA_API_TOKEN
# 3. Test with verbose logging
export GRPC_VERBOSITY=DEBUG
python your_script.py
Corporate proxies: If behind a proxy, ensure gRPC traffic is allowed on port 443:
Testing policies without running the full agent¶
Use the harness directly in a test:
import pytest
from sondera import CedarPolicyHarness, Agent, Decision, Tool, ToolRequestContent, Stage, Role
from sondera.harness.cedar.schema import agent_to_cedar_schema
@pytest.fixture
async def harness():
agent = Agent(
id="test",
provider_id="test",
name="Test Agent",
tools=[Tool(name="Bash", description="Shell commands")],
)
policy = '''
@id("no-rm-rf")
forbid(principal, action == Test_Agent::Action::"Bash", resource)
when { context.parameters.command like "*rm -rf*" };
'''
h = CedarPolicyHarness(policy_set=policy, schema=agent_to_cedar_schema(agent))
await h.initialize(agent=agent)
return h
@pytest.mark.asyncio
async def test_blocks_rm_rf(harness):
result = await harness.adjudicate(
Stage.PRE_TOOL, Role.MODEL,
ToolRequestContent(tool_id="Bash", args={"command": "rm -rf /"})
)
assert result.decision == Decision.DENY
@pytest.mark.asyncio
async def test_allows_safe_commands(harness):
result = await harness.adjudicate(
Stage.PRE_TOOL, Role.MODEL,
ToolRequestContent(tool_id="Bash", args={"command": "ls -la"})
)
assert result.decision == Decision.ALLOW
Run with:
LangGraph: Middleware not firing¶
Check middleware is in the list:
Check agent initialization:
Google ADK: Plugin callbacks not called¶
Check plugin is registered with Runner:
Strands: Hook not intercepting¶
Check hook is passed to Agent:
AWS credentials not working? Strands uses Amazon Bedrock by default. Ensure your AWS credentials are configured:
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
Error Handling¶
Sondera raises specific exceptions you can catch and handle gracefully.
Exception Hierarchy¶
SonderaError (base)
├── ConfigurationError # Missing config (API key, endpoint)
├── AuthenticationError # Invalid or expired token
├── TrajectoryError # Trajectory not found
│ └── TrajectoryNotInitializedError # adjudicate() before initialize()
├── PolicyError
│ ├── PolicyViolationError # Policy denied an action
│ └── PolicyEvaluationError # Policy syntax/evaluation failure
├── AgentError # Agent registration/lookup failed
└── ToolError
└── ToolBlockedError # Tool blocked by policy (BLOCK strategy)
Catching Specific Exceptions¶
from sondera import CedarPolicyHarness
from sondera.exceptions import (
SonderaError,
ConfigurationError,
AuthenticationError,
TrajectoryNotInitializedError,
PolicyViolationError,
PolicyEvaluationError,
ToolBlockedError,
)
try:
result = await harness.adjudicate(stage, role, content)
except TrajectoryNotInitializedError:
# Forgot to call initialize() first
await harness.initialize(agent=agent)
result = await harness.adjudicate(stage, role, content)
except PolicyEvaluationError as e:
# Policy has syntax error or references unknown fields
logger.error(f"Policy error: {e}")
# Fail open or closed based on your requirements
raise
except SonderaError as e:
# Catch-all for other Sondera errors
logger.error(f"Sondera error: {e}")
raise
Exception Attributes¶
Some exceptions include useful attributes:
try:
result = await harness.adjudicate(stage, role, content)
except PolicyViolationError as e:
print(e.stage) # Stage where violation occurred
print(e.reason) # Why the policy denied
print(e.adjudication) # Full Adjudication object
try:
await execute_tool(tool_call)
except ToolBlockedError as e:
print(e.tool_name) # Which tool was blocked
print(e.tool_args) # Arguments that were blocked
Remote Harness Errors¶
When using SonderaRemoteHarness, handle connection issues:
from sondera import SonderaRemoteHarness
from sondera.exceptions import AuthenticationError, ConfigurationError
try:
harness = SonderaRemoteHarness(
sondera_api_key=os.getenv("SONDERA_API_KEY"),
)
await harness.initialize(agent=agent)
except ConfigurationError:
# Missing API key or endpoint
logger.error("SONDERA_API_KEY not set")
raise
except AuthenticationError:
# Invalid or expired token
logger.error("Invalid API key")
raise
except Exception as e:
# Connection failures, network issues
logger.error(f"Connection failed: {e}")
raise
Graceful Degradation¶
For non-critical applications, you might want to continue without policy enforcement if the harness fails:
async def safe_adjudicate(harness, stage, role, content):
"""Adjudicate with graceful fallback."""
try:
return await harness.adjudicate(stage, role, content)
except PolicyEvaluationError as e:
# Log but allow (fail open)
logger.warning(f"Policy evaluation failed: {e}")
return Adjudication(decision=Decision.ALLOW, reason="Policy evaluation failed")
except TrajectoryNotInitializedError:
# Initialize and retry
await harness.initialize(agent=agent)
return await harness.adjudicate(stage, role, content)
Fail Open vs Fail Closed
The example above fails open (allows on error). For security-critical applications, fail closed instead:
Getting Help¶
- Slack — Ask questions, share what you're building: Join Slack
- GitHub Issues — Bug reports and feature requests: github.com/sondera-ai/sondera-harness-python/issues
- Cedar Docs — Deep dive into the policy language: cedarpolicy.com