🤖 implementation references: headless sessions and gremlins
This commit is contained in:
@@ -47,5 +47,14 @@ Full id not recorded here; spacecraft notes mention "Sonnet 5".
|
||||
- Both extremes failed. Many tiny fresh implementers relearn the code each time. One implementer carried across tasks reached about 960k tokens and drifted (spacecraft, 2026-09-29; model probably 5.5 through the alias).
|
||||
- Measured: dirigent blocks of 11–36 min of agent work used 186k–344k tokens each (Sonnet, 2026-10-01). None ran out.
|
||||
- Guesstimates (unmeasured): a trunk read at intake of about 100k–250k; leave room for fallout and the fix pass rather than running close to the comfortable limit.
|
||||
- With gremlins (2026-10-16), the trunk read is the base's training, not the Athanor's context. Measured: the Phase 0 large base was 182k tokens (P0.2) and the smoke base 23.7k; the Athanor reads plans and reports, not code (preflight report; Phase 3 contract A13).
|
||||
|
||||
**Future work:** orchestrator introspection of subagent token use, and signals into running work (dirigent is working on this). Until then, the Athanor records what usage it sees in completion results and judges by block count and size.
|
||||
|
||||
- Update 2026-10-16: gremlins (headless sessions) report per-call usage through `usage` (tokens by cache column, models used, priced cost). Agent-tool subagents still do not.
|
||||
|
||||
## Notes added 2026-10-16
|
||||
|
||||
- Plan mode (`--permission-mode plan`) silently upgrades a Haiku session to Sonnet; `init.model` still shows Haiku, so read `modelUsage` for what ran. The docs only imply it (research `F2a`, `F2b`).
|
||||
- Aliases map to model versions per CLI release: `haiku` is Haiku 4.5 on `claude` 2.1.283 and Haiku 5.5 on 2.1.299 (research `F2c`). Pin a full id where the version matters.
|
||||
- Opus 5.5 may refuse a bare "run this blocking command" brief as pointless; say why the command is wanted (observed in the Phase 0 trunk notes, preflight report, 2026-10-16).
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Spawning agents in Claude Code
|
||||
|
||||
Harness mechanics for dispatching, resuming and forking agents in Claude Code. Other harnesses get their own `spawning-<harness>.md` when needed.
|
||||
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 text on that date. Not tested at runtime unless the row says so.
|
||||
- **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.
|
||||
@@ -18,6 +18,7 @@ The harness changes. When a claim fails, fix it here with a new date.
|
||||
- **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.
|
||||
|
||||
@@ -51,17 +52,79 @@ The harness changes. When a claim fails, fix it here with a new date.
|
||||
| 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
|
||||
## 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` is actually offered as an agent type in a given session. | unverified: the schema mentions it, but on 2026-10-05 it was not in the list of agent types offered to the reviewer |
|
||||
| A fork copies the **caller**, not an arbitrary agent. Only the agent holding a context (e.g. the Athanor as trunk) can fork it. | assumed (no parameter names a fork source; Igor's skill calls it "forking yourself") |
|
||||
| A fork starts with the caller's full transcript, so it starts at the caller's token count. | assumed, not measured |
|
||||
| Can a fork fork again? Does a foreground fork return its result to the caller? Does it see the caller's preloaded skills? | unknown; check in a throwaway preflight before relying on trunk and forks |
|
||||
| `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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user