Execution Log: remove the process runner — tmux-only dispatch
Append-only log of progress for the user's visibility. Checked in alongside the spec at
docs/specs/issue-156/.
Phase transitions
| Phase | Entered | Reviewed/approved by | Notes |
|---|---|---|---|
| requirements-definition | 2026-08-05 | Issue #156: stale runner: "process" record silently downgrades tmux delivery. Owner's decision on the ticket: remove the process runner entirely; tmux only. | |
| design | 2026-08-05 | Delete the two-runner switchyard: one dispatch plane (deliver into tmux, else the issue-80/89/146 respawn path). Legacy records heal lazily — no migration tool. | |
| tasks-breakdown | 2026-08-05 | 15-task DAG; docs-parity (schema↔routing-options.md) pinned to one commit. | |
| implementation | 2026-08-05 | On claude/github-issue-156-w0x1bz. | |
| needs-review | 2026-08-05 | PR opened; tier-3 human approval happens there. | |
| complete |
Pull requests
| PR | Branch | Status |
|---|---|---|
| #158 | claude/github-issue-156-w0x1bz | open — awaiting tier-3 human approval |
Progress entries
2026-08-05 — spec drafted
- Phase: requirements → design → tasks
- Did: read the full dispatch plane (
Dispatcher._dispatch_one/_spawn_for→HarnessAdapter.resume/spawnvsTmuxRunner.deliver/spawn→_respawn_tmux/_try_resume), the registry (Session.runnerdefault"process"),check_dependencies, the interaction-mode/runner coupling, and swept every non-source mention (configs, schema, docs, capability docs, skill references, tests). Confirmed the reported root cause: the per-record runner selector with a silent"process"default, consulted on every dispatch after the first spawn, with no reconciliation against config. - Decided: per the owner's ticket comment, remove the runner rather than reconcile it. Critic one-shot invocation (
oneshot_argv, issue-108) is explicitly retained — it is a review mechanism, not the process runner. Legacy records (notmuxTarget) heal through the existing respawn path on their next event; no migration tool. Leftoverrouting.runnerconfig warns and is ignored, never fatal. - Next: implement T1–T14 (TDD per task).
2026-08-05 — implemented, tests re-hosted, docs swept
- Phase: implementation → needs-review
- Did: T1–T13. Source:
Session.runnerremoved (legacy key ignored on read);check_dependencies(web_enabled)with tmux unconditional;TmuxRunner.deliverreportssession_missingfor an empty target (the lazy-healing seam); adapters loseresume/spawn/_run/_resume_argv/DispatchResultand_spawn_argv→_oneshot_argv(critic surface kept);InteractionConfigdrops the runner coupling; the dispatcher is one plane (_dispatch_onetmux-only,_spawn_for→_spawn_tmux, leftoverrouting.runnerwarns); commands/announcer sweep. Tests: the dispatcher fleet re-hosted on a sharedFakeTmux/StubInteractiveAdapterseam inconftest.pyacross 9 files; new pins for the leftover-key warning, the ignored legacyrunnerfield, tmux-always-required, and the AC2.4 healing scenario (a legacy record with notmuxTargetrespawns a tmux session resuming the recorded conversation). Docs: schema + both shipped configs,routing-options.md(docs-parity holds), state/concepts/ install/commands docs, five capability docs (history rows added), three skill reference docs,decision-056(021's runner choice superseded). - Deviations from the plan, and why:
- The test re-host introduced shared doubles in
conftest.pyrather than per-file copies — nine files needed the identicalFakeTmux, and a drifting copy per file is how the old stub let the process path linger. pyproject.tomlgained a[tool.pyright]executionEnvironments entry: test modules now import the shared doubles from their siblingconftest.py, which pytest resolves but pyright needed telling about._log_usagewent with the headless path (tmux TUIs report no JSON usage; critic usage telemetry is separate and untouched).
- The test re-host introduced shared doubles in
- Evidence: see Final validation evidence below.
- Next: T15 — PR + reviewer briefing, then human approval (tier 3).
Review cycles
- Self-review 1 (residual-vocabulary sweep): found and fixed stale process-runner prose in
announce.py's module docstring and thesession.registeredschema line ineventlog.py; confirmed nosession.runner/config.runner/ headless-dispatch references remain incli/the_loopoutside intentional issue-156 explanations. - Self-review 2 (behavioural edges): traced the legacy-record path end to end (
deliver("")→session_missing→_respawn_tmux→_try_resumewith the recorded id) and found AC2.4 had no first-class integration pin — addedtest_legacy_record_without_a_tmux_target_heals_via_respawn(stub tmux, realTmuxRunner), which proves the respawn resumes the recorded conversation and back-fillstmuxTarget. - Self-review 3 (typing/format):
FakeTmux/StubInteractiveAdapternow subclass the realTmuxRunner/HarnessAdapterso pyright checks the doubles against the real contracts (caught aterminate_harnesssignature drift); ruff format drift on the new test fixed.
Security review (gate)
- Checklist outcome: pass. This change only deletes an execution path. No new inputs, flags, or privileged operations. The untrusted-payload boundary is unchanged (payload → prompt only, never argv/path/target);
_SESSION_ID_REstill gates recorded ids entering a resume argv;_LOOP_TARGET_REstill gates whatterminate_harnessmay signal. The abuse case frombugfix.md(doctored record steering a silent headless resume) is closed structurally — the silent path no longer exists, and the surviving path is loud (named tmux session, events, announce comment). Fail-closed: no tmux → refused start / failed dispatch, never invisible execution. Risk tier 3 <security.review.humanSignOffMinTier(4), so no named human sign-off is required beyond the PR approval.
Final validation evidence
Same commands the pre-commit hooks and CI run, all from the repo root:
uv run --project cli pytest cli/tests— 1155 passed, 2 skippeduv run ruff check cli hooks— All checks passeduv run ruff format --check cli hooks— 127 files already formatteduv run pyright cli— 0 errors, 0 warningsnpx --yes markdownlint-cli2@0.18.1 "**/*.md"— 375 files, 0 errorsuv run python scripts/validate_config.py— all six configs VALIDuv run the-loop check issue-156 --recompute --fail-on block— exit 0 (WAIT requirements-approval— the normal state of an open PR)