Skip to content

packages coding agent daemon

Zachary BENSALEM edited this page Aug 15, 2026 · 1 revision

Daemon

Active contributors: kt, Sebastian Müller, Seth Karten

Purpose

The daemon is the headless background service that keeps AgentSession instances alive when no terminal is attached. It listens on a per-user Unix socket (or a Windows named pipe), owns live session runtimes, and exposes a versioned JSONL wire protocol so clients can create, attach to, detach from, prompt, and administer sessions without disposing the underlying agent loop. A global supervisor owns the socket and spawns one isolated worker process per root session; each worker runs the same AgentDaemon in worker mode. Sessions keep running when the terminal disconnects and can be reattached later, and scheduled jobs (cron, heartbeats) keep firing without any client connected.

Directory layout

packages/coding-agent/src/modes/daemon/
  active-session-state.ts          # ActiveSessionState + DaemonSocketClient bookkeeping
  command-recovery-journal.ts      # Idempotency journal at the supervisor boundary
  compact-session-stream.ts        # Slim attach: compact assistant deltas + chunked snapshots
  daemon-catalog-process.ts        # Separate process that scans on-disk sessions
  daemon-client-env.ts             # HERDR_* client env allowlist, exec env pinning
  daemon-client.ts                 # DaemonClient: connect, hello, request, auto-reconnect
  daemon-errors.ts                 # Serialize/deserialize structured command errors
  daemon-extension-binding.ts      # Binds extensions inside the daemon context
  daemon-mode.ts                   # AgentDaemon: socket server, sessions, command handlers
  daemon-protocol.ts               # Wire contract: commands, events, versions, capabilities
  daemon-runtime-identity.ts       # Build ID + executable paths for stale detection
  daemon-session-id.ts             # Display session ids and suffix matching
  daemon-session-list.ts           # SessionSummary, lifecycle/activity, session lists
  daemon-session-summarizer.ts     # Background recap/verdict generation per session
  daemon-socket.ts                 # Socket path lease, 0600 socket, cleanup
  daemon-supervisor-ownership.ts   # Durable owner registry, startup fences, shutdown admission
  daemon-supervisor.ts             # Global supervisor: socket owner, worker lifecycle
  daemon-worker-client.ts          # Supervisor-side client for worker channels
  daemon-worker-protocol.ts        # Worker auth, subscribe, passivation, update commands
  heartbeat-catalog.ts             # Heartbeat listing helpers for the agents view
  mutation-drain-latch.ts          # Drains mutating commands during update restart
  saved-session-catalog.ts         # Daemon-side saved-session list/rename/delete
  saved-session-info.ts            # Serializes SessionInfo for the wire
  snapshot-transcript-cache.ts     # Chunked snapshot transcript generation
  worker-recovery-journal.ts       # Worker-side recovery journal

Key abstractions

Type Path One-line description
DAEMON_PROTOCOL_VERSION (8), DAEMON_SCHEMA_REVISION (15) packages/coding-agent/src/modes/daemon/daemon-protocol.ts Wire version and schema revision; every change is classified and dual-compat tested
DaemonCommand, DaemonOutbound, DAEMON_COMMAND_COMPATIBILITY packages/coding-agent/src/modes/daemon/daemon-protocol.ts The command/event union and per-command min-version/schema/capability gates
DaemonClient, DaemonHello packages/coding-agent/src/modes/daemon/daemon-client.ts Client transport: JSONL over a socket, hello handshake, request correlation, reconnection
AgentDaemon packages/coding-agent/src/modes/daemon/daemon-mode.ts Socket server, session map, command dispatch, event broadcast
ActiveSessionState, DaemonSocketClient packages/coding-agent/src/modes/daemon/active-session-state.ts Per-session runtime plus attached client set, event generation/sequence
SessionSummary, SessionLifecycle, SessionActivity packages/coding-agent/src/modes/daemon/daemon-session-list.ts Durable per-session view used by list, attach, and the agents view
bindActiveSessionState packages/coding-agent/src/modes/daemon/daemon-extension-binding.ts Binds extensions to a session in daemon context; dialogs travel as extension_ui_request
DaemonSupervisor packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Global supervisor: owns the socket, spawns/recovers per-session workers
CommandRecoveryJournal packages/coding-agent/src/modes/daemon/command-recovery-journal.ts Durable received/result/ack records so uncertain commands are never replayed
DaemonSessionSummarizer packages/coding-agent/src/modes/daemon/daemon-session-summarizer.ts Periodic + debounced recap/verdict generation with a cheap model
DaemonCatalogClient packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts Out-of-process saved-session scanning with progress streaming

How it works

Socket, framing, and capability negotiation

defaultDaemonSocketPath() (packages/coding-agent/src/modes/daemon/daemon-socket.ts) returns $TMPDIR/prime-agent-<uid>/daemon.sock (0600, dir 0700). The supervisor binds it; prepareDaemonSocketPath uses a proper-lockfile lease so concurrent launchers cannot race. When a client connects, the server immediately sends a daemon_hello frame carrying the protocol name/version, schemaId (protocol-8-schema-15-d28eaade1789), schemaRevision (15), app version, runtime identity, a per-connection clientId, and the serverCapabilities list.

Command framing is JSONL. At protocol >= 7 commands are wrapped in a DaemonCommandEnvelope (type: "command", id, protocol, clientId, nested command); events use a parallel envelope (type: "event", id, activeSessionId, sequence, cursor, emittedAt). The client advertises its own capabilities in attach (attach_snapshot, event_sequence, extension_ui, slim_attach, chunked_snapshot, client_owned_sessions). Before sending anything, DaemonClient.request checks the negotiated hello against DAEMON_COMMAND_COMPATIBILITY (min protocol, min schema revision, or a DaemonServerCapability) and throws DaemonCapabilityUnavailableError when the server cannot honor the command. Every wire change is classified backward-compatible, capability-gated, or incompatible, and both new-client/old-daemon and old-client/new-daemon combinations are covered by tests (see how-to-contribute/patterns-and-conventions.md).

Attach, resume, and replay

Attach (case "attach" in packages/coding-agent/src/modes/daemon/daemon-mode.ts) hydrates or reuses the ActiveSessionState, adopts client env, and returns a DaemonAttachResult: a DaemonSessionSnapshot (state, messages, session tree, RLM child snapshots), lastEventSequence/lastEventCursor, and a DaemonReplayInfo computed from the client's resumeCursor. Events carry monotonic sequences plus a per-session generation; a client that reconnects with a cursor gets a complete, partial, or unavailable replay verdict. Slim clients (slim_attach) skip the legacy session_attached frame and chunked clients stream the transcript as session_snapshot_begin/chunk/end. Terminal disconnect only detaches the client: the session stays resident (or in its worker) and keeps streaming to state.clients, so work is not lost.

sequenceDiagram
    participant C as DaemonClient / DaemonAgentConnection
    participant D as AgentDaemon (daemon-mode)
    participant S as ActiveSessionState

    C->>D: connect (unix socket)
    D-->>C: daemon_hello (protocol 8, schema 15, serverCapabilities, clientId)
    C->>D: attach { activeSessionId, capabilities, resumeCursor, env }
    D->>S: getOrHydrateBoundSessionState
    D-->>C: attach response (snapshot, replay info, lastEventSequence)
    D->>D: register client in state.clients
    loop while streaming
        S-->>C: session_event (message_update, tool events, ...)
    end
    Note over C,S: terminal disconnects; client socket closes
    D->>D: detachClient; session keeps running
    C->>D: reconnect + attach with resumeCursor
    D-->>C: attach (replay: complete / partial / unavailable)
    S-->>C: session_attached state/messages resume
Loading

Reconnection is handled in DaemonClient.autoReconnect (packages/coding-agent/src/modes/daemon/daemon-client.ts): on socket close the client calls recoverDaemon (from main.ts this is ensureInteractiveDaemonRunning), reconnects, re-waits for the hello, re-checks command compatibilities against the new hello, and replays in-flight command envelopes that were awaitingReconnect. DaemonAgentConnection (packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts) layers the interactive surface on top: attach(), snapshot assembly, and the closed event.

seedMessages at protocol >= 8

new_session gained an optional seedMessages field, gated by protocol >= 8, schema revision >= 15, and the seed_messages server capability (NEW_SESSION_SEED_MESSAGES_COMMAND in packages/coding-agent/src/modes/daemon/daemon-protocol.ts). DaemonAgentConnection.newSession checks assertSeedMessagesSupported before sending; the daemon appends the seed messages to the fresh SessionManager through a setup hook before the swap resolves (case "new_session" in daemon-mode.ts, via seedMessageToSessionMessage). Older daemons ignore the unknown field, so clients never send it unless the negotiated hello meets the floor.

Heartbeats, cron, and session summarization

AgentCronScheduler (from packages/coding-agent/src/core/cron-jobs.ts) runs scheduled prompts and heartbeats in the daemon; runCronJob wakes a session (even an idle, passivated one) and delivers a prompt with a followUpQueueKey such as heartbeat:<jobId>. Heartbeat changes broadcast a global heartbeats_changed event. DaemonSessionSummarizer (daemon-session-summarizer.ts) sweeps every 25 seconds and debounces turn-end activity, generating <recap>/<status> verdicts with a cheap prime-inference model (qwen/qwen3-30b-a3b-instruct-2507); only settled idle verdicts are persisted.

Supervisor and worker split

The supervisor (daemon-supervisor.ts) is the process that holds the socket, validates its durable owner record (daemon-supervisor-ownership.ts, keyed by socket and descriptor dir), and spawns one worker per root session. Each worker runs AgentDaemon in worker mode: it authenticates with worker_auth plus a supervisor generation claim, speaks a private-framed transport, and answers the same public DaemonCommands while the supervisor fronts the socket. Mutating commands are journaled in CommandRecoveryJournal before dispatch so a crash mid-command is answered as uncertain rather than replayed. The supervisor also handles idle-worker eviction, worker passivation (child passivation caps), update restart (drain, fence, publish via prepare_update_restart), and re-spawns workers whose descriptors say they died.

Session worker

The worker transport helpers live in packages/coding-agent/src/modes/session-worker/private-framing.ts and packages/coding-agent/src/modes/daemon/daemon-worker-protocol.ts. DaemonWorkerDescriptor records the worker pid, socket path, recovery journal, root session, and lifecycle (starting/ready/recovering/failed); supervisors write these under ~/.prime/agent/daemon-workers/. daemon-worker-client.ts is the supervisor-side client, and worker-recovery-journal.ts persists worker-side recovery intent. Client-owned sessions (client_owned_sessions capability) get their own worker owned by the creating client, promotable to resident.

Daemon lifecycle commands

  • prime-agent status runs discoverDaemons + runPs in packages/coding-agent/src/cli/daemon-ps.ts: it finds every daemon on the machine (parsing ss -lxp on Linux or lsof on macOS, plus a sweep of the default socket dir), probes each with hello + list, and reports status (current/stale/unreachable/orphan-file).
  • prime-agent doctor [--fix] runs the same discovery; with --fix it runs runReap, which only touches clearly safe targets (orphan socket files, reachable idle daemons on non-default sockets) and re-probes before killing.
  • prime-agent shutdown [--force] runs runShutdownAll: without --force it requires a TTY confirmation (daemon-stop-confirm.ts), takes a shutdown admission lease (acquireDaemonShutdownAdmission), gracefully shuts down each daemon, force-kills unreachable workers when asked, and removes stale sockets.
  • prime-agent update drains mutations through the coordinator in packages/coding-agent/src/cli/daemon-update-restart.ts, restarts the daemon, and restores sessions from the update-restart manifest.

Integration points

  • Clients reach the daemon exclusively through DaemonClient (daemon-client.ts) and DaemonAgentConnection (packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts); the interactive mode, print/json/rpc/acp modes, and the agents view all go through this seam.
  • main.ts bootstraps the daemon via ensureInteractiveDaemonRunning and maybeStartDaemonEarly in packages/coding-agent/src/cli/daemon-launch.ts; DaemonAgentConnection gets recoverDaemon so reconnects re-spawn a missing daemon.
  • The agents view (packages/coding-agent/src/modes/agents-view/) consumes SessionSummary, heartbeats, and saved-session catalogs over the same protocol.
  • Session state is preserved because the daemon never disposes the runtime on detach; JSONL transcripts under ~/.prime/agent/sessions/ back --resume and the saved-session catalog.

Entry points for modification

  • Add a daemon command: extend the DaemonCommand union and DAEMON_COMMAND_COMPATIBILITY in packages/coding-agent/src/modes/daemon/daemon-protocol.ts, handle it in AgentDaemon.handleCommand in daemon-mode.ts, and add both dual-compat tests plus a capability entry if it is optional.
  • Change session lifecycle or residency: active-session-state.ts, daemon-session-list.ts, and the passivation/eviction logic in daemon-supervisor.ts.
  • Change the attach payload: DaemonAttachResult, slim/chunked snapshot handling in daemon-mode.ts and snapshot-transcript-cache.ts, and compact-session-stream.ts for the assistant delta compaction.
  • Bump the wire version: DAEMON_PROTOCOL_VERSION / DAEMON_SCHEMA_REVISION / DAEMON_SCHEMA_ID in daemon-protocol.ts, then follow the compatibility maps and tests per the policy in how-to-contribute/patterns-and-conventions.md.

Key source files

File Why it matters
packages/coding-agent/src/modes/daemon/daemon-protocol.ts The entire wire contract, versions, and compatibility maps
packages/coding-agent/src/modes/daemon/daemon-mode.ts The AgentDaemon socket server and command handlers
packages/coding-agent/src/modes/daemon/daemon-client.ts Client transport, hello, capability checks, reconnect
packages/coding-agent/src/modes/daemon/daemon-supervisor.ts Supervisor: socket ownership, worker lifecycle, eviction, update restart
packages/coding-agent/src/modes/daemon/daemon-supervisor-ownership.ts Durable owner registry, startup fences, shutdown admission
packages/coding-agent/src/modes/daemon/daemon-session-list.ts Session summaries consumed by list/attach/agents view
packages/coding-agent/src/modes/daemon/daemon-extension-binding.ts How extensions run in a headless daemon context
packages/coding-agent/src/modes/daemon/command-recovery-journal.ts Command idempotency and crash semantics
packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts The client-side AgentConnection adapter over the daemon

Related pages

Clone this wiki locally