tickmarkr 2.6.6 → 2.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/adapters/claude-code.js +8 -2
  2. package/dist/adapters/codex-commit-check.d.ts +3 -2
  3. package/dist/adapters/codex-commit-check.js +37 -10
  4. package/dist/adapters/codex.js +10 -4
  5. package/dist/cli/commands/beat.d.ts +7 -2
  6. package/dist/cli/commands/beat.js +144 -77
  7. package/dist/cli/commands/fleet.js +66 -21
  8. package/dist/cli/commands/init.js +18 -7
  9. package/dist/cli/commands/plan.js +16 -18
  10. package/dist/cli/commands/verify.js +4 -2
  11. package/dist/cli/help.d.ts +1 -0
  12. package/dist/cli/help.js +14 -6
  13. package/dist/cli/index.d.ts +1 -1
  14. package/dist/cli/index.js +1 -1
  15. package/dist/drivers/herdr.js +2 -2
  16. package/dist/drivers/orca.d.ts +12 -4
  17. package/dist/drivers/orca.js +200 -39
  18. package/dist/gates/baseline.d.ts +9 -0
  19. package/dist/gates/baseline.js +14 -3
  20. package/dist/gates/cache.js +1 -1
  21. package/dist/gates/evidence.js +3 -3
  22. package/dist/gates/review.d.ts +1 -1
  23. package/dist/gates/review.js +77 -13
  24. package/dist/gates/run-gates.d.ts +21 -6
  25. package/dist/gates/run-gates.js +439 -224
  26. package/dist/gates/test-manifest.d.ts +42 -0
  27. package/dist/gates/test-manifest.js +203 -118
  28. package/dist/run/beat-lifecycle.d.ts +239 -0
  29. package/dist/run/beat-lifecycle.js +1782 -0
  30. package/dist/run/daemon.d.ts +4 -0
  31. package/dist/run/daemon.js +129 -40
  32. package/dist/run/git-trust.d.ts +102 -0
  33. package/dist/run/git-trust.js +334 -0
  34. package/dist/run/git.d.ts +24 -3
  35. package/dist/run/git.js +169 -30
  36. package/dist/run/journal.js +15 -3
  37. package/dist/run/lease.d.ts +23 -0
  38. package/dist/run/lease.js +232 -38
  39. package/dist/run/merge.js +4 -4
  40. package/dist/run/stall.js +20 -1
  41. package/dist/run/supervision.d.ts +8 -0
  42. package/dist/run/supervision.js +24 -4
  43. package/dist/run/verification-job.d.ts +53 -0
  44. package/dist/run/verification-job.js +159 -0
  45. package/dist/tui/ink/fleet-app.d.ts +16 -2
  46. package/dist/tui/ink/fleet-app.js +265 -141
  47. package/package.json +1 -1
  48. package/skills/tickmarkr-auto/SKILL.md +80 -27
  49. package/skills/tickmarkr-loop/SKILL.md +87 -27
  50. package/skills/tickmarkr-overseer/SKILL.md +125 -47
  51. package/skills/tickmarkr-overseer/scripts/watch-journal.sh +9 -7
@@ -12,8 +12,9 @@ Use the canonical loop skill's [cockpit, parked decisions and printed twins](../
12
12
  for each target: delivered views are **1 Home, 4 Run, 5 Evidence**; Fleet/Bootstrap and
13
13
  Plan/Health remain follow-ons with CLI entries. The recorded 1/3 merged, human T2, blocked
14
14
  T3 case stays PARTIAL despite tip pass. Review the Run confirmation, read back the appended
15
- `task-approved` receipt, and explicitly launch `tickmarkr resume <runId>` with the host-owned split form below when no live owner exists;
16
- approval records permission and never dispatches work itself. Non-TTY readers keep `status`,
15
+ `task-approved` receipt, and explicitly launch `tickmarkr resume <runId>` with the detached launch below when no live owner exists;
16
+ approval records permission and never dispatches work itself. A waive needs an `OOB: executed`
17
+ read at the task head (the walkthrough's record example); a static CLEAN never funds one. Non-TTY readers keep `status`,
17
18
  `report` and default-watch line output, with `--watch --plain` as the TTY fallback.
18
19
  Keep `name: tickmarkr-auto` and the installed links to canonical `skills/` intact.
19
20
 
@@ -92,37 +93,89 @@ After sending, **confirm delivery** by reading the target pane and verifying the
92
93
  1. **Prepare** — confirm the target list. Run the [binary preflight](#binary-preflight-before-compile-or-run). Check `git status`, confirm no tickmarkr run is active, and work from a non-main branch.
93
94
  2. **Compile** — run `tickmarkr compile <spec-or-directory>`. Fix source-spec defects instead of editing the generated graph.
94
95
  3. **Plan** — run `tickmarkr plan`. Review routes, capability-floor warnings, and human gates before execution.
95
- 4. **Run** — launch `tickmarkr run` using the [host-owned split form](#host-owned-daemon-and-detached-beats), and start detached beats with their run-end stop items. A watch ending the seat's turn is no watch: keep a **blocking journal consumer** alive for the run's terminal events — the shipped watcher below, or a foreground `until grep` on the run's terminal events — and ensure it is re-armed at most every twenty minutes. Never rely on a `Monitor`-only wake. Watch the run journal rather than polling agents, using the shipped watcher — `.claude/skills/tickmarkr-overseer/scripts/watch-journal.sh <state-dir>/runs 20 28800` — which takes a line baseline at arm time, then wakes ONCE on `run-end`, `task-human`, `task-failed` or `consult-verdict` and grades the run-end summary against every execution clause for you; the debt clause is yours — read CURRENT `tickmarkr status <runId>` (step 5). Re-arm after every wake. ⛔ Never `tail -F | grep -m1` (run-end is the journal's last line, so tail never notices the broken pipe and the watcher hangs forever) and never a pane-level done wait (it fires on every agent turn end, not mission end). ⚠ A bare whole-file `grep -q '"event":"run-end"'` is the trap the watcher exists to avoid: on a resume it matches the PREVIOUS run's run-end and returns instantly, so a re-armed watcher reads as coverage that does not exist. Resolve blocked interactions in the relevant agent session.
96
+ 4. **Run** — launch `tickmarkr run` using the [detached launch](#host-owned-daemon-and-detached-beats), and start detached beats with their run-end stop items. A watch ending the seat's turn is no watch: keep a **blocking journal consumer** alive for the run's terminal events — the shipped watcher below, or a foreground `until grep` on the run's terminal events — and ensure it is re-armed at most every twenty minutes. Never rely on a `Monitor`-only wake. Watch the run journal rather than polling agents, using the shipped watcher — `.claude/skills/tickmarkr-overseer/scripts/watch-journal.sh <state-dir>/runs 20 28800` — which takes a line baseline at arm time, then wakes ONCE on `run-end`, `task-human`, `task-failed` or `consult-verdict` and grades the run-end summary's execution clauses for you — `EXECUTION COMPLETE` or `NOT GREEN`, never green, because a run-end record is history; the debt clause is yours — read CURRENT `tickmarkr status <runId>` (step 5). Re-arm after every wake. ⛔ Never `tail -F | grep -m1` (run-end is the journal's last line, so tail never notices the broken pipe and the watcher hangs forever) and never a pane-level done wait (it fires on every agent turn end, not mission end). ⚠ A bare whole-file `grep -q '"event":"run-end"'` is the trap the watcher exists to avoid: on a resume it matches the PREVIOUS run's run-end and returns instantly, so a re-armed watcher reads as coverage that does not exist. Resolve blocked interactions in the relevant agent session.
96
97
  5. **Verify and consolidate** — continue only after a green run. A run is green when the run-end event exists in the journal, the tip verify is not "failed", the summary's `failed`, `human`, `blocked` and `pending` buckets are all empty, and CURRENT `tickmarkr status <runId>` reads its owed checks outstanding empty AND known (`outstanding 0`) — a run with a parked task is partial, not green. Empty execution buckets alone are not green either (D-660): every bucket empty and the tip passed, but an operator waived one review, leaves one accepted-risk review check owed — status reads `outstanding 1 (T7 review)` and `run`/`resume` still exit 0 on execution alone, so the run is execution complete, not green. It turns green only when a `tickmarkr verify --record <runId>` discharge moves CURRENT status to `outstanding 0`, including a discharge landing after run-end — the historical run-end record keeps the old count, so never read debt from it; `outstanding unknown` is never green. Tickmarkr consolidates accepted work on `tickmarkr/<runId>` and never signs off to the main branch. A human controls any later release merge.
97
98
  6. **Record** — `tickmarkr report <runId> --md` prints Markdown to stdout; redirect explicitly beside the spec (for example `tickmarkr report <runId> --md > feature.record.md`) and commit the execution record when the repository tracks those records.
98
99
  7. **Continue** — move to the next requested target. If a target fails or is parked, stop with the journal evidence rather than silently skipping it.
99
100
 
100
101
  ## Host-owned daemon and detached beats
101
102
 
102
- Launch the daemon as a SPLIT of the orchestrator's own pane in the ORCH tab, owned by the
103
- host PTY. Never launch it as an agent harness background task or in a separate tab. The daemon
104
- self-places its board from that split, keeping both daemon and board in the ORCH tab.
105
-
106
- - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**:
107
- `orca terminal split --terminal "$ORCA_TERMINAL_HANDLE" --direction vertical --command "tickmarkr run"`.
108
- - **On herdr (`HERDR_ENV=1`)**: use `herdr pane split` on the orchestrator's own pane, then
109
- `herdr pane run <new> "tickmarkr run"` in the returned split.
110
-
111
- For `resume`, use the same host-owned split form with `tickmarkr resume <runId>` in place of
112
- `tickmarkr run`. After either launch, read this repository's lock pid and walk its ppid chain
113
- in the process table to the host PTY; verify no agent session is an ancestor. If an agent is
114
- an ancestor, stop that launch and relaunch through the host split before continuing.
115
-
116
- Start `tickmarkr beat <tier> --seat <seat> --loop` as a DETACHED background process, one per
117
- (tier, seat), from the repository root. Use setsid plus nohup (or the host's equivalent detached
118
- session facility), redirect output to a file, and record a pidfile under the repository's state
119
- dir. Verify ppid 1 and no agent-session ancestor. Never put beats in a harness background task
120
- or a visible tab. The lifecycle must be idempotent: start checks the pidfile against the live
121
- process payload and returns an "already running" no-op for the same (repo, tier, seat); status
122
- checks that payload and beat freshness; stop sends `--stand-down` for the same tier and seat,
123
- verifies the loop exits within one interval, then removes its pidfile. A stale or reused pid
124
- must never be killed without matching its live payload. Include a run-end stop item for every
125
- beat started, also on failure, park or handoff; verify DISARMED and remove retired beat files.
103
+ Launch the daemon DETACHED, with no pane of its own: one `tickmarkr run` or `tickmarkr resume <runId>`
104
+ process in its own session, its stdout and stderr appended to `<state-dir>/daemon.log`, and the
105
+ ORCH's recorded address as its board anchor, so the daemon self-places its one board beside the
106
+ ORCH in the ORCH tab. Never launch it in a visible split, pane or tab, as an agent harness
107
+ background task, or tied to an agent session: the launching shell may end, and the daemon keeps
108
+ running and logging. The detach wrapper is `node`, which tickmarkr already requires — no private
109
+ script, no extra visible daemon pane and no CLI log flag:
110
+
111
+ ```bash
112
+ # On Orca: <ORCH handle> is the ORCH terminal handle
113
+ cd <repo> && ORCA_TERMINAL_HANDLE="<ORCH handle>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr run >> <state-dir>/daemon.log 2>&1 < /dev/null
114
+ cd <repo> && ORCA_TERMINAL_HANDLE="<ORCH handle>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr resume <runId> >> <state-dir>/daemon.log 2>&1 < /dev/null
115
+ # On herdr: <ORCH pane id> is the ORCH pane id
116
+ cd <repo> && HERDR_PANE_ID="<ORCH pane id>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr run >> <state-dir>/daemon.log 2>&1 < /dev/null
117
+ cd <repo> && HERDR_PANE_ID="<ORCH pane id>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr resume <runId> >> <state-dir>/daemon.log 2>&1 < /dev/null
118
+ ```
119
+
120
+ - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: run the
121
+ `ORCA_TERMINAL_HANDLE` lines; `<ORCH handle>` is the ORCH's own `$ORCA_TERMINAL_HANDLE`.
122
+ - **On herdr (`HERDR_ENV=1`)**: run the `HERDR_PANE_ID` lines; `<ORCH pane id>` is the ORCH's own
123
+ `$HERDR_PANE_ID`.
124
+
125
+ After either launch, read this repository's lock pid from `<state-dir>/graph.lock` and confirm it:
126
+ `kill -0 <pid>` succeeds, `ps -o pgid= -p <pid>` prints that same pid (its own session group;
127
+ ppid 1 is never proof on its own), walk its ppid chain and verify no agent session is an ancestor,
128
+ and read `<state-dir>/daemon.log`. No live holder means the launch failed: read the log, fix the
129
+ cause and run the same line again — never a second daemon beside a live holder, and never a
130
+ visible split to watch it; the daemon-placed board is the live surface.
131
+
132
+ Detached beats are product-owned, one per (tier, seat), from the repository root, through the
133
+ shipped lifecycle verbs — never a hand-rolled setsid plus nohup wrapper, pidfile or shell loop:
134
+
135
+ ```bash
136
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
137
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
138
+ cd <repo> && tickmarkr beat stop <tier> --seat <seat>
139
+ ```
140
+
141
+ `start` launches the `--loop` child in its own session, logs to `<state-dir>/supervision/<tier>.log`,
142
+ records its pid, birth, argv and cwd, and exits 0 only after it reads back that exact child's own
143
+ advancing beat. A repeat start for the same (tier, seat) is an "already running" no-op; a stale,
144
+ dead, foreign, reused-pid, unreadable or busy tier is a nonzero refusal that changes nothing.
145
+ Verify ppid 1 is never proof on its own — an orphan of a dying session shows it too; `status`
146
+ is the check: it reads the recorded process identity and beat freshness, writes nothing, and exits
147
+ nonzero for STALE, MISMATCH or UNREADABLE. `stop` signals only that recorded pid after its
148
+ generation and identity still match, and reads back DISARMED. Never put beats in a harness
149
+ background task or a visible tab. Include a run-end `beat stop` item for every beat started, also
150
+ on failure, park or handoff, and require its DISARMED read-back.
151
+
152
+ **Legacy beat migration.** `beat start` refuses a legacy (pre-lifecycle, unowned) arm nonzero and
153
+ prints its exit under the RECORDED seat; `beat stop` refuses it too, so a stop followed by a start
154
+ is a dead end, never a migration. When the refusal names `stop pid <N> first`, that pid is the old
155
+ `--loop` writer: stop exactly that pid yourself (`kill <N>`, then confirm `kill -0 <N>` fails) —
156
+ tickmarkr never signals a process it did not launch. Then run the printed step under the seat it
157
+ names, start again and read back ARMED:
158
+
159
+ ```bash
160
+ cd <repo> && tickmarkr beat <tier> --seat <recorded seat> --stand-down
161
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
162
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
163
+ ```
164
+
165
+ **Crash recovery.** A crashed owner, a writer killed inside its claimed tick or a killed recovery
166
+ taker leaves its claim, removal lock or stage directory behind. `beat status` only observes: it
167
+ exits nonzero, names the dead pids, prints the command that recovers them and changes no file —
168
+ status never recovers. Run the command it prints; the next ordinary `stop` or `start` recovers what
169
+ was left, with a notice naming each dead pid, and the start reads back ARMED:
170
+
171
+ ```bash
172
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
173
+ cd <repo> && tickmarkr beat stop <tier> --seat <seat>
174
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
175
+ ```
176
+
177
+ Never delete a claim, lock or stage file by hand. A live or unprovable holder refuses BUSY naming
178
+ it: wait for that holder to finish or exit, then run the same command.
126
179
 
127
180
  Keep only watchers that must WAKE the seat in the harness. Re-arm them on each wake and at
128
181
  their harness cap; daemon and beat lifetimes must not depend on that cap.
@@ -88,36 +88,88 @@ When spawning consultants (agents gathering synthesis input for decisions like S
88
88
  1. **Prepare** — start from the requested spec. Run the [binary preflight](#binary-preflight-before-compile-or-run). Check `git status`, confirm no tickmarkr run is active, and work from a non-main branch.
89
89
  2. **Compile** — run `tickmarkr compile <spec>`. Correct compilation errors in the spec, never in the generated graph.
90
90
  3. **Plan** — run `tickmarkr plan`. Review the routing table, capability-floor warnings, and every human gate, including work that each gate blocks.
91
- 4. **Run** — launch `tickmarkr run` using the [host-owned split form](#host-owned-daemon-and-detached-beats), and start detached beats with their run-end stop items. A watch ending the seat's turn is no watch: keep a **blocking journal consumer** alive for the run's terminal events — the shipped watcher below, or a foreground `until grep` on the run's terminal events — and ensure it is re-armed at most every twenty minutes. Never rely on a `Monitor`-only wake. Watch the run journal rather than polling agents, using the shipped watcher — `.claude/skills/tickmarkr-overseer/scripts/watch-journal.sh <state-dir>/runs 20 28800` — which takes a line baseline at arm time, then wakes ONCE on `run-end`, `task-human`, `task-failed` or `consult-verdict` and grades the run-end summary against every execution clause for you; the debt clause is yours — read CURRENT `tickmarkr status <runId>` (step 5). Re-arm after every wake. ⛔ Never `tail -F | grep -m1` (run-end is the journal's last line, so tail never notices the broken pipe and the watcher hangs forever) and never a pane-level done wait (it fires on every agent turn end, not mission end). ⚠ A bare whole-file `grep -q '"event":"run-end"'` is the trap the watcher exists to avoid: on a resume it matches the PREVIOUS run's run-end and returns instantly, so a re-armed watcher reads as coverage that does not exist. Resolve blocked interactions in the agent session; do not turn them into proxy questions.
91
+ 4. **Run** — launch `tickmarkr run` using the [detached launch](#host-owned-daemon-and-detached-beats), and start detached beats with their run-end stop items. A watch ending the seat's turn is no watch: keep a **blocking journal consumer** alive for the run's terminal events — the shipped watcher below, or a foreground `until grep` on the run's terminal events — and ensure it is re-armed at most every twenty minutes. Never rely on a `Monitor`-only wake. Watch the run journal rather than polling agents, using the shipped watcher — `.claude/skills/tickmarkr-overseer/scripts/watch-journal.sh <state-dir>/runs 20 28800` — which takes a line baseline at arm time, then wakes ONCE on `run-end`, `task-human`, `task-failed` or `consult-verdict` and grades the run-end summary's execution clauses for you — `EXECUTION COMPLETE` or `NOT GREEN`, never green, because a run-end record is history; the debt clause is yours — read CURRENT `tickmarkr status <runId>` (step 5). Re-arm after every wake. ⛔ Never `tail -F | grep -m1` (run-end is the journal's last line, so tail never notices the broken pipe and the watcher hangs forever) and never a pane-level done wait (it fires on every agent turn end, not mission end). ⚠ A bare whole-file `grep -q '"event":"run-end"'` is the trap the watcher exists to avoid: on a resume it matches the PREVIOUS run's run-end and returns instantly, so a re-armed watcher reads as coverage that does not exist. Resolve blocked interactions in the agent session; do not turn them into proxy questions.
92
92
  5. **Verify and consolidate** — accept only a green run. A run is green when the run-end event exists in the journal, the tip verify is not "failed", the summary's `failed`, `human`, `blocked` and `pending` buckets are all empty, and CURRENT `tickmarkr status <runId>` reads its owed checks outstanding empty AND known (`outstanding 0`) — a run with a parked task is partial, not green. Empty execution buckets alone are not green either (D-660): every bucket empty and the tip passed, but an operator waived one review, leaves one accepted-risk review check owed — status reads `outstanding 1 (T7 review)` and `run`/`resume` still exit 0 on execution alone, so the run is execution complete, not green. It turns green only when a `tickmarkr verify --record <runId>` discharge moves CURRENT status to `outstanding 0`, including a discharge landing after run-end — the historical run-end record keeps the old count, so never read debt from it; `outstanding unknown` is never green. Tickmarkr consolidates accepted task work on `tickmarkr/<runId>`; it never signs off to the main branch. A human may later merge that integration branch through the repository's normal release process.
93
93
  6. **Record** — `tickmarkr report <runId> --md` prints Markdown to stdout. Redirect it explicitly beside the source spec (for example `tickmarkr report <runId> --md > feature.record.md`) and commit the execution record when the repository tracks those records. Then [stand down](#stand-down-mission-end-and-retirement).
94
94
 
95
95
  ## Host-owned daemon and detached beats
96
96
 
97
- Launch the daemon as a SPLIT of the orchestrator's own pane in the ORCH tab, owned by the
98
- host PTY. Never launch it as an agent harness background task or in a separate tab. The daemon
99
- self-places its board from that split, keeping both daemon and board in the ORCH tab.
100
-
101
- - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**:
102
- `orca terminal split --terminal "$ORCA_TERMINAL_HANDLE" --direction vertical --command "tickmarkr run"`.
103
- - **On herdr (`HERDR_ENV=1`)**: use `herdr pane split` on the orchestrator's own pane, then
104
- `herdr pane run <new> "tickmarkr run"` in the returned split.
105
-
106
- For `resume`, use the same host-owned split form with `tickmarkr resume <runId>` in place of
107
- `tickmarkr run`. After either launch, read this repository's lock pid and walk its ppid chain
108
- in the process table to the host PTY; verify no agent session is an ancestor. If an agent is
109
- an ancestor, stop that launch and relaunch through the host split before continuing.
110
-
111
- Start `tickmarkr beat <tier> --seat <seat> --loop` as a DETACHED background process, one per
112
- (tier, seat), from the repository root. Use setsid plus nohup (or the host's equivalent detached
113
- session facility), redirect output to a file, and record a pidfile under the repository's state
114
- dir. Verify ppid 1 and no agent-session ancestor. Never put beats in a harness background task
115
- or a visible tab. The lifecycle must be idempotent: start checks the pidfile against the live
116
- process payload and returns an "already running" no-op for the same (repo, tier, seat); status
117
- checks that payload and beat freshness; stop sends `--stand-down` for the same tier and seat,
118
- verifies the loop exits within one interval, then removes its pidfile. A stale or reused pid
119
- must never be killed without matching its live payload. Include a run-end stop item for every
120
- beat started, also on failure, park or handoff; verify DISARMED and remove retired beat files.
97
+ Launch the daemon DETACHED, with no pane of its own: one `tickmarkr run` or `tickmarkr resume <runId>`
98
+ process in its own session, its stdout and stderr appended to `<state-dir>/daemon.log`, and the
99
+ ORCH's recorded address as its board anchor, so the daemon self-places its one board beside the
100
+ ORCH in the ORCH tab. Never launch it in a visible split, pane or tab, as an agent harness
101
+ background task, or tied to an agent session: the launching shell may end, and the daemon keeps
102
+ running and logging. The detach wrapper is `node`, which tickmarkr already requires — no private
103
+ script, no extra visible daemon pane and no CLI log flag:
104
+
105
+ ```bash
106
+ # On Orca: <ORCH handle> is the ORCH terminal handle
107
+ cd <repo> && ORCA_TERMINAL_HANDLE="<ORCH handle>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr run >> <state-dir>/daemon.log 2>&1 < /dev/null
108
+ cd <repo> && ORCA_TERMINAL_HANDLE="<ORCH handle>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr resume <runId> >> <state-dir>/daemon.log 2>&1 < /dev/null
109
+ # On herdr: <ORCH pane id> is the ORCH pane id
110
+ cd <repo> && HERDR_PANE_ID="<ORCH pane id>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr run >> <state-dir>/daemon.log 2>&1 < /dev/null
111
+ cd <repo> && HERDR_PANE_ID="<ORCH pane id>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr resume <runId> >> <state-dir>/daemon.log 2>&1 < /dev/null
112
+ ```
113
+
114
+ - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: run the
115
+ `ORCA_TERMINAL_HANDLE` lines; `<ORCH handle>` is the ORCH's own `$ORCA_TERMINAL_HANDLE`.
116
+ - **On herdr (`HERDR_ENV=1`)**: run the `HERDR_PANE_ID` lines; `<ORCH pane id>` is the ORCH's own
117
+ `$HERDR_PANE_ID`.
118
+
119
+ After either launch, read this repository's lock pid from `<state-dir>/graph.lock` and confirm it:
120
+ `kill -0 <pid>` succeeds, `ps -o pgid= -p <pid>` prints that same pid (its own session group;
121
+ ppid 1 is never proof on its own), walk its ppid chain and verify no agent session is an ancestor,
122
+ and read `<state-dir>/daemon.log`. No live holder means the launch failed: read the log, fix the
123
+ cause and run the same line again — never a second daemon beside a live holder, and never a
124
+ visible split to watch it; the daemon-placed board is the live surface.
125
+
126
+ Detached beats are product-owned, one per (tier, seat), from the repository root, through the
127
+ shipped lifecycle verbs — never a hand-rolled setsid plus nohup wrapper, pidfile or shell loop:
128
+
129
+ ```bash
130
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
131
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
132
+ cd <repo> && tickmarkr beat stop <tier> --seat <seat>
133
+ ```
134
+
135
+ `start` launches the `--loop` child in its own session, logs to `<state-dir>/supervision/<tier>.log`,
136
+ records its pid, birth, argv and cwd, and exits 0 only after it reads back that exact child's own
137
+ advancing beat. A repeat start for the same (tier, seat) is an "already running" no-op; a stale,
138
+ dead, foreign, reused-pid, unreadable or busy tier is a nonzero refusal that changes nothing.
139
+ Verify ppid 1 is never proof on its own — an orphan of a dying session shows it too; `status`
140
+ is the check: it reads the recorded process identity and beat freshness, writes nothing, and exits
141
+ nonzero for STALE, MISMATCH or UNREADABLE. `stop` signals only that recorded pid after its
142
+ generation and identity still match, and reads back DISARMED. Never put beats in a harness
143
+ background task or a visible tab. Include a run-end `beat stop` item for every beat started, also
144
+ on failure, park or handoff, and require its DISARMED read-back.
145
+
146
+ **Legacy beat migration.** `beat start` refuses a legacy (pre-lifecycle, unowned) arm nonzero and
147
+ prints its exit under the RECORDED seat; `beat stop` refuses it too, so a stop followed by a start
148
+ is a dead end, never a migration. When the refusal names `stop pid <N> first`, that pid is the old
149
+ `--loop` writer: stop exactly that pid yourself (`kill <N>`, then confirm `kill -0 <N>` fails) —
150
+ tickmarkr never signals a process it did not launch. Then run the printed step under the seat it
151
+ names, start again and read back ARMED:
152
+
153
+ ```bash
154
+ cd <repo> && tickmarkr beat <tier> --seat <recorded seat> --stand-down
155
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
156
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
157
+ ```
158
+
159
+ **Crash recovery.** A crashed owner, a writer killed inside its claimed tick or a killed recovery
160
+ taker leaves its claim, removal lock or stage directory behind. `beat status` only observes: it
161
+ exits nonzero, names the dead pids, prints the command that recovers them and changes no file —
162
+ status never recovers. Run the command it prints; the next ordinary `stop` or `start` recovers what
163
+ was left, with a notice naming each dead pid, and the start reads back ARMED:
164
+
165
+ ```bash
166
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
167
+ cd <repo> && tickmarkr beat stop <tier> --seat <seat>
168
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
169
+ ```
170
+
171
+ Never delete a claim, lock or stage file by hand. A live or unprovable holder refuses BUSY naming
172
+ it: wait for that holder to finish or exit, then run the same command.
121
173
 
122
174
  Keep only watchers that must WAKE the seat in the harness. Re-arm them on each wake and at
123
175
  their harness cap; daemon and beat lifetimes must not depend on that cap.
@@ -137,7 +189,7 @@ run/task, original park `#L`, actor/reason, exact argv, consequence and enactor.
137
189
  confirms; `n`/Esc cancel; Enter never confirms. Read the receipt's appended `task-approved`
138
190
  line and actor/reason back from the journal. Approval records permission, never dispatch,
139
191
  a passed gate or task completion. With no live owner it says **approved; resume required**:
140
- exit the observer and launch `tickmarkr resume <runId>` explicitly with the same host-owned split form above. A matching live daemon
192
+ exit the observer and launch `tickmarkr resume <runId>` explicitly with the same detached launch above. A matching live daemon
141
193
  enacts at its next task boundary; if a different live run owns the repository lock, wait
142
194
  for that run to end before resuming this one.
143
195
 
@@ -165,7 +217,15 @@ Run offers only validated park verbs: human/attempt-cap/other non-gate parks all
165
217
  infra allows approve or `--recheck`; review gate-fail allows `--waive`, `--uphold` or
166
218
  `--recheck`; other gate-fail allows waive/recheck. Waive satisfies only the identified
167
219
  failed gate, uphold funds a fixed attempt carrying review findings, and recheck reruns the
168
- declared battery without satisfying a gate. A stall park that recorded a `reapFailure`
220
+ declared battery without satisfying a gate. A waive needs EXECUTED evidence: mark every
221
+ out-of-band (OOB) read behind it `OOB: static` (diff and source read, nothing run) or
222
+ `OOB: executed` (the finding's claim run at the task's head commit, exit code and log on file).
223
+ Static CLEAN never backs a waive — a static read that missed an executed material defect looks
224
+ like one that found none, and a static read relabelled executed is a false record. When the
225
+ finding's reproduction, executed at the task head, fails, the ruling is uphold or recheck; waive
226
+ only when it passes and the record names it, keeping the park/gate binding and the owed check
227
+ (`outstanding 1 (T2 review)` until `tickmarkr verify --record <runId>`):
228
+ `D-NNN WAIVE T2 review — park <line>@<ts> --gate review — OOB: executed — <reproduction> at <task-head-sha>: exit 0, log <path>`. A stall park that recorded a `reapFailure`
169
229
  (unreadable or surviving worker census) allows approve or `--recheck --park <line>@<ts>`:
170
230
  recheck re-verifies that attempt's owned census and gates its harvested commits with no
171
231
  worker only with an explicitly recorded empty survivors array (`[]`) — a missing,
@@ -26,6 +26,30 @@ again on the new token; never re-issue the old one (D-470: an unbound approve wa
26
26
  gate). A failed task keeps its recheck with its bound failure token: status prints
27
27
  `failed — T3 — failure <line>@<ts>`, and `tickmarkr approve <runId> T3 --recheck --park <line>@<ts>`
28
28
  re-gates its landed commits with no worker.
29
+
30
+ **A waive is funded by EXECUTED evidence, never by a static read.** Every out-of-band (OOB) read
31
+ behind a ruling records its mode: `OOB: static` — the diff and source were read and nothing ran — or
32
+ `OOB: executed` — the finding's claim was run against the task's head commit (a test, a reproduction
33
+ or a command) with its exit code and log path on file. Only an executed read backs a waive. A static
34
+ CLEAN never does, however confident: a static read that missed an executed material defect reads
35
+ exactly like one that found nothing, and a static read relabelled `executed` is a false record.
36
+ Replay it before ruling. The reviewer's material finding says a killed taker strands the claim; a
37
+ static OOB read of the diff reports CLEAN; the finding's reproduction, executed at the task head,
38
+ exits 1 with the strand on disk — that static CLEAN would have waived a real defect, so the ruling
39
+ is uphold or recheck. Waive only on a supported executed read: the same reproduction at the task head
40
+ exits 0, its log is on file, and the record names both. The waive keeps its park and gate binding and
41
+ its debt: CURRENT `tickmarkr status <runId>` reads `outstanding 1 (T2 review)` until
42
+ `tickmarkr verify --record <runId>` discharges it. Record example:
43
+
44
+ ```text
45
+ D-NNN WAIVE T2 review — park <line>@<ts> --gate review
46
+ OOB: executed — `<reproduction>` at <task-head-sha>: exit 0, log <path>
47
+ finding: <the reviewer's material claim> — not reproduced by that execution
48
+ debt: outstanding 1 (T2 review) until `tickmarkr verify --record <runId>`
49
+ ```
50
+
51
+ A record whose OOB line reads `static`, or names no command, exit code and log, funds no waive: the
52
+ ruling is uphold or recheck.
29
53
  Manual UI retains the receipt; the daemon-owned board gracefully stands down only its own
30
54
  presence before closing its owned pane. Non-TTY supervision keeps `status`, `report` and
31
55
  default-watch line output (`--watch --plain` is also available on a TTY). Keep the canonical
@@ -121,13 +145,13 @@ through brief lineage. **An executor choice nobody made is still an executor cho
121
145
  tab OVERSEER; create ONE tab ORCHESTRATOR.
122
146
  **FIVE-TAB CANON (standing operator layout — corrected three times on 2026-07-27, layout approved
123
147
  2026-07-29, re-earned 2026-08-17):**
124
- - `OVERSEER` — you. Do not add a live run surface here: launch the daemon from the ORCH tab
125
- using the host-owned split form below; it self-places the shipped board there.
126
- - `ORCH` — the orchestrator, its host-owned daemon split, and the daemon-placed, run-id-pinned shipped board BESIDE it: the board
148
+ - `OVERSEER` — you. Do not add a live run surface here: launch the daemon detached at the ORCH's
149
+ recorded address (the detached launch below); it self-places the shipped board in the ORCH tab.
150
+ - `ORCH` — the orchestrator and the daemon-placed, run-id-pinned shipped board BESIDE it (the daemon itself runs detached, with no pane): the board
127
151
  takes the RIGHT half of the tab and the orchestrator's own narration keeps the LEFT half. (It was a
128
152
  full-width board above a narration rail until 2026-08-25; the operator changed it, because a task
129
153
  table is a few rows and it was spending height it did not need while squeezing the narration.) **Look for that `role: "watch"` pane; never hand-place or hand-roll a live
130
- run surface. Only the orchestrator, daemon and board belong here: a work seat NEVER splits into the ORCH tab.** Operator verbatim:
154
+ run surface. Only the orchestrator and board belong here: a work seat NEVER splits into the ORCH tab.** Operator verbatim:
131
155
  *"in orch tab should be the orch and the watcher only."* Re-earned 2026-08-17: a planning seat split
132
156
  beside the orchestrator, and the operator caught it, again. The daemon owns this vertical stack and
133
157
  places it the same way at every terminal width; neither the worker-pane halving floor, nor a
@@ -147,7 +171,7 @@ through brief lineage. **An executor choice nobody made is still an executor cho
147
171
  fraction (`ORCH · v1.19 4/5`, updated on every task-done); tickmarkr opens ONE TAB PER TASK, labelled
148
172
  with the task id and holding that task's worker plus its judge/review/consult panes (tickmarkr
149
173
  updates it). Never long context strings or ✓-chains.
150
- - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: Do not load the `herdr` skill and do not map a Herdr workspace. Work from the current Orca terminal context. Keep the overseer in the launching terminal, name it in the same act — `orca terminal rename --terminal "$ORCA_TERMINAL_HANDLE" --title "OVERSEER · <version>"` (tab title; see the seat-name law under Seat-spawn recipes) — and inspect terminals with `orca terminal list --json`. Launch the daemon in a host-owned split of the orchestrator terminal using the form below; its self-placed watch board stays in the ORCH tab.
174
+ - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: Do not load the `herdr` skill and do not map a Herdr workspace. Work from the current Orca terminal context. Keep the overseer in the launching terminal, name it in the same act — `orca terminal rename --terminal "$ORCA_TERMINAL_HANDLE" --title "OVERSEER · <version>"` (tab title; see the seat-name law under Seat-spawn recipes) — and inspect terminals with `orca terminal list --json`. Launch the daemon detached at the orchestrator terminal's recorded handle using the detached launch below — no pane of its own; its self-placed watch board stays in the ORCH tab.
151
175
  2. **Orchestrator**: First consume a successful `tickmarkr fleet --pick consult` in the seat repository, per the Fleet selection contract below; then launch the returned adapter/model with your agent host.
152
176
  - **On herdr (`HERDR_ENV=1`)**: Spawning on current herdr is two-step — the one-shot `agent start --cwd` form was removed in the herdr CLI redesign and now fails with `unknown option` (OBS-138): first create the pane with `herdr tab create --workspace <ws> --cwd <repo> --label "ORCH · <version>"` and parse `result.root_pane.pane_id` from its JSON, then start the agent in it. For Claude Code, use `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions` (append `--model <picked-model>` after the `--` from the Fleet receipt). For Codex, use `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` (add `--model <picked-model>` from the Fleet receipt). The unsandboxed flag is REQUIRED: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation — do not downgrade it. Workers you never spawn — tickmarkr spawns its own visible worker panes. Auxiliary agents you do spawn (consultants, reviewers, scouts) follow the same forms: never launch a claude session in plan mode or default permission mode for autonomous work — both stall on per-command approval prompts nobody is watching; claude is always `--permission-mode bypassPermissions --settings '{"promptSuggestionEnabled":false}'`. **For a codex consultant, use `-a never --sandbox workspace-write` — NOT `--sandbox read-only`.** ⚠ **`--sandbox read-only` CONTRADICTS this skill's own completion protocol and will hang the seat.** Every seat you spawn is told to deliver an ARTIFACT ending in a terminal MARKER, because that is the only completion signal the artifact watcher can key on (`done` is turn end). A read-only sandbox cannot write that artifact, so codex blocks on `Would you like to make the following edits?` for its OWN report — and the report exists ONLY in the pending edit, so abandoning the prompt destroys the work rather than merely delaying it. Measured 2026-08-28: a consultant spawned `--sandbox read-only` finished a 14,604-byte verdict, sat blocked on the write, and the operator saw the prompt before the supervising tier did. `read-only` is correct ONLY for a seat that writes nothing at all — which, under the artifact+marker rule, is no seat this skill tells you to spawn. When the prompt does appear, answer **"Yes, and don't ask again for these files"** rather than plain yes: plain yes re-blocks on the next write of the same file. **That `--settings` pair is not cosmetic and it is not optional:** claude-code's AUTOSUGGEST renders context-plausible ghost text into an idle seat's prompt line that is BYTE-IDENTICAL to a typed draft in text-format reads (OBS-482), so a supervising tier cannot tell a seat's own unsent work from a rendering artifact without `agent read --format ansi`. Turning the suggester off at spawn removes the ambiguity at its source instead of paying for the discrimination at every read. Verified against the shipped binary: `claude --settings '{"promptSuggestionEnabled":false}' -p …` exits 0 with a real response, and the key appears in the binary's own settings schema. **For kimi, pass `-y`** (`herdr agent start <name> --kind kimi --pane <id> -- -y`) — the adapter already launches its own workers that way (`src/adapters/kimi.ts:204`), and a kimi seat spawned without it sits on an approval prompt having done nothing. **Herdr cannot see that state**: it reports a kimi pane as `agent_status: working` with `screen_detection_skipped: true` while the prompt is up, so the BLOCKED-STATE watcher below is blind on this vendor and the spawn flag is the ONLY control. Every vendor you spawn needs its auto-approve form named here; a vendor absent from this list is a seat that will hang.
153
177
  - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: Seats spawn with `orca terminal create` on a path worktree selector with a command:
@@ -197,20 +221,20 @@ through brief lineage. **An executor choice nobody made is still an executor cho
197
221
  The orchestrator's run log, handoff, and plan live inside its sandbox root and are artifacts of that orchestrator.
198
222
  The overseer reads them from there, using the path the orchestrator reports; do not substitute the
199
223
  overseer's repository or a machine-global planning directory. The orchestrator brief must say: if the seat's
200
- sandbox denies writes under `.git`, report the denial to the overseer and stop; the overseer launches the daemon itself; use the host-owned split of the ORCH pane, never the overseer tab;
224
+ sandbox denies writes under `.git`, report the denial to the overseer and stop; the overseer launches the daemon itself; use the detached launch at the recorded ORCH address, never a pane and never the overseer tab;
201
225
  and the daemon host is never sandboxed.
202
226
 
203
227
  **Sandbox-denial daemon launch (OVERSEER):** use the ORCH address recorded at seat creation,
204
228
  re-resolved by title if stale; the launching overseer's own terminal is not the target.
205
229
 
206
230
  - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: record the ORCH's
207
- `result.terminal.handle` from its seat-create receipt. For the fallback, run
208
- `orca terminal split --terminal <ORCH handle> --direction vertical --command "tickmarkr run"`.
231
+ `result.terminal.handle` from its seat-create receipt. For the fallback, run the detached
232
+ `ORCA_TERMINAL_HANDLE="<ORCH handle>"` line of the detached launch below with that recorded handle.
209
233
  Here `$ORCA_TERMINAL_HANDLE` names the OVERSEER's own terminal; use it only when the ORCH
210
234
  launches its own daemon, never when the OVERSEER launches on the ORCH's behalf.
211
235
  - **On herdr (`HERDR_ENV=1`)**: record the ORCH's `result.root_pane.pane_id` from its tab-create
212
- receipt. For the fallback, use `herdr pane split` targeting that recorded ORCH pane id, then
213
- `herdr pane run <new> "tickmarkr run"`; never target the OVERSEER's pane.
236
+ receipt. For the fallback, run the detached `HERDR_PANE_ID="<ORCH pane id>"` line of the detached
237
+ launch below with that recorded ORCH pane id; never target the OVERSEER's pane.
214
238
 
215
239
  The same recorded ORCH address is required for a fallback `tickmarkr resume <runId>`.
216
240
 
@@ -308,34 +332,86 @@ For Leg-2 (both hosts), a Codex reviewer under `workspace-write` must be briefed
308
332
  `<repo>/.tickmarkr/overseer/verdicts/<task>.md`, and its verdict must be written there before it is read.
309
333
  ## Host-owned daemon and detached beats
310
334
 
311
- Launch the daemon as a SPLIT of the orchestrator's own pane in the ORCH tab, owned by the
312
- host PTY. Never launch it as an agent harness background task or in a separate tab. The daemon
313
- self-places its board from that split, keeping both daemon and board in the ORCH tab.
314
-
315
- - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: only when the ORCH
316
- launches its own daemon, use
317
- `orca terminal split --terminal "$ORCA_TERMINAL_HANDLE" --direction vertical --command "tickmarkr run"`.
318
- When the OVERSEER launches the sandbox-denial fallback, substitute the ORCH handle recorded
319
- at seat creation: `orca terminal split --terminal <ORCH handle> --direction vertical --command "tickmarkr run"`.
320
- - **On herdr (`HERDR_ENV=1`)**: use `herdr pane split` on the orchestrator's own pane, then
321
- `herdr pane run <new> "tickmarkr run"` in the returned split. When the OVERSEER launches the
322
- fallback, target the ORCH pane id recorded at seat creation, never the OVERSEER's pane.
323
-
324
- For `resume`, use the same host-owned split form with `tickmarkr resume <runId>` in place of
325
- `tickmarkr run`. After either launch, read this repository's lock pid and walk its ppid chain
326
- in the process table to the host PTY; verify no agent session is an ancestor. If an agent is
327
- an ancestor, stop that launch and relaunch through the host split before continuing.
328
-
329
- Start `tickmarkr beat <tier> --seat <seat> --loop` as a DETACHED background process, one per
330
- (tier, seat), from the repository root. Use setsid plus nohup (or the host's equivalent detached
331
- session facility), redirect output to a file, and record a pidfile under the repository's state
332
- dir. Verify ppid 1 and no agent-session ancestor. Never put beats in a harness background task
333
- or a visible tab. The lifecycle must be idempotent: start checks the pidfile against the live
334
- process payload and returns an "already running" no-op for the same (repo, tier, seat); status
335
- checks that payload and beat freshness; stop sends `--stand-down` for the same tier and seat,
336
- verifies the loop exits within one interval, then removes its pidfile. A stale or reused pid
337
- must never be killed without matching its live payload. Include a run-end stop item for every
338
- beat started, also on failure, park or handoff; verify DISARMED and remove retired beat files.
335
+ Launch the daemon DETACHED, with no pane of its own: one `tickmarkr run` or `tickmarkr resume <runId>`
336
+ process in its own session, its stdout and stderr appended to `<state-dir>/daemon.log`, and the
337
+ ORCH's recorded address as its board anchor, so the daemon self-places its one board beside the
338
+ ORCH in the ORCH tab. Never launch it in a visible split, pane or tab, as an agent harness
339
+ background task, or tied to an agent session: the launching shell may end, and the daemon keeps
340
+ running and logging. The detach wrapper is `node`, which tickmarkr already requires — no private
341
+ script, no extra visible daemon pane and no CLI log flag:
342
+
343
+ ```bash
344
+ # On Orca: <ORCH handle> is the ORCH terminal handle
345
+ cd <repo> && ORCA_TERMINAL_HANDLE="<ORCH handle>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr run >> <state-dir>/daemon.log 2>&1 < /dev/null
346
+ cd <repo> && ORCA_TERMINAL_HANDLE="<ORCH handle>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr resume <runId> >> <state-dir>/daemon.log 2>&1 < /dev/null
347
+ # On herdr: <ORCH pane id> is the ORCH pane id
348
+ cd <repo> && HERDR_PANE_ID="<ORCH pane id>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr run >> <state-dir>/daemon.log 2>&1 < /dev/null
349
+ cd <repo> && HERDR_PANE_ID="<ORCH pane id>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr resume <runId> >> <state-dir>/daemon.log 2>&1 < /dev/null
350
+ ```
351
+
352
+ - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: run the
353
+ `ORCA_TERMINAL_HANDLE` lines. Only when the ORCH
354
+ launches its own daemon is `<ORCH handle>` its own `$ORCA_TERMINAL_HANDLE`. When the OVERSEER
355
+ launches the sandbox-denial fallback, substitute the ORCH handle recorded
356
+ at seat creation; the OVERSEER's own `$ORCA_TERMINAL_HANDLE` would put the board in its tab.
357
+ - **On herdr (`HERDR_ENV=1`)**: run the `HERDR_PANE_ID` lines; `<ORCH pane id>` is the ORCH's own
358
+ `$HERDR_PANE_ID`. When the OVERSEER launches the fallback, substitute the ORCH pane id recorded
359
+ at seat creation, never the OVERSEER's pane.
360
+
361
+ After either launch, read this repository's lock pid from `<state-dir>/graph.lock` and confirm it:
362
+ `kill -0 <pid>` succeeds, `ps -o pgid= -p <pid>` prints that same pid (its own session group;
363
+ ppid 1 is never proof on its own), walk its ppid chain and verify no agent session is an ancestor,
364
+ and read `<state-dir>/daemon.log`. No live holder means the launch failed: read the log, fix the
365
+ cause and run the same line again — never a second daemon beside a live holder, and never a
366
+ visible split to watch it; the daemon-placed board is the live surface.
367
+
368
+ Detached beats are product-owned, one per (tier, seat), from the repository root, through the
369
+ shipped lifecycle verbs — never a hand-rolled setsid plus nohup wrapper, pidfile or shell loop:
370
+
371
+ ```bash
372
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
373
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
374
+ cd <repo> && tickmarkr beat stop <tier> --seat <seat>
375
+ ```
376
+
377
+ `start` launches the `--loop` child in its own session, logs to `<state-dir>/supervision/<tier>.log`,
378
+ records its pid, birth, argv and cwd, and exits 0 only after it reads back that exact child's own
379
+ advancing beat. A repeat start for the same (tier, seat) is an "already running" no-op; a stale,
380
+ dead, foreign, reused-pid, unreadable or busy tier is a nonzero refusal that changes nothing.
381
+ Verify ppid 1 is never proof on its own — an orphan of a dying session shows it too; `status`
382
+ is the check: it reads the recorded process identity and beat freshness, writes nothing, and exits
383
+ nonzero for STALE, MISMATCH or UNREADABLE. `stop` signals only that recorded pid after its
384
+ generation and identity still match, and reads back DISARMED. Never put beats in a harness
385
+ background task or a visible tab. Include a run-end `beat stop` item for every beat started, also
386
+ on failure, park or handoff, and require its DISARMED read-back.
387
+
388
+ **Legacy beat migration.** `beat start` refuses a legacy (pre-lifecycle, unowned) arm nonzero and
389
+ prints its exit under the RECORDED seat; `beat stop` refuses it too, so a stop followed by a start
390
+ is a dead end, never a migration. When the refusal names `stop pid <N> first`, that pid is the old
391
+ `--loop` writer: stop exactly that pid yourself (`kill <N>`, then confirm `kill -0 <N>` fails) —
392
+ tickmarkr never signals a process it did not launch. Then run the printed step under the seat it
393
+ names, start again and read back ARMED:
394
+
395
+ ```bash
396
+ cd <repo> && tickmarkr beat <tier> --seat <recorded seat> --stand-down
397
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
398
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
399
+ ```
400
+
401
+ **Crash recovery.** A crashed owner, a writer killed inside its claimed tick or a killed recovery
402
+ taker leaves its claim, removal lock or stage directory behind. `beat status` only observes: it
403
+ exits nonzero, names the dead pids, prints the command that recovers them and changes no file —
404
+ status never recovers. Run the command it prints; the next ordinary `stop` or `start` recovers what
405
+ was left, with a notice naming each dead pid, and the start reads back ARMED:
406
+
407
+ ```bash
408
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
409
+ cd <repo> && tickmarkr beat stop <tier> --seat <seat>
410
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
411
+ ```
412
+
413
+ Never delete a claim, lock or stage file by hand. A live or unprovable holder refuses BUSY naming
414
+ it: wait for that holder to finish or exit, then run the same command.
339
415
 
340
416
  Keep only watchers that must WAKE the seat in the harness. Re-arm them on each wake and at
341
417
  their harness cap; daemon and beat lifetimes must not depend on that cap.
@@ -452,8 +528,9 @@ journal tail to decide what happens next, or sweeping orphans — you have taken
452
528
  ensure it is re-armed at most every twenty minutes. Never rely on a `Monitor`-only wake.
453
529
  - **On herdr (`HERDR_ENV=1`)**: Use the shipped journal instruments:
454
530
  **All four are covered by one shipped instrument** — `scripts/watch-journal.sh <runs-dir> [poll] [cap]
455
- [events-csv]` — which arms on a line baseline, wakes once, and grades a `run-end` against every green
456
- clause. `scripts/watch-parks.sh` stays the park-specific wake for THIS seat (it counts parks and speaks
531
+ [events-csv]` — which arms on a line baseline, wakes once, and grades a `run-end`'s execution clauses —
532
+ `EXECUTION COMPLETE` or `NOT GREEN`, never green: green is CURRENT `tickmarkr status <runId>` at
533
+ `outstanding 0`. `scripts/watch-parks.sh` stays the park-specific wake for THIS seat (it counts parks and speaks
457
534
  about rulings); the two overlap on `task-human` deliberately, and arming both is coverage, not a bug.
458
535
  - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: Arm a file/journal consumer for the same terminal events and inspect the seat with `orca terminal read --terminal <handle> --screen --json`; re-arm after every wake.
459
536
  - **Daemon liveness ≠ journal activity.** A dead daemon emits no events, so journal watchers sleep through
@@ -1166,14 +1243,14 @@ watching it. Two thirds of that line were constants, not measurements.
1166
1243
 
1167
1244
  The beat is one shipped command and it arms through two explicit verbs, `--new-arm` and `--loop` —
1168
1245
  never a bare invocation. `--loop` already implies `--new-arm`: it creates a durable arm and beats in
1169
- this process every 10 seconds, exiting on its own within one interval after a stand-down. Run it from
1170
- the repo root as a detached setsid+nohup process with a pidfile, following the idempotent
1171
- start/stop/status lifecycle in Host-owned daemon and detached beats; never as a harness task or visible tab:
1246
+ this process every 10 seconds, exiting nonzero with its reason within one interval after a stand-down.
1247
+ Never launch it by hand: `beat start` runs it detached and owns it, following the start/stop/status
1248
+ lifecycle in Host-owned daemon and detached beats; never as a harness task or visible tab:
1172
1249
 
1173
1250
  ```bash
1174
- # Payload for the detached start above; do not execute as a foreground or harness task.
1175
- cd <repo> && tickmarkr beat overseer --seat <overseer-agent-or-pane> --loop
1176
- cd <repo> && tickmarkr beat overseer --seat <overseer-agent-or-pane> --stand-down # deliberately hand off; --loop exits
1251
+ # beat start owns the detached --loop child; do not execute the loop as a foreground or harness task.
1252
+ cd <repo> && tickmarkr beat start overseer --seat <overseer-agent-or-pane>
1253
+ cd <repo> && tickmarkr beat stop overseer --seat <overseer-agent-or-pane> # deliberately hand off; reads back DISARMED
1177
1254
  ```
1178
1255
 
1179
1256
  **The legacy wrapper loop, `while :; do tickmarkr beat overseer --seat <pane>; sleep 10; done`, is the
@@ -1185,7 +1262,8 @@ wrote, so a stray shell loop left running by a predecessor seat kept a stood-dow
1185
1262
  shell or not, never does. The pre-2.1.3 forms `while :; do tickmarkr beat overseer; sleep 10; done`
1186
1263
  and `tickmarkr beat overseer --stand-down` are preserved here only as older migration warnings: both
1187
1264
  are now rejected outright because neither declares which seat the tier speaks for. Do not copy or
1188
- run either legacy form.
1265
+ run either legacy form. To leave an arm a legacy writer left, run the **Legacy beat migration** steps in Host-owned
1266
+ daemon and detached beats: the printed `--stand-down` under the recorded seat, then `beat start`.
1189
1267
 
1190
1268
  One beat per LIVE PROCESS, deliberately: `--loop`'s recorded pid is that process's own, so the tier's
1191
1269
  liveness is exactly as verifiable as the process table — stop the loop, or let it die, and the
@@ -82,19 +82,21 @@ report() {
82
82
  done=$(bucket done "$line"); failed=$(bucket failed "$line")
83
83
  human=$(bucket human "$line"); blocked=$(bucket blocked "$line")
84
84
  pending=$(bucket pending "$line")
85
- # GREEN IS A CONJUNCTION, AND THE SHORT FORM OF IT IS WRONG. "run-end plus tip verify" passes a run
86
- # that ended `done=[T1,T3,T4] human=[T2]` — three delivered, one PARKED — and calling that green is
87
- # how a park becomes invisible. Grade every clause here so the reader never has to remember to.
85
+ # NO RUN-END RECORD CERTIFIES GREEN. "run-end plus tip verify" passes a run that ended
86
+ # `done=[T1,T3,T4] human=[T2]` — three delivered, one PARKED — and even empty buckets leave the debt
87
+ # clause open: a waived review is still owed (D-660), and a `verify --record` discharge that lands
88
+ # AFTER run-end moves only CURRENT status. This row is history, so the best it can say is
89
+ # EXECUTION COMPLETE; green is read from CURRENT `tickmarkr status <runId>` at `outstanding 0`.
88
90
  if [ "$tv" != "failed" ] && [ -z "$failed$human$blocked$pending" ]; then
89
- verdict="GREEN"
91
+ verdict="EXECUTION COMPLETE"
90
92
  else
91
93
  verdict="NOT GREEN"
92
94
  fi
93
95
  echo "RUN_END $run — $verdict (tipVerify=${tv:-unknown})"
94
96
  echo " done=[${done}] failed=[${failed}] human=[${human}] blocked=[${blocked}] pending=[${pending}]"
95
- [ "$verdict" = "GREEN" ] \
96
- && echo " all four buckets empty and tip verify is not failed — this run is green" \
97
- || echo " a non-empty bucket above is the reason; name it, never report this run as green"
97
+ [ "$verdict" = "EXECUTION COMPLETE" ] \
98
+ && echo " execution buckets empty and tip verify is not failed — not yet green: read CURRENT \`tickmarkr status $run\`; only \`outstanding 0\` is green, \`outstanding N (...)\` and \`outstanding unknown\` are not, and this run-end record's own count is historical" \
99
+ || echo " a non-empty bucket or a failed tip above is the reason; name it, never report this run as green"
98
100
  ;;
99
101
  task-human)
100
102
  echo "TASK_HUMAN $(field taskId "$line") — $run"