attenu

Docs › Examples › Example · LlamaIndex source: examples/integrations/llama_index/README.md · updated · markdown

attenu-guard × LlamaIndex agents

Enforced authority attenuation for AgentWorkflow handoffs. Tested against llama-index-core 0.14.23 (Python 3.12; the framework needs >=3.9).

What it hooks

# Moment Hook (public API)
1 Delegation — agent A hands off to agent B GuardedAgentWorkflow.get_tools() wraps the framework-injected handoff tool and mints B's Guard with guard_of(A).delegate(B, grants[B], task=reason). A structural refusal also clears next_agent, so control never reaches an agent with no authority.
2 Tool invocation guarded_tool(fn, scope=..., context=...) returns a FunctionTool whose wrapper declares ctx: Context. LlamaIndex injects the live Context into such tools, so guard.check(...) runs before the wrapped body.

On denial the wrapper raises AuthorityDenied; AgentWorkflow._call_tool turns any tool exception into ToolOutput(is_error=True, exception=exc), so the model sees a recoverable tool error while callers keep the structured Decision on tool_output.exception.decision. The tool body never runs.

Guards are live handles, so they cannot live in ctx.store (it deep-copies on write and JSON-serializes between .run() calls). The store holds only a run token; the Guards hang off a process-local registry keyed by it.

Run it

python examples/integrations/llama_index/demo.py     # no API key needed

You will see: the orchestrator hand off to a summarizer with {crm.read} / 5 000 rows / egress none; crm_query(4200) run; the poisoned crm_export(...) denied with scope_not_granted + ceiling_exceeded; every call after revoke() denied with revoked; a greedy handoff request met down to the parent's authority and refused at the point of use; a handoff to an ungranted agent refused outright; then AuditLog.verify() -> True and the delegation graph.

Tests: python -m pytest -q tests/integrations/test_llama_index.py. The offline model is llama_index.core.llms.MockFunctionCallingLLM with a scripted response_generator emitting ToolCallBlocks. live_smoke.py runs the same story against a real LLM (RUN_LIVE=1).