44 lines
5.3 KiB
Markdown
44 lines
5.3 KiB
Markdown
# 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).
|