Recursion Bound Policy
The recursion-bound policy enforces OWASP LLM10c (unbounded consumption, sub-control "runaway recursion"). It caps four counters that together bound the work an agent can do in a single run:
reasoning_depth— number of reasoning steps in the chaintool_call_count— number of tool calls so far in the workflowdelegation_depth— how deep the agent-spawn tree goesconversation_user_turns— total user turns in the conversation
Pure threshold checks — no I/O, sub-microsecond per evaluation. Tier 1 (cheap).
Rules
| Rule | Type | Default | Description |
|---|---|---|---|
max_reasoning_depth | integer | 8 | Maximum reasoning steps before BLOCK |
max_tool_calls | integer | 25 | Maximum tool invocations |
max_delegation_depth | integer | 3 | Maximum agent-spawn nesting depth |
max_user_turns | integer | 50 | Maximum user turns in the conversation |
action_on_violation | string | "block" | "block" or "warn" |
Any rule set to null (or omitted from rules) is skipped — the counter is not checked.
How It Works
The handler runs at all three phases with the same evaluation logic. Comparison is current >= limit (inclusive), so max_tool_calls: 25 blocks on the 25th call.
| Phase | Behavior |
|---|---|
before_workflow | Catches resumed workflows that already exceed a budget |
mid_execution | Primary enforcement — fires between steps the moment any counter crosses the threshold |
after_workflow | Audit pass — flags runs that approached or exceeded the ceiling |
Context Attributes Read
| Attribute | Counter | Source |
|---|---|---|
context.reasoning_depth | reasoning depth | Reasoning steps in the chain |
context.tool_call_count | tool calls | Running count of tool invocations |
context.delegation_depth | delegation depth | Agent-spawn nesting |
context.conversation_user_turns | user turns | Conversation length |
All four are read with int(getattr(context, name, 0) or 0) — missing attributes default to 0.
Example Policy
Customer-facing agent — conservative caps to keep costs predictable and prevent any single conversation from running away:
{
"max_reasoning_depth": 6,
"max_tool_calls": 15,
"max_delegation_depth": 2,
"max_user_turns": 30,
"action_on_violation": "block"
}
Long-running research agent — more headroom for tool calls but strict delegation depth:
{
"max_reasoning_depth": 20,
"max_tool_calls": 100,
"max_delegation_depth": 1,
"action_on_violation": "warn"
}
SDK Integration
import waxell_observe as waxell
waxell.init()
@waxell.observe(agent_name="research-agent", enforce_policy=True)
async def research(query: str) -> str:
# As the agent makes tool calls and reasons,
# context.tool_call_count and context.reasoning_depth grow.
# mid_execution blocks the moment any counter hits its limit.
return await deep_research(query)
Observability
| Field | Example |
|---|---|
| Category | recursion-bound |
| Action | block |
| Reason | "Recursion bound exceeded: tool_call_count=25 >= limit 25" |
| Metadata | {"phase": "mid", "signal": "recursion_bound", "counter": "tool_call_count", "current": 25, "limit": 25, "owasp": "LLM10"} |
Delegation depth violation:
| Field | Example |
|---|---|
| Reason | "Recursion bound exceeded: delegation_depth=4 >= limit 3" |
| Metadata | {"counter": "delegation_depth", "current": 4, "limit": 3} |
Common Gotchas
- Comparison is
>=, not>.max_tool_calls: 25means the 25th tool call triggers the block — the agent can make at most 24 calls. Set the limit to26if you want to permit 25. - Omitting a rule disables that specific check. A policy with only
max_tool_callsset leaves reasoning depth, delegation depth, and user-turn count unbounded. - Counters are read from the context, not maintained by the handler. If your runtime doesn't increment
tool_call_count, the check effectively no-ops. max_delegation_depth: 0means no delegation allowed. The check fires whendelegation_depth >= 0, which is always true on a delegating agent. To allow exactly one level of delegation, setmax_delegation_depth: 2.- All four counters fire the same violation
signal: "recursion_bound". Use thecountermetadata field to distinguish which limit was hit. before_workflowrarely fires unless you're resuming a long-running session with pre-populated counters.- Overlap with Safety Policy
max_tool_calls. Both check the same counter. The safety policy is the broader cap; recursion-bound is the OWASP-aligned variant. Pick one or set safety's higher than recursion-bound's.
Next Steps
- Policy Categories — All 49 categories
- Safety Policy — Broader execution limits + content filters
- Delegation Policy — Multi-agent trust and delegation rules
- Rate Limit Policy — Cross-run rate / concurrency control