Skip to main content

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 chain
  • tool_call_count — number of tool calls so far in the workflow
  • delegation_depth — how deep the agent-spawn tree goes
  • conversation_user_turns — total user turns in the conversation

Pure threshold checks — no I/O, sub-microsecond per evaluation. Tier 1 (cheap).

Rules

RuleTypeDefaultDescription
max_reasoning_depthinteger8Maximum reasoning steps before BLOCK
max_tool_callsinteger25Maximum tool invocations
max_delegation_depthinteger3Maximum agent-spawn nesting depth
max_user_turnsinteger50Maximum user turns in the conversation
action_on_violationstring"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.

PhaseBehavior
before_workflowCatches resumed workflows that already exceed a budget
mid_executionPrimary enforcement — fires between steps the moment any counter crosses the threshold
after_workflowAudit pass — flags runs that approached or exceeded the ceiling

Context Attributes Read

AttributeCounterSource
context.reasoning_depthreasoning depthReasoning steps in the chain
context.tool_call_counttool callsRunning count of tool invocations
context.delegation_depthdelegation depthAgent-spawn nesting
context.conversation_user_turnsuser turnsConversation 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

FieldExample
Categoryrecursion-bound
Actionblock
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:

FieldExample
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: 25 means the 25th tool call triggers the block — the agent can make at most 24 calls. Set the limit to 26 if you want to permit 25.
  • Omitting a rule disables that specific check. A policy with only max_tool_calls set 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: 0 means no delegation allowed. The check fires when delegation_depth >= 0, which is always true on a delegating agent. To allow exactly one level of delegation, set max_delegation_depth: 2.
  • All four counters fire the same violation signal: "recursion_bound". Use the counter metadata field to distinguish which limit was hit.
  • before_workflow rarely 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