Files
reliquary/docs/workpad/reports/2026-10-16-08-headless-forks-trunk-notes.md
T
2026-10-16 18:44:28 +02:00

54 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`, branch `headless-forks`, cut from `d373689`.
- 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.py` scrub, command line, stream parser, Windows pid+ctime liveness, job-object query; `spawn3.py` CIM 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 in `plugins/` changes before Phase 3.
## Host facts that shape the helper (measured, see the preflight report)
- `uv` 0.5.13 at `~/.local/bin/uv`; `uv run <abs>/script.py` picks CPython 3.13.1 from a cached archive venv. `sys.executable` is the venv `Scripts\python.exe`; `sys._base_executable` is the real interpreter. A `pyproject.toml` or a `.python-version = 3.9` in the cwd is ignored. uv writes one line to stderr, nothing to stdout. Inside `uv run` the env gains `VIRTUAL_ENV` and a PATH head entry `<venv>\Scripts`; no `UV_*` vars.
- No job objects anywhere (Bash tool, uv, supervisors: `IsProcessInJob` false). 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 to `WmiPrvSE.exe` and 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=0` and every child of it with `CREATE_NO_WINDOW` (`0x08000000`); never `DETACHED_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 for `claude` must be shipped explicitly (file next to the job) and the binary path must be absolute.
- A Bash call that exceeds its `timeout` is **moved to the background**, not killed; its completion notification reached this subagent. Keep the `timeout: 600000` rule anyway.
- Two binaries: PATH `claude` 2.1.283, `$CLAUDE_CODE_EXECPATH` 2.1.299 (Desktop). Pin the latter.
- Host `CLAUDE_EFFORT=high` today (the research saw `medium`); scrubbed anyway.
- Only `G:/dev/projects/reliquary` is trusted in `~/.claude.json`; the worktree inherits trust (no warning); the scratch clone does not (project `permissions.allow` ignored, stderr says so).
## Invariants Phase 1 must keep
- stdout = exactly one JSON object; diagnostics and uv chatter on stderr.
- Usage: fork `result.usage` equals the `modelUsage` delta against the base, field for field; cost delta = `total_cost_usd` minus the base's cumulative total. Stream assistant events carry interim `output_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 --local` from `G:` to `C:` 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 `claude` exits (seen: `sleep 240`, orphaned). `kill` by tree cannot reach it once `claude` is gone.
- Unlisted models are not refused: Claude Code substitutes a permitted model and emits a `system/informational` warning. Detect it; do not trust `init.model` alone.
## 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, `usage` md).
## 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-unit `uv run plugins/spire/skills/gremlins/tests/test_gremlins.py [Class …]` and G-where `uv run plugins/spire/skills/gremlins/gremlins.py where`. A single class runs as `uv run …/test_gremlins.py ClassName` or `ClassName.test_name`.
- Words: the trained session is the **base**; a headless worker spawned from it is a **gremlin**; the command is `spawn` (job kind `spawn`, `method: "fork"`); env marker `SPIRE_GREMLIN_CHILD`; state dir `<run log without .md>-gremlins/`.
- Code map of `forks.py` before the rename (line numbers at `7351c40`): constants `:40-180`; paths and cygpath `:270-345`; atomic files and `RegistryLock` `: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 `claude` calls in Phase 3.
- File rule (user): Read, Write and Edit for files; never `cat`/`sed`/heredocs to read or change files.