7.6 KiB
7.6 KiB
Trunk notes — headless forks build (spire:forks)
Written by the Athanor (claude-opus-5-5[1m]) at Phase 0, 2026-10-16. For fresh agents and a successor Athanor. A page, not a design.
Where things are
- Build plan (authoritative):
docs/workpad/plans/2026-10-16-02-headless-forks-build.md. Plan:plans/2026-10-16-01-headless-forks.md. Design:designs/2026-10-16-01-headless-forks.md(runtime amendment at the end: Python under uv). Research:research/2026-10-16-00-claude-headless-fork-sessions.md. - Run log:
docs/workpad/reports/2026-10-16-08-headless-forks-run.md. Preflight report (Phase 0 output, the Phase 1 defaults):docs/workpad/reports/2026-10-16-09-headless-forks-preflight.md. - Checkout: worktree
G:\dev\projects\reliquary\.claude\worktrees\headless-forks, branchheadless-forks, cut fromd373689. - Phase 0 probes and raw streams (throwaway, not in git):
C:\Users\g4b\AppData\Local\Temp\claude\G--dev-projects-reliquary\42c87f82-5542-46aa-8d01-4166e3f0bcfe\scratchpad\headless-forks-p0\—probes/(code Phase 1 may reuse:p0lib.pyscrub, command line, stream parser, Windows pid+ctime liveness, job-object query;spawn3.pyCIM spawn),raw/(streams, summaries),clone/(scratch clone, untrusted workspace). - New code goes to
plugins/spire/skills/forks/only (forks.py,preamble.md,templates/,tests/test_forks.py). Nothing else inplugins/changes before Phase 3.
Host facts that shape the helper (measured, see the preflight report)
uv0.5.13 at~/.local/bin/uv;uv run <abs>/script.pypicks CPython 3.13.1 from a cached archive venv.sys.executableis the venvScripts\python.exe;sys._base_executableis the real interpreter. Apyproject.tomlor a.python-version = 3.9in the cwd is ignored. uv writes one line to stderr, nothing to stdout. Insideuv runthe env gainsVIRTUAL_ENVand a PATH head entry<venv>\Scripts; noUV_*vars.- No job objects anywhere (Bash tool, uv, supervisors:
IsProcessInJobfalse). The Bash tool's stop kills by process tree (parent links). A supervisor whose launcher is still alive dies with the Bash call; one spawned through CIM (Win32_Process.Create) is parented toWmiPrvSE.exeand survives. - No visible windows. An unhidden CIM spawn opens a Windows Terminal window on the user's desktop (user report). Spawn the supervisor with
Win32_ProcessStartup.ShowWindow=0and every child of it withCREATE_NO_WINDOW(0x08000000); neverDETACHED_PROCESS/CREATE_NEW_CONSOLE. Tests check it. - CIM-spawned processes get the registry default user env, not the caller's (no
CLAUDE_CODE_EXECPATH, no Git Bash PATH). The env forclaudemust be shipped explicitly (file next to the job) and the binary path must be absolute. - A Bash call that exceeds its
timeoutis moved to the background, not killed; its completion notification reached this subagent. Keep thetimeout: 600000rule anyway. - Two binaries: PATH
claude2.1.283,$CLAUDE_CODE_EXECPATH2.1.299 (Desktop). Pin the latter. - Host
CLAUDE_EFFORT=hightoday (the research sawmedium); scrubbed anyway. - Only
G:/dev/projects/reliquaryis trusted in~/.claude.json; the worktree inherits trust (no warning); the scratch clone does not (projectpermissions.allowignored, stderr says so).
Invariants Phase 1 must keep
- stdout = exactly one JSON object; diagnostics and uv chatter on stderr.
- Usage: fork
result.usageequals themodelUsagedelta against the base, field for field; cost delta =total_cost_usdminus the base's cumulative total. Stream assistant events carry interimoutput_tokens; never sum them. - Cache verdict from the first assistant event; base context = input + cache creation + cache read of the base's last assistant message (stream and transcript agree).
- Every git call
git --no-optional-locks.
Known traps
git clone --localfromG:toC:cannot hardlink; use--no-hardlinks.- Opus 5.5 may refuse a bare "run this blocking command" brief as pointless; say why it is wanted.
- A fork whose Bash tool backgrounds a command leaves that command running after
claudeexits (seen:sleep 240, orphaned).killby tree cannot reach it onceclaudeis gone. - Unlisted models are not refused: Claude Code substitutes a permitted model and emits a
system/informationalwarning. Detect it; do not trustinit.modelalone.
State for Phase 3 (added 2026-10-16 18:15 +0200, end of Phase 2)
- Phases 0–2 are built, reviewed and fixed (Phase 2 fix pass
d2d5104; its re-check is Igor's call). The helper is complete for Phase 3's purposes: every command in build plan §6 G-text item 6 is in the dispatch (where init train fork continue rebase wait list usage expire kill tick, internal_supervise). - Phase 3 changes only text (
forks/SKILL.md,agents/athanor.md,implementation/SKILL.md,references/*, README) and the two manifests. The run log's "Successor boot" section lists every review item routed to the Phase 3 text; treat that list as part of the brief. - The real JSON and exit codes to write the text against: run
where, and<command> --help(prints one JSON object). The contract digests describe result fields, reasons and flags as built. - Live smoke references for numbers and shapes:
…\headless-forks-p0\smoke1\(Phase 1) and…\smoke2\(Phase 2: rebase, pings,usagemd).
Phase 3 (added 2026-10-16 18:45 +0200, Phase 3 Athanor claude-opus-5-5[1m])
- The brief for every Phase 3 block is the digest
docs/workpad/plans/2026-10-16-18-gremlins-phase3-contract.md, including its closing "Amendment after pre-build check" section, which wins over the sections above it. The check that produced it:docs/workpad/reports/2026-10-16-19-gremlins-phase3-digest-check.md. - The skill is renamed in block 3.1:
plugins/spire/skills/forks/→plugins/spire/skills/gremlins/(gremlins.py,tests/test_gremlins.py). From 3.1 on, the gates are G-unituv run plugins/spire/skills/gremlins/tests/test_gremlins.py [Class …]and G-whereuv run plugins/spire/skills/gremlins/gremlins.py where. A single class runs asuv run …/test_gremlins.py ClassNameorClassName.test_name. - Words: the trained session is the base; a headless worker spawned from it is a gremlin; the command is
spawn(job kindspawn,method: "fork"); env markerSPIRE_GREMLIN_CHILD; state dir<run log without .md>-gremlins/. - Code map of
forks.pybefore the rename (line numbers at7351c40): constants:40-180; paths and cygpath:270-345; atomic files andRegistryLock:380-570; ledger:593-623; processes (ctypes, ctime, liveness, table, tree, kill):626-895;_run/git:898-918; scrub:920-991; binary/uv/python:994-1077; C8:1080-1127; stream, usage, cache, warmth:1130-1372; job files, templates,spawn_supervisor:1374-1460;_conclude/_rebase_succeeded/finalize_job:1463-1670; orchestrator stamp,reap_lost,kill_job:1684-1786; argparse:1794-1990;where/init:1998-2235; launches:2238-2927; ping/tick:2930-3112;Supervisor:3115-3338;wait/list/kill/expire:3341-3496;usage:3499-3728; dispatch:3731-3787. - Test classes:
PathTests ScrubTests LockTests AtomicTests PidTests ProcessTreeTests InitTests CliTests SubprocessTests RuleTests WarmthTests TickTests UsageTests LaunchTests(the last uses the real CIM detach and the stream stub; slow). Full suite about 150 s; run it only in block 3.8. - Test cadence (user): py_compile plus the touched test classes between blocks; the full suite once at phase end. No live
claudecalls in Phase 3. - File rule (user): Read, Write and Edit for files; never
cat/sed/heredocs to read or change files.