Skip to content

Session and remote-control architecture

This page is the architecture analysis for the sessions/persistence/remote module. It complements the implementation pages by focusing on what a session actually is, where state lives, how restore/fork/rewind compose, and how remote variants reuse the same envelope rather than re-listing each flag.

Scope: durable JSONL transcripts, in-memory session envelope, resume/continue/fork/rewind flows, remote sessions, teleport, Remote Control, session API/event families, and storage seams. Implementation specifics live in Session resume and transcripts, Remote control and teleport, and Session API, events, and storage.

Module purpose

This module owns the state plane of the agent runtime. It treats a session as the unit of durability and the address of a runtime instance, and it makes that unit visible to:

  1. Local persistence (JSONL transcripts under the Claude config directory).
  2. CLI restore/fork/rewind paths.
  3. Headless/SDK transports that need to refer to a session by ID.
  4. Remote variants (--remote, --teleport, Remote Control) that project the same envelope onto a network bridge.

Architecture thesis

A session is a two-layer object: a durable transcript layer (local-jsonl) and a live runtime layer (the in-memory envelope). Both layers are addressed by the same session ID. All other features—resume, continue, fork, rewind, remote, teleport, Remote Control—are operations on this pair, not separate state systems.

Source anchors

Semantic aliasSourceApproximate locationString or symbolArchitectural meaning
LocalJsonlTranscriptSourcecli.jsline ~11, byte 0xd67ctranscriptSource:"local-jsonl"Default classification of the durable layer.
ProjectSessionStoreRootcli.jsline ~143, byte 0xf20f0projectsConfig-root projects helper; addresses one project’s session files.
SessionJsonlNamePatterncli.jsline ~143, byte 0xf231e${H}.jsonlPer-session filename pattern.
CurrentSessionFileResolvercli.jsline ~486, byte 0x30ac74${v$()}.jsonlCurrent-session file resolver.
SessionDiscoverycli.jsline ~2773, byte 0x79c004async function jHH(H,$)Latest/resume discovery — turns CLI intent into a session target.
SessionRestorecli.jsline ~9514, byte 0xc8b160async function OG8(H,$,q)Restore — produces the in-memory envelope from the durable layer.
ContinueLatestFlagcli.jsline ~19525, byte 0xdc114e-c, --continueResolve target = “latest in cwd.”
ResumeSessionFlagcli.jsline ~19525, byte 0xdc11af-r, --resume [value]Resolve target = explicit ID, picker, or search.
ForkSessionFlagcli.jsline ~19525, byte 0xdc1235--fork-sessionResume into a new session ID instead of mutating the original.
NoSessionPersistenceFlagcli.jsline ~19525, byte 0xdc16e9--no-session-persistenceDisables the durable layer for this run.
ResumeSessionAtGuardcli.jsline ~19324, byte 0xda33e5--resume-session-at requires --resumeHeadless restore-validation rule.
SessionIdPinFlagcli.jsline ~19525, byte 0xdc14e6--session-id <uuid>Pin a specific session ID; intersects with --continue/--resume validation.
InteractiveResumePickercli.jsline ~19550, byte 0xdca950await aa4(Y7, ...)Interactive picker/search path called from the root action.
BridgeStateFramecli.jsline ~19356, byte 0xdaf5aaenqueue({type:"system",subtype:"bridge_state",state:bH,detail:pH,...})Bridge-state frame used by remote variants.
TranscriptMirrorFramecli.jsline ~9434, byte 0xc59a40transcript_mirrorLocal mirror of remote transcript so SDK/headless behavior is symmetric.
SessionStateFramecli.jsline ~7240, byte 0xb2d4fcsession_state_changedIdle/running/requires_action frame attached to the envelope.
RemotePermissionBridgecli.jsline ~9564, byte 0xcc4986createCanUseToolPermission bridge wired into remote/SDK transports.
TranscriptRetentionSettingcli.jsline ~185, byte 0x119cabcleanupPeriodDaysSetting that bounds the durable layer’s retention window.

Internal decomposition

flowchart TD
Cli[CLI flags + entrypoint env] --> Resolver[Session target resolver]
Resolver -->|new| Fresh[Fresh session id]
Resolver -->|latest| DiscoveryLatest[Latest-session discovery]
Resolver -->|explicit / picker| DiscoveryExplicit[Explicit/search discovery]
Resolver -->|from-pr / connect / teleport / remote| RemoteRef[Remote reference]
Resolver -->|--session-id| Pinned[Pinned id]
Fresh --> Envelope[In-memory session envelope]
DiscoveryLatest --> Restore[Session restore]
DiscoveryExplicit --> Restore
Pinned --> Restore
Restore --> Envelope
Envelope --> Durable[Per-session JSONL transcript]
Envelope --> Headless[Headless / SDK loop]
Envelope --> TUI[Interactive TUI loop]
Envelope --> Remote[Remote bridge plane]
Remote --> BridgeState[bridge_state]
Remote --> Mirror[transcript_mirror]
Remote --> Permission[createCanUseTool]
Remote --> RemoteSession[--remote / --teleport / Remote Control]
Durable -->|cleanup window| Retention[cleanupPeriodDays]
Sub-componentResponsibility
Target resolverMaps --continue/-r/--from-pr/--session-id/--connect/--teleport/--remote/--remote-control plus picker into a single session reference.
SessionRestoreReads the durable JSONL, reconstructs permission/model/agent/deferred-tool state, and produces the envelope.
SessionDiscoveryLocates “latest” or “matching” sessions by walking the project’s JSONL directory.
EnvelopeThe live runtime view: session ID, working dir, model, permission mode, agent set, tool registry, hooks, and event sink.
Persistence sinkAppends a line per event to ${sessionId}.jsonl; respects --no-session-persistence.
Bridge planeFor remote variants, wraps the envelope with bridge_state, transcript_mirror, and remote permission flow.
InteractiveResumePickerInteractive fallback when --resume value is ambiguous.

Public interface

Inputs

SurfaceEffect
--continue / -cResolve to the most recent session in cwd.
--resume [value] / -rResolve by explicit ID, picker, or search term.
--session-id <uuid>Pin an explicit ID; rejected with incompatible flags.
--fork-sessionResume into a new ID; durable history is preserved.
--no-session-persistenceSkip the durable layer; resume becomes unavailable.
--resume-session-at <message id>Truncate restored history (headless only).
--rewind-files <user-message-id>Restore files to a prior state and exit; no model turn.
--from-pr <ref>PR-based resume path classified through the same resolver.
--connect, --remote, --teleport, --remote-control / --rcMap the envelope to remote/host transports.
cleanupPeriodDays settingBounds the durable layer’s retention window.
Managed setting disableRemoteControlBlocks Remote Control activation at the policy boundary.

Outputs

OutputConsumer
${sessionId}.jsonlLocal transcript reader, future --continue/--resume, exporter tools.
transcript_mirror framesSDK/headless consumers; remote bridges.
bridge_state framesRemote callers/UIs watching bridge connectivity.
session_state_changed framesHosts that drive long-running automation.
permission_denied / can_use_tool framesRemote/host approval consumers.
Resume warnings (e.g. permission mode mismatch)UI/UX surfaces.

Internal collaborators

CollaboratorContract
Runtime lifecycleProduces the resolved target and hands the envelope to the chosen mode.
Context/model loopProvides session events (messages, tool uses, results, errors) for the durable sink.
Tool/permission runtimePersists tool-use lifecycle events and produces decisions remote consumers see.
Hooks subsystemReceives SessionStart, SessionEnd, PreCompact, PostCompact, Setup.
MCP/pluginsRe-applied at restore time so the envelope reflects the same tools as the original session.
Telemetry/opsReceives resume/restore/save events and shutdown signals.
Remote bridgeUses envelope + bridge state to mediate with hosted services.

Design decisions

  1. Sessions are addressable by ID, locally or remotely. ${sessionId}.jsonl is the canonical key; remote variants reuse the same identity rather than introducing a parallel scheme.
  2. Durable layer is JSONL, not a database. Append-only line files make restore deterministic, support tail-based observation, and avoid coupling the runtime to a storage engine.
  3. Restore reconstructs the envelope, not just history. SessionRestore also re-applies permission mode, model, agents, and deferred tools so the resumed session behaves like its prior self.
  4. Fork is a first-class operation. --fork-session separates “I want to continue” from “I want a divergent copy” so transcripts are not silently overwritten.
  5. Rewind is its own subcommand-like flag. --rewind-files is a file-restore-only path that cannot run a turn; this prevents accidental model runs against an inconsistent file tree.
  6. No-persistence is opt-in, not the default. Persistence by default keeps resume reliable; explicit opt-out exists for ephemeral pipelines.
  7. Remote variants project the envelope, not the loop. --remote, --teleport, --remote-control swap transports but reuse permission and event flow; downstream code does not branch on “remote vs local.”
  8. Picker is a UX fallback, not a separate path. InteractiveResumePicker is invoked when resolver input is ambiguous; it ultimately returns into the same SessionDiscovery/SessionRestore flow.
  9. Retention is a setting, not a runtime branch. cleanupPeriodDays lets ops control disk usage without changing session semantics.

State plane

LayerLifetimeOwner
Process argv/envProcessRuntime lifecycle
Settings (user/project/local/managed)User/processSettings module
Live envelope (session ID, permissions, agents, tools, hooks, model)ProcessThis module
Durable JSONL transcriptUntil cleanup or rewindThis module
Remote bridge stateConnectionThis module + remote transport
Telemetry/log filesConfigured windowOps module

This separation is what lets resume, fork, and rewind operate without touching other modules’ state.

Failure modes

FailureBehavior
--resume value matches nothingPicker fallback or precise error.
--resume-session-at without --resumeHeadless validation rejects before any restore.
--rewind-files combined with a promptRejected; rewind is a standalone operation.
Permission mode mismatch on resumeWarning is surfaced before the loop starts.
Disk full / JSONL write errorPersistence layer surfaces the error; durable layer can be disabled for the remainder of the run if needed.
Bridge disconnectbridge_state frame is emitted; reconnection logic owns retry decisions.
Managed policy disables Remote Control after activationNew activations are blocked; existing remote sessions terminate through the normal shutdown path.
Concurrent writers to the same session fileThe single-writer pattern (one process per session ID) is implied; violating it is undefined.

Extension points

ExtensionHow it plugs in
Additional resume sourceAdd another branch in the target resolver that produces a session reference; do not bypass SessionRestore.
New durable layer (e.g. cloud transcripts)Implement the persistence sink interface; keep JSONL semantics for local fallback.
New remote transportProject the envelope through the bridge plane; emit bridge_state and transcript_mirror for parity.
Custom retention policyUse settings; runtime should not branch on retention specifics.
Hook into restoreUse SessionStart / Setup hooks rather than wrapping SessionRestore.

Caveats

  • The exact set of fields restored by SessionRestore is implementation-defined; this page documents observable categories (permission mode, model, agent set, deferred tools).
  • Remote/teleport/Remote Control variants share many control-frame primitives; their differences are documented in the implementation page.
  • The Anthropic SDK bundle contributes many session_id//v1/sessions/... strings that are unrelated to Claude Code’s local session module; this page only describes the local module.

Created and maintained by Yingting Huang.