2026-07-17

Claude Agent SDK: The Session Boundary Most Hosts Miss

A six-request admission drill shows why Claude Agent SDK hosting must bind each subprocess, transcript, working directory, tool policy, and credential path to one tenant.

Claude Agent SDK: The Session Boundary Most Hosts Miss cover illustration

The Claude Agent SDK is easy to underestimate because the first call looks like an ordinary library function. It is not a thin request wrapper. Each query launches and supervises a Claude Code subprocess over standard input and output. That process owns a shell, a working directory, and session files on local disk.

That one implementation fact should decide the host design. A service that stores a session ID in a database but lets every tenant share a directory has not made sessions durable or isolated. It has only made them addressable.

The older name, Claude Code SDK, still appears in searches and examples. The current Python package is claude-agent-sdk; the TypeScript package is @anthropic-ai/claude-agent-sdk. Both bundle the native Claude Code binary for their supported platform. Package installation gets an agent loop running. It does not supply tenant ownership, durable artifacts, egress policy, credential isolation, or a recovery plan.

Evidence boundary: this revision uses current Anthropic Agent SDK documentation and a deterministic six-request admission fixture. The fixture evaluates two declared hosting policies. It does not install the SDK, invoke Claude, start a subprocess, deploy a sandbox, connect a SessionStore, or measure model quality, latency, cost, compatibility, or isolation strength.

The SDK's first architectural fact is a subprocess

Calling query() starts a separate claude process and exchanges a stream of messages with it. One active session maps to one subprocess. Ten simultaneous sessions mean ten process trees, not ten rows inside a stateless HTTP worker. The child process inherits a working directory unless the application passes an explicit cwd.

A host therefore needs an answer to a concrete question: which tenant owns the process, directory, transcript, credentials, and cancellation signal? “The API token was valid” is not enough. Authentication tells the service who made a request. Admission decides which existing state that caller may reopen and which tools the resulting process may use.

Anthropic's hosting guide describes four lifecycle patterns: a container per one-shot task, a long-running container with active sessions, a hybrid container that hydrates from durable storage, and a multi-agent container with several subprocesses. These are not interchangeable deployment recipes. Each puts process lifetime, idle cost, local files, and failure recovery in a different place.

A session has three kinds of state, not one

The SDK writes conversation transcripts under ~/.claude/projects/, or below the configured CLAUDE_CONFIG_DIR. Project and user CLAUDE.md memory files live separately. Files created or changed by tools live in the working directory.

Only the first category is a session transcript.

The distinction becomes operational during resume. A SessionStore can mirror transcript entries to S3, Redis, Postgres, or another adapter so a different host can reload the conversation. It does not mirror CLAUDE.md or the working tree. The session documentation is equally explicit: conversation persistence is not filesystem checkpointing.

StateDefault ownerWhat resumption needsCommon mistake
Conversation transcriptClaude subprocess on local diskMatching directory or a governed SessionStoreSaving only the session ID
Memory and settingsUser and project configuration pathsTenant-scoped configuration policyLetting a shared host load another tenant's settings
Working artifactsThe session working directoryVolume, object sync, checkpoint, or deliberate discardAssuming transcript restore recreates files
Four isolated runtime spaces representing one-shot, long-running, restorable, and multi-process agent session patterns
Conversation lifetime, process lifetime, and filesystem lifetime are separate choices. A hosting pattern is credible only when it names all three.

Validation: the six-request admission drill

A small deterministic fixture compared two host manifests against the same six synthetic requests. The shared prototype used one working directory, local-only transcripts, credentials inside the agent environment, unrestricted egress, bypassPermissions, mutation tools, and no turn, budget, wall-clock, process, or mirror-failure controls.

The governed profile bound each directory to tenant and session, mirrored transcripts externally, checked session ownership, used dontAsk with Read, Glob, and Grep, injected credentials beyond the agent boundary, restricted egress, and declared turn, budget, time, CPU, memory, and process ceilings.

The workload contained three ordinary read-only requests. It also contained a cross-tenant resume, a resume for an unknown session, and a new request asking for Edit and Bash.

ProfileHosting controls failedAcceptedDenied
Shared prototype10 of 1060
Tenant-bound host0 of 1033

The fixture SHA-256 is f10abb5e059ea94fbc3358474b5485bfe47b70cc56666bc389b6f5c80b0aa345. That hash binds the result to the six requests used in the comparison; it is not a certificate for a production system.

Why the shared prototype admitted every request

The prototype had no concept of transcript ownership. A session identifier was treated as sufficient authority to resume. Tenant B could therefore present Tenant A's identifier and reach the same nominal session. The unknown identifier was accepted too, leaving later code to decide whether a fresh conversation or a failure would occur.

Its permission configuration had a second trap. allowedTools is an auto-approval list, not a complete tool boundary. Unlisted tools remain available to permission evaluation. When the permission mode is bypassPermissions, those unmatched tools are approved at the mode step. Listing Read beside that mode does not make the process read-only.

Everything looked permissive because it was.

The prototype also had no durable transcript mirror. A process restart, node move, or scale-down could remove the conversation even if the application still held its ID. The shared working directory created the opposite problem for artifacts: files could survive long enough to leak across sessions without belonging to any durable, tenant-scoped record.

The governed host denied the right three

The tenant-bound host accepted the two Tenant A reads and the new Tenant C read. It rejected the cross-tenant resume with session_owner_mismatch, rejected the missing transcript with unknown_session, and rejected the requested Edit and Bash tools before starting work.

Those denials are the useful part of the result. A green model response would not prove that a host keeps sessions separate. A refusal ledger can show which boundary made the decision while the request is still cheap to stop.

The local profile used these starting ceilings: 12 turns, a three-dollar model budget, five minutes wall clock, one CPU, 1 GiB memory, and 100 processes. They are fixture values, not Anthropic recommendations. Anthropic's hosting guide separately describes 1 GiB RAM, 5 GiB disk, and one CPU per agent as a reasonable initial estimate for a fresh instance, then tells operators to size against representative session length and tool activity.

Permissions are not a tool list

The current SDK permission sequence matters. Hooks run first. Deny rules follow. Ask rules may route a request to a callback. The permission mode is evaluated before allow rules, and an unresolved request finally reaches canUseTool. A hook that returns allow does not erase a later deny or ask rule.

For a non-interactive read-only worker, allowedTools: ["Read", "Glob", "Grep"] paired with permissionMode: "dontAsk" has a clear meaning: those tools are pre-approved and everything unresolved is denied rather than waiting for a person who is not there. For a policy that must inspect every call, use a PreToolUse hook. Auto-approved tools never reach canUseTool.

This unexecuted TypeScript sketch shows the shape, not a benchmark:

for await (const message of query({
  prompt,
  options: {
    cwd: `/work/${tenantId}/${sessionId}`,
    resume: priorSessionId,
    sessionStore,
    allowedTools: ["Read", "Glob", "Grep"],
    permissionMode: "dontAsk",
    maxTurns: 12,
    maxBudgetUsd: 3
  }
})) {
  await record(message);
}

The outer service must authorize priorSessionId for tenantId before constructing these options. An SDK option cannot recover a missing application-level ownership check.

Choose the container lifetime after the conversation lifetime

A one-shot task can use an ephemeral container and discard it after retaining approved artifacts. A continuously interactive session needs a live process and sticky routing while it runs. An intermittently used conversation can hydrate its transcript from a store into a temporary container. Closely collaborating agents may share a container, but each still needs its own working directory and settings boundary.

Start with the user promise. If the product promises only a report, destroying the environment after one query is a feature. If it promises “come back tomorrow and continue,” preserving only the transcript is insufficient when tomorrow's answer depends on yesterday's files. If it promises immediate follow-ups, cold hydration and a new subprocess may be the wrong latency trade-off.

Persistence should be purchased deliberately. A conversation that never ends accumulates context, artifacts, authority, and recovery obligations. A durable outer worker can instead start a clean bounded SDK job for each task and keep only evidence the product actually owns.

Treat SessionStore failure as durability loss

SessionStore mirrors transcript entries; it does not replace the local write. Anthropic documents append and load as required adapter methods and provides a conformance suite for implementations. Optional listing, summary, deletion, and subkey methods affect continuation, management, and subagent restoration.

The hosting documentation also names a sharp failure mode. If the store rejects a transcript batch, the SDK can retry it up to three attempts in total. After a persistent failure it emits a mirror_error system message and continues the query. A timed-out append is not retried.

Continuing is reasonable library behavior. Quietly promising the user a resumable session after that event is not. The host should mark durability degraded, alert on the message, avoid claiming a successful checkpoint, and decide whether to stop, retry outside the SDK, or complete as explicitly non-resumable.

Adapter correctness needs its own test. Preserve entry order, make load return data deep-equal to what was appended, serialize concurrent summary updates, and cascade deletion when the product promises that deleting a main session removes its subkeys.

Failure modes at the credential boundary

An agent process isolated inside a transparent chamber while credentials and network routes remain behind separate guarded boundaries
Permissions shape tool decisions. Filesystem, network, credential, resource, and tenant isolation need controls outside the generated agent loop.

A permission rule is not a sandbox. The secure-deployment guidance treats the agent as code that may be influenced by repository files, webpages, and user input. High-impact deployments put credentials outside the execution boundary, route outbound traffic through a narrow proxy, mount only required files, drop capabilities, and cap CPU, memory, and process count.

Credential proxying changes the failure radius. The agent can ask an approved service for an approved operation without ever reading the underlying secret. Egress policy then constrains where data can leave. A hostname allowlist without TLS inspection has limitations, and an allowed service with overly broad credentials can still become an exfiltration channel. The network story has to match the threat model.

Keep settings tenant-scoped. A shared host should not accidentally load another tenant's project rules, CLAUDE.md, plugins, or MCP configuration. Give every process a deliberate working directory and configuration directory; disable sources the product does not own.

The minimum production contract

A production review should be able to answer these questions without reading the prompt:

  • Which authenticated tenant owns the session ID, transcript key, working directory, and live subprocess?
  • Is this a new conversation, a specific resume, a continuation of the most recent directory session, or a fork?
  • Which tools are available, which are auto-approved, which are denied, and where can every call be intercepted?
  • What stops the run on turn, cost, wall-clock, cancellation, memory, CPU, disk, and process limits?
  • Which filesystem paths and network destinations are reachable from inside the boundary?
  • Where do credentials live, and can the agent print or copy them?
  • Which transcript batches reached durable storage, and what happens after mirror_error?
  • How are working artifacts checkpointed, discarded, or restored independently from the conversation?
  • What evidence is retained when the result authorizes a merge, deployment, message, payment, or other consequential action?

If one field is implicit, make it a manifest field. If one denial is important, exercise it before launch. If one state transition matters, give it an owner and an observable terminal status.

When managed execution is the more honest choice

Self-host the Claude Agent SDK when the programmable loop and execution environment are part of the product: custom tools, a specific filesystem, private routing, controlled session storage, or deep integration with an existing worker plane. That choice means operating the subprocesses and every boundary around them.

Anthropic now points teams that do not need infrastructure control or a custom data plane toward Managed Agents. A managed option moves the sandbox and agent runtime to the provider. It does not remove application authorization, data classification, tool design, output verification, or consequential-action approval.

The deciding question is not whether a demo can call query(). It is whether the team wants to own process placement, state hydration, tenant isolation, permission semantics, egress, credentials, observability, and recovery. If those are accidental side jobs, the architecture is already giving an answer.


Evidence and current references