Delegated agents never exceed their parents
An agent working for another agent may use only what every agent above it may use. Start by logging what would be refused, then enforce it.
- See which delegations use more than the delegating agent may
- Enforce that a child's tools are a subset of its parent's
- Also hold children to the parent's own tool list
When an agent hands work to another agent (ask_agent, delegate, A2A, or a chain passed over MCP), the child's tool calls used to be decided on the child alone. An orchestrator that a policy forbade from a tool could reach it anyway by asking a child that had the tool.
Liyagent now checks every agent above the child in the delegation chain, on every tool the child is offered and every call it makes.
What is checked
For each agent above the child:
- Cedar (always): the same server gate and
call_tooldecision the ancestor's own call would get. Aforbidon the parent holds through all of its children, however deep. - Strict (
strict: true): also the ancestor's own tool list. A child may use only tools that its parent attaches. Built-in agents and names with no registry row have no tool list, so they are held to Cedar only.
Only the chain this instance recorded counts. A chain that a caller merely claims in a header or message never narrows or widens anything here.
Modes
GET /api/delegation/attenuation
PUT /api/delegation/attenuation
{"mode": "log", "strict": false}| Mode | What happens |
|---|---|
log (default) | Calls go through exactly as before. What enforcement would refuse is written as delegation.attenuation.would_deny, once per chain, tool and reason every 10 minutes. |
enforce | The tool is left out of the child's tool list. A call to it is refused with "… is working for orchestrator, and orchestrator may not call wiki/edit". Each refusal is written as delegation.attenuation.denied. |
off | Nothing is checked. |
log is the default because many live set-ups depend on the opposite. For example, Liya routes requests to the HR and SAP agents, which hold tools Liya itself does not.
Before you switch to enforce, read the would_deny rows. Then either give the parent the permits it needs, or accept that those delegations will stop. Changing the setting needs access.write and is audited as delegation.attenuation.settings.