Files
reliquary/docs/workpad/reports/2026-10-16-09-headless-forks-preflight.md
T

188 lines
25 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.
# Preflight report — headless forks, Phase 0
**GO** — every STOP item passed (P0.0 uv, P0.1 detach on rung 3 hidden, P0.2 large base, P0.3 usage); P0.4 and P0.6 passed too, so the parallel cap stays 2 and pings default on.
- **Date**: 2026-10-16, 08:16–12:16 +0200 (times from `date`; probing ended 10:13, the clock jumped from 10:13 to 12:16 between two consecutive commands, cause unknown)
- **Run by**: the Athanor, model id `claude-opus-5-5[1m]`, from its own Bash (Agent-spawned subagent of the Desktop session, Windows 10, Git Bash)
- **Build plan**: [plans/2026-10-16-02-headless-forks-build.md](../plans/2026-10-16-02-headless-forks-build.md) §6 Phase 0 (amends plan §7 Phase 0)
- **Branch / checkout**: `headless-forks` in `.claude/worktrees/headless-forks`, from `d373689`
- **Raw files** (not in git): `C:\Users\g4b\AppData\Local\Temp\claude\G--dev-projects-reliquary\42c87f82-5542-46aa-8d01-4166e3f0bcfe\scratchpad\headless-forks-p0\` — `$P0` below. Probe code in `$P0\probes\`, streams and summaries in `$P0\raw\<item>\`.
- **Binary under test**: `$CLAUDE_CODE_EXECPATH` = Desktop `claude.exe` 2.1.299 (PATH `claude` is 2.1.283; used once by accident, noted at P0.1).
- **Spend**: **$5.68** client estimate (`total_cost_usd`), summed as per-call deltas over 45 calls by `$P0\probes\spend.py`; plus three killed `claude` calls that wrote no result (`r3-kill3`, `r3-kill5`, `r5-kill`, a few cents). Limit was $25 before P0.2 completed; $4.87 had been spent by 08:55, after P0.2–P0.5, P0.7 and P0.8.
## Defaults Phase 1 must use
| Topic | Default | From |
|---|---|---|
| Spawn rung | **Rung 3, hidden**: the launcher creates the supervisor through CIM with a hidden window: `powershell -NoProfile -NonInteractive -Command "$si = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly -Property @{ShowWindow=[uint16]0}; Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine=…; CurrentDirectory=…; ProcessStartupInformation=$si}"`, reading `ReturnValue` and `ProcessId`. Launch cost about 0.3 s. Without `ShowWindow=0` the supervisor opens a **visible console window** (Windows Terminal tab titled with the interpreter path; user report and positive control). | P0.1 |
| No visible windows | Every child the supervisor starts (`claude`, any helper process) is spawned with `CREATE_NO_WINDOW` (`0x08000000`), never `DETACHED_PROCESS` or `CREATE_NEW_CONSOLE` (a console-less parent makes each console child open its own window). `claude`'s Bash children inherit the hidden console: no window seen with a 10-process tree (`claude.exe` → `bash.exe` ×3 → `PING.EXE`, 4 `conhost.exe`). Phase 1 tests and the smoke check "no visible window of the tree" (EnumWindows + IsWindowVisible on the tree's pids, plus visible `ConsoleWindowClass`/`CASCADIA_HOSTING_WINDOW_CLASS` windows). | P0.1 retest |
| Supervisor env | CIM gives the child the registry default user env, not the caller's. The launcher writes the full scrubbed child env into the job's state (e.g. `jobs/<job>.json` or a sidecar) and the supervisor passes exactly that env to `claude`. The binary path is stored absolute (it is in the fingerprint anyway). | P0.1 |
| Interpreter | `sys._base_executable` (differs from `sys.executable` under `uv run`: venv launcher `…\archive-v0\<id>\Scripts\python.exe` vs `…\uv\python\cpython-3.13.1-…\python.exe`), with `-I`. `-I` drops the script dir from `sys.path`; the supervisor must not rely on it. | P0.0, C6 |
| Kill | `taskkill /T /F /PID <supervisor>`; verify by recorded pid + creation time. | P0.1 |
| Scrub additions (uv) | Drop `VIRTUAL_ENV`; drop PATH entries under `VIRTUAL_ENV` or `sys.prefix` (one entry: `<venv>\Scripts`, prepended). uv sets no `UV_*` vars here; dropping `UV_*` stays as a guard. Keep `PYTHONDONTWRITEBYTECODE` (host-set, not uv). | P0.0 |
| `where` must report (host `CLAUDE_CODE_*` and kin on neither list) | `CLAUDE_CODE_SESSION_ATTENDED`, `CLAUDE_CODE_DISABLE_CRON`, `CLAUDE_CODE_EAGER_FLUSH`, `CLAUDE_CODE_SIMPLE`, `CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES`, `CLAUDE_CODE_ORGANIZATION_UUID`, `CLAUDE_CODE_ACCOUNT_UUID`, `CLAUDE_CODE_USER_EMAIL`, `CLAUDE_CODE_OAUTH_SCOPES`, `CLAUDE_CODE_DISABLE_TERMINAL_TITLE`, `CLAUDE_CODE_STARTUP_FAILURE_RESULTS`, `CLAUDE_AGENT_SDK_MCP_NO_PREFIX`, `CLAUDE_PREVIEW_CLASSIFIER_FLOOR`, `DISABLE_MICROCOMPACT`. All were inherited (kept) in every probe call and auth worked. | P0.0 env listing |
| Usage rule | Per call: the four columns and the 1h/5m split come from the `result` event's top-level `usage` (`input_tokens`, `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `cache_creation.ephemeral_1h_input_tokens` / `ephemeral_5m_input_tokens`). For a fork this equals the `modelUsage` delta against the base's cumulative `modelUsage`, field for field (7 of 7 forks). `models_used` = the `modelUsage` keys whose counts grew. Cost Δ = `total_cost_usd` − the session total before the call (= the `modelUsage[m].costUSD` delta, 7 of 7). Never sum stream assistant events for `output_tokens`: they carry interim counts (seq forks: 102 summed vs 1,448 real). | P0.3 |
| `usage_mismatch` | Compare `result.usage` with the `modelUsage` delta (they agreed exactly here). | P0.3 |
| Cache verdict input | First assistant event's `usage` (input and cache fields are final there). Base context = `input + cache_creation + cache_read` of the base's last assistant message; the stream and the transcript give the same number (182,204). | P0.2, P0.7 |
| Parallel cap | **2** (P0.4 passed). | P0.4 |
| Pings | **on** by default (`init --pings on`): a ping 50 min after the base's last hit kept it warm to 65 min (P0.6 pass). A ping can itself re-create part of the prefix (7,463 of 88,571 here) and still count as a hit. | P0.6 |
| Fingerprint fields | Keep all launch fields of design §3.3. **Effort** stays pinned but is not cache-defining: a `--effort medium` fork of a `high` base read 182,202 of 182,204 tokens from cache, same as the control. `init` has no effort field (`per_turn_effort_active: true` only). | P0.5 |
| Model ids | Full ids accepted; `[1m]` accepted (`init.model` and the `modelUsage` key both read `claude-opus-5-5[1m]`; `message.model` reads `claude-opus-5-5`). Compare `models_used` with the requested id including the suffix. | P0.5 |
| `availableModels` rule (C8, D5) | A requested id is **admitted** if a list contains the id itself or its family alias (`opus`, `sonnet`, `haiku`, `fable`); no list in user/project/local settings admits everything. An id that is not admitted is **not refused**: Claude Code silently runs a permitted model and emits `{"type":"system","subtype":"informational","level":"warning","content":"Model \"<id>\" is restricted by your organization's settings. Using <other> instead."}`. So `init` applies the admit rule (exit 2 per C8), and at runtime the supervisor treats that event, or a `modelUsage` key ≠ the requested id, as `failed`, reason `model_refused`. In reliquary all four family aliases are listed, so every full id is admitted (U4 is moot for Sonnet and Haiku). | P0.5 |
| Injection (R4) | `!` injection **ran** in an Agent-tool subagent's preload (probe `where` called 4.8 s after the headless launch). `allowed-tools: Bash(uv:*)` did **not** pre-approve the subagent's later `uv run …/probe.py ping` (denied: "This command requires approval"). SKILL.md must tell projects to add a `Bash(uv run:*)` (or `Bash(uv:*)`) allow rule; reliquary has `Bash(uv:*)`. Caveat: the probe dir was an untrusted workspace. | P0.8 |
| Fork session ids | `--session-id <uuid>` with `--resume <base> --fork-session` is honoured: the fork gets that id. The helper may pre-assign fork ids. | P0.7 |
## Results per item
| Item | Result | Variant / notes |
|---|---|---|
| P0.0 uv | **pass** | (a) repo cwd, (b) `pyproject.toml` cwd, (c) `.python-version = 3.9` cwd: all rc 0, stdout one JSON line, Python 3.13.1 in all three, no `.venv`/`uv.lock` created. |
| P0.1 detach | **pass, rung 3 hidden** | Rungs 1 and 2 failed (d). Rung 3 passed (a)–(d) and the tree-kill but showed a visible console window (fails the added "no visible window" criterion). Rung 3 with `ShowWindow=0` and `CREATE_NO_WINDOW` children passed (a), (b), (d), the tree-kill and "no visible window"; (c) passed (1,000 s, no window in 24 checks). |
| P0.2 large base | **pass** | Base 182,204 context tokens, three sequential forks each read 182,202 on the first request (0.99999), no compaction. |
| P0.3 usage | **pass** | Rule above; reproducible on 7 forks (1, 3 and 7 turns). |
| P0.4 parallel | **pass** | No `index.lock`, both edits intact, both first requests 0.99999; the Athanor gated and committed both. |
| P0.5 flags and models | **pass (all accepted)** | Table below. |
| P0.6 TTL | **pass** | A's fork at 65 min after a 50-min ping: 1.0004; B's fork at 65 min without ping: 0.080 (`partial`). |
| P0.7 transcripts | **recorded** | Message usage present on every assistant line; copied base messages keep `message.id` and `uuid`; R6 honoured. |
| P0.8 injection proxy | **injection yes, pre-approval no** | Informational; fallback text applies. |
| Spend trigger | **not hit** | $5.68 total; $4.87 by 08:55 (P0.2 long done) |
STOP criteria (plan decision 1, build plan §6): P0.0 pass, P0.1 pass (rung 3), P0.2 pass, P0.3 pass. No STOP. No fallback beyond the ladder was needed; rung 3 needed the hidden-window fix added after the coordinator's message.
---
## P0.0 uv
- Probe: `$P0\probes\p00_probe.py`, the plan §2 PEP 723 header. Command: `uv run "C:/…/probes/p00_probe.py"` from the Athanor's Bash. Raw: `$P0\raw\p00\`.
- uv 0.5.13 (c456bae5e 2024-12-27). Python chosen: CPython 3.13.1, the cached archive venv `C:\Users\g4b\AppData\Local\uv\cache\archive-v0\1V7ITIfzQwCDPBZsZhb9M`. The same venv in all three cwds.
- (a) cwd = worktree; (b) cwd = `$P0\p00-pyproject` with `[project]` (deps `requests`, hatchling build): no sync, no `.venv`, no `uv.lock`; (c) cwd = `$P0\p00-pyver` with `.python-version` = `3.9` (3.9.16 is installed): 3.13.1 chosen, `requires-python >= 3.10` wins.
- stdout: exactly the probe's one line in every run. stderr: one line, `Reading inline script metadata from …`.
- Latency: warm 125–134 ms (5 runs); cold uv cache (fresh `UV_CACHE_DIR`) 532 ms. The first-ever run in the real cache was not timed (the first probe call warmed it).
- Env diff Bash → inside `uv run`: added `VIRTUAL_ENV` (= the archive venv); PATH gains `<venv>\Scripts` at the head; no `UV_*`; `PYTHONDONTWRITEBYTECODE=1` is host-set (present in Bash too).
- Resolved tools inside `uv run`: `git` = `C:\Program Files\Git\mingw64\bin\git.EXE` (2.34.1.windows.1), `claude` = `C:\Users\g4b\.local\bin\claude.EXE` (2.1.283), `uv` = `C:\Users\g4b\.local\bin\uv.EXE`, `python` = the venv's.
- `sys.executable` = `…\archive-v0\1V7ITIfzQwCDPBZsZhb9M\Scripts\python.exe`; `sys._base_executable` = `C:\Users\g4b\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\python.exe` (C6 applies).
## P0.1 detach
Probes: `p01_launch.py` (run under `uv run`, spawns the supervisor by rung), `p01_sup.py` (supervisor: ticks every 10 s, a python sleeper child, `claude -p` calls at given offsets), `p01_check.py` (pid + creation-time liveness, CIM descendant snapshot, `taskkill /T /F`). Raw: `$P0\raw\p01\<tag>\` (`launch.json`, `state.json`, `tick.log`, `sleeper.log`, `check-*.json`, `claude*.jsonl`).
Job objects: `IsProcessInJob` was **false** for the `uv run` launcher and for every supervisor, so `CREATE_BREAKAWAY_FROM_JOB` had nothing to break from (A5's premise does not hold on this host today).
What the Bash tool does: a call that exceeds its `timeout` (5 s and 8 s tried) is **not killed**; it is moved to the background ("Command did not complete within its 5s timeout and was moved to the background"), and its completion notification arrived in this subagent. To get the "killed Bash call" of (d), the backgrounded task was stopped with `TaskStop`, which kills the **process tree by parent links**.
| Rung | Tag | (a) uv exits | (b) Bash call ends | (c) ≥ 15 min, growing, completes | (d) Bash call killed (launcher still alive) | Tree-kill |
|---|---|---|---|---|---|---|
| 1 `DETACHED_PROCESS\|CREATE_NEW_PROCESS_GROUP\|CREATE_BREAKAWAY_FROM_JOB` (`0x1000208`) | `r1-main`, `r1-tkill` | pass | pass | pass: 1,020 s, ticks 1→3→12→103 over checks at 08:21:04, 08:21:28, 08:23:02, 08:45:23; `claude` at +20 s and +935 s both `P01-OK` | **fail**: supervisor and sleeper dead at the first check after `TaskStop` (08:21:28) | — |
| 2 same without breakaway (`0x208`) | `r2-tkill` | — | — | — | **fail** (same) | — |
| 3 CIM `Win32_Process.Create` | `r3-main`, `r3-tkill`, `r3-kill5` | pass | pass | pass: 1,020 s, ticks 2→103, `claude` at +20 s and +933 s both `P01-OK` | **pass**: alive after `TaskStop`; parent is `WmiPrvSE.exe` (pid 34932) | **pass**: 8 processes (`claude.exe` → `bash.exe` ×3 → `PING.EXE`, `conhost.exe` ×2, sleeper) all dead by pid + creation time; no `PING.EXE` left |
**Retest for visible windows (coordinator's message, 09:52; user saw a Python console window on the desktop).** Criterion added to P0.1: no visible window from the supervisor or any descendant. Window check: `EnumWindows` + `IsWindowVisible` for the tree's pids, plus any visible `ConsoleWindowClass` / `CASCADIA_HOSTING_WINDOW_CLASS` window.
| Variant | Tag | Visible window | (a) | (b) | (c) | (d) | Tree-kill |
|---|---|---|---|---|---|---|---|
| Rung 3 as first run (positive control, 15 s) | `r3-wincontrol` | **yes**: Windows Terminal window (`CASCADIA_HOSTING_WINDOW_CLASS`, pid 19872) titled `…\cpython-3.13.1-…\python.exe` | — | — | — | — | — |
| Rung 3 hidden: `Win32_ProcessStartup.ShowWindow=0`; supervisor children `CREATE_NO_WINDOW` | `r5-main`, `r5-tkill`, `r5-kill` | **none**, checked 26 times incl. during a `claude` call and during `claude` → `bash` → `PING` | pass | pass | pass: 1,000 s, ticks 1→101 over 24 checks 09:55:54–10:13:01, `claude` at +20 s and +935 s both `P01-OK`, done | pass (alive after `TaskStop` at 09:56:31, check 09:56:43) | pass: 11 processes dead by pid + creation time, no `PING.EXE` left |
| Popen `CREATE_NO_WINDOW\|CREATE_NEW_PROCESS_GROUP\|CREATE_BREAKAWAY_FROM_JOB`, children `CREATE_NO_WINDOW` (informational) | `r6-win` | none (3 checks incl. a `claude` call) | pass | pass | not run | not run; expected to fail like rungs 1/2 (same parentage) | not run |
So the earlier visible windows came from every rung-3 supervisor before the fix (`r3-*`, and the P0.2 and P0.6 probes, which were launched with the same unhidden CIM call), and presumably (not measured) from the console children of the rung-1/2 `DETACHED_PROCESS` supervisors (sleeper, `claude`), which get a new console each when the parent has none.
Also recorded:
- `r1-dprime` (informational): rung 1 with the launcher already exited, then the Bash call stopped while a later process ran → supervisor **survived**. Rung 1 dies only while its launcher is alive in the stopped tree. That matters for the helper because `wait` stays alive while it would launch pings; rung 3 avoids the question.
- CIM env: a CIM-created process gets the registry default user env. 58 of the Bash env's variables were missing, among them every `CLAUDE*` var, `CLAUDE_CODE_EXECPATH`, `HOME`, `MSYSTEM`, Git's PATH entries (`$P0\raw\p01\wmi-env.json`). Effect seen: `r3-main` and `r3-tkill` (spawned before the fix) ran PATH `claude` 2.1.283 instead of 2.1.299. Fixed in the probe by shipping `env.json`; `r3-kill2`…`r3-kill5` and P0.2/P0.6 ran 2.1.299.
- Kill-test retries: `r3-kill` (2.1.283) let the harness background `sleep 240` and exited; the backgrounded `sleep.exe` was left **orphaned** after `claude` exited (killed by hand, pid 31372). `r3-kill2` was missed (the scratch clone took 20 min, 08:24→08:45, and the target finished). `r3-kill3` killed before the Bash child existed. `r3-kill4`: Opus 5.5 declined a bare blocking `ping` as pointless. `r3-kill5` with an honest brief: pass (above).
- Spend in P0.1: about $0.20 (killed calls `r3-kill3`/`r3-kill5` wrote no result and are not counted).
## P0.2 large base
- Probe `p02_base_forks.py`, run detached via `spawn3.py` (rung 3, still unhidden then: it showed a console window while it ran). cwd = scratch clone `$P0\clone` (`git clone --local --no-hardlinks` of `d373689`; `--no-hardlinks` because `G:`→`C:` cannot hardlink). Raw: `$P0\raw\p02\`.
- Fingerprint: `--model claude-opus-5-5 --effort high --permission-mode dontAsk --tools Bash,Read,Edit,Write,Glob,Grep,Skill --allowedTools <same> --disallowedTools "Bash(git push:*),Bash(git rebase:*),Bash(git reset --hard:*),Bash(claude:*)" --strict-mcp-config --mcp-config $P0\mcp-empty.json --append-system-prompt-file $P0\preamble-probe.md`, `--output-format stream-json --verbose`, prompt on stdin, scrubbed env (design §4 + `VIRTUAL_ENV` + venv PATH), `CLAUDE_CODE_PROMPT_CACHE_TTL=1h`, `DISABLE_AUTOUPDATER=1`.
- Base `d7c7a3aa-dfd5-40df-87f2-ccc82e49a369`: read 42 files (all `plugins/spire/**/*.md` plus 7 workpad docs) with Read, 43 turns, 74.7 s, context **182,204** tokens (last message: 2 + 45,677 created + 136,525 read), all writes `ephemeral_1h`, cost $1.6298, no compaction event.
| Fork | Turns | First request: cache read / created | Ratio | Cost Δ | Commit (clone) |
|---|---|---|---|---|---|
| seq 1 | 7 | 182,202 / 1,242 | 0.99999 | $0.3116 | `64dbefa` |
| seq 2 | 7 | 182,202 / 1,247 | 0.99999 | $0.3091 | `9bfefb7` |
| seq 3 | 7 | 182,202 / 1,259 | 0.99999 | $0.3029 | `bb5514d` |
Each fork read `plugins/spire/CLAUDE.md`, wrote `scratch/p0-fork-<k>.md`, ran `git diff --check`, `git add`, `git commit`, `git log`, and replied with its hash. No denials. No re-run needed.
## P0.3 usage
Analysis `p03_usage.py` → `$P0\raw\p03-usage.json`.
- The `result` event carries `usage`, `modelUsage`, `total_cost_usd`, `num_turns`, `is_error`, `permission_denials` (plus `subagent_stats`, `terminal_reason`, timing fields). `usage` also has `iterations[]` and `output_tokens_details.thinking_tokens`; `modelUsage[m]` has `costUSD`, `contextWindow` (1,000,000 for `claude-opus-5-5` here), `canonicalModel`, `costBasis`.
- On every fork (seq 1–3, par 1–2, two 1-turn forks) `result.usage` == `modelUsage` delta vs the base for all four columns, and `total_cost_usd` delta == `modelUsage.costUSD` delta. Example seq 1: input 14, output 1,448, created 3,113 (all 1h), read 1,288,193; cost Δ $0.311559.
- Summing stream assistant events (last event per message id) matches input and both cache columns but not output (102 vs 1,448): stream events carry interim `output_tokens`.
- `num_turns` ≠ assistant messages on the base (43 vs 7 message ids): turns count tool round trips, several Read calls share one message. The helper's `turns` should come from `num_turns`.
## P0.4 parallel
Two forks launched together from the base (`par1`, `par2`, threads in the same probe), each writing `scratch/p0-par-<k>.md` and running `git status`, no commits. No `index.lock` in either stream; both files intact (3 lines each); both first requests 182,202 read (0.99999). The Athanor then gated (`git add -N` + `git diff --check`, rc 0) and committed each file by explicit path: clone `0f72edb`, `9d3ddc1`.
## P0.5 flags and models
Probe `p05_flags.py` → `$P0\raw\p05\`. Tool-less calls (`--tools ""`) unless noted.
| Check | Outcome | Default |
|---|---|---|
| `--effort` with `CLAUDE_EFFORT` scrubbed | accepted; not visible in `init`; a `medium` fork of the `high` base hit 0.99999 like the `high` control | keep pinned; not cache-defining |
| `--strict-mcp-config` + empty config | `mcp_servers: []`, no `mcp__*` tools (the claude.ai Docs connector is gone) | keep |
| `Skill` in `--tools` | accepted; `init.tools` lists it | keep |
| `[1m]` in `--model` | accepted; `init.model` and `modelUsage` key `claude-opus-5-5[1m]` | keep |
| `apiKeySource` | present, `"none"` (OAuth) | check stays |
| `--agent p0agent` (project `.claude/agents/p0agent.md`) + `--append-system-prompt-file` | both markers quoted (`AGENT-MARKER-3KX`, `PREAMBLE-MARKER-7Q4`); control without `--agent` quoted none | keep |
| agent `tools: Read` vs `--tools Bash,Read` | `init.tools` = `["Read"]`: the intersection | the agent file can only narrow `--tools` |
| `claude-opus-5-5` (listed) | runs as asked | — |
| `claude-sonnet-5-5`, `claude-haiku-5-5` (unlisted full ids, aliases listed), trusted checkout | run as asked | admitted via family alias |
| `claude-opus-4-6` (bare; only `[1m]` form listed), trusted checkout | runs as asked | admitted via `opus` |
| unlisted family | none exists (all four aliases listed); **skipped** in project settings, tested via `--settings` instead ↓ | — |
| `--settings '{"availableModels":["claude-opus-5-5"]}'`, request `claude-sonnet-5-5` | **substituted**: ran `claude-opus-5-5` with the `system/informational` warning quoted above | `model_refused` |
| same list, request `claude-opus-5-5` | runs | — |
| `--settings '{"availableModels":["opus","sonnet"]}'`, request `claude-sonnet-5-5` | runs as asked | alias admits full id |
Trust: in untrusted dirs (scratch clone, `$P0\p05-cwd`, `$P0\p08-cwd`) stderr says "Ignoring 36 permissions.allow entries from .claude/settings.json: this workspace has not been trusted". `G:/dev/projects/reliquary` is trusted; a tool-less call in the worktree printed no warning (trust is inherited by subdirectories). Project agents still load in untrusted dirs. The model rows marked "trusted checkout" ran tool-less in `G:\dev\projects\reliquary` (deviation, logged in the run log).
## P0.6 TTL
Probe `p06_ttl.py`, detached via `spawn3.py` (rung 3, unhidden at the time) at 08:48:45. cwd = `$P0\ttl-cwd` (empty, not a git repo), the P0.2 fingerprint (same tools, mode, preamble, strict MCP, TTL pinned 1h). Training content was passed in the prompt (no tools used): A = five orbit/dirigent research and report files, B = six superpowers research and report files from the clone; "reply READY". Raw: `$P0\raw\p06\` (`state.json`, `train*.jsonl`, `ping*.jsonl`, `fork*.jsonl`).
| Step | Elapsed since base's first event | First request: cache read / created | Ratio to base context | Cost Δ |
|---|---|---|---|---|
| train A (`9c2fa6d6-…`) | — | 0 / 88,569 (all 1h) | context 88,571 | $0.7086 |
| train B (`79af9c73-…`) | — | 7,469 / 85,406 | context 92,877 | $0.6848 |
| ping A (`Reply with only: OK`, `--max-turns 1`) | 3,004.6 s (50.1 min) | 81,144 / 7,463 | 0.916 | $0.0760 |
| fork A | 3,904.9 s (65.1 min), 15 min after the ping | 88,607 / 0 | **1.0004** | $0.0178 |
| fork B (no ping) | 3,902.7 s (65.0 min) | 7,469 / 85,444 | **0.080** | $0.6851 |
- **Pass**: A's fork ≥ 0.9 × A's context after the 50-min ping; B's fork < 0.5 × B's context. B shows `partial` (the 7,469-token shared prefix stayed warm, as A4/C5 predicted).
- A's fork read more than A's context (88,607 > 88,571) because the ping's identical prompt was cached too.
- The ping itself re-created 7,463 tokens instead of reading them (0.916, still a hit by C5). That size matches the shared system/tool prefix (7,469). Cause not isolated; it did not stop the ping from refreshing the base prefix.
- Economics: a cold start (fork B) cost $0.685; ping + warm fork cost $0.094. One ping pays for itself many times over at this base size.
## P0.7 transcripts (informational)
`p07_transcripts.py` → `$P0\raw\p07\p07.json`.
- Transcript dir = `~/.claude/projects/<cwd with every non-alphanumeric → '-'>/` (exists for the clone).
- Base transcript: 128 lines, 46 assistant lines, **all 46 carry `message.usage`**; several lines per message id (one per content block). Base context from the transcript's last assistant line = 182,204 = the stream's number. `max_base_tokens` can be read from transcripts.
- Fork seq 1 transcript: all 7 base message ids and all 106 base `uuid`s are present (copied unchanged), under the fork's own `sessionId`; no `fork*` field. Dedupe by message id works for the trial.
- R6: `--session-id f2806537-… --resume <tiny base> --fork-session` → `init.session_id` and `result.session_id` both `f2806537-…`; the fork recalled the base's word.
## P0.8 injection and pre-approval proxy (informational)
`p08_inject.py` in `$P0\p08-cwd` (copy of the project settings with `Bash(uv:*)`/`Bash(uvx:*)` removed; skill `forkprobe` with `allowed-tools: Bash(uv:*)` and `` !`uv run "${CLAUDE_SKILL_DIR}/probe.py" where` ``; agent `forkprobe` preloading it). Headless `claude -p --tools Task,Bash --permission-mode default --allowedTools Task,Agent` dispatched `forkprobe`. Raw: `$P0\raw\p08\`.
- Injection in the subagent: **yes**. The reply quoted `{"probe": "forkprobe", "marker": "INJECT-MARKER-W9P", …}`; `probe-calls.log` shows one `where` call 4.8 s after launch.
- Pre-approval: **no**. `permission_denials` holds the subagent's `uv run "…\probe.py" ping` ("This command requires approval").
- Wall time: 13.7 s for the whole run; the injection completed within the first 4.8 s.
- Caveat: the dir is an untrusted workspace (its project allow list was ignored anyway, which is what the probe needed). Whether `allowed-tools` pre-approves in a trusted project is untested; Phase 4 checks it with the real plugin.
## Not tested / open
- Survival of a rung-3 supervisor past the **end of the Athanor's turn** (all checks were inside one turn). Parentage under `WmiPrvSE.exe` makes a tree-kill from the agent impossible, so it is expected to hold; Phase 1's smoke can confirm it across a hand-back.
- What effort actually changes (no observable in `init` or in a 1-turn reply).
- Why the 50-min ping re-created 7,463 prefix tokens.
- `rate_limit_event` lines in every stream carry plan utilisation (`five_hour` 0.02, `seven_day` 0.56 this morning). Not used by the plan; a cheap input for R11 if Phase 1 wants it.
- Whether the Desktop's orphaned background Bash tasks (fork backgrounding a command) can be prevented by flags; the default is a preamble line.