Files

19 KiB

Spawning agents in Claude Code

Harness mechanics for dispatching, resuming and forking agents in Claude Code, and for the headless sessions (claude -p) that spire:gremlins drives. Other harnesses get their own spawning-<harness>.md when needed.

Each claim carries a status and a date:

  • verified: read from the tool schema or the Claude Code docs, or reproduced in a local test, on that date; the row names which. Not tested at runtime unless the row says so.
  • observed: seen in real runs; the mechanism may be assumed. Source given.
  • assumed: inferred, not checked. Treat as a hypothesis.
  • unknown: open; check before relying on it.

The harness changes. When a claim fails, fix it here with a new date.

Rules of thumb

  • Foreground for sequential work. Pass run_in_background: false when the next step depends on the result. Background only for independent leaves whose result can go to the session root.
  • Resume only from the session root. Igor may SendMessage a finished agent (the Athanor between phases, an adept). A subagent should not resume its own children; it dispatches fresh foreground agents instead.
  • Pinned agents: omit model. The per-call alias overrides the agent's frontmatter pin. Pass model to a pinned agent only when you mean to override it, and say so in the journal or run log.
  • Unpinned agents: pass model. Otherwise they inherit the caller's model, which may be Fable.
  • Stamp the model id. Every agent reads its exact id from its system prompt and writes it into its output.
  • Complex blocks go to gremlins via spire:gremlins; never resume or fork a headless session by hand. The helper owns the flags, the cache bookkeeping and the usage ledger (see "Headless sessions" below).
  • Leaf briefs say "do this yourself; do not dispatch further agents." Only orchestrators (the Athanor) are meant to fan out.
  • Notifications report a turn ending, not a job finishing. Read the result text before concluding anything. Check ListAgents before resuming.

Dispatch and notification

Claim Status
Agent calls run in the background by default; run_in_background: false runs them in the foreground and returns the result to the caller. verified 2026-10-05 (Agent tool schema)
Background children of a subagent notify the session root, not their parent. The parent sees nothing and stalls until the root feeds it the result. observed 2026-09-24 and 2026-09-28 (spacecraft journals, "athanor stalled twice … nudged with results"); dirigent memory, settled 2026-09-29. Mechanism assumed.
An agent that hands work to a background child and then yields notifies completed at once, before any deliverable exists. Expect one more notification per background grandchild, each with its own task-id. observed 2026-09-22 (Spire harness notes)
The same task-id may notify more than once (one per turn). Track the deliverable, not the count. observed 2026-09-22
Background grandchildren show up in ListAgents as flat, top-level agents of the session. If one is still writing your output path, do not resume its parent: that starts a second writer. TaskStop the child first if you mean to abandon it. observed 2026-09-22
TaskList is the todo list, unrelated to agents. TaskOutput on an agent dumps its whole transcript into your context; do not use it for agents. observed 2026-09-22
A general-purpose agent has the Agent tool and will delegate if its brief reads like an orchestration brief. observed 2026-09-22

Resume

Claim Status
SendMessage to a finished agent's name or id resumes it with its context intact. A new Agent call starts fresh. verified 2026-10-05 (Agent tool schema)
A resume issued from inside a subagent runs in the background; its completion goes to the root and the subagent stalls. From the root (Igor) it is safe, because the root is where the notification lands. observed once 2026-10-05 (spacecraft journal: orchestrator resumed its implementer via SendMessage, went background, yielded). Mechanism assumed from the routing above.

Models

Claim Status
The per-call model parameter accepts aliases only (opus, sonnet, haiku, fable) and takes precedence over the agent definition's model frontmatter. verified 2026-10-05 (Agent tool schema)
With model omitted, a pinned agent runs on its pin; an unpinned agent inherits the caller's model unless a default subagent model is configured. verified 2026-10-05 (Agent tool schema)
A full model id (e.g. claude-opus-5-5) can only be set in the agent definition's frontmatter, so pinning by full id means a pinned agent dispatched without model. verified 2026-10-05 (follows from the alias-only parameter)
The opus alias overrode 4.6 pins in 184 of 184 spawns (they ran on 5.5). observed 2026-09-27 (spacecraft MVP retrospective)
A 4.6 Igor passing model: "opus" to the then 4.6-pinned Athanor got a 4.6 run (the dirigent end review that passed with zero findings). observed 2026-10-05 (user's attribution; the dirigent report recorded the model as unknown). Consistent with both the alias following the caller and the pin holding, so it is not evidence for alias-over-pin. The 184/184 row and the schema are.
What the opus alias resolves to (the caller's model, a session default, or the newest Opus). unknown. Check the stamped id of every plan, attack and review instead of relying on it.
Every agent sees its exact model id in its system prompt. observed 2026-10-05 for general-purpose agents (prompt shows claude-opus-5-5); not checked for plugin agents with their own bodies
Headless --model takes full ids (and the [1m] suffix), so gremlins pin by full id; the per-call alias limit above is an Agent-tool limit. observed 2026-10-16 (preflight report, P0.5)
Context window of the bare claude-opus-5-5 pin versus a [1m] suffix (other Spire pins carry [1m]). resolved 2026-10-05: all Opus-pinned Spire agents (igor, adept, athanor, zathras) pinned to claude-opus-5-5[1m] (user decision; Pinky, when it exists, stays 4.6); the ~600k guesstimate in models.md assumes the larger window

Fork (Agent tool)

Spire no longer uses Agent-tool forks (goal decision 2026-10-16): complex blocks go to gremlins, headless forks of a trained base (see "Headless sessions" below). The rows stay as a record of what the Agent tool offers.

Claim Status
The Agent tool schema documents subagent_type: "fork": a fork always inherits the caller's model; the model parameter is ignored. verified 2026-10-05 (schema text)
fork was not offered as an agent type on 2026-10-05 or on 2026-10-15. observed 2026-10-15 (agent-type lists of those sessions; design 2026-10-16-01 §9)
A fork inherits the current conversation only; no parameter names another agent to fork. verified 2026-10-15 (docs, sub-agents)
A fork has the same system prompt, tools, model and history as the forking session. verified 2026-10-15 (docs, sub-agents)
An Agent-tool fork cannot fork again: "A fork can't spawn further forks." A headless --fork-session of a fork works. verified 2026-10-15 (docs, sub-agents) for the Agent tool; verified 2026-10-16 (research 2d, local test T6) for headless
Fork mode is on by default in interactive sessions (v2.1.232 and later), off under -p and the SDK unless CLAUDE_CODE_FORK_SUBAGENT=1. With it on, all subagents run in the background and run_in_background is removed. verified 2026-10-15 (docs, sub-agents)
Agent-tool forks and subagents use the 5 min cache bucket by default; the main conversation (including -p runs) gets 1 h on a Claude subscription within plan usage. verified 2026-10-16 (research F4a; docs, prompt caching)
Whether a foreground Agent-tool fork returns its result to the caller, and whether it sees the caller's preloaded skills. unknown 2026-10-16 (never tested; Spire does not use Agent-tool forks)

Headless sessions (claude -p)

spire:gremlins (command spawn) is the only supported way to start, resume or fork one; these rows say why its command line is what it is. Tested on Windows with claude 2.1.283 and 2.1.299. Research ids (1a to F5b) refer to docs/workpad/research/2026-10-16-00-claude-headless-fork-sessions.md; P0.x ids to docs/workpad/reports/2026-10-16-09-headless-forks-preflight.md.

Claim Status
--output-format json prints one result object: session_id, result, usage (with the cache fields), modelUsage, total_cost_usd, permission_denials, subagent_stats. With stream-json --verbose the first event is system/init with session_id, model, tools, agents, skills, plugins, permissionMode. verified 2026-10-16 (research 1a; docs, headless; local tests T1, T6)
--resume <id> finds the session by id in any project on the machine; without --fork-session it appends to the same id. verified 2026-10-16 (research 1b; docs; local test T5)
--session-id <uuid> lets the caller choose the id up front, so no output parsing is needed. verified 2026-10-16 (research 1d; docs, --help)
--continue means the most recent conversation in the cwd, -p sessions included. Do not use it for fan-out; pass explicit ids. verified 2026-10-16 (research 1c; docs)
--fork-session gives the run a new session id and a copy of the base history. verified 2026-10-16 (research 2a; --help 2.1.283; docs)
The base stays unchanged on disk and in later resumes; forks do not see each other. verified 2026-10-16 (research 2b; docs, sessions; local tests T2 to T5)
Parallel forks of one base work and each gets its own id and transcript. Resuming the same id in two processes without forking interleaves both into one transcript. verified 2026-10-16 (research 2c; local tests T2 and T3 in parallel; docs)
A fork of a fork works. verified 2026-10-16 (research 2d; local test T6)
Forks hit the cache on the shared prefix (T2 and T3 each read 14,650 tokens against the base's 8,661 created). verified 2026-10-16 (research 3a; local test)
A cache hit needs an identical tool set, model and system prompt. A fork with different flags read 0 and re-created 17,474; one that changed only the model read 4,293 and created 7,213. verified 2026-10-16 (research 3b; local tests T6, T7)
A fork does not inherit the base's permission mode, --allowedTools or --tools (T6 came up in default with all 43 tools). Pass them again, identically, on every fork. verified 2026-10-16 (research 4b; local test T6; docs, sessions)
Also not restored on resume: --mcp-config, --settings, --plugin-dir, --fallback-model, --add-dir. The system prompt (including --append-system-prompt) is recorded at the first request and reused on resume and fork until compaction; later flag text is ignored (--system-prompt-snapshot, --help 2.1.283). verified 2026-10-16 (research 4c; docs, sessions and CLI reference)
The model is restored on resume; --model on the fork overrides it (a haiku fork of a sonnet base ran on haiku). verified 2026-10-16 (research 4d; docs; local test T7)
total_cost_usd and modelUsage on a resumed or forked run are cumulative for the conversation, base spend included (v2.1.277 and later). Per-call cost is a difference against the base's total; never sum results. --max-budget-usd counts only the call's own spend. verified 2026-10-16 (research 3d, F4e; docs, headless, cost tracking)
The 1 h cache TTL is the default only for the main-conversation bucket (interactive turns and -p runs) on a Claude subscription within plan usage; usage credits, API key and cloud providers default to 5 min, as does the main conversation over the plan limit. verified 2026-10-16 (research F4a, 3c; docs, prompt caching; local ephemeral_1h writes in T1 to T7)
The TTL is configurable: CLAUDE_CODE_PROMPT_CACHE_TTL or promptCacheTtl (main bucket), CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL or subagentPromptCacheTtl (everything else), each 5m or 1h, v2.1.242 and later. ENABLE_PROMPT_CACHING_1H=1 sets both; FORCE_PROMPT_CACHING_5M=1 overrides everything. verified 2026-10-16 (research F4b; docs, prompt caching)
A cache hit resets the TTL timer, so forks spaced under the TTL keep the base prefix warm. Parallel sessions in the same directory share a prefix; a different cwd or git-status snapshot changes it, so run every fork from the base's cwd. verified 2026-10-16 (research F4c, F4d; docs, prompt caching)
Under -p nothing can prompt: without a host or --permission-prompt-tool, anything that would prompt is denied, listed in permission_denials, and the run continues. dontAsk denies anything not pre-allowed. verified 2026-10-16 (research 4a; docs, headless and permission modes)
system/init.model is not the model that ran: read modelUsage or assistant.message.model. verified 2026-10-16 (research 5d, F2a, F2c; local tests M1, M2)
--permission-mode plan upgrades a Haiku session to Sonnet. The docs imply it (availableModels section) and do not state it as a rule. observed 2026-10-16 (research 5d, F2a, F2b; local tests T1 to T5, M1, M2; docs only indirect)
Aliases resolve by CLI version: haiku is Haiku 4.5 on 2.1.283 and Haiku 5.5 on 2.1.299 (Haiku 5.5 needs v2.1.293 or later, Sonnet 5.5 v2.1.284 or later). verified 2026-10-16 (research F2c; local tests; docs, model config)
Without --bare, -p loads installed plugins, skills, agents, hooks, MCP and CLAUDE.md like an interactive session. verified 2026-10-16 (research 5a; docs; local test T6)
--bare skips all of that discovery and refuses OAuth (Not logged in); it needs ANTHROPIC_API_KEY. verified 2026-10-16 (research 5b, F3d; docs; local test E2)
--model (alias or full id), --agents, --agent, --append-system-prompt[-file], --settings, --max-budget-usd, --max-turns and --json-schema exist in 2.1.283. verified 2026-10-16 (research 5c; --help; docs)
A nested claude -p inherits CLAUDECODE=1 and CLAUDE_CODE_CHILD_SESSION=1; no recursion guard blocks it, and the session is still persisted. verified 2026-10-16 (research 6a; docs, env vars; local env listing)
Agent depth is counted per process from its main conversation; the parent's depth is not passed down. A nested claude -p can use the Agent tool, and a second level nests too. At the depth limit the Agent tool is simply absent: no error. verified 2026-10-16 (research F1a to F1d; local tests D1 to D3; docs, env vars)
A nested process does not count against the parent's Agent depth or concurrency caps. assumed 2026-10-16 (research 6b; inferred from per-process subagent_stats, not tested with a deep parent)
--bg does not combine with -p. verified 2026-10-16 (research 6c; docs, headless)
A headless run is invisible to the parent's task notifications, ListAgents and TaskStop: the orchestrator must block on Bash or poll. assumed 2026-10-16 (research 6c; not a tracked Agent, never tested from a subagent)
Transcripts land in ~/.claude/projects/<cwd with every non-alphanumeric character as '-'>/<session-id>.jsonl, one file per fork, each holding the copied base history under its own sessionId; 30-day retention. verified 2026-10-16 (research 6d; docs, sessions; local listing)
The nested process inherits the Desktop host's env: CLAUDE_CODE_MESSAGING_SOCKET, CLAUDE_CODE_ENTRYPOINT=claude-desktop and the OAuth-refresh variables, among others. Removing twelve of those vars (F3a) with env -u drops the Artifact tools (35 to 32 built-ins); auth still works. observed 2026-10-16 (research 6e as corrected by F3a, F3b; local tests T6, E1)
The messaging_socket_path in system/init is the nested process's own pipe, different on every run and never the host's. This corrects the first reading of 6e. verified 2026-10-16 (research F3c; local tests T6, E1)
Whether the nested process's pipe can reach the host app, and which single env var gates the Desktop Artifact tools. unknown 2026-10-16 (research F3c; not isolated)
The PATH claude (2.1.283, native installer) and the Desktop host's bundled claude.exe (2.1.299, reachable as $CLAUDE_CODE_EXECPATH) are separate installs. verified 2026-10-16 (research F5a; local)
A fork made by another binary or on another model misses the base's cache: a 2.1.299 fork of a 2.1.283 base read 0 and created 15,020. Pin one binary and a full model id for the base and all its forks. verified 2026-10-16 (research F5b; local test V1; docs, prompt caching)

Observed in the Phase 0 preflight on this Windows host (2026-10-16):

Claim Status
uv run adds VIRTUAL_ENV and a venv Scripts entry at the head of PATH, and sets no UV_* vars; stdout stays clean (one JSON line). observed 2026-10-16 (preflight report, P0.0)
No job objects on this host (IsProcessInJob false for the uv run launcher and every supervisor). observed 2026-10-16 (preflight report, P0.1)
The Bash tool's TaskStop kills by process tree (parent links). A supervisor created through CIM Win32_Process.Create (parent WmiPrvSE.exe) survived the stopped Bash call; rungs that start it as a child of the launcher died with it. observed 2026-10-16 (preflight report, P0.1)
A supervisor surviving the end of the agent's turn (the hand-back) was not tested in Phase 0; the parentage under WmiPrvSE.exe makes it expected. unknown 2026-10-16 (preflight report, "Not tested / open"; Phase 4 smoke)
An unhidden CIM spawn opens a visible Windows Terminal window. The fix is Win32_ProcessStartup.ShowWindow=0 plus CREATE_NO_WINDOW on every child; no window was seen in 26 checks, including a claude to bash to PING tree. observed 2026-10-16 (preflight report, P0.1)
A CIM-created process gets the registry default user env, not the caller's (58 of the Bash env's variables missing, among them every CLAUDE* var and CLAUDE_CODE_EXECPATH), so the launcher must pass the full scrubbed env to the supervisor itself. observed 2026-10-16 (preflight report, P0.1)
The Bash tool backgrounds a call that passes its timeout; it does not kill it. observed 2026-10-16 (preflight report, P0.1(d))
An unlisted model (not admitted by availableModels) is substituted silently: the run uses a permitted model and emits a system/informational warning ("Model … is restricted by your organization's settings. Using … instead."). observed 2026-10-16 (preflight report, P0.5)
An ! injection runs in an Agent-tool subagent's skill preload, but the skill's allowed-tools did not pre-approve the subagent's later uv run call (denied: "This command requires approval"). The probe dir was an untrusted workspace; Phase 4 re-checks in a trusted project. observed 2026-10-16 (preflight report, P0.8)

Agent types

Claim Status
Plugin agent types (athanor, adept, researcher, …) can be missing in remote sessions, where /reload-plugins is not available. Fallback: general-purpose with an explicit model, and the agent file's body pasted into the brief. observed 2026-10-05 (reliquary Igor journal, 21:00)
Explore and Plan are read-only (no Edit/Write). Do not use them for tasks that must write a file. observed 2026-09-22 (Spire model tiers)