📝 Gremlins run log: block 3.1 note
This commit is contained in:
@@ -527,3 +527,48 @@ Intake 2026-10-16 18:23 +0200, a fresh (successor) Athanor, `claude-opus-5-5[1m]
|
||||
- First turn, by Igor's instruction: contract digest only, nothing built. Digest: `docs/workpad/plans/2026-10-16-18-gremlins-phase3-contract.md`. It pins the rename map, the `Platform` interface, the path matrix, the Windows-only gate, the N3/N4 fixes, the flaky-kill investigation, the WSL procedure, the text per file and the release; its §12 lists the 20 choices the higher sources left open, for Igor's check before the build turn.
|
||||
- Block plan (digest §1): 3.1 rename, 3.2 seam and gate, 3.3 N3/N4/kill flake, 3.4 WSL check (the Athanor), 3.5 procedure text, 3.6 reference rows, 3.7 platform handoff, 3.8 README, version, full suite, release commit. Deviation from the build plan's three-block Phase 3 table, with the reason in the digest.
|
||||
- Trunk notes get a Phase 3 section at the start of the build turn.
|
||||
|
||||
### Block 3.1 — Rename to gremlins
|
||||
|
||||
- Model: `claude-opus-5-5[1m]` (fresh block owner, own tools, no agents dispatched)
|
||||
- Start / end: Fri Oct 16 18:44:53 WEDT 2026 / Fri Oct 16 20:24:21 WEDT 2026 (`date`; the end is the code commit, this note followed)
|
||||
- Done (digest §2 plus amendment A8(a), A9, A10):
|
||||
- `git mv plugins/spire/skills/forks → gremlins`, `forks.py → gremlins.py`, `tests/test_forks.py → tests/test_gremlins.py`, `templates/fork.md → templates/spawn.md`. The untracked `__pycache__` dirs moved with the folder and were deleted.
|
||||
- `gremlins.py`: `GremlinsError`, `cmd_spawn`, `detach_supervisor` (body unchanged), `DETACH_METHOD` (`where` keeps its `spawn` key), `CHILD_MARKER`/`SCRUB_PINNED` `SPIRE_GREMLIN_CHILD`, CIM carrier vars `SPIRE_GREMLIN_DETACH_CMD`/`_CWD` (in `POWERSHELL_CIM` and its caller), state dir suffix `-gremlins` (`state_dir_for`, `run_for_state`), `spawned_phases` in `phase_boundary`, `prog="gremlins.py"`, "spire:gremlins helper", module docstring, `_diag` prefix `gremlins:`. Command `spawn` (in `COMMANDS`, `HANDLERS`, `CHILD_REFUSED`) with `--method {fork}` (default `fork`; constant `SPAWN_METHODS`). Job kind `spawn` in `REAL_KINDS`, `CAPPED_KINDS`, `USAGE_KINDS`, `HIT_KINDS` and every kind check (`_conclude` cache/cold_start/holder/hit, `finalize_job` head_at_end, `phase_boundary`, `run_active`, `Supervisor.run` hit_written, `cmd_continue`). New `method` field: `_new_entry` default `None`, `spawn` sets `args.method`; carried in the result, the ledger line, the R1 reconciled ledger line (`ledger_from_result`) and `LIST_JOB_KEYS`; the `spawn` launch return is `{job, base, method, base_state, warm_until, rebase_due, retrain_due, reasons}`. Base key `gremlins` (train, `_rebase_succeeded`, `cmd_spawn`, `LIST_BASE_KEYS`). Help texts, refusals, N3 message, `usage` md fallback text, "gremlins never writes settings", "preamble.md missing next to gremlins.py" per §2.3/§2.4. Continuation lines re-aligned after the longer exception name.
|
||||
- Prompt text: `preamble.md` per §2.6 with the handoff-note format inlined as a bullet template (A8(a); the pointer to `implementation/references/run-log.md` is gone); `templates/spawn.md`, `train.md`, `rebase.md` per §2.6 (every `${…}` placeholder and the stale-snapshot sentence kept); `commit_rule` strings per §2.6.
|
||||
- Tests per §2.7: `gremlins`/`_load_gremlins`/`GREMLINS_PY`/`sys.modules["gremlins"]`, prefix `gremlins-test-` (setUp, `tearDownModule` filter `'gremlins' + '-test-'`, the M8 marker), helpers `spawn`, `spawn_marked`, `fake_spawn`, `SPAWN_RESULT`, `SESS_SPAWN`; test names `test_train_spawn_continue_end_to_end`, `test_usage_rule_real_spawn`, `test_allowed_inside_a_gremlin`. Also renamed for clarity (fakes of the detach, not of `spawn`): `TickTests` local `spawn` → `fake_detach`, `dead_spawn` → `dead_detach`. The stub's `--fork-session` handling is unchanged.
|
||||
- Changed assertion lines (old → new). The rest of the 191 removed / 200 added `assert` lines differ only by the mechanical identifier rename (`forks.` → `gremlins.`, `ForksError` → `GremlinsError`, `self.fork(` → `self.spawn(`, `fork_marked`, `fake_fork`, `FORK_RESULT`, `SESS_FORK`, `FORKS_PY`, `spawn_supervisor` → `detach_supervisor`, `SPAWN_METHOD` → `DETACH_METHOD`); checked by normalising those and diffing the removed against the added assert lines:
|
||||
- A9 (vacuous state-dir checks now derived from `state_dir_for`): `PathTests.test_gitbash_rooted_without_cygpath` `os.path.exists(drive + "\\tmp\\forks-m13-x-run-forks")` → `os.path.exists(gremlins.state_dir_for(drive + "\\tmp\\gremlins-m13-x-run.md"))` (run name `/tmp/forks-m13-x-run.md` → `/tmp/gremlins-m13-x-run.md`); `CliTests.test_commands_need_init` and `CliTests.test_child_refuses_kill_expire_init` `run_log[:-3] + "-forks"` → `gremlins.state_dir_for(run_log)`; `SubprocessTests.test_error_case_stdout_invariant` `os.path.join(self.tmp, "x-run-forks")` → `gremlins.state_dir_for(run_log)`. No other literal state-dir suffix in an `exists` check. The two `PathTests.test_state_dir_*` expected values (they test `state_dir_for` itself) stay literal: `…-x-run-forks` → `…-x-run-gremlins`.
|
||||
- N3 text, 6 lines: 5 × `assertIn("inside a fork", …)` → `assertIn("inside a gremlin", …)` (`CliTests.test_child_refuses_launching`, `test_child_refuses_kill_expire_init`, `TickTests.test_tick_command_and_n3`, `LaunchTests.test_refusals`, `test_rebase_end_to_end`), and 1 × `assertNotIn("inside a fork", …)` → `assertNotIn("inside a gremlin", …)`. The refused command in those argv lists: `"fork"` → `"spawn"`.
|
||||
- Env marker, 3 lines: `env["SPIRE_FORKS_CHILD"]` → `env["SPIRE_GREMLIN_CHILD"]` (`ScrubTests.test_scrubbed_env`, the end-to-end test, the ping end-to-end test); the stub's `ENV_KEYS` entry likewise (not an assertion line).
|
||||
- Kind values: `r2["kind"] == "fork"` → `"spawn"`; `list(out["kinds"]) == ["train", "rebase", "fork", "continue", "ping"]` → `[…, "spawn", …]`; `k["fork"][x]` → `k["spawn"][x]`; `k["fork"]["cost_usd"]` → `k["spawn"]["cost_usd"]`; `out["kinds"]["fork"]["calls"]` → `out["kinds"]["spawn"]["calls"]`; `js["kinds"]["fork"]["cache_read"]` → `js["kinds"]["spawn"]["cache_read"]`; R1 `(ln["kind"], …) == ("fork", …)` → `("spawn", …)`; `assertIn("| fork | 2 |", …)` → `"| spawn | 2 |"`. Non-assert lines feeding exact-match assertions: the `UsageTests` fixture kinds `"fork"` → `"spawn"` (j0002, j0004, j0005), the md phase table rows `| fork | 2 | …` and `| fork | 1 | …` → `| spawn | … |`, the fallback `n/a (no fork, …)` → `n/a (no spawn, …)`; `_new_entry(…, "fork", …)` / `add_job(reg, "fork", …)` → `"spawn"` in `ProcessTreeTests.test_reap_lost_bounds_by_heartbeat`, `WarmthTests`, `TickTests`, `UsageTests`.
|
||||
- Template text: `prompt.index("## Rules for this fork")` → `"## Rules for this gremlin"`.
|
||||
- Key sets, tightened (one key added each): end-to-end train result `set(r) == {…}` gains `"method"`; spawn launch return `set(f_out) == {…}` gains `"method"`.
|
||||
- Added (§2.7 `method`): `f_out["method"] == "fork"` (launch return); `r2["method"] == "fork"` (spawn result); `r["method"] is None` (train result); `r3["method"] is None` (continue result); ledger: the per-result loop now also compares `kind` and `method`, plus `[led[j]["method"] …] == [None, "fork", None]`; `list` job rows `[(kind, method)] == [("train", None), ("spawn", "fork"), ("continue", None)]`; `lst["bases"][0]["gremlins"] == [fj]`; R1 reconciled line `ln["method"] == "fork"` (fixture result and entry carry `method: "fork"`).
|
||||
- Tightened: the end-to-end "continue of a non-spawn job" refusal also asserts `out["error"] == "continue takes a spawn job"` (was exit code only).
|
||||
- None dropped or loosened.
|
||||
- Gates:
|
||||
1. `uv run python -m py_compile …/gremlins.py …/tests/test_gremlins.py`: exit 0.
|
||||
2. `uv run plugins/spire/skills/gremlins/tests/test_gremlins.py PathTests ScrubTests LockTests AtomicTests PidTests ProcessTreeTests InitTests CliTests RuleTests LaunchTests.test_train_spawn_continue_end_to_end LaunchTests.test_refusals`: exit 0, `Ran 76 tests in 14.182s`, `OK`. Extra (classes this block edited outside the gate list, not the full suite): `UsageTests TickTests WarmthTests`: exit 0, `Ran 32 tests in 6.612s`, `OK`. Logs in the session scratchpad `…\scratchpad\gremlins-3.1\`.
|
||||
3. G-where `uv run plugins/spire/skills/gremlins/gremlins.py where`: exit 0, one line, `ok: true`, `spawn: cim-hidden`, `scrub.pinned.SPIRE_GREMLIN_CHILD: "1"`, binary 2.1.299 via `CLAUDE_CODE_EXECPATH`, `warnings: []`. Also checked: `--help` lists `spawn`; `spawn --method clone` exits 2 (`invalid choice … (choose from fork)`).
|
||||
4. Residue: `git grep -n "spire:forks\|forks\.py\|SPIRE_FORKS\|skills/forks" -- plugins/` is empty. `git grep -n -i "fork" -- plugins/spire/`, every hit classified:
|
||||
- claude flag `--fork-session`: `gremlins.py:161`, `:1953`, `:2761`, `:2894`, `:3076`; `test_gremlins.py:1096`, `:1149`, `:1150` (the stub), `:1907`, `:2452`, `:2473`, `:3055`, `:3499`.
|
||||
- `method` value `"fork"` and the `--method` choices: `gremlins.py:6`, `:161`, `:163`, `:1941`, `:1942`; `test_gremlins.py:2252`, `:2268`, `:2285`, `:2418`, `:2426`, `:2435`, `:2486`, `:2493`.
|
||||
- prose about the session mechanism: `gremlins.py:6` ("a fork of the base's session"), `:1942`, `:1980` (rebase help: "Fork a base's session …"), `:2867` (rebase docstring: "a fork of B"); `preamble.md:3`; `templates/rebase.md:3`; `templates/spawn.md:3`; `templates/train.md:3`; `test_gremlins.py:1485` ("forked session"), `:2467` ("a continue resumes, it does not fork").
|
||||
- Agent-tool fork rows (3.5/3.6 text, untouched): `agents/athanor.md:32`; `implementation/SKILL.md:45`, `:49`; `implementation/references/spawning-claude-code.md:3`, `:56`, `:60`–`:64`.
|
||||
- A10 (Igor text and non-session meanings, untouched): `igor/SKILL.md:66`, `igor/references/harness-notes.md:3`, `igor/references/mindset.md:11`, `implementation/references/superpowers.md:36`.
|
||||
5. Leftover processes (20:24, `Get-CimInstance Win32_Process` by command line `gremlins-test-` or `_supervise`): none (only the query's own shell and PowerShell).
|
||||
- No live `claude` calls (stub only).
|
||||
- Deviations from plan:
|
||||
- `ledger_from_result` (R1 reconciliation) also carries `method` (from the result), so a reconciled line keeps the fixed key set of §2.4. Not named in §2.4, follows from "always present".
|
||||
- `_diag` stderr prefix `forks:` → `gremlins:` (not in the map; a residue otherwise).
|
||||
- Test-only renames beyond §2.7 (`fake_detach`, `dead_detach`), because both fake `detach_supervisor`, not `spawn`.
|
||||
- Flagged for review:
|
||||
- Help text of `where` still says "spawn method" and the key is still `spawn`; 3.2 replaces both with `platform.detach`.
|
||||
- `spawn_failed` and `taskkill_rc` are unchanged (3.2 renames them); `test_spawn_failed` is as it was apart from the identifier rename.
|
||||
- Test continuation lines that were aligned under `forks.` are now 3 columns off (`gremlins.` is longer); cosmetic, left as they are to keep the diff mechanical. `gremlins.py` was re-aligned.
|
||||
- `git status` shows `preamble.md` and `templates/train.md` as delete + add (their text changed by more than git's rename threshold); `git log --follow -M30%` finds them.
|
||||
- The renamed small templates change only text the digest names; the `${…}` placeholder set is unchanged in all three.
|
||||
- Left for later: 3.2 (`where` shape, `detach_failed`, `tree_kill_rc`, platform seam); SKILL.md and the other Phase 3 text (3.5, 3.6); workpad documents keep the old names (history, per digest §2.8).
|
||||
- Expected broken: nothing known. The full suite was not run (it runs once in 3.8); `SubprocessTests` and the rest of `LaunchTests` were not run in this block.
|
||||
- Commits: `01c4bc8` (rename), then this note's commit.
|
||||
|
||||
Reference in New Issue
Block a user