tickmarkr 1.92.1 → 1.96.0
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.
- package/dist/adapters/model-lints.d.ts +1 -1
- package/dist/adapters/model-lints.js +116 -52
- package/dist/cli/commands/beat.d.ts +1 -0
- package/dist/cli/commands/beat.js +50 -0
- package/dist/cli/commands/status.js +166 -9
- package/dist/cli/commands/verify.js +52 -26
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/compile/gsd.js +34 -1
- package/dist/compile/native.js +26 -1
- package/dist/drivers/herdr.d.ts +26 -1
- package/dist/drivers/herdr.js +77 -29
- package/dist/gates/acceptance.js +26 -4
- package/dist/gates/baseline.d.ts +10 -1
- package/dist/gates/baseline.js +35 -6
- package/dist/run/daemon.d.ts +5 -0
- package/dist/run/daemon.js +25 -4
- package/dist/run/merge.d.ts +6 -1
- package/dist/run/merge.js +46 -11
- package/fixtures/gsd-sample/07-live-check/07-01-PLAN.md +1 -1
- package/fixtures/gsd-sample/PROJECT.md +19 -0
- package/fixtures/payload-shape/p99-shaped.spec.md +68 -0
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +2 -1
- package/skills/tickmarkr-loop/SKILL.md +2 -1
- package/skills/tickmarkr-overseer/SKILL.md +189 -15
- package/skills/tickmarkr-overseer/scripts/seat-send.sh +140 -0
- package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +5 -2
- package/skills/tickmarkr-overseer/scripts/watch-pending-input.sh +17 -1
package/dist/run/merge.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { shq } from "../adapters/types.js";
|
|
4
|
-
import { classifyFailureOutput, fingerprint, freshFailures } from "../gates/baseline.js";
|
|
4
|
+
import { ceilingKillResult, classifyFailureOutput, effectiveCeilingMs, fingerprint, freshFailures, } from "../gates/baseline.js";
|
|
5
5
|
import { tickmarkrDir } from "../graph/graph.js";
|
|
6
6
|
import { gitHead, linkNodeModules, resolveIntegrationBranch, sh, shGit, shGitOk, WORKTREES_DIR } from "./git.js";
|
|
7
7
|
export function integrationBranch(cfg, runId) {
|
|
@@ -53,21 +53,55 @@ export async function mergeTask(intWt, taskBranch, message, gatedCommit) {
|
|
|
53
53
|
export async function verifyIntegrationTip(intWt, commands, runDir, baseline) {
|
|
54
54
|
const results = [];
|
|
55
55
|
for (const [gate, cmd] of Object.entries(commands)) {
|
|
56
|
-
const
|
|
56
|
+
const entry = baseline?.commands[gate];
|
|
57
|
+
// OBS-534: the ceiling is the BATTERY's, derived by effectiveCeilingMs from the same baseline entry
|
|
58
|
+
// this loop already reads for forgiveness two lines down — never the flat DEFAULT_SHELL_TIMEOUT_MS
|
|
59
|
+
// `sh` defaults to. A suite whose capture measured 600007ms carries a recorded 1800021ms ceiling;
|
|
60
|
+
// running it under 600000ms here SIGKILLed a green tip three times while every per-task gate passed.
|
|
61
|
+
const ceilingMs = effectiveCeilingMs(entry);
|
|
62
|
+
const r = await sh(cmd, intWt, ceilingMs);
|
|
57
63
|
const raw = r.stdout + "\n" + r.stderr;
|
|
58
64
|
const stripped = raw.split(intWt).join("");
|
|
59
|
-
const
|
|
65
|
+
const artifact = join(runDir, `tip-verify-${gate}.log`);
|
|
66
|
+
// Battery parity on the ceiling too (baseline.ts Q24): the kill is read BEFORE the exit code is
|
|
67
|
+
// interpreted at all. A SIGKILLed battery never returned a verdict, so no line of its partial
|
|
68
|
+
// output is one — fingerprinting it is what produced the `<unrecognized failure output>` an
|
|
69
|
+
// operator cannot act on. The kill reader's own text (ceiling + elapsed) and cause replace it.
|
|
70
|
+
const killed = ceilingKillResult(gate, r, ceilingMs);
|
|
71
|
+
if (killed) {
|
|
72
|
+
writeFileSync(artifact, raw);
|
|
73
|
+
results.push({
|
|
74
|
+
gate,
|
|
75
|
+
cmd,
|
|
76
|
+
pass: false,
|
|
77
|
+
exitCode: r.code,
|
|
78
|
+
fingerprints: [],
|
|
79
|
+
details: killed.details,
|
|
80
|
+
cause: killed.meta?.classification,
|
|
81
|
+
artifact,
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
60
85
|
const { failing, unreadable } = freshFailures(entry, stripped);
|
|
86
|
+
const cause = r.code === 0 ? undefined : classifyFailureOutput(stripped);
|
|
61
87
|
// `?? 1` is the battery's own default (baseline.ts compareToBaseline): an exitCode-less legacy
|
|
62
88
|
// entry reads as red-at-baseline there, so it must read the same here or old baselines silently
|
|
63
|
-
// lose forgiveness.
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
89
|
+
// lose forgiveness. OBS-534 (T2): a capture killed at its ceiling now records a CAUSE and no
|
|
90
|
+
// verdict, and that default must not launder the missing exit code back into red-at-baseline.
|
|
91
|
+
// Only a recorded verdict is forgivable, so this reads the same predicate the battery does
|
|
92
|
+
// (baseline.ts `baselineRed`) — `infra` first, the legacy default only after it. freshFailures
|
|
93
|
+
// already drops the killed capture's flushed fingerprints, but it cannot close this alone: an
|
|
94
|
+
// output whose only shape is a diagnostic HEADING (vitest's "Unhandled Errors" banner) is
|
|
95
|
+
// fingerprintable yet OBS-42-exempt from rejecting, so `failing` comes back empty, `unreadable`
|
|
96
|
+
// false and the cause reads "regression" — every other guard satisfied, and a real red forgiven
|
|
97
|
+
// against a capture that never finished asking the question.
|
|
98
|
+
const baselineRed = entry !== undefined && entry.infra !== true && (entry.exitCode ?? 1) !== 0;
|
|
99
|
+
// Battery parity on the infra rule too (T9): infrastructure-only output means the runner never
|
|
100
|
+
// completed a suite — nothing was verified, so nothing is forgivable, however familiar its
|
|
101
|
+
// fingerprints. Stricter-than-battery edge kept: unreadable output never forgives.
|
|
102
|
+
const forgiven = r.code !== 0 && baselineRed && failing.length === 0 && !unreadable && cause !== "infra";
|
|
68
103
|
const pass = r.code === 0 || forgiven;
|
|
69
|
-
|
|
70
|
-
if (artifact)
|
|
104
|
+
if (!pass)
|
|
71
105
|
writeFileSync(artifact, raw);
|
|
72
106
|
results.push({
|
|
73
107
|
gate,
|
|
@@ -79,7 +113,8 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline) {
|
|
|
79
113
|
: forgiven ? `exit ${r.code} but only baseline-recorded failures (forgiven vs baseline)`
|
|
80
114
|
: `exit ${r.code}`,
|
|
81
115
|
...(forgiven ? { forgiven: true } : {}),
|
|
82
|
-
...(
|
|
116
|
+
...(cause ? { cause } : {}),
|
|
117
|
+
...(pass ? {} : { artifact }),
|
|
83
118
|
});
|
|
84
119
|
}
|
|
85
120
|
return results;
|
|
@@ -20,7 +20,7 @@ Implement the first objective sentence. Additional prose that is not the title.
|
|
|
20
20
|
</objective>
|
|
21
21
|
|
|
22
22
|
<context>
|
|
23
|
-
|
|
23
|
+
@fixtures/gsd-sample/PROJECT.md
|
|
24
24
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
25
25
|
@~/somewhere/outside.md
|
|
26
26
|
</context>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Sample project — vendored GSD fixture input
|
|
2
|
+
|
|
3
|
+
This file exists so the sibling phase's `07-01-PLAN.md` can cite a repo-relative `<context>` path that
|
|
4
|
+
is **part of the fixture itself**. It previously cited `.planning/PROJECT.md`, which resolved only
|
|
5
|
+
inside the private development checkout: the public export strips `.planning/` at any depth, so the
|
|
6
|
+
compiler's context-reachability refusal (v1.96 T3) correctly failed eight tests on the exported tree
|
|
7
|
+
and the release ritual's pre-tag proof caught it. A vendored fixture must carry its own inputs.
|
|
8
|
+
|
|
9
|
+
Nothing here is read by an assertion — the fixture only needs the path to resolve. The prose stands in
|
|
10
|
+
for the project brief a real GSD plan would point a worker at.
|
|
11
|
+
|
|
12
|
+
## Objective
|
|
13
|
+
|
|
14
|
+
Ship a small feature end to end, with each plan in the phase owning one objective sentence.
|
|
15
|
+
|
|
16
|
+
## Constraints
|
|
17
|
+
|
|
18
|
+
- One plan, one objective.
|
|
19
|
+
- A plan's `<context>` block promises the worker can read every repo-relative path it lists.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!-- tickmarkr:spec -->
|
|
2
|
+
<!-- provenance: the shape of .planning/phases/99-arabic-coverage as compiled on 2026-08-18 into
|
|
3
|
+
run-20260818-185710-0000000000000011 (41 tasks). OBS-535's fix — files[] is write scope, not
|
|
4
|
+
payload — was measured against that graph (41 unreadable-payload lints to 0; 31 overflow lints
|
|
5
|
+
when the window is forced to 50k) and against nothing in this repo, because no fixture carried
|
|
6
|
+
its shape. This one does, at five tasks instead of forty-one.
|
|
7
|
+
|
|
8
|
+
The four shape features that made every task on that graph skip its context-window comparison,
|
|
9
|
+
each present below:
|
|
10
|
+
1. a brace-glob write scope (`scripts/{a,b}`) — a set, not a document; unmeasurable as one file
|
|
11
|
+
2. an output declared in files[] (`*-SUMMARY.md`) — absent from the base tree BY CONSTRUCTION
|
|
12
|
+
3. a wave-1 artifact consumed by a TRANSITIVE dependent (T3 reads what T1 writes, via T2)
|
|
13
|
+
4. the same artifact cited by a task with NO producer upstream (T4) — the actionable class,
|
|
14
|
+
which on the real graph was 17 tasks citing a self-gitignored tree (RULING-P99-14)
|
|
15
|
+
T5 adds the class no commit can ever satisfy: a ref under a gitignored directory.
|
|
16
|
+
|
|
17
|
+
Compile this fixture from a NON-REPO directory. compileNative's context reachability check
|
|
18
|
+
(native.ts:684) fails open when git cannot answer, and T3/T4/T5 deliberately cite paths absent
|
|
19
|
+
from any base tree — that absence is the fixture's whole subject. -->
|
|
20
|
+
|
|
21
|
+
## T1: Land the instruments the later waves measure with
|
|
22
|
+
- goal: Write the two audit instruments and this task's own summary, so a later wave has something to read
|
|
23
|
+
- shape: implement
|
|
24
|
+
- files: scripts/{audit-strict.mjs,census.mjs}, .planning/payload-shape/T1-SUMMARY.md
|
|
25
|
+
- context: docs/payload-shape/PLAN.md
|
|
26
|
+
- complexity: 3
|
|
27
|
+
- acceptance:
|
|
28
|
+
- command: node -e "process.exit(0)"
|
|
29
|
+
- judge: both instruments exist and the summary records what they measured
|
|
30
|
+
|
|
31
|
+
## T2: Rewrite the localisation sources the instruments flag
|
|
32
|
+
- goal: Apply the instrument's findings across the localisation sources named in the write scope
|
|
33
|
+
- shape: implement
|
|
34
|
+
- deps: T1
|
|
35
|
+
- files: src/i18n/{ar/common.json,en/common.json,ar/dossier.json,en/dossier.json,ar/intake.json,en/intake.json,ar/tasks.json,en/tasks.json,ar/settings.json,en/settings.json}, .planning/payload-shape/T2-SUMMARY.md
|
|
36
|
+
- context: docs/payload-shape/PLAN.md, scripts/audit-strict.mjs
|
|
37
|
+
- complexity: 5
|
|
38
|
+
- acceptance:
|
|
39
|
+
- judge: every key src/i18n/ar/common.json shares with its en counterpart carries a non-empty Arabic value, and .planning/payload-shape/T2-SUMMARY.md records the count it rewrote
|
|
40
|
+
|
|
41
|
+
## T3: Close the census the second instrument opens
|
|
42
|
+
- goal: Read the census instrument written two waves back and close its remaining senses
|
|
43
|
+
- shape: implement
|
|
44
|
+
- deps: T2
|
|
45
|
+
- files: src/i18n/{ar/common.json,en/common.json}, .planning/payload-shape/T3-SUMMARY.md
|
|
46
|
+
- context: docs/payload-shape/PLAN.md, scripts/census.mjs
|
|
47
|
+
- complexity: 4
|
|
48
|
+
- acceptance:
|
|
49
|
+
- judge: the census reports no unresolved sense for any rewritten key
|
|
50
|
+
|
|
51
|
+
## T4: Report on the audit without depending on the task that writes it
|
|
52
|
+
- goal: Summarise the audit's findings for the operator record
|
|
53
|
+
- shape: chore
|
|
54
|
+
- files: .planning/payload-shape/T4-SUMMARY.md
|
|
55
|
+
- context: docs/payload-shape/PLAN.md, scripts/audit-strict.mjs
|
|
56
|
+
- complexity: 2
|
|
57
|
+
- acceptance:
|
|
58
|
+
- judge: .planning/payload-shape/T4-SUMMARY.md states both the flagged-site count and the total key count it was measured against
|
|
59
|
+
|
|
60
|
+
## T5: Apply the standing ruling to the rewritten sources
|
|
61
|
+
- goal: Enforce the ruling's terminology decisions across the sources T2 rewrote
|
|
62
|
+
- shape: chore
|
|
63
|
+
- deps: T2, T3
|
|
64
|
+
- files: src/i18n/{ar/common.json,en/common.json}
|
|
65
|
+
- context: docs/payload-shape/PLAN.md, .state/RULING-TERMINOLOGY.md
|
|
66
|
+
- complexity: 2
|
|
67
|
+
- acceptance:
|
|
68
|
+
- judge: no file under src/i18n/ar contains the string "Dossier" or "Engagement" in Latin script
|
package/package.json
CHANGED
|
@@ -19,12 +19,13 @@ When working in a multi-agent terminal environment, decide your role before star
|
|
|
19
19
|
- **Claude Code:** `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions`
|
|
20
20
|
- **Codex:** `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` — the unsandboxed flag is REQUIRED, not optional: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation (`git worktree add` cannot lock the ref). Do not downgrade this flag; the herdr pane and repo scope are the containment.
|
|
21
21
|
- **Auxiliary agents you 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` (tickmarkr's own adapter uses exactly this for workers, judges, and consults). A read-only codex consultant may use `--sandbox read-only`; any codex session that must touch git needs the unsandboxed flag above.
|
|
22
|
+
- **Auxiliary seats run as the CLI's interactive TUI in their visible pane — never headless** (`claude -p` / `codex exec`): headless buffers output until exit so the pane renders idle for the entire run, is blind to SessionStart hook errors (a broken and a fixed hook both return green), and gives a stall watcher no midpoint — silent-time equals lifetime. Headless is for exit-code probes only (a quota check that wants `rc`), never for work anyone must watch.
|
|
22
23
|
|
|
23
24
|
Outside a multi-agent terminal environment, run the loop directly.
|
|
24
25
|
|
|
25
26
|
## Stand-down (mission end and retirement)
|
|
26
27
|
|
|
27
|
-
On each mission's terminal state, after the record commit and operator notification: the orchestrator stops every monitor and background task it started, prints one final stand-down line, and leaves nothing queued in its input box. A finished session with an armed watcher or pre-filled input is a loaded gun.
|
|
28
|
+
On each mission's terminal state, after the record commit and operator notification: the orchestrator stops every monitor and background task it started, sweeps the heartbeat/beat files its watchers wrote (a stale beat beside a live one reads as coverage to whoever globs the directory), prints one final stand-down line, and leaves nothing queued in its input box. A finished session with an armed watcher or pre-filled input is a loaded gun.
|
|
28
29
|
|
|
29
30
|
## Invariants
|
|
30
31
|
|
|
@@ -18,6 +18,7 @@ When working in a multi-agent terminal environment, decide your role before star
|
|
|
18
18
|
- **Claude Code:** `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions`
|
|
19
19
|
- **Codex:** `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` — the unsandboxed flag is REQUIRED, not optional: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation (`git worktree add` cannot lock the ref). Do not downgrade this flag; the herdr pane and repo scope are the containment.
|
|
20
20
|
- **Auxiliary agents you 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` (tickmarkr's own adapter uses exactly this for workers, judges, and consults). A read-only codex consultant may use `--sandbox read-only`; any codex session that must touch git needs the unsandboxed flag above.
|
|
21
|
+
- **Auxiliary seats run as the CLI's interactive TUI in their visible pane — never headless** (`claude -p` / `codex exec`): headless buffers output until exit so the pane renders idle for the entire run, is blind to SessionStart hook errors (a broken and a fixed hook both return green), and gives a stall watcher no midpoint — silent-time equals lifetime. Headless is for exit-code probes only (a quota check that wants `rc`), never for work anyone must watch.
|
|
21
22
|
|
|
22
23
|
Outside a multi-agent terminal environment, run the loop directly.
|
|
23
24
|
|
|
@@ -60,7 +61,7 @@ When spawning consultants (agents gathering synthesis input for decisions like S
|
|
|
60
61
|
|
|
61
62
|
## Stand-down (mission end and retirement)
|
|
62
63
|
|
|
63
|
-
- **Orchestrator, on terminal state** (green, failed, or parked), after the record commit and operator notification: stop every monitor and background task you started, print one final stand-down line, and leave NOTHING queued in your input box. A finished session with an armed watcher or pre-filled input is a loaded gun — a retired v1.40 orchestrator sat idle with "merge … tag, publish" unsent in its input; one stray Enter would have shipped a duplicate release.
|
|
64
|
+
- **Orchestrator, on terminal state** (green, failed, or parked), after the record commit and operator notification: stop every monitor and background task you started, sweep the heartbeat/beat files your watchers wrote (a stale beat beside a live one reads as coverage to whoever globs the directory), print one final stand-down line, and leave NOTHING queued in your input box. A finished session with an armed watcher or pre-filled input is a loaded gun — a retired v1.40 orchestrator sat idle with "merge … tag, publish" unsent in its input; one stray Enter would have shipped a duplicate release.
|
|
64
65
|
- **Supervisor, when a mission completes** (and always before spawning the next orchestrator): verify the orchestrator stood down, then close its tab. Seeming input-box text in a retired pane can be the TUI's dim ghost-text suggestion, not queued input — confirm with an ANSI read (dim escape around the text) or type-one-char-and-read-back before treating it as the loaded gun; close the tab either way. The journal, execution record, OBS ledger, and memory hold the story; pane scrollback is disposable. Never leave a retired agent idle with watchers armed.
|
|
65
66
|
|
|
66
67
|
## The loop
|
|
@@ -12,6 +12,19 @@ to the user with evidence.
|
|
|
12
12
|
The mission is the skill argument. If empty, ask the user what to run end-to-end before doing anything else.
|
|
13
13
|
Requires `HERDR_ENV=1`; if unset, say so and stop.
|
|
14
14
|
|
|
15
|
+
**THE ENGINE IS THE DEFAULT EXECUTOR.** A mission that names a milestone, a phase, or a spec runs the
|
|
16
|
+
loop: `tickmarkr compile` → `plan` → `run` → `report`, and the JOURNAL is the record. `compile` ingests
|
|
17
|
+
GSD phase plans (`src/compile/gsd.ts`), so *"this repo uses GSD"* is not a reason to bypass it. The
|
|
18
|
+
supervised GSD flow (below) is the EXCEPTION: it exists only on an explicit operator order, recorded in
|
|
19
|
+
the brief WITH its costs named — no journal (so no per-task adapter/model record), no routing, no
|
|
20
|
+
enforced gate battery, no enforced cross-vendor review; each is re-implemented by hand or silently lost.
|
|
21
|
+
**Measured 2026-08-18 (P98):** the operator triggered this skill expecting the engine; the mission ran
|
|
22
|
+
as GSD legs instead — 12 seats, 11 of them one model checking that same model's work, and *"which model
|
|
23
|
+
ran each task"* was unanswerable from every mission artifact (no ruling, log, or brief named a model;
|
|
24
|
+
the answer took session-file archaeology). The flip from the engine (P89, journaled runs on disk) to
|
|
25
|
+
GSD legs (P92) had been RULED NOWHERE — no ledger entry decides it — and then propagated for six phases
|
|
26
|
+
through brief lineage. **An executor choice nobody made is still an executor choice, and it compounds.**
|
|
27
|
+
|
|
15
28
|
## Setup
|
|
16
29
|
|
|
17
30
|
0. **Adopt before you build.** If this workspace already has a supervision hierarchy — an
|
|
@@ -22,11 +35,17 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
|
|
|
22
35
|
status, and either ADOPT the
|
|
23
36
|
existing orchestrator (updated brief, re-armed watchers) or, if the old hierarchy is dead, archive the
|
|
24
37
|
stale brief and build fresh.
|
|
38
|
+
⚠ **Adopting a hierarchy silently adopts its EXECUTOR CHOICE.** The P92→P98 GSD drift propagated
|
|
39
|
+
exactly this way: each overseer read the prior brief, reproduced "the same two-leg pattern as the
|
|
40
|
+
last three phases", and the unruled bypass of the engine became load-bearing through repetition.
|
|
41
|
+
At every adopt, re-derive the executor question — *"why is this milestone not compiled?"* — and if
|
|
42
|
+
the answer is not a recorded operator order, route the mission back through the engine.
|
|
25
43
|
0a. **READ THE PROJECT MEMORY BEFORE YOU START — it already contains discipline you are about to re-earn.**
|
|
26
44
|
`~/.claude/projects/<cwd-slug>/memory/` (slug = the absolute cwd with `/` → `-`). Read `MEMORY.md`, then
|
|
27
|
-
`ls` the topic entries and open every one whose name concerns METHOD
|
|
28
|
-
milestone — names like `*-discipline`, `*-drill`,
|
|
29
|
-
`consults-*`, `agent
|
|
45
|
+
`ls` the topic entries and open every one whose name concerns METHOD, DISCIPLINE, or a STANDING
|
|
46
|
+
OPERATOR LAYOUT/CONVENTION rather than a shipped milestone — names like `*-discipline`, `*-drill`,
|
|
47
|
+
`*-parity`, `*-least-permission`, `context-reset-*`, `consults-*`, `agent-*`, `*-tab-layout`,
|
|
48
|
+
`*-visible-*`, `*-panes*`, `user-tabs-*`.
|
|
30
49
|
**Earned 2026-08-04, expensively.** That directory held `…-falsification-drill-discipline.md`, written
|
|
31
50
|
three weeks earlier: *"a gate or grep-pin is assumed WRONG until a falsification drill proves it bites…
|
|
32
51
|
run the drill that should redden it and SEE the red before trusting green."* That is Evidence discipline
|
|
@@ -35,8 +54,32 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
|
|
|
35
54
|
mission-scoped brief. **A memory that exists and is never opened costs more than one that was never
|
|
36
55
|
written, because everyone assumes the lesson is somewhere.** Entries may predate a project rename; search
|
|
37
56
|
by concept, not by the current product name.
|
|
57
|
+
**And the sweep scope is itself a recorded defect: a filter limited to discipline names SKIPS the
|
|
58
|
+
layout canon, and that skip is paid.** The layout entry records a 2026-08-05 correction it caused —
|
|
59
|
+
*"widen the start-of-session read to include layout/convention entries, not only discipline ones"* —
|
|
60
|
+
and on 2026-08-17 the same skip put a planning seat inside the ORCH tab. A standing operator layout is
|
|
61
|
+
not cosmetic; it is how the operator reads the fleet, and it binds exactly like a discipline rule.
|
|
38
62
|
1. Load the `herdr` skill. `herdr pane list` to map the workspace — the focused pane is yours. Rename your
|
|
39
63
|
tab OVERSEER; create ONE tab ORCHESTRATOR.
|
|
64
|
+
**FIVE-TAB CANON (standing operator layout — corrected three times on 2026-07-27, layout approved
|
|
65
|
+
2026-07-29, re-earned 2026-08-17):**
|
|
66
|
+
- `OVERSEER` — you. Do not add a second live run surface: the daemon self-places the shipped board
|
|
67
|
+
beside the supervising seat that invokes the run.
|
|
68
|
+
- `ORCH` — the orchestrator and the daemon-placed, run-id-pinned shipped board beside it. **Look for
|
|
69
|
+
that `role: "watch"` pane; never hand-place or hand-roll a live run surface. Nothing else, ever: a
|
|
70
|
+
work seat NEVER splits into the ORCH tab.** Operator verbatim: *"in orch tab should be the orch and
|
|
71
|
+
the watcher only."* Re-earned 2026-08-17: a planning seat split beside the orchestrator, and the
|
|
72
|
+
operator caught it, again. The daemon owns the side placement and board-first width allocation;
|
|
73
|
+
neither the worker-pane halving floor nor an overseer split command places this pane.
|
|
74
|
+
- Worker/seat tabs — tickmarkr opens ONE TAB PER TASK itself; GSD-leg seats get the same treatment
|
|
75
|
+
(own tab, or a shared WORKERS tab), never the ORCH tab.
|
|
76
|
+
- `CONSULT · <topic>` — ONE shared tab for ALL consultants of a round, side-by-side splits; never one
|
|
77
|
+
tab per consultant; do NOT auto-close after adjudication (operator, 2026-08-09 — keep the round's
|
|
78
|
+
panes until the thread is confirmed finished or a successor round supersedes them).
|
|
79
|
+
- `REVIEW <task>` — reviewer panes.
|
|
80
|
+
Never multiply beyond these without asking. **Tabs you did not create are the OPERATOR'S — provenance
|
|
81
|
+
decides: never close, rename, reuse, or send input to one, however idle it looks.** An "idle
|
|
82
|
+
stale-looking" tab an overseer once swept was the operator's in-progress thinking (2026-07-14).
|
|
40
83
|
**Live tab labels (standing operator rule, 2026-07-12):** on every decision or state change (role
|
|
41
84
|
handoff, task done/merged, run end) rename the affected tabs — and keep labels SHORT: the role as the
|
|
42
85
|
main name plus at most ONE hot-state token. Vocabulary: ORCH carries the milestone and progress
|
|
@@ -48,8 +91,9 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
|
|
|
48
91
|
truncated brief silently drops policy. Write the full brief to `<repo>/.tickmarkr/overseer/ORCH-BRIEF.md`
|
|
49
92
|
(inside the tickmarkr state dir — already self-gitignored, no exclude step needed), then send one line:
|
|
50
93
|
`herdr pane run <orch> "Read .tickmarkr/overseer/ORCH-BRIEF.md and follow it exactly."` The brief MUST contain: the
|
|
51
|
-
mission, the pane mechanics below, rules 1–2,
|
|
52
|
-
|
|
94
|
+
mission, the five-tab canon from step 1, the pane mechanics below, rules 1–2, the GSD-leg rules
|
|
95
|
+
(below) whenever the mission dispatches `/gsd:*` legs, and require a verbatim one-sentence
|
|
96
|
+
acknowledgment of the human-checkpoint rule before anything is dispatched.
|
|
53
97
|
**⚠ HARVEST BEFORE YOU DELETE.** At mission end the brief dir goes — but a long mission accumulates
|
|
54
98
|
*method guards* in that brief (how to know a thing, not what is true of this spec), and deleting them
|
|
55
99
|
re-earns each one at full price on the next mission. So before removing the dir: lift every durable,
|
|
@@ -57,7 +101,7 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
|
|
|
57
101
|
and only then delete. A guard's home must outlive the mission that earned it. The project ledger does
|
|
58
102
|
NOT count as that home — `CLAUDE.md` itself says planning records are read-only archives and current
|
|
59
103
|
guidance belongs in the memory file or the shipped docs.
|
|
60
|
-
4. Arm the watcher (Supervision). Report the hierarchy map (pane ids + names) to the user.
|
|
104
|
+
4. Arm the watcher and your own supervision beat (Supervision). Report the hierarchy map (pane ids + names) to the user.
|
|
61
105
|
|
|
62
106
|
## Supervising tickmarkr as the executor — WHO DOES WHAT
|
|
63
107
|
|
|
@@ -69,7 +113,7 @@ and the first thing to get right is that **almost none of it is yours**.
|
|
|
69
113
|
| | ORCHESTRATOR | OVERSEER |
|
|
70
114
|
|---|---|---|
|
|
71
115
|
| `compile` · `plan` · `run` · `resume` | **owns** | never |
|
|
72
|
-
| journal watchers
|
|
116
|
+
| journal and dialog watchers; verifying the daemon's live surface | **owns** | watches the ORCHESTRATOR, not the run |
|
|
73
117
|
| orphan sweeps, worker pane hygiene | **owns** | — |
|
|
74
118
|
| reading a gate failure and assembling its evidence | **owns** | reads the file it writes |
|
|
75
119
|
| **deciding** a gate, spend, or ship | never | **owns** |
|
|
@@ -89,10 +133,11 @@ journal tail to decide what happens next, or sweeping orphans — you have taken
|
|
|
89
133
|
|
|
90
134
|
### What the ORCHESTRATOR does, and what you require of it
|
|
91
135
|
|
|
92
|
-
- **
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
136
|
+
- **The live surface arrives with the run.** `tickmarkr run` is stdout-silent until run-end by design;
|
|
137
|
+
its daemon self-places one shipped `role: "watch"` board beside the supervising seat and pins that
|
|
138
|
+
board to the daemon's run id. **Look for the matching daemon-placed pane.** If it is absent, treat that
|
|
139
|
+
as a daemon/run liveness fault and use the normal recovery path; never hand-place, hand-roll, or launch
|
|
140
|
+
a replacement live surface.
|
|
96
141
|
- **The journal is the source of truth**, not panes. Watchers go on `run-end` / `task-human` /
|
|
97
142
|
`task-failed` / `consult-verdict`; never sleep-poll inside an agent turn. **Never key a watcher on an
|
|
98
143
|
agent's `done`** — that is turn end and fires the moment a seat finishes acknowledging you.
|
|
@@ -192,12 +237,68 @@ is a lossy summary nobody trusts while a clean session re-oriented from disk-ver
|
|
|
192
237
|
good, not after. If your own context cannot be read by the watcher, say so to the operator and ask for the
|
|
193
238
|
number — an unmeasured budget is not a small budget.
|
|
194
239
|
|
|
240
|
+
## Supervising GSD legs — when the mission dispatches `/gsd:*` instead of `tickmarkr run`
|
|
241
|
+
|
|
242
|
+
Milestones that alternate GSD legs (`/gsd:plan-phase N`, `/gsd:execute-phase N`) under this hierarchy get
|
|
243
|
+
none of the engine's dispatcher, routing, or gates — every guarantee `tickmarkr run` provides has to be
|
|
244
|
+
demanded in the brief instead. **This path is the EXCEPTION and requires the recorded operator order
|
|
245
|
+
named in the default-executor rule at the top of this skill.** Five guarantees get dropped every time
|
|
246
|
+
they are left implicit:
|
|
247
|
+
|
|
248
|
+
0. **A seats ledger stands in for the journal's assignment records.** Every seat spawn — orchestrator,
|
|
249
|
+
planner, checker, executor, verifier, consult — appends ONE JSON line to
|
|
250
|
+
`<state-dir>/overseer/seats.jsonl` in the same act as the spawn:
|
|
251
|
+
`{"ts":"<iso>","seat":"<name>","pane":"<id>","tab":"<label>","adapter":"<cli>","model":"<model>","role":"<role>","brief":"<path>"}`.
|
|
252
|
+
The journal answers *"which model ran each task"* in one line; without this file the answer is
|
|
253
|
+
session-file archaeology, and the model monoculture it would have exposed stays invisible (measured
|
|
254
|
+
2026-08-18: 12 seats, no artifact naming any seat's model). ⚠ Interim per rule 27 — removal
|
|
255
|
+
condition: the engine runs the milestone and the journal is the record.
|
|
256
|
+
|
|
257
|
+
1. **GSD's Agent-tool default is OVERRIDDEN — name the trap in every seat brief.** `/gsd:plan-phase` and
|
|
258
|
+
`/gsd:execute-phase` fan their work out to in-process Task subagents: invisible to every watcher tier,
|
|
259
|
+
billed to the seat's own context, visible only in the seat's status footer. Measured 2026-08-17 (P98
|
|
260
|
+
leg 1): one planning seat ran FIVE in-process subagents to ≈855k tokens — two of them an unexplained
|
|
261
|
+
duplicate respawn pair — and the operator caught it from the footer, not from any tier of supervision.
|
|
262
|
+
The violation was first recorded 2026-07-10, and that record already states the fix: *"generic
|
|
263
|
+
'visible panes' wording is not enough — name the GSD trap."* The concrete mechanism is claude-code
|
|
264
|
+
TEAMMATES (the seat's footer roster; `Teammate @<name> finished` notices): they run INSIDE the
|
|
265
|
+
parent seat's turn, so the seat cannot drain its message queue until every teammate returns —
|
|
266
|
+
**unwatchable and unsteerable are the same defect** (the queued-message law, Pane mechanics).
|
|
267
|
+
Measured 2026-08-18: two freeze-class directives sat queued behind a planner's teammate fan-out
|
|
268
|
+
while the plan set they froze was still editable from that queue, and THE OPERATOR ended the turn
|
|
269
|
+
by hand (Esc, twice) because no seat owned the interrupt. Every GSD seat brief states: subagents
|
|
270
|
+
run as visible herdr panes in per-task tabs (`herdr agent start …`), and a seat report whose work
|
|
271
|
+
was produced by invisible subagents is rejected on read.
|
|
272
|
+
2. **Seats are interactive TUI, never headless `-p`.** The P92–P96 exec lane —
|
|
273
|
+
`cat brief | claude -p … ` in a visible pane — satisfied visibility in the letter only: `claude -p`
|
|
274
|
+
buffers output until exit, so the pane renders idle for the entire run; it is blind to SessionStart
|
|
275
|
+
hook errors (a broken and a fixed hook both return green); and its silence has no midpoint for a
|
|
276
|
+
stall watcher to catch — silent-time equals lifetime. Standing operator rule since 2026-07-13:
|
|
277
|
+
consults and one-off LLM calls run as the CLI's real interactive TUI in a visible named pane.
|
|
278
|
+
Headless is for exit-code probes — a quota check that wants `rc`, never work anyone must watch.
|
|
279
|
+
3. **Buy seat diversity from the live capability matrix, at every dispatch.** When one vendor's model
|
|
280
|
+
quota collapses, the reflex is to collapse every seat onto the surviving model and hold the
|
|
281
|
+
cross-vendor CLI back for a late probe — P97 ran planner, checker and verifier as one family that
|
|
282
|
+
way, and three same-family passes confirmed one wrong anchored conclusion with the refuting fact in
|
|
283
|
+
the room. `<state-dir>/doctor.json` already lists every installed+authed adapter and its models (nine
|
|
284
|
+
were authed on 2026-08-17 while every seat ran claude). Priority when independence is scarce:
|
|
285
|
+
**verifier > checker > planner > executors** — the independent seat goes cross-vendor
|
|
286
|
+
(`herdr agent start … --kind codex`), ruled at dispatch, never debated under time pressure.
|
|
287
|
+
4. **Gate every exec lane with the shipped battery, not hand-rolled greps.**
|
|
288
|
+
`tickmarkr verify --base <ref> --criteria <file>` is the standalone form of the engine's own gates —
|
|
289
|
+
build/test/lint diffed against a recorded baseline, evidence, scope, plus the semantic judges — one
|
|
290
|
+
fail-closed verdict, no daemon, no retries. A per-lane grep gate re-implements a weaker version of
|
|
291
|
+
this and passes on source text the screen never renders, which is exactly the class the acceptance
|
|
292
|
+
judge exists to reject. One command per lane, named in the lane's own brief.
|
|
293
|
+
|
|
195
294
|
## Pane mechanics that bite
|
|
196
295
|
|
|
197
296
|
- **Verified send protocol**: `herdr agent send` writes WITHOUT Enter, and `pane run`'s Enter can be swallowed
|
|
198
297
|
by bracketed-paste on long payloads. Robust sequence: read the pane (bare prompt required) → send-text →
|
|
199
298
|
sleep 2–3s → send-keys Enter → read back (input empty / agent `working`). Never report "briefed" without
|
|
200
|
-
the read-back. Long content goes in a brief file, never pane text.
|
|
299
|
+
the read-back. Long content goes in a brief file, never pane text. `scripts/seat-send.sh` encodes
|
|
300
|
+
this whole path — size guard, atomic prompt, prompt-line read-back, optional interrupt — and never
|
|
301
|
+
auto-resends.
|
|
201
302
|
**PROBE THE READ-BACK WITH THE SHORTEST DISTINCTIVE TOKEN — a commit hash, a pid, an OBS id — NEVER a
|
|
202
303
|
sentence.** A long phrase crosses the pane's render wrap boundary, so grepping for it returns zero on a
|
|
203
304
|
message that arrived intact, and **a badly-probed successful send is byte-identical to a truncated one.**
|
|
@@ -206,6 +307,35 @@ number — an unmeasured budget is not a small budget.
|
|
|
206
307
|
(OBS-396): a grep for the full sentence returned 0 while a grep for one word of the same sentence
|
|
207
308
|
returned 1. This trap lives *inside* the verification step above, which is why it survives — the rule
|
|
208
309
|
that is supposed to catch dropped sends is the rule that manufactures the phantom.
|
|
310
|
+
**AND A CONTENT PROBE — TOKEN, TAIL, FULL TEXT — CANNOT VERIFY DELIVERY AT ALL, only presence.**
|
|
311
|
+
Measured 2026-08-18: `herdr pane read` includes the INPUT BOX, so an unsubmitted message renders
|
|
312
|
+
identically to a submitted one and every content-based probe returns the same answer in both
|
|
313
|
+
states. A freeze hold was "verified delivered" by three independent content methods and had never
|
|
314
|
+
been submitted; the receiving seat stopped 19 minutes later without ever reading it — the frozen
|
|
315
|
+
set was protected by nothing but the operator's manual Esc. **The prompt line is the only state
|
|
316
|
+
that discriminates** — text sitting on `❯` is exactly what will not run — and a delivery report
|
|
317
|
+
is prompt-line state alone, or nothing: a decorative check beside a real one reads as
|
|
318
|
+
corroboration (two greens, one of which was never capable of disagreeing).
|
|
319
|
+
- **A MESSAGE TO A WORKING SEAT IS A QUEUED MESSAGE, AND THE QUEUE DRAINS ONLY AT TURN BOUNDARIES.**
|
|
320
|
+
Delivery is not arrival: `agent prompt` to a `working` claude seat lands in its queue (`Press up to
|
|
321
|
+
edit queued messages` on the seat's prompt line is the tell) and is READ only when the current turn
|
|
322
|
+
ends — and with in-process teammates a turn runs 20–40 minutes, so steering latency equals subagent
|
|
323
|
+
runtime. Measured 2026-08-17/18 (P98 leg 1): a FREEZE HOLD and a checker-release directive stacked
|
|
324
|
+
behind a planner's teammate fan-out — the freeze forbade edits its own queue could still trigger —
|
|
325
|
+
and the OPERATOR ended the turn by hand. Three consequences:
|
|
326
|
+
- **A queued hold is not a hold.** A freeze-class or superseding directive to a `working` seat is
|
|
327
|
+
delivered by INTERRUPT, and the interrupt is the SUPERVISING tier's move, never left to the
|
|
328
|
+
operator: `send-keys esc` → re-read status → esc once more if still working (two, bounded) →
|
|
329
|
+
verify idle → prompt → verify. The interrupt loses the seat's in-flight step; for a
|
|
330
|
+
correctness-class directive that is the price, and it is cheaper than a voided verdict.
|
|
331
|
+
- **Never stack a correction behind a stale directive.** A queued message executes in a context that
|
|
332
|
+
no longer matches the one it was written in. When conditions change, do not append another
|
|
333
|
+
message — interrupt, then send ONE directive that NAMES the queue and overrides it (*"your queue
|
|
334
|
+
holds X and Y; act on neither; current state is Z"*), and require the seat to report what its
|
|
335
|
+
queue held before acting. The receiving seat re-validates every queued item against CURRENT state.
|
|
336
|
+
- Five distinct send failures in one leg — front-truncation, sitting unsubmitted, two silent losses,
|
|
337
|
+
a probe that mistook its own echo for a reply — is what a prose send protocol costs under load:
|
|
338
|
+
run `scripts/seat-send.sh` instead.
|
|
209
339
|
- **Guard-before-Enter** (race-safe prompt answering): chain with `&&` — pane get shows `blocked` && pane
|
|
210
340
|
read shows the expected option under the cursor && only then send-keys. If no longer `blocked`, someone
|
|
211
341
|
already answered; do nothing.
|
|
@@ -235,9 +365,38 @@ number — an unmeasured budget is not a small budget.
|
|
|
235
365
|
unaffected (one more reason to prefer them).
|
|
236
366
|
- Stale typed input is unclearable via CLI — supersede it:
|
|
237
367
|
`pane run "<-- disregard everything before this arrow (stale draft). ACTUAL: <message>"`.
|
|
368
|
+
**But DISCRIMINATE before you supersede or file it: text on an idle seat's prompt line has FOUR
|
|
369
|
+
authors** — the seat's own draft, an operator, another agent's `agent send` (writes WITHOUT Enter),
|
|
370
|
+
and claude-code's AUTOSUGGEST, which renders context-plausible ghost text BYTE-IDENTICAL to a typed
|
|
371
|
+
draft in text-format reads (OBS-482). The check is mechanical and only works at observation time:
|
|
372
|
+
`agent read --format ansi` — dim/grey SGR around the text = autosuggest ghost, NOT input. Measured
|
|
373
|
+
2026-08-17 (D-206): an unattributed instruction was found in an orchestrator's box, superseded
|
|
374
|
+
defensively, and its origin stayed UNRESOLVED — the one probe that discriminates was not taken while
|
|
375
|
+
the text still sat there. An origin question you can close in ten seconds at the pane becomes
|
|
376
|
+
permanently open the moment anyone clears the box.
|
|
238
377
|
|
|
239
378
|
## Supervision watcher
|
|
240
379
|
|
|
380
|
+
**Arm your OWN tier first, in the same call chain that arms everything else.** `status` derives each
|
|
381
|
+
tier's state from a beat file the tier itself writes, so a seat that never beats reads `ABSENT` — and
|
|
382
|
+
`ABSENT` means *never armed*, which is a lie about a seat that is working the run. Measured on the P99
|
|
383
|
+
run: `orchestrator ARMED / overseer ABSENT / watch ABSENT` for the whole milestone, with a live overseer
|
|
384
|
+
watching it. Two thirds of that line were constants, not measurements.
|
|
385
|
+
|
|
386
|
+
The beat is one shipped command and the loop is yours, run from the repo root as its own
|
|
387
|
+
`run_in_background` Bash call:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
cd <repo> && while :; do tickmarkr beat overseer; sleep 10; done # 10s = SUPERVISION_BEAT_MS
|
|
391
|
+
tickmarkr beat overseer --stand-down # at stand-down, in the same act
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
One beat per invocation, deliberately: the loop is what proves the seat is alive, so a command that
|
|
395
|
+
kept beating on its own would keep reporting a dead seat as healthy. Stop the loop — or die — and the
|
|
396
|
+
tier ages to `STALE` (never `ABSENT`) within six beats, which is the state that says *armed, then lost*.
|
|
397
|
+
Stand down explicitly when you hand off, or a deliberate exit reads as a death. Same rule as rule 29
|
|
398
|
+
below, now with a conventional path the other tier already reads: `tickmarkr status` shows it.
|
|
399
|
+
|
|
241
400
|
Arm the bundled watcher as its OWN Bash call with `run_in_background` — chaining it after other commands
|
|
242
401
|
with `&` orphans it from the wake chain. It prints one wake reason and exits; re-arm after every wake.
|
|
243
402
|
|
|
@@ -309,9 +468,9 @@ herdr agent wait <name> --until blocked --timeout <ms> # run_in_background
|
|
|
309
468
|
⚠ **Its exit status is not evidence.** That command exits **0 on timeout** and **0 when the pane is gone**,
|
|
310
469
|
exactly as it does on a real block — so confirm every wake by READING the pane before acting on it.
|
|
311
470
|
|
|
312
|
-
**And note what no watcher can cover:** the
|
|
313
|
-
|
|
314
|
-
|
|
471
|
+
**And note what no watcher can cover:** the supervision beat above proves this seat is ARMED and nothing
|
|
472
|
+
more — it is a liveness claim, not a wake signal, so the seat still improvises the wakes, and an improvised
|
|
473
|
+
set is where a whole failure class hides. A **host** permission
|
|
315
474
|
modal is invisible to tickmarkr entirely, so no `src/**` change closes that one — it is covered here or
|
|
316
475
|
nowhere.
|
|
317
476
|
|
|
@@ -324,6 +483,12 @@ nowhere.
|
|
|
324
483
|
It wakes when every named file exists AND ends with its terminal marker, and on timeout it reports each
|
|
325
484
|
file as READY / PARTIAL / ABSENT so a quiet arm still proves the watcher was alive. Tell each seat, in its
|
|
326
485
|
brief, the exact marker its report must end with — you cannot watch for a marker you never demanded.
|
|
486
|
+
**Arm on the marker YOU demanded, verified against the FILE — never on the seat's report of its own
|
|
487
|
+
marker.** Measured 2026-08-17: a seat reported its sweep "ends `SWEEP-END`"; the file on disk ended
|
|
488
|
+
`ORDER4-END`. A watcher armed on the reported marker never fires while the artifact sits COMPLETE, and
|
|
489
|
+
that hang is byte-identical to a seat still working. The script prints each unfinished file's actual
|
|
490
|
+
last line on every timeout heartbeat — read it there, and when in doubt `tail -1` the artifact, never
|
|
491
|
+
the transcript's claim about it.
|
|
327
492
|
|
|
328
493
|
**Arm it in the same call as the spawn, not the next one.** A watcher armed "after I finish this step"
|
|
329
494
|
leaves a gap exactly as wide as however long you stay busy, and you will be busy — you just spawned work.
|
|
@@ -386,6 +551,10 @@ orchestrator turn boundary.
|
|
|
386
551
|
safe 53 → floor 108"*), and it splits right only while `paneWidth/2 ≥ 108 + 2` (`herdr.ts:494`),
|
|
387
552
|
otherwise **down**. Apply the same test by hand: `herdr pane layout --pane <id>`, halve the width,
|
|
388
553
|
and if the halves fall under the floor, split `--direction down`.
|
|
554
|
+
- **The daemon-placed ORCH board is outside this manual split rule.** The halving bound protects
|
|
555
|
+
worker-pane trailers; the daemon, not the overseer, places the run-id-pinned shipped board beside
|
|
556
|
+
the supervising seat and owns its board-first width allocation. Look for that pane and do not split,
|
|
557
|
+
place, or recreate it.
|
|
389
558
|
- **Binary splits cannot produce an even 3-column row at any width.** 220 goes to 110/55/55 whichever
|
|
390
559
|
pane you split. **At a 220-col terminal the width-derived cap is TWO side-by-side panes**; a third
|
|
391
560
|
seat goes below one of them, or into its own tab. "Three panes" is a *height* heuristic
|
|
@@ -713,6 +882,11 @@ twice.** They are mission-independent on purpose: nothing here names a task, a l
|
|
|
713
882
|
**Write beats to a conventional path inside the repository the other tier already reads**, one file per
|
|
714
883
|
tier, and state the path when you report. Then have the reader name the tiers that are ABSENT, never
|
|
715
884
|
the ones present: a list of what IS armed is producible by a seat whose watchers are all dead.
|
|
885
|
+
**And a beat OUTLIVES its mission unless something sweeps it.** Namespace beats per phase/leg and
|
|
886
|
+
sweep them at stand-down: measured 2026-08-17, a beats directory held SEVEN stale beats from prior
|
|
887
|
+
phases beside four live tiers, and a stale beat beside live ones reads as coverage to anyone globbing
|
|
888
|
+
the directory — the outliving-its-trigger failure (rule 11) in file form. Only age distinguishes a
|
|
889
|
+
frozen beat from a live one, so the reader states ages, and the writer removes what it retires.
|
|
716
890
|
30. **A JOURNAL WATCHER ON A RESUMABLE RUN MUST SCOPE TO THE CURRENT ENGAGEMENT.** A resumed run's journal
|
|
717
891
|
still contains the PREVIOUS `run-end`. A watcher that greps the whole file for its terminal event finds
|
|
718
892
|
that old one immediately, concludes the run is over, and exits — on every resume, which is exactly when
|