Delegation policy¶
The delegation policy controls the capability surface an agent receives when it is spawned as a delegation target. By default, a delegated agent inherits the spawner's full capability — the same as pre-policy behavior. Optionally, a default-deny mode narrows every unbound delegate to a restrictive floor without requiring per-agent topology bindings.
Configuration¶
| Value | Behavior |
|---|---|
inherit (default) |
Delegate inherits the spawner's capability surface — byte-identical to pre-policy. |
deny |
An unbound delegate receives the _delegate floor (see below). |
Only the unbound-delegate fallback is affected: a top-level agent and any topology-bound delegate are unchanged regardless of this setting.
The _delegate floor¶
When capability_default=deny, an unbound delegate — one spawned via the A2A
request path with no topology capability_profile binding — is narrowed by the
built-in _delegate profile.
Built-in deny set (same taxonomy as the _untrusted profile, single-sourced
from _FLOORED_DENY_CLASSES):
| Class | Denied tools | Rationale |
|---|---|---|
re-delegation |
run_prompt, send_to_session |
Prevent an unbound delegate from reaching into another agent's context (formerly delegate_to_agent, retired proposal 0067 P6, #3978) — run_prompt (P4d, #4117) and send_to_session (P5, #4101) both do this the same way, send_to_session's wake=False included (architect ruling, 2026-08-10) |
exec |
exec |
Execution requires explicit operator authorization |
mcp-install |
mcp_install_registry, mcp_install_package, mcp_install_local |
MCP server installation is a high-privilege, operator-controlled action |
skill-install |
skill_install_local, skill_install_source |
Registering skills from untrusted content is a persistence vector (#2548); mirrors mcp-install |
pipeline-install |
pipeline_install_local, pipeline_install_source |
Registering pipelines from untrusted content — same persistence vector as skill-install |
spawn |
spawn_session, spawn_agent, create_topology |
Unbounded sub-session/agent spawn is a DoS vector (#2103); peer of re-delegation |
pipeline-run |
run_pipeline |
Launching a pipeline is spawn-adjacent — peer of spawn |
memory-write |
remember_shared, remember_agent, forget_memory |
Persistence from an unbound delegate requires deliberate opt-in |
(#3429 removed the second, catalog-qualified spelling every tool used to also carry — each denied tool above is now the tool's one and only invocable name, with no per-class alias expansion step.)
The floor is overridable: an operator file
.reyn/capability_profiles/_delegate.yaml replaces the built-in profile. A
malformed override falls back to the built-in (surfaced on stderr) — a typo
must not silently drop the floor.
Binding replaces the floor (= binding is the re-grant)¶
A topology capability_profile binding replaces the _delegate floor, not
composes with it. This is the re-grant mechanism:
- Unbound delegate →
_delegatefloor applied. - Bound delegate → topology binding replaces the floor. The bound profile
is the full ContextualLayer for that agent; the
_delegatefloor is not additionally composed.
The reason: compose_resolved is most-restrictive-wins. Composing a floor that
denies exec with a profile that allows it would still deny — the floor would
be un-grantable. Instead, the registry applies the floor ONLY when the delegate
is unbound; a binding means the operator has deliberately expressed capability
for that role.
Recursive propagation (no laundering)¶
The is_delegate flag is set on every A2A request-path load, regardless of
the spawner's own status. A re-granted coordinator that receives a topology
binding — and thus has exec re-granted — still marks its own sub-delegates
is_delegate=True. Those sub-delegates, if unbound, still receive the
_delegate floor.
Consequence: a re-granted coordinator cannot "launder" the floor to an unbound sub-delegate by passing through its own wider capability. The floor propagates to every hop in the delegation chain.
reyn audit — gateway:delegation-unsafe¶
The reyn audit command includes a static delegation-safety rule
(gateway:delegation-unsafe, rule 4) that scans the project's topology and
capability profiles for re-grants of dangerous classes.
What is scanned¶
OPT-A reachability-precise scoping: only roles with an inbound can_send
edge (= actual delegation targets in the A2A request path) are flagged. An
outbound-only role (e.g. a hierarchy's top coordinator that legitimately holds
run_prompt/send_to_session) has no inbound delegation path and is not a
delegation target — it is not flagged, avoiding a false HIGH exit.
_delegate.yaml override: the override file is scanned unconditionally
(no reachability check needed — it is the global unbound-delegate floor).
Findings¶
| Finding | Severity | Condition |
|---|---|---|
| Bound profile re-grants a class | HIGH | re-delegation, exec, mcp-install, skill-install, pipeline-install, spawn, or pipeline-run class permitted |
| Bound profile re-grants a class | MED | memory-write or destructive-fs class permitted |
_delegate.yaml re-grants a class |
HIGH / MED | Same class-to-severity mapping |
| Posture nudge | INFO | capability_default=inherit while any topology has a delegation edge |
The destructive-fs class (delete_file) is audit-only — it is not on
the runtime _delegate floor because it is already gated by the FILE_WRITE
permission system. The audit surfaces it as a re-grant judgment for
delegate-reachable roles. It is the one deliberate audit ⊋ floor delta — every
other audit class below is single-sourced from the same _FLOORED_DENY_CLASSES
the runtime floor uses, so the audit and the floor cannot drift apart.
Exit behavior: reyn audit exits non-zero only on a HIGH finding — CI-safe
(a HIGH blocks a deploy; MED and INFO are informational).
Audit classes¶
| Class | Severity | Tools |
|---|---|---|
re-delegation |
HIGH | run_prompt, send_to_session |
exec |
HIGH | exec |
mcp-install |
HIGH | mcp_install_registry, mcp_install_package, mcp_install_local |
skill-install |
HIGH | skill_install_local, skill_install_source |
pipeline-install |
HIGH | pipeline_install_local, pipeline_install_source |
spawn |
HIGH | spawn_session, spawn_agent, create_topology |
pipeline-run |
HIGH | run_pipeline |
memory-write |
MED | remember_shared, remember_agent, forget_memory |
destructive-fs |
MED | delete_file (audit-only, not on runtime floor) |
Usage¶
End-to-end flow¶
A → delegates to B
↓
is_delegate=True
↓
registry.resolved_profile_for("B", is_delegate=True)
↓
no topology binding?
├── capability_default=inherit → (None, frozenset()) [pre-policy]
└── capability_default=deny → _delegate floor applied
B → delegates to C
↓
is_delegate=True (ALWAYS — recursive)
↓
C unbound → _delegate floor (even if B was re-granted by a binding)
See also¶
- Concepts: Capability profile § Default-deny delegation narrowing — the
_delegatesection in the capability-profile overview - Concepts: Capability profile — the full ∩ model, ProfileLayer vs ContextualLayer, self-edit
- Concepts: Multi-agent — topology, delegation,
can_sendedges - Reference: reyn.yaml § delegation —
delegation.capability_defaultconfig