Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .agents/skills/fleet-onboarding/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,16 @@ down because the user asked.
./scripts/reconcile.sh status
```

Two things to pass on, once:
Three things to pass on, once:

- It **reconciles and does not decide**. No dispatch, no cancel, no reorder,
and it writes no record itself — `scripts/queue.sh` stays the only writer,
which is what keeps the queue single-writer.
- It **will occasionally type one line into Mission Control**, and only ever
the same one: that N tasks are ready and nothing will dispatch them. That is
the loop telling the actor who may act; an unprompted line there is this and
not a bug. It arrives once per transition and never while the lead is
mid-turn.
- To switch it off for good: `./scripts/reconcile.sh stop`. To bring it back:
`./scripts/reconcile.sh start`.

Expand Down
8 changes: 7 additions & 1 deletion .agents/skills/fleet-queue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -655,14 +655,20 @@ spotted.
```

It folds `watch` continuously and runs `collect`, `shepherd` and `refuel` on
their own intervals. Three things to know and nothing else:
their own intervals. Four things to know and nothing else:

- **It changes nothing about how you work.** You still plan, still write
briefs, still `dispatch`. It reconciles the RECORDS with the world; deciding
what runs is yours and it has no verb for it.
- **`queue.sh` is still the only writer.** The loop shells out and never
touches a record. So `list` and the TUI pane cannot start disagreeing with
it.
- **It will type one line at you, and only ever this one:** that N tasks are
ready and nothing will dispatch them. A blocker cleared, the loop may not
act on it, and you were not looking — on 2026-09-10 that sat for six and a
half hours until the operator asked for status. Treat the line as `plan`
already run: `dispatch`. It arrives once per transition and never mid-turn,
so a second one means the ready set has grown again.
- **Run the commands anyway when you want an answer NOW.** `collect` is
idempotent and reading it yourself is always allowed; the loop only means you
are rarely the first to notice.
Expand Down
11 changes: 6 additions & 5 deletions .agents/skills/update-fleet/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ lead's standing context — so it needs both the reinstall in §3 and the
hand-over in §8. A change to `orchestration/voice.example.conf` (or your own
`voice.conf`) needs the same two: it moves what the rendered payload calls you.

`scripts/lib/queue.py` is deliberately absent from this table. The
reconciler's loop never sources it — every pass calls `./scripts/queue.sh` as
a fresh subprocess, so a change there reaches the loop on its very next call,
with no restart needed. §6 covers only `scripts/reconcile.sh` itself, which
the running loop does hold in memory.
`scripts/lib/queue.py` is deliberately absent from this table, and so is
`scripts/lib/notify_lead.py`. The reconciler's loop never sources either — every
pass shells out to `./scripts/queue.sh` and to `python3
scripts/lib/notify_lead.py` as fresh subprocesses, so a change to either
reaches the loop on its very next call, with no restart needed. §6 covers only
`scripts/reconcile.sh` itself, which the running loop does hold in memory.

Run §3–§6 in any order, then §7, then §8 last — §8 is the one that cannot be
automated, and everything else should already be done when you raise it.
Expand Down
31 changes: 24 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,13 @@ names every path and the reason for each.
what keeps the seam honest rather than merely asserted.
- `orchestration/reconcile/` — the reconciler's runtime state: its supervisor's
pid, the heartbeat proving its loop is ticking, its log, the advisory `nudge`
flag and the `down` flag. Written by `./scripts/reconcile.sh` and created on
first start. The loop's code is tracked; nothing it writes is.
flag, the `down` flag, and `notified.json` — which ready tasks the lead has
already been woken about, so a transition is told once. That last one is
runtime state and not a record for the same reason as all the others: "the
lead has been told" is true of one machine's loop and one conversation, and
writing it onto a task would make the loop a second writer over the queue.
Written by `./scripts/reconcile.sh` and created on first start. The loop's
code is tracked; nothing it writes is.
- `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
Expand Down Expand Up @@ -179,14 +184,26 @@ The loop, driven by `./scripts/queue.sh`:
that `ensure` honours the flag and `start` clears it. It consumes `queue.sh
watch` continuously and calls `collect`,
`shepherd` and `refuel` on separate intervals; its header argues every number
and is the full usage. Three things about it are load-bearing:
and is the full usage. Four things about it are load-bearing:

- **It writes nothing.** Every effect goes through `./scripts/queue.sh`, which
stays the only writer over the records. It calls exactly `watch`,
`collect`, `shepherd` and `refuel`, and
`scripts/reconcile-selftest.sh` asserts that the set is those four.
- **It writes no record.** Every effect on the queue goes through
`./scripts/queue.sh`, which
stays the only writer over the records; its own runtime directory above holds
the rest. It calls exactly `watch`, `collect`,
`shepherd`, `refuel` and the read-only `plan`, and
`scripts/reconcile-selftest.sh` asserts that the set is those five and argues
in place why a READ may join it while `dispatch` never may.
- **It reconciles; it does not decide.** No dispatch, no cancel, no reorder,
and it does not re-decide `refuel`'s rule about a spent quota window.
- **It tells the lead when the ready set grows, which is the one thing it says
out loud.** A task whose blocker clears is ready and has no actor: the loop
may not dispatch, and the lead only acts when spoken to — on 2026-09-10 that
cost six and a half hours. So after `collect` it reads `plan` and, when the
ready set has grown, types one line into the lead's terminal naming what is
ready and the command that sends it. Once per transition, never into a lead
mid-turn, and silent when no lead session is running.
`scripts/lib/notify_lead.py` owns those three rules. Notifying is not
deciding: nothing moves, and the choice is still the lead's.
- **`nudge` is the accelerator and never the guarantee.** A worker's Claude
Code `Stop` hook can call `./scripts/reconcile.sh nudge` to bring the
periodic pass forward; a worker that died on a token limit fires no hook at
Expand Down
15 changes: 14 additions & 1 deletion FLEET.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,21 @@ YAML by hand. Nothing to push — the map is gitignored.
restarts nothing while that window is spent; see `AGENTS.md` and
`fleet-queue` §5c.

**A landing is not an ending — it is what unblocks the next task.** So `plan`
is the last thing you run before you go quiet, not the first thing you run when
the operator asks. `collect` prints that advice every time something lands; it
is an instruction, and a ready set left sitting is work with no actor at all.

**Steps 4, 6 and 8 do not have to wait for you to remember them.**
`./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 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.
it is up, and `logs` says what it has been doing. It is also the one thing that
will speak to you unprompted, and only ever to say that the ready set has grown
— read that line as `plan` already run for you, and dispatch.

The operator watches all of that in the TUI queue pane rather than by asking
you: `interface/fleet_queue.lua` draws the queue in a thurbox column, `F3`
Expand Down Expand Up @@ -311,6 +318,12 @@ 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
reorders anything. Choosing what runs is still yours.
- **It wakes you when the ready set grows, and that is not it deciding.** A
task whose blocker clears has no actor — the loop may not dispatch it and you
are not looking — so it reads `plan` and types one line into your terminal
naming what is ready and the command that sends it. Once per transition, and
never while you are mid-turn. The decision it hands you is still yours to
make; what it took away was the six hours before you knew there was one.
- **It writes no record.** Every effect goes through `./scripts/queue.sh`,
which stays the only writer, exactly as the pane stays a pure reader.
- **It is stoppable, and a stop stays stopped.** `orchestration/reconcile/down`
Expand Down
11 changes: 6 additions & 5 deletions scripts/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,15 @@ check_queue() {
# The reconciler's lifecycle, the part of it that can break silently: it runs
# `collect`, so a second instance or a stop that does not stop costs closed
# tasks and reaped sessions.
# reconcile-selftest.sh proves adoption, a durable stop, and the two claims
# reconcile-selftest.sh proves adoption, a durable stop, and the three claims
# that are specific to it — that the four cadences are four separate clocks,
# and that the ONLY thing it ever asks the queue to do is watch, collect,
# shepherd and refuel. It stubs the queue command, so it needs no thurbox, no
# `gh` and no network.
# that the ONLY things it ever asks the queue to do are watch, collect,
# shepherd, refuel and the read-only plan, and that the one line it sends the
# lead goes out on a transition rather than on every pass. It stubs the queue
# command and thurbox-cli, so it needs no thurbox, no `gh` and no network.
check_reconcile() {
if ./scripts/reconcile-selftest.sh >/dev/null; then
ok "reconcile: adopts rather than duplicates, a stop stays stopped, and it writes nothing"
ok "reconcile: adopts rather than duplicates, a stop stays stopped, it writes no record, and it wakes the lead once per transition"
else
# Re-run visibly: a failing claim is the whole message.
./scripts/reconcile-selftest.sh
Expand Down
Loading