In this article
September 15, 2026
September 15, 2026

Agent session chains that can't escalate

An agent can open a child session of itself for a sub-task. The chain re-derives authority at every hop and can never outlive its root.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

In April we wrote about delegation chains in multi-agent systems: when one agent hands work to another, the second agent should authenticate as itself, and its effective permissions for the delegated task should be bounded by both its own role and the authorization the first agent was operating under. The advice was to evaluate both and deny unless both pass.

That shape is still right. Agent Auth, now in early access, ships part of it as a primitive, and it's worth being precise about which part, because the name "chaining" suggests something broader than what it does.

The problem with one session per run

Give a long-running agent a single session and you get a single unit of everything. One expiry. One revocation. One permission set covering the safe work and the dangerous work alike.

Picture a migration agent working through four hundred records over six hours. Most of what it does is read and transform. A small slice writes to billing. With one session, the token doing the reads is the token doing the writes, and the only way to stop the billing writes is to stop the entire run and start over.

What you want is for the risky leg to be its own thing: separately scoped in time, separately revocable, still unmistakably the same agent acting for the same person.

What a chain actually is

An agent presents its own access token in an agent_delegated mint and receives a new session on the same instance:

  
curl -X POST https://api.workos.com/agents/blueprints/agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY/tokens \
  -H "Authorization: Bearer sk_example_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "agent_delegated",
    "agent_access_token": "<the agent's current access token>",
    "intent": "billing-writeback-batch-17"
  }'
  

The result is a child session that records its parent, forming a chain back to the root. The child has its own access token, its own single-use refresh token, and its own sid. It can be revoked without touching the parent. Like every other Agent Auth call, the mint happens server-side with your API key, so your backend decides when a chain is created rather than the agent deciding for itself.

That last point is easy to skim past and worth sitting with. "The agent chains a session" is shorthand. Your backend chains a session on the agent's behalf, after the agent asks for one. The agent never holds the credential that makes chaining possible.

Three things a chain can't do

The guarantees are the interesting part, because they're what make a chain safe to hand out.

  • A chain can't outlive its root. Every hop is bounded by the root session's created_at plus the blueprint's max_age_seconds. It doesn't matter how many children you create or how many times each one refreshes. Set max_age_seconds to six hours and the whole tree is gone six hours after the root session began, including a child created in hour five with a refresh token whose own lifetime would otherwise run past it. Token lifetimes are clamped to what remains, so a child near the end of the window gets a token that expires with the window rather than one that outlives it.
  • A chain can't gain a permission. Permissions are re-derived from current authority at each hop rather than trusted from the token being presented. A child session isn't handed the parent's permission list. It's recomputed against the blueprint ceiling and, for delegated instances, against the delegating user's permissions as they stand at that moment. If the user lost a role between the root session and the child, the child reflects the loss. The delegating user carries through unchanged, so the act.sub claim on a fourth-generation token still names the same human who authorized the first one.
  • A chain can't become a different agent. The presented token must have been minted from the same blueprint, and the new session lands on the same instance. An agent can only chain as itself.
A timeline over seven hours. A root session runs from hour zero to hour six, where a solid vertical line marks the max_age_seconds ceiling. Three child sessions sit below it, each created while its parent is still running: the first from hour one to three and a half, the second from two and a half to five, the third from four and a half onward. The third child's bar is solid up to the ceiling at hour six and then continues as a dashed outline to hour seven, showing the lifetime it would otherwise have had.

Revocation runs downhill

Revoking a session invalidates its refresh token and every access token issued under it, then cascades to every session chained beneath it. Revoke the root and the tree goes with it. Revoke one child and its own descendants go, while the root and its siblings keep running.

That's the property that makes the migration example work. Chain a child session for each billing batch and a bad batch can be killed on its own, mid-run, without discarding five hours of completed work.

Delegated chains have a second kill switch you get for free. Authority flows from the delegating user's own session, so when that session ends, chain mints and refreshes are refused. The person signs out and the tree stops extending itself, whatever depth it had reached.

What chaining is not

Chaining is an agent subdividing its own authority over time. It is not one agent handing authority to a different agent.

This matters because the April guide framed the problem as agent A delegating to agent B, and agent_delegated reads like the API for exactly that. It isn't. Same blueprint, same instance, same agent. If you're building a supervisor that farms work out to specialist agents, chaining is not the mechanism.

For that, give each agent its own blueprint with its own ceiling, and have your orchestrator mint each agent's session server-side. When the work originated with a person, mint each one as user_delegated with that user's access token, and every agent in the fan-out carries the same act.sub while holding only the permissions its own blueprint allows. The supervisor's ceiling and the specialist's ceiling are independent, which is what you want: a supervisor that can dispatch a billing task doesn't need to be able to perform one.

The two checks from the April post still apply at the tool boundary. Agent Auth tells you the caller is a legitimately scoped agent acting for a known user. It doesn't tell you whether this particular call, on this particular record, should be allowed.

Choosing where to chain

A chain costs you a mint and buys you an independent revocation boundary. Spend it where that boundary is worth having.

  • Chain around irreversibility. Writes, sends, payments, deletes. Anything where "stop this immediately" is a sentence someone might say.
  • Chain around a batch or a phase, not around every tool call. A session per record in a four hundred record migration gives you four hundred revocation units you'll never use individually and a lot of mints.
  • Use intent to say why the child exists. It rides along in the token and into your logs, so billing-writeback-batch-17 turns an anonymous child session into something you can find later. It's caller-supplied and unverified, so treat it as a label, not a control.
  • Set max_age_seconds to the job, not the product. The root anchors everything beneath it, which makes it the single most consequential number in the blueprint. A migration agent that runs for six hours should not have a twenty four hour ceiling because that felt like a safe default.

Watching it from outside

agent.instance.session.created and agent.instance.session.revoked fire through webhooks and the Events API, and GET /agents/sessions lists what's currently alive, filtered by organization, blueprint, or instance.

One limitation to plan around: the session object doesn't expose a pointer to its parent. The chain exists and is enforced, but you can't reconstruct its shape by walking the API. If you want the tree, record the parent yourself at mint time, or encode the position in intent where it will at least appear in your logs.

What you can watch without that: live session count per instance, which is a decent proxy for a fan-out that isn't converging, and sessions still alive close to a root's max_age_seconds ceiling, which usually means a job is running longer than whoever wrote the blueprint expected.

Try it

Agent Auth is in early access. If you have an agent whose risky ten percent is currently sharing a session with its safe ninety percent, chaining is the shape of the fix. Get in touch and we'll enable it for your environment, and the docs cover blueprints, instances, chaining, and revocation in full.