Agent Delegation & M2M Hiring
Lead agents provision sub-agents with sliced budgets, inherited permissions, and automatic termination.
Overview
In production AI systems, agents don't work alone. A lead agent — an orchestrator, planner, or coordinator — needs to spin up sub-agents for specialized tasks: research, code generation, data extraction, summarization. BrainstormRouter treats this as M2M hiring: one agent provisions another with a job description, a budget slice, and a permission set.
This is not API key sharing. Each sub-agent gets its own cryptographic identity, its own budget allocation, and its own trust evaluation. When the parent agent's job is done, the entire delegation tree tears down automatically.
How delegation works
A lead agent calls the bootstrap endpoint to provision a sub-agent:
Lead Agent BrainstormRouter
│ │
│─── POST /v1/agent/delegate ───────▸│
│ Authorization: Bearer <JWT> │
│ Body: { │
│ role: "researcher", │
│ budget_usd: 0.50, │
│ ttl_seconds: 300, │
│ permissions: ["completions"] │
│ } │
│ │
│◂── 200 { ─│
│ agent_id, jwt, certificate, │
│ budget_remaining, expires_at │
│ } │
│ │
│─── Sub-agent makes requests ──────▸│
│ using its own identity │
│ │
The sub-agent receives its own JWT, its own ephemeral certificate (if CAF is enabled), and its own budget allocation. It operates as an independent agent with its own trust level — starting at full but subject to the same graduated trust evaluation as any other agent.
Budget slicing
A parent agent allocates budget from its own remaining balance. The sub-agent cannot spend more than its allocation, and the parent's available budget decreases by the allocated amount immediately (not on spend):
└── Delegate $0.50 to researcher
└── Delegate $1.00 to coder
└── Remaining: $3.50
Researcher spends $0.32
└── Researcher remaining: $0.18
└── Parent remaining: $3.50 (unchanged — already deducted)
If the sub-agent doesn't use its full allocation, the unspent amount returns to the parent when the delegation expires or is revoked. Budget slicing is recursive — a sub-agent can delegate further if it has the delegate permission.
// Lead agent provisions a sub-agent with $0.50 budget
const subAgent = await client.agents.delegate({
role: "researcher",
budget_usd: 0.5,
ttl_seconds: 300,
permissions: ["completions", "memory.read"],
});
console.log(subAgent.agent_id); // "agent-sub-a1b2c3"
console.log(subAgent.budget_remaining); // 0.50
console.log(subAgent.expires_at); // ISO timestamp, 5 minutes from now
Permission inheritance
A sub-agent's permissions are always a subset of its parent's permissions. The delegation endpoint enforces this constraint — requesting a permission the parent doesn't hold returns a 403:
Parent permissions: [completions, memory.read, memory.write, delegate]
└── Sub-agent requests: [completions, memory.read] ✓ Granted
└── Sub-agent requests: [completions, cert.revoke] ✗ Rejected (parent lacks cert.revoke)
This prevents privilege escalation through delegation. An agent cannot create a sub-agent with more power than itself, regardless of how the delegation request is constructed.
Revocation cascading
When a parent agent is revoked — whether by administrator action, trust degradation to quarantine, or TTL expiry — all of its sub-agents are revoked simultaneously. The cascade is recursive:
├── Researcher (auto-revoked)
│ └── Fact-checker (auto-revoked)
└── Coder (auto-revoked)
Revocation is immediate. In-flight requests from sub-agents are allowed to complete, but subsequent requests are rejected. If CAF is enabled, all certificates in the delegation tree are added to the revocation set.
TTL auto-termination
Every delegation has a mandatory TTL (time-to-live). When the TTL expires:
- The sub-agent's JWT becomes invalid
- The sub-agent's certificate (if any) is revoked
- Unspent budget returns to the parent
- The sub-agent's trust history is preserved for audit
TTL is intentionally short by default (5 minutes) to match the ephemeral certificate lifecycle. Long-running delegations can set longer TTLs, but the maximum is bounded by the parent's own remaining TTL — a sub-agent cannot outlive its parent.
SDK example: bootstrap a sub-agent
import { BrainstormRouter } from "@brainstormrouter/sdk";
// Lead agent client (already authenticated)
const lead = new BrainstormRouter({
apiKey: "br_live_...",
clientCert: leadCert,
clientKey: leadKey,
});
// Provision a sub-agent for research
const sub = await lead.agents.delegate({
role: "researcher",
budget_usd: 0.5,
ttl_seconds: 300,
permissions: ["completions", "memory.read"],
});
// Create a client for the sub-agent
const researcher = new BrainstormRouter({
apiKey: sub.jwt,
clientCert: sub.certificate,
clientKey: sub.private_key,
});
// Sub-agent makes requests under its own identity
const result = await researcher.chat.completions.create({
model: "anthropic/claude-sonnet-4:floor",
messages: [{ role: "user", content: "Research Q4 market trends" }],
});
// When done, explicitly revoke (or let TTL handle it)
await lead.agents.revoke({ agent_id: sub.agent_id });
curl
# Provision sub-agent
curl -X POST https://api.brainstormrouter.com/v1/agent/delegate \
-H "Authorization: Bearer $LEAD_JWT" \
--cert lead.crt --key lead.key \
-H "Content-Type: application/json" \
-d '{
"role": "researcher",
"budget_usd": 0.50,
"ttl_seconds": 300,
"permissions": ["completions", "memory.read"]
}'
# Use returned JWT and cert for sub-agent requests
curl https://api.brainstormrouter.com/v1/chat/completions \
-H "Authorization: Bearer $SUB_AGENT_JWT" \
--cert sub.crt --key sub.key \
-d '{"model": "anthropic/claude-sonnet-4:floor", "messages": [...]}'
See Agent Identity & CAF for the certificate lifecycle and Graduated Trust for how sub-agents are evaluated independently.