DEV Community

Armorer Labs
Armorer Labs

Posted on

Agent runs need their own session identity, not the human's

One of the quiet assumptions that breaks first when agents move from demos to production is that the agent should run as the human user.

It feels natural: the human opened the laptop, the human owns the repo, the human has the API key. Why give the agent its own credentials? Why route its traffic through its own session? Why log its actions separately?

The answer is the same in every incident we have seen locally: the agent and the human have different risk profiles, different lifetimes, and different blast radii. Reusing the human's session collapses those differences and makes the audit trail unusable.

Three failure modes that show up fast.

1. Scope creep is invisible when the agent inherits the user's OAuth token. A local coding agent asked to open a small PR inherits every scope the human's GitHub or Google session has. A pull request turns into a repo write, a repo write turns into an org-level action, and none of it shows up as the agent. It shows up as the user, at 3am, from an unfamiliar IP. If the agent had its own session with a scoped token, the scope ceiling would have been visible at the point of request, not after the fact.

2. Revocation is impossible. If you discover the agent did something wrong, you have two choices: rotate every credential the human uses (which logs them out of their browser, their email, their cloud console), or leave the credential in place because the human needs to keep working. Neither is good. A separate agent identity can be revoked without touching the human, and the rotation event itself becomes a receipt.

3. Receipts collapse into noise. When the agent's MCP calls, browser navigations, and shell commands are mixed into the human's session log, the log stops being a usable operator artifact. A reviewer cannot answer what did the agent do in run 42, because the run is interleaved with the human's own actions, and vice versa. A separate session gives every run its own contiguous trail, and that trail is the unit of accountability.

The pattern we keep landing on is the same one we wrote up in the local-control-plane posts: give the agent its own credential, its own scope object, its own session identity, and its own receipt stream. The human's session stays clean and revocable; the agent's session is the unit of audit and the unit of revocation. They meet at a small, explicit boundary, usually the tool call, where policy, scope, and approval decisions are made.

A practical starting point if you are not ready to fork credentials yet: at least give the agent a separate user profile in your browser, a separate service-account key for any cloud API it touches, and a separate log stream keyed to the agent run id. The blast radius drops immediately, and the audit trail becomes usable for the first time.

The interesting design question we are still iterating on is where the identity boundary lives for multi-agent runs. When an orchestrator spawns sub-agents, do you mint a new session per sub-agent, or do you keep one session and tag actions with the sub-agent id? We are leaning toward per-sub-agent session for anything that touches a tool, and a shared session only for the orchestrator's own planning loop. Curious what others are doing.


Disclosure: this post is from Armorer Labs, the team building Armorer (a local control plane for AI agents) and Armorer Guard (a Rust scanner that runs policy at the tool-call boundary). The patterns above are what we use internally for our own agent runs.

Top comments (3)

Collapse
 
jugeni profile image
Mike Czerwinski

Per-sub-agent session for anything tool-touching is the shape I would also lean toward. The open question I would flag from a different angle: separate sessions solve credential and audit isolation, but they do not isolate prompt-inherited assumptions.

A sub-agent with its own session but with a parent-authored task string in its context inherits whatever ontology the parent used to describe the world. If the parent misclassified something as low-risk write, the child's scope check runs against a scope object that was correct for that classification. Revocation catches credential leak; it does not catch category leak.

Boundary you want at each spawn: not just session identity and scope, but a check that the parent's framing did not smuggle a classification the sub-agent inherits without a chance to challenge it. The credential is easy to compare across sessions. The vocabulary that shaped the request is harder to compare, and it is where scope drift starts before the scope object ever moves.

Collapse
 
armorer_labs profile image
Armorer Labs

Yes, that is the part I would separate from session isolation.

A child session can have fresh credentials and still inherit a bad boundary if the parent hands it a pre-classified world. I would make the spawn record carry two things independently: the authority envelope and the framing envelope. The authority envelope says what the child may touch. The framing envelope says which assumptions came from the parent, which were observed directly, and which risk labels the child is allowed to challenge before tool use.

The useful runtime check is not only "does this child have the right scope?" It is "which upstream claim caused this scope to be issued, and has that claim been independently grounded?" If the parent says "low-risk write," the child should either see the evidence that made it low risk or downgrade to review before acting.

That keeps revocation and audit honest. Revoking credentials handles leaked authority; challenging inherited framing handles category drift. They need separate receipts because they fail in different ways.

Disclosure: I work on Armorer Labs.

Collapse
 
jugeni profile image
Mike Czerwinski

Framing envelope with challenge-before-tool-use is the piece I would want too, and I would add one axis to it: freshness of the framing itself.

A parent's classification of low-risk write is honest at spawn time and can be stale at act time, especially for long-lived children. The upstream claim that grounded the scope was true against a world that may have moved. So the framing envelope wants not only source-of-claim but expiry-or-review-trigger against changes in the referenced state. A child that acts on parent framing older than the last relevant state change should downgrade to review, not because the framing was wrong, but because it is stale as evidence.

This is where substrate matters. Framing captured as static labels ages worse than framing captured as pointers to observable state the child can re-check. Second form lets the child self-detect drift; first form makes the child trust a snapshot. The runtime check I want is: did the child re-verify each inherited assumption against the current state before the tool call, or did it act on ambient inheritance?