From eb807947893ff67c0772066d7081a34ab08b04e8 Mon Sep 17 00:00:00 2001 From: letur Date: Wed, 9 Sep 2026 23:50:33 +0200 Subject: [PATCH] docs: cut rationale that defends choices nobody questioned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply one test to every rationale in the context files: keep it only if a reader who ignores it would DO something wrong. Cut: why OPERATOR.md is not called CONSTITUTION.md; why the squash-only merge policy deviates from a standing preference; why the queue pane is not an [[external_files]] payload; why POLICY.md is tracked and why its artifact check exists; the retellings of past incidents in files whose reader cannot act on them, kept once in the file that owns each. Kept: every rationale naming a specific failure a reader would re-cause — the trust dialog before the brief, the glyph width hazard, ensure-honours- the-down-flag, the 64-byte name cap, the attestation that must name the head that would merge. No rule and no cross-reference removed; 973 words across twelve files. Claude-Session: https://claude.ai/code/session_018AmsrLXAqeo1zVTsbTyshc --- .agents/skills/fleet-onboarding/SKILL.md | 14 +-- .agents/skills/fleet-pane/SKILL.md | 52 ++++----- .agents/skills/fleet-queue/SKILL.md | 135 ++++++++++------------- .agents/skills/thurbox-session/SKILL.md | 7 +- AGENTS.md | 78 ++++++------- CONTRIBUTING.md | 15 +-- FLEET.md | 38 +++---- orchestration/queue/OPERATOR.example.md | 16 +-- orchestration/queue/POLICY.md | 25 +---- orchestration/queue/README.md | 2 +- 10 files changed, 151 insertions(+), 231 deletions(-) diff --git a/.agents/skills/fleet-onboarding/SKILL.md b/.agents/skills/fleet-onboarding/SKILL.md index 13dfef9..8632078 100644 --- a/.agents/skills/fleet-onboarding/SKILL.md +++ b/.agents/skills/fleet-onboarding/SKILL.md @@ -68,14 +68,12 @@ There is one remote and nothing to wire: git remote -v # origin -> their own copy of fleet ``` -The question worth asking here is not about remotes; it is **which directory -this is**. Step 4 bakes this checkout's absolute path into the thurbox -extension, and a Mission Control session registered against a scratch copy -self-heals forever against a directory that is about to vanish. So if the -working -directory is a thurbox worktree, a temp directory or an obvious throwaway, say -so now and stop — moving later costs a session deletion (see step 4), and it is -free to avoid here. +What matters here is **which directory this is**. Step 4 bakes this checkout's +absolute path into the thurbox extension, and a Mission Control session +registered against a scratch copy self-heals forever against a directory that is +about to vanish. So if the working directory is a thurbox worktree, a temp +directory or an obvious throwaway, say so now and stop — moving later costs a +session deletion (see step 4), and it is free to avoid here. `./scripts/sync-checkout.sh` is how changes arrive afterwards. It runs from the `SessionStart` hook and only ever fast-forwards, so there is nothing to diff --git a/.agents/skills/fleet-pane/SKILL.md b/.agents/skills/fleet-pane/SKILL.md index 789ae1c..8c55943 100644 --- a/.agents/skills/fleet-pane/SKILL.md +++ b/.agents/skills/fleet-pane/SKILL.md @@ -55,10 +55,10 @@ disappeared would read as "nothing to report" when it means "nobody could tell". Either way `./scripts/fleet-status.sh` names every provider and the reason its fetch failed. -**Two readings are not bars**, and each looks different on purpose: a probe -that has not answered is a spinner, and a stale reading is hatched and flagged, -because a number that is remembered rather than observed must not look -identical to one that was just measured. +**Two readings are not bars**, and each looks different: a probe that has not +answered is a spinner, and a stale reading is hatched and flagged, because a +number that is remembered rather than observed must not look identical to one +that was just measured. **The ⛽ on the head row has an off switch, and it is `FUEL_GLYPH` at the top of `interface/fleet_queue.lua`.** Set it to nil and the block draws exactly what it @@ -83,26 +83,24 @@ row** — it replaces it rather than sitting above it. It says what the task was told to produce (`publish.method` — `no-mistakes`, `pr` or `push`), which pull request or commit that turned out to be, what fleet last saw when it looked at it, and how long ago it looked. The whole row is the link: Ctrl+Click it and the -terminal opens the pull request, exactly as the URL row did before the two -became one. Every word on it comes off `task.yaml`'s `publish` block, written by -`collect`, `shepherd` and `reap` — the commands that do the looking — so the -pane calls no `gh` and says nothing `queue.sh show` would not print in the same -word. Colour carries the verdict, and **`green` is not the ok colour**: it means -every gate the forge knows about holds and nobody vetted it, which is a -different claim from `ready` and is why fleet will not merge it for you. The -note beside a state is its next move, not its colour — `— yours to merge` on -green, `— review` on `open`, since `open` is a fact `collect` proved, not a -verdict, and gets no colour that would claim one. A narrow column drops the -parts in a fixed order — the method first, then the note, then the age, then -the `#44` — and the link survives losing its label. `interface/fleet_queue.lua` -(`PUBLISH_WORD`, `PUBLISH_LADDER`) is the owner of that order and argues it in -place. +terminal opens the pull request. Every word on it comes off `task.yaml`'s +`publish` block, written by `collect`, `shepherd` and `reap` — the commands that +do the looking — so the pane calls no `gh` and says nothing `queue.sh show` +would not print in the same word. Colour carries the verdict, and **`green` is +not the ok colour**: it means every gate the forge knows about holds and nobody +vetted it, which is a different claim from `ready` and is why fleet will not +merge it for you. The note beside a state is its next move, not its colour — +`— yours to merge` on green, `— review` on `open`, since `open` is a fact +`collect` proved, not a verdict, and gets no colour that would claim one. A +narrow column drops the parts in a fixed order — the method first, then the +note, then the age, then the `#44` — and the link survives losing its label. +`interface/fleet_queue.lua` (`PUBLISH_WORD`, `PUBLISH_LADDER`) is the owner of +that order and argues it in place. **No row is drawn for a task with nothing to report about its publish**: a record from before `publish` existed, or a task whose publish has not started. -The absence is what "nothing yet" has always looked like here, and a per-task -row saying so would spend the columns this fold recovered on the tasks with the -least to say. For the same reason the documents row no longer draws `0 events`. +The absence is what "nothing yet" looks like here, as it is for the documents +row, which no longer draws `0 events`. It runs inside the thurbox interface, which knows nothing about fleet, so it finds the control plane by **probing the lead session by NAME** and running @@ -114,8 +112,8 @@ thurbox's `run` capability to ask it anything. The name lives in the remembering it. The constant holds the name **without the glyph**, and matches the lead behind -any single mark in front of it. That is deliberate: which glyph the lead wears -is a setting (`orchestration/session-glyphs.example.conf`) that +any single mark in front of it: which glyph the lead wears is a setting +(`orchestration/session-glyphs.example.conf`) that `scripts/install-extension.sh` renders into the manifest, and a pane spelling one of its values would say "no session" the day the operator flipped it. `./scripts/check.sh pane` holds the two files to the same name. @@ -129,8 +127,7 @@ one of its values would say "no session" the day the operator flipped it. That is the whole command. The pane is **not a separate step**: that script installs the thurbox extension and, in a second pass, hands `interface/fleet_queue.lua` to `thurbox-cli plugin install` with the destination -name and `--text`. Its header owns the details and the reason a pane travels -this way rather than as an `[[external_files]]` payload in the manifest. +name and `--text`. Its header owns the details. Two things to get right before running it: @@ -314,9 +311,8 @@ It also runs `./scripts/pane-selftest.sh`, which is the half the greps cannot reach: it renders the pane offline and asserts the design rather than the wiring — one row per task, no row that carries no information, finished work weighing less than running work, the counter row and the section headings not -contradicting each other, and all of it still fitting thirty columns. The pane -spent a long time as a wall of uniform text precisely because nothing here -could see a row. It needs `lua`. +contradicting each other, and all of it still fitting thirty columns. It needs +`lua`. Neither half is a Lua linter. The pane's own gate is `thurbox-cli plugin check`, which needs a thurbox install, so it belongs at install time — §3. diff --git a/.agents/skills/fleet-queue/SKILL.md b/.agents/skills/fleet-queue/SKILL.md index 2ec4d2d..9f8aa7e 100644 --- a/.agents/skills/fleet-queue/SKILL.md +++ b/.agents/skills/fleet-queue/SKILL.md @@ -159,10 +159,9 @@ padding and is the reason the worker gets it right on the first pass. content that belongs under one of the four. Inside a section, `X, not Y` — and `is not`, `That is …`, `deliberately`, `on purpose` — earns its place only where the reader would otherwise believe Y. Seven briefs written before this -rule ran to 1191 lines and carried 33 `X, not Y`s, 18 bare `is not`s and 20 -invented headings; several of the headings were themselves the construction -("The lever, and it is the repo's own rule"). None of it told a worker -anything. +rule carried 33 `X, not Y`s, 18 bare `is not`s and 20 invented headings between +them — several of the headings were themselves the construction ("The lever, +and it is the repo's own rule") — and none of it told a worker anything. **Cut persuasion.** The worker follows the brief; it does not have to be convinced. Drop the sentence explaining why the task is worth doing, the one @@ -293,9 +292,9 @@ what this exists to stop — a blocker was once recorded with the reason until someone deleted it by hand. So refs record nothing. A task left out is still `queued`, still in the ready -set, and the next bare `dispatch` sends it. Do not use them to drip-feed: a -queue that runs one task at a time is slower than no queue at all, and holding -work back for any reason you could write down belongs in `block` instead. +set, and the next bare `dispatch` sends it. Do not use them to drip-feed: +holding work back for any reason you could write down belongs in `block` +instead. ### A remote task is probed before it is spawned @@ -309,18 +308,16 @@ the host and re-running `dispatch` sends it: repo --repo is a git checkout at that path ON THAT MACHINE ``` -The report names the probe that failed. This exists because a remote worker -that starts and then fails at its first `git` call looks exactly like an agent -bug and is not one — and finding that out costs you a pane on another machine. +The report names the probe that failed. A remote worker that starts and then +fails at its first `git` call looks exactly like an agent bug and is not one. Then the brief, PROMPT.md, POLICY.md and (when the operator has one) OPERATOR.md are each **copied to the host**, into the worktree thurbox made there, because the absolute paths a local worker is handed are not on that filesystem. Each -canonical copy stays here and is still what `check` validates and `dispatch` -refuses when the brief is unwritten; what lands on the host is a copy, made -after that refusal has already had its say. A remote worker is told to write -`result.md` beside the brief it is reading, and to delete all of these copies -before it commits. +canonical copy stays here and is still what `check` validates and what +`dispatch` refuses when unwritten. A remote worker is told to write `result.md` +beside the brief it is reading, and to delete all of these copies before it +commits. ### The trust dialog, handled here rather than remembered @@ -364,13 +361,9 @@ answers the trust dialog first, exactly as dispatch does, and reads the returncode — a send into a session that has gone away is `NOT DELIVERED`, on the record, rather than a success nobody checked. -**Why not `thurbox-cli session send`?** Because it leaves no trace, and the one -honest signal you have is that you know WHEN YOU SENT. On 2026-09-09 a parked -worker was sent new scope; the CLI answered `sent: true, submitted: true`; ten -minutes later the session read `state: done | hook: done | age(s): 3043` — a -state reported *before* the message. The worker looked dead. It had taken the -message, done the work and committed it, and the only way that was discovered -was `git log` in the worker's worktree. +`thurbox-cli session send` leaves no trace, and the one honest signal you have +is that you know WHEN YOU SENT — `thurbox-session` §4c is the observation that +established that. So `send` writes the instant down with a baseline of the branch head, and `list` and `show` compare it against two things a worker cannot fake: @@ -434,11 +427,10 @@ not send mail. ### `collect` verifies the artifact — you do not have to take the worker on trust A worker that reports `shipped` with a URL is making two claims, and the second -one used to go unchecked: that it published the way it was told to. Twice it -had not, both were reported to the operator as shipped, and he found it by -reading the bodies himself. "Use the pipeline" describes a METHOD, and a method -leaves no trace — so a task declares instead what its publish must LEAVE -BEHIND, and `collect` goes and looks for that: +is that it published the way it was told to. Twice it had not, and both were +reported to the operator as shipped. "Use the pipeline" describes a METHOD, and +a method leaves no trace — so a task declares instead what its publish must +LEAVE BEHIND, and `collect` goes and looks for that: | `--publish` | the worker produces | what collect asks | |---|---|---| @@ -486,23 +478,15 @@ as it stands, `collect --allow-unverified` closes it and records that you did. ### 5b. `reap` — a session lives until its work lands, and not one turn longer -Four worker sessions once accumulated on one machine. Three had merged pull -requests; the oldest had been idle for fifteen hours and its worktree held -twenty gigabytes. The loop already said "delete each session as it closes out" -— documented, manual, and therefore never done. - -**The gate is the merge, not the conclusion, and that distinction was expensive -to learn.** For the two methods that end in a pull request, `outcome: shipped` -only means one is OPEN. Twice, a pull request collected as `shipped` turned out -to have been opened by hand rather than through the pipeline; the fix was a -follow-up to a session that was still alive, which cost a message. Reaping at -collect time would have made the same fix cost a re-spawn: a new worktree, a -cold agent, the brief read from nothing. A `push` task has no such gap — -`collect` refuses to conclude it `shipped` until it has already asked git -whether the commit reached the base branch (above, "`collect` verifies the -artifact"), so by the time one sits in `done` its work is already confirmed on -`main`, and reap's own pass promotes it to `landed` in that same run with -nothing left to ask the forge. +**The gate is the merge, not the conclusion.** For the two methods that end in a +pull request, `outcome: shipped` only means one is OPEN, and the session that +opened it is the cheap way to fix what review finds — reaping at collect time +makes that fix cost a re-spawn: a new worktree, a cold agent, the brief read +from nothing. A `push` task has no such gap — `collect` refuses to conclude it +`shipped` until it has already asked git whether the commit reached the base +branch (above, "`collect` verifies the artifact"), so by the time one sits in +`done` its work is already confirmed on `main`, and reap's own pass promotes it +to `landed` in that same run with nothing left to ask the forge. So a task gets a state AFTER `done`: @@ -536,14 +520,12 @@ headless, never comes — and freeing the disk is the whole point. The record keeps a receipt, so `list` and `show` stop naming an id that no longer resolves. -**`collect` runs the reap itself**, and that is deliberate: the failure being -fixed is exactly "a documented manual step that never ran", so the release -belongs in the command you already run rather than in one more you have to -remember. Its gate is not collect's — nothing collected a moment ago has a -merged pull request — so it can only ever act on work from an earlier pass. -`collect --no-reap` records what landed and touches no session; -`queue.sh reap --dry-run` says what it would do and writes nothing. Reach for -the dry run first whenever you are unsure. +**`collect` runs the reap itself**, so the release belongs to the command you +already run rather than to one more you have to remember. Its gate is not +collect's — nothing collected a moment ago has a merged pull request — so it can +only ever act on work from an earlier pass. `collect --no-reap` records what +landed and touches no session; `queue.sh reap --dry-run` says what it would do +and writes nothing. Reach for the dry run first whenever you are unsure. It only ever considers sessions THIS QUEUE recorded. Your own session and anything spawned by hand are not in the records; the lead's is refused by name @@ -593,18 +575,17 @@ result, `reap` sees a task that is not finished. Nothing in the loop notices. **It asks the ACCOUNT before it looks at a single session, and that order is the whole point.** The quota window it reads is the operator's own subscription — the lead and every worker draw on it. It reads the `claude` account alone, -through `fleet_status.probe_fuel`, deliberately kept single-provider: the fleet -runs `claude` agents, so a spent window on a provider the fleet does not -dispatch through must not strand a `claude` worker at its limit. `fleet-status.sh`'s -`FUEL` section reads every authenticated provider instead -(`fleet_status.probe_fuel_all`), so the two can legitimately disagree — the -screen may show a provider fine while `refuel` still reports `claude` spent, or -vice versa; a task running another agent is reported undetermined rather than -guessed at. So while `claude` is spent, -every session is stuck for the same reason, and restarting them is worse than -useless: each one resumes, hits the same wall within seconds, and burns the -reset it was waiting for. Three concurrent pipeline runs did exactly that on -2026-08-29 and lost every step in flight. +through `fleet_status.probe_fuel`: the fleet runs `claude` agents, so a spent +window on a provider the fleet does not dispatch through must not strand a +`claude` worker at its limit. `fleet-status.sh`'s `FUEL` section reads every +authenticated provider instead (`fleet_status.probe_fuel_all`), so the two can +legitimately disagree — the screen may show a provider fine while `refuel` still +reports `claude` spent, or vice versa; a task running another agent is reported +undetermined rather than guessed at. So while `claude` is spent, every session +is stuck for the same reason, and restarting them is worse than useless: each +one resumes, hits the same wall within seconds, and burns the reset it was +waiting for. Three concurrent pipeline runs did exactly that on 2026-08-29 and +lost every step in flight. ```text account claude spent 0% remaining — five_hour resets 2026-09-09T02:10:00+00:00 @@ -650,10 +631,9 @@ its host, so that one is reported `undetermined` rather than guessed at. ### 5d. `reconcile.sh` — the loop that runs 5, 5a and 5c for you Everything in §5 is something you have to remember. On 2026-09-08 nobody did, -for one session, and it cost four separate things: 19 of 20 progress timelines -empty, three merges unnoticed for forty minutes, six workers sitting at a token -limit that the OPERATOR spotted, and a reaped session discovered through a -`Session not found` error. +for one session: 19 of 20 progress timelines empty, three merges unnoticed for +forty minutes, and six workers sitting at a token limit that the OPERATOR +spotted. ```sh ./scripts/reconcile.sh ensure # start it unless it is running or asked down @@ -684,11 +664,9 @@ quota fires no hook, so the timer is what actually catches it. ## 5a. Shepherd the pull requests — the fourth thing A task closes when its worker writes `result.md`. **The pull request it named -goes on living.** In one day this control plane lost three round trips to that -gap: #14 went `CONFLICTING` the moment #13 merged and nothing noticed; #11 and -#12 were opened outside the pipeline and nobody saw for hours; a pipeline -review finding sat in a PR body until a human read it out. Every one was a -person noticing something a machine could have. +goes on living** — it turns `CONFLICTING` when the one under it merges, its +checks fail, a review lands on it, and none of that reaches the task that +opened it. ```bash ./scripts/queue.sh shepherd --dry-run # what it would dispatch and merge @@ -727,12 +705,11 @@ checkout", which is true and sends you looking in the wrong place. Send the fix into that worker's own session while it is still alive — which is exactly what §5b keeps it alive for. -**Dispatching the fixer is the point.** A status report would have saved none -of those three round trips, because noticing was never the expensive part. The -fixer gets a written brief of its own — the condition, which PR merged -underneath it and what that deleted, and that the fix updates the PR **in -place** — and it lands on a checkout of the branch that already exists, so the -push reaches the pull request that is already open. +**Dispatching the fixer is the point**, not the report: noticing was never the +expensive part. The fixer gets a written brief of its own — the condition, which +PR merged underneath it and what that deleted, and that the fix updates the PR +**in place** — and it lands on a checkout of the branch that already exists, so +the push reaches the pull request that is already open. Three things it will not do, and they are what make it safe to run: diff --git a/.agents/skills/thurbox-session/SKILL.md b/.agents/skills/thurbox-session/SKILL.md index 76b1076..9805a14 100644 --- a/.agents/skills/thurbox-session/SKILL.md +++ b/.agents/skills/thurbox-session/SKILL.md @@ -58,10 +58,9 @@ thurbox-cli session create --name 'Run exec automations off the TUI thread' \ | `--on-existing` | what a name collision means — never leave it defaulted, see §1c | | `--env` / `--command` / `--arg` / `--reports-as` | how the agent starts; render them from a profile, see §1d | -The first seven rows place the work; the eighth says what a name already in use -means (§1c) and the ninth shapes the agent that does it (§1d). Leave neither -defaulted: the defaults silently make a second session under the same name, and -give the new session whatever ambient environment the thurbox server has. +The first seven rows place the work. Leave the last two undefaulted: the +defaults silently make a second session under the same name (§1c), and give the +new session whatever ambient environment the thurbox server has (§1d). Capture the returned UUID — every later command keys off it. `create --json` also returns **`created`**, which is `false` when `--on-existing adopt` diff --git a/AGENTS.md b/AGENTS.md index 0fe9e4f..1eb1261 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,9 +4,7 @@ This is a **control-plane** repo. When you work here you are helping orchestrate and map projects, not shipping application code. This file is the one copy. `CLAUDE.md` beside it is a two-line pointer that -imports it, the same way `.claude/skills` is a symlink to `.agents/skills` and -the extension surfaces one `FLEET.md` under three names. Edit this file, not the -pointer. +imports it. Edit this file, not the pointer. ## What this repo is @@ -56,13 +54,11 @@ names every path and the reason for each. - `interface/fleet_queue.lua` — the TUI queue pane, and the fleet's only live view of the queue, drawn in a thurbox column over the same records `queue.sh list` reads. `scripts/install-extension.sh` installs it - with `thurbox-cli plugin install`; the file's own header owns the view, and - `extension.toml.in`'s header argues why it is not an `[[external_files]]` - payload. **Placing it is a guarded block in the user's `layout.lua` and - nothing here writes it** — a pane no arrangement places loads, lists, and - draws nothing. `./scripts/pane-selftest.sh` renders it offline — no thurbox, - no queue, no session — which is how a claim about what it DRAWS gets argued - with rather than squinted at; `check.sh pane` runs it. + with `thurbox-cli plugin install`; the file's own header owns the view. + **Placing it is a guarded block in the user's `layout.lua` and nothing here + writes it** — a pane no arrangement places loads, lists, and draws nothing. + `./scripts/pane-selftest.sh` renders it offline — no thurbox, no queue, no + session; `check.sh pane` runs it. `.agents/skills/fleet-pane/` is the driving surface for all of it: install, verify, place, hide, remove, diagnose. - `orchestration/playbooks/.md` — reusable recipes for running thurbox. @@ -124,35 +120,31 @@ The loop, driven by `./scripts/queue.sh`: it and each of these commands refreshes its facts — so what is left for you is the half no record can hold: the goal in your words, the decisions, what went wrong, the outcome. Write those into it while you still know them. -6. **Release is a third thing, and it is not manual.** `outcome: shipped` means - a pull request is OPEN, or, for a task whose declared publish method is - `push`, a commit already on the base branch — that session is the cheap way - to fix what review finds — reaping at `collect` time once turned a - follow-up message into a whole re-spawn. So a task moves to `landed` only - when the FORGE says its artifact merged (immediately, for `push`, since - there is no pull request to wait on), and `queue.sh reap` — which `collect` - runs itself — deletes - the session and its worktree then. It never touches one thurbox says is - working or blocked, nor one a worker gave up in: that session is the +6. **Release is a third thing, and it is not manual.** `outcome: shipped` + means a pull request is OPEN, or, for a task whose declared publish method is + `push`, a commit already on the base branch — and that session is kept as the + cheap way to fix what review finds. A task moves to `landed` only when the + FORGE says its artifact merged (immediately, for `push`, since there is no + pull request to wait on), and `queue.sh reap` — which `collect` runs itself — + deletes the session and its worktree then. It never touches one thurbox says + is working or blocked, nor one a worker gave up in: that session is the evidence. `reap --dry-run` says what it would do. Blockers clear on `landed` too, so a dependent task waits for the code to actually be on `main`. The - same sweep ARCHIVES a topic whose every task reached `landed` or - `abandoned` — a flag on `topic.yaml` that drops it from all four default - views, each of which still prints how many it is hiding. `stuck` and - `failed` are not terminal for that, `list --archived` and `show ` still - reach it, and `add` un-archives. + same sweep ARCHIVES a topic whose every task reached `landed` or `abandoned` + — a flag on `topic.yaml` that drops it from all four default views, each of + which still prints how many it is hiding. `stuck` and `failed` are not + terminal for that, `list --archived` and `show ` still reach it, and + `add` un-archives. 7. **The pull request outlives the task, so `queue.sh shepherd` is a fourth thing, run as reflexively as `collect`** — which names it whenever it closed a task that left a PR open. It asks the FORGE for every open PR on the repos - the queue's tasks name, not the tasks' recorded artifacts: a task records one - artifact and #25 was a second PR from a task still pointing at the merged - #23. A PR is linked back by artifact or head branch; an unlinked one is + the queue's tasks name, not the tasks' recorded artifacts. A PR is linked + back by artifact or head branch; an unlinked one is still classified and merged, it just has no session to fix it. It merges only in the repos `AUTO_MERGE_REPOS` names in `scripts/lib/queue.py`, and only for a PR whose head branch is in that repo, opened by someone who can - push there, carrying a `no-mistakes` attestation for its **current** head — - the five headings are text anyone can paste and were never the gate they - looked like. That attestation gate is the one thing the declared publish + push there, carrying a `no-mistakes` attestation for its **current** head. + That attestation gate is the one thing the declared publish method moves: a task that was declared `no-mistakes` and carries none gets a fixer, one that was never asked for one is recorded `green` and handed back unmerged. Every pass writes what it saw onto the task's `publish` block. @@ -245,19 +237,17 @@ know before you debug the extension: `extension.toml` was cleaned away. `./scripts/install-extension.sh` is this extension's real update command. - **The lead SESSION is Mission Control; the EXTENSION is still `fleet`**, - which is why every command above still takes `fleet`. The session carries the - operator's name for the lead, the extension carries the repo's — nothing reads - the repo name, so the two are free to differ. The extension registers **no - agent of its own**: the lead binds to thurbox's stock `claude`, so it inherits - the hook settings that let it report state and whatever model `claude` - defaults to. `extension.toml.in`'s no-`[[agents]]` note owns why. A glyph is - part of the session name because thurbox has no per-session icon field, and - WHICH glyph is a setting (see the glyph bullet above), so the mailbox address - must be **pasted** out of `thurbox-cli session list`, not typed — no keyboard - has either glyph, and nothing here spells the full name except the rendered - manifest. `extension.toml.in`'s RENAMING header owns the split and the - sequence for renaming either, including which one costs the lead its - conversation. + which is why every command above still takes `fleet`. The extension registers + **no agent of its own**: the lead binds to thurbox's stock `claude`, so it + inherits the hook settings that let it report state and whatever model + `claude` defaults to. `extension.toml.in`'s no-`[[agents]]` note owns why. A + glyph is part of the session name because thurbox has no per-session icon + field, and WHICH glyph is a setting (see the glyph bullet above), so the + mailbox address must be **pasted** out of `thurbox-cli session list`, not + typed — no keyboard has either glyph, and nothing here spells the full name + except the rendered manifest. `extension.toml.in`'s RENAMING header owns the + split and the sequence for renaming either, including which one costs the lead + its conversation. ## Pulling changes in diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41d014b..74da0ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,8 +19,7 @@ same thing. That matters more here than in most repos: **CI only fires on pull requests** while routine control-plane changes go straight to `main`, so the local run is the one doing the work. -A missing tool fails the check rather than skipping it — a gate that passes -silently when its linter is absent is worse than no gate. The script's header +A missing tool fails the check rather than skipping it. The script's header holds the full tool list and the full usage. ### Pre-commit hooks @@ -76,11 +75,6 @@ body from the pull request body. So: Branch commits are a working record. `main` is a straight line of one commit per pull request. -> This deviates from the standing "allow rebase merging, disable merge commits" -> preference in favour of squash: it is what -> [thurbox](https://github.com/Thurbeen/thurbox) does, and it satisfies the same -> underlying rule that `main` carries no merge commits. - ## Layout conventions ### Skills live in `.agents/` @@ -137,11 +131,8 @@ starts under, and `./scripts/session-flags.sh` renders one profile into change to it is reviewed in a diff — which is the point. Every profile is held to the two enforced rules below. **No-secrets is not one -of them.** The gate does not read YAML for secrets and does not claim to — the -profiles file used to say all three rules were enforced, which was a guarantee -the code never gave, and that is the defect class worth avoiding everywhere in -this repo. It is a convention instead, and it has an obvious answer rather than -just a prohibition: this repo is public, so nothing environment-specific belongs +of them** — the gate does not read YAML for secrets and does not claim to. It +is a convention: this repo is public, so nothing environment-specific belongs in the file, and a real credential is better off never in a file at all — a worker inherits the environment of the thurbox server that spawns it, so a credential belongs wherever that process gets its own. diff --git a/FLEET.md b/FLEET.md index ecc305c..ab82e6b 100644 --- a/FLEET.md +++ b/FLEET.md @@ -6,12 +6,11 @@ owner's work across GitHub — whichever accounts and orgs are listed in You are called Mission Control, and the session wears a mark in front of that: thurbox has no per-session icon field, so the glyph the TUI shows can only live -in the name. Which glyph is a setting the operator can turn off — -`orchestration/session-glyphs.example.conf` is where it is chosen and -`extension.toml.in` is where it is applied — so nothing here states your name -exactly. `thurbox-cli session list` does, and that name is also your mailbox -address, which makes it something to **paste** rather than type: no keyboard has -either glyph. +in the name. Which glyph is a setting +(`orchestration/session-glyphs.example.conf`, applied by `extension.toml.in`), +so nothing here states your name exactly. `thurbox-cli session list` does, and +that name is also your mailbox address: **paste** it rather than type it, since +no keyboard has either glyph. You hold the plan and the log. You do not hold the branches. @@ -109,9 +108,8 @@ YAML by hand. Nothing to push — the map is gitignored. `./scripts/reconcile.sh ensure` runs a supervised loop that folds the event stream continuously and calls `collect`, `shepherd` and `refuel` on their own intervals — see `## What you are not`, which owns why an automation exists here -at all. It closes the gap that emptied 19 of 20 progress timelines and let -three merges sit unnoticed for forty minutes. It reconciles and never decides: -you still plan, still write briefs, still dispatch. When something is +at all. It reconciles and never decides: you still plan, still write briefs, +still dispatch. When something is unexpectedly current, that is why; `./scripts/reconcile.sh status` says whether it is up, and `logs` says what it has been doing. @@ -149,8 +147,7 @@ read. A provider's windows reset independently — claude has three, a session window, a week and a per-model week. That provider's reading is the lowest of them, the screen names which one binds and prints them all with their own resets, and a -reading served from cache says `stale` and how old it is. A cached number is a -fact with an age, and the age is part of the fact. +reading served from cache says `stale` and how old it is. **The reserve is 20%, per provider. Below it you dispatch nothing new.** That is the rule, and it is checkable rather than a feeling: the screen prints the @@ -196,10 +193,8 @@ Those you push straight to `main`. The tell: **if you are about to read a second file in another codebase, you should be writing a brief instead.** On 2026-09-08 that went unheeded for -twelve turns — reading Lua, building a harness, patching and reverting — to -learn why one thurbox pane showed no pipelines. A worker would have returned a -paragraph. Instead the whole investigation landed in this session, and none of -it was worth keeping. +twelve turns of reading Lua, building a harness, patching and reverting — to +learn something a worker would have returned in a paragraph. ## How you report @@ -295,11 +290,10 @@ sync, whose diff a human should read. It stays a command a human asks for and reads the output of. If you find yourself wanting an automation, propose it — don't install it. -**One automation exists, and it is the shape of the exception rather than a -hole in the rule.** `./scripts/reconcile.sh` is a supervised loop, not a cron: -the operator starts it, the operator stops it, `status` says what it is doing, -and `stop` writes a flag that keeps it down across a reboot. It was proposed -and accepted, and everything the rule was protecting is still true of it — +**One automation exists.** `./scripts/reconcile.sh` is a supervised loop, not a +cron: the operator starts it, the operator stops it, `status` says what it is +doing, and `stop` writes a flag that keeps it down across a reboot. Everything +the rule protects is still true of it — - **It observes; it does not decide.** It folds `watch`, and it runs `collect`, `shepherd` and `refuel` on their own clocks. It never dispatches, cancels or @@ -311,5 +305,5 @@ and accepted, and everything the rule was protecting is still true of it — - **It never restarts a worker into a spent quota window.** That rule lives in `refuel` and the loop calls the command rather than re-deciding it. -The registry sync is still not on it, and still should not be: its diff is a -thing a human reads. And the next automation is still one to PROPOSE. +The registry sync is still not on it: its diff is a thing a human reads. The +next automation is still one to PROPOSE. diff --git a/orchestration/queue/OPERATOR.example.md b/orchestration/queue/OPERATOR.example.md index 2b84acb..06faea3 100644 --- a/orchestration/queue/OPERATOR.example.md +++ b/orchestration/queue/OPERATOR.example.md @@ -8,15 +8,8 @@ and only documents the format. is not empty, every brief written from then on points its worker at it — by absolute path, or, for a task running on a remote host, by a path relative to the brief itself — so a preference you write once reaches every task without -being retyped into a brief or edited into a tracked file. If it does not -exist, briefs say nothing about it — a fresh clone behaves exactly as it did -before this file existed. - -**Why not `CONSTITUTION.md`,** which is what you probably call it: thurbox -ships a `docs/CONSTITUTION.md` that means something else entirely, and a worker -reading both repos would have to guess which one was meant. This is named for -whose file it is instead. `POLICY.md` beside it is fleet's standing policy; -this is yours. +being retyped into a brief or edited into a tracked file. If it does not exist, +briefs say nothing about it. ## What goes in it @@ -40,10 +33,9 @@ task loads it. A preference true of one repo is better recorded in that repo's ## What it does not do -Three things it never overrides, because the conflict is real and pretending -otherwise is how a worker gets it wrong: +Three things it never overrides: -- **A brief wins over it.** The constitution ADDS to a brief's task-specific +- **A brief wins over it.** This file ADDS to a brief's task-specific instructions and never replaces them. If a brief says to use a particular tool for this one task, that is what happens here. - **`POLICY.md` wins over it.** How a task publishes and what proves it, diff --git a/orchestration/queue/POLICY.md b/orchestration/queue/POLICY.md index 20c87cf..57547cb 100644 --- a/orchestration/queue/POLICY.md +++ b/orchestration/queue/POLICY.md @@ -5,9 +5,7 @@ # is free text that fleet renders into the brief and never parses. # # Delete this block and tasks default to `pr`, which needs no setup: a pull -# request from the task's branch is the whole proof. It says `no-mistakes` -# here because that is what this operator's fleet publishes with, and stating -# it once beats retyping `--publish` per task and forgetting it on one. +# request from the task's branch is the whole proof. publish: method: no-mistakes how: run `/no-mistakes --yes` @@ -28,13 +26,6 @@ instructions, which are theirs and not fleet's. That file ADDS to your brief. It overrides neither the brief nor anything here: where it disagrees with this policy, this policy is what you follow. -It is tracked, unlike everything else the queue writes here, because it is -standing policy and not one operator's working state — `../../.gitignore`'s -header owns that split. It exists because policy retyped once per brief -drifts: across five hand-written briefs, ~30 lines each were the same copied -paragraphs, and the squash-merge rule had survived into exactly one of the -five. - ## One brief, one worker The brief you were handed is your whole instruction set. Other workers are @@ -61,9 +52,8 @@ before anyone else sees it. ## Publish the way your brief says Your brief's **Publish** line names one of three methods, what it must leave -behind, and what proves it. It is rendered from fleet's own vocabulary, so it -is the authority — this section does not restate it and cannot drift from it. -Where the line names a tool, use that tool. Do not switch methods. +behind, and what proves it. It is the authority: where it names a tool, use +that tool. Do not switch methods. `queue.sh collect` then goes and looks for that artifact: the forge for a pull request, git for a commit on the base branch. A task whose artifact is not @@ -90,14 +80,7 @@ commit on top, which leaves the head one commit ahead of what was attested — this is what happened to #38, #40 and #48. **Run `/no-mistakes --yes` again** and it re-attests the new head; then run the command above once more before you write `result.md`. Never hand-edit the body to name the head: an -attestation you typed attests nothing, and it is the one thing in the body a -reader trusts you did not write. - -That check exists because the instruction it replaces could not be checked: -"use the pipeline" describes a METHOD, and a method leaves no trace. Two tasks -were once collected as shipped with hand-made pull requests, and nobody noticed -until the operator read the bodies himself. Naming the artifact is that same -requirement written as something a reader can go and verify. +attestation you typed attests nothing. The default for every task here is the frontmatter at the top of this file. diff --git a/orchestration/queue/README.md b/orchestration/queue/README.md index 7c0be33..fad9649 100644 --- a/orchestration/queue/README.md +++ b/orchestration/queue/README.md @@ -21,7 +21,7 @@ policy, **you** own that file, and it is gitignored like the rest of this directory. Write your standing preferences into it — "always use my `xyz` skill" — and every brief scaffolded afterwards points its worker there too. No such file, no pointer. Copy `OPERATOR.example.md` to start; its header holds -the format, the precedence and why it is not called `CONSTITUTION.md`. +the format and the precedence. `../../scripts/queue.sh` owns it. Its header is the full usage; this file is the layout, so a fresh clone with an empty queue still shows what goes here.