Background
Production AI agent platforms increasingly use sub-agent patterns: a parent agent (orchestrator) spawns short-lived child runtimes (workers, parallel tasks, tool-specific helpers) to do focused work. Today AAuth has no first-class way to express this:
- A sub-agent has no verifiable identity tied to its parent.
- A sub-agent has no authorization path that leverages the parent's grant.
- Operators have no way to attribute sub-agent activity to its spawning runtime.
The user has already consented to the parent. Sub-agents need to operate under that consent without per-spawn re-prompting, while still being identifiable for revocation and audit.
Proposal
Sub-agents in three pieces.
1. Sub-agent identity
A sub-agent is an agent whose agent token contains a non-empty act claim referencing the parent.
{
"iss": "https://vendor.example",
"sub": "aauth:planner.7f3c+search1@vendor.example",
"cnf": { "jwk": ... },
"ps": "https://ps.example",
"act": { "agent": "aauth:planner.7f3c@vendor.example" }
}
act != ∅ in the agent token is the marker for sub-agent status. Verifiers check this; the local-part naming is for log readability only.
Local-part naming convention (MUST):
Sub-agent locals are <parent-local>+<discriminator>. Top-level locals MUST NOT contain +. Email convention (dick+foo@gmail.com). Verifiers MUST NOT parse the local part for protocol decisions — act is the source of truth. The convention is for operational readability.
2. Authorization flow
Sub-agents MUST NOT call the PS directly. The parent obtains auth tokens for them by calling the PS:
POST /auth-token at PS
Signature-Key: parent's agent token ← signs HTTP request
Body:
resource_token: sub-agent's resource token
actor_token: sub-agent's agent token
PS processes this as a normal authorization request from the parent:
- Verifies HTTP signature against the parent's
cnf.jwk.
- Verifies
actor_token (sub-agent's agent token) and that act.agent names the parent.
- Verifies
resource_token is bound to the sub-agent's key.
- Looks up the parent's grant for the resource_token's scope. Same logic as a normal request from the parent.
- If grant covers it, issues an auth token bound to the sub-agent's key with
act.agent = parent.
Same authorization logging as for the parent. If the user has already consented, the response is immediate. If not, the same interaction surfaces (for the parent) as for any new request.
The flow:
sub-agent → resource (gets resource token bound to sub-agent's key)
sub-agent → parent (passes resource token via IPC)
parent → PS (with sub-agent's resource token + agent token)
PS → parent (auth token bound to sub-agent's key)
parent → sub-agent (passes auth token via IPC)
sub-agent → resource (with auth token, signs with own key)
3. Single-level depth (MUST)
Sub-agents MUST NOT spawn their own sub-agents. Two parallel rules:
- PS MUST reject token requests from agents whose agent token has non-empty
act.
- AP MUST reject spawn requests where the introducing party's agent token has non-empty
act.
The chain is at most one level deep (top-level agent → sub-agent).
Rationale
Why single-level
Every production AI agent platform we surveyed caps sub-agent depth at 1:
- Claude Code: subagents cannot spawn other subagents. "Single-level structure prevents infinite nesting and keeps the execution hierarchy simple and manageable."
- OpenAI Codex:
agents.max_depth defaults to 1. "Raising this value can turn broad delegation instructions into repeated fan-out, which increases token usage, latency, and local resource consumption."
- Spring AI: subagents cannot spawn their own subagents. The Task tool is explicitly excluded from subagent tool lists.
Where deeper structure exists in agent frameworks (LangGraph hierarchical teams, CrewAI hierarchical process), it's design-time graph composition, not runtime recursive spawning. The pattern is: orchestrator decomposes work, dispatches to workers, collects results. If a worker discovers more work, control returns to the orchestrator and it dispatches again.
For genuinely deep workflows, AAuth already has the right primitive: chained top-level agents (resource-acting-as-agent). Each chain level is a real principal with its own grant — no bubble-up authorization, no parent bottleneck.
Why "sub-agents MUST NOT call PS"
- Simpler PS state: PS grants are keyed only on top-level agents. No sub-agent grant entries to track or revoke.
- Parent retains control: every authorization passes through the parent. Parent can refuse, attenuate, rate-limit, audit.
- Natural revocation propagation: revoke parent's grant → next sub-agent authorization fails. Existing auth tokens expire normally (≤1 hour).
- No promotion path complexity: a sub-agent can never accumulate independent authority. To become independent, you get a fresh parentless agent token from the AP — a different identity.
Parent-mediated flow
The parent is the principal whose grant is checked; the sub-agent is the bearer of the resulting auth token. The PS treats the request like any normal authorization request from the parent — same grant lookup, same consent UX if needed, same logging. The novelty is just: bind the resulting auth token to the sub-agent's key, and put act.agent = parent in the chain.
This isn't token "cloning" — there's no prior auth token being propagated. It's a fresh authorization request, with a different binding target than usual.
Why + convention as MUST
Operationally, log readability matters. Sub-agent identifiers like aauth:planner.7f3c+search1@vendor.example immediately tell an operator "sub-agent of planner.7f3c" without fetching the agent token. The email + convention (dick+foo@gmail.com) is universally understood. MUST-level (rather than SHOULD) ensures operators can rely on it ecosystem-wide. Verifiers still MUST NOT parse — act remains the cryptographic source of truth.
The cost is one breaking change to the local-part grammar (the existing example aauth:cli+instance.1@tools.example becomes aauth:cli-instance.1@tools.example or similar).
Open questions
- Are there real production use cases the orchestrator pattern (top-level + workers, depth 1) doesn't cover? If so, are they better served by chained top-level agents instead?
- Should the AP's spawn protocol be normative or live in the bootstrap document (non-normative)? Lean toward bootstrap — the artifact (agent token shape) is what other parties verify; acquisition is a separate concern, parallel to top-level agent token bootstrap.
References
Background
Production AI agent platforms increasingly use sub-agent patterns: a parent agent (orchestrator) spawns short-lived child runtimes (workers, parallel tasks, tool-specific helpers) to do focused work. Today AAuth has no first-class way to express this:
The user has already consented to the parent. Sub-agents need to operate under that consent without per-spawn re-prompting, while still being identifiable for revocation and audit.
Proposal
Sub-agents in three pieces.
1. Sub-agent identity
A sub-agent is an agent whose agent token contains a non-empty
actclaim referencing the parent.{ "iss": "https://vendor.example", "sub": "aauth:planner.7f3c+search1@vendor.example", "cnf": { "jwk": ... }, "ps": "https://ps.example", "act": { "agent": "aauth:planner.7f3c@vendor.example" } }act != ∅in the agent token is the marker for sub-agent status. Verifiers check this; the local-part naming is for log readability only.Local-part naming convention (MUST):
Sub-agent locals are
<parent-local>+<discriminator>. Top-level locals MUST NOT contain+. Email convention (dick+foo@gmail.com). Verifiers MUST NOT parse the local part for protocol decisions —actis the source of truth. The convention is for operational readability.2. Authorization flow
Sub-agents MUST NOT call the PS directly. The parent obtains auth tokens for them by calling the PS:
PS processes this as a normal authorization request from the parent:
cnf.jwk.actor_token(sub-agent's agent token) and thatact.agentnames the parent.resource_tokenis bound to the sub-agent's key.act.agent= parent.Same authorization logging as for the parent. If the user has already consented, the response is immediate. If not, the same interaction surfaces (for the parent) as for any new request.
The flow:
3. Single-level depth (MUST)
Sub-agents MUST NOT spawn their own sub-agents. Two parallel rules:
act.act.The chain is at most one level deep (top-level agent → sub-agent).
Rationale
Why single-level
Every production AI agent platform we surveyed caps sub-agent depth at 1:
agents.max_depthdefaults to 1. "Raising this value can turn broad delegation instructions into repeated fan-out, which increases token usage, latency, and local resource consumption."Where deeper structure exists in agent frameworks (LangGraph hierarchical teams, CrewAI hierarchical process), it's design-time graph composition, not runtime recursive spawning. The pattern is: orchestrator decomposes work, dispatches to workers, collects results. If a worker discovers more work, control returns to the orchestrator and it dispatches again.
For genuinely deep workflows, AAuth already has the right primitive: chained top-level agents (resource-acting-as-agent). Each chain level is a real principal with its own grant — no bubble-up authorization, no parent bottleneck.
Why "sub-agents MUST NOT call PS"
Parent-mediated flow
The parent is the principal whose grant is checked; the sub-agent is the bearer of the resulting auth token. The PS treats the request like any normal authorization request from the parent — same grant lookup, same consent UX if needed, same logging. The novelty is just: bind the resulting auth token to the sub-agent's key, and put
act.agent= parent in the chain.This isn't token "cloning" — there's no prior auth token being propagated. It's a fresh authorization request, with a different binding target than usual.
Why
+convention as MUSTOperationally, log readability matters. Sub-agent identifiers like
aauth:planner.7f3c+search1@vendor.exampleimmediately tell an operator "sub-agent ofplanner.7f3c" without fetching the agent token. The email+convention (dick+foo@gmail.com) is universally understood. MUST-level (rather than SHOULD) ensures operators can rely on it ecosystem-wide. Verifiers still MUST NOT parse —actremains the cryptographic source of truth.The cost is one breaking change to the local-part grammar (the existing example
aauth:cli+instance.1@tools.examplebecomesaauth:cli-instance.1@tools.exampleor similar).Open questions
References