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
12 changes: 8 additions & 4 deletions .agents/skills/fleet-onboarding/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,12 @@ a typo or an org the token cannot see, and it is fixable in seconds now.
./scripts/install-extension.sh
```

It renders `extension.toml` (gitignored — it carries this clone's absolute
path) from `extension.toml.in`, then installs it. Verify, rather than trusting
the installer's own closing message:
It renders two gitignored files and installs them: `extension.toml` from
`extension.toml.in` (it carries this clone's absolute path), and
`FLEET.rendered.md` from `FLEET.md` (it carries the two names in
`orchestration/voice.example.conf` — what the lead calls the operator, and what
it answers to; copy that file to `voice.conf` beside it to change either).
Verify, rather than trusting the installer's own closing message:

```bash
thurbox-cli extension status fleet --json
Expand Down Expand Up @@ -294,7 +297,8 @@ never the mechanism: a worker that ran out of quota fires no hook at all.
## 7. Hand over

**Nothing this skill wrote is tracked.** `registry/owners.txt`,
`registry/repos.generated.yaml` and `extension.toml` are all gitignored, so
`registry/repos.generated.yaml`, `extension.toml` and `FLEET.rendered.md` are
all gitignored, so
`git status` is clean and there is nothing to commit or push. That is the
design, not a step you forgot: this repo is public, and an index of every repo
the operator can reach — along with one machine's absolute paths — does not
Expand Down
10 changes: 6 additions & 4 deletions .agents/skills/update-fleet/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,17 @@ do. Otherwise map the list:

| A path in the range | Step | Why |
|---|---|---|
| `extension.toml.in`, `FLEET.md` — or a `reinstall-extension:` line | §3 | the installed extension no longer matches what it was rendered from |
| `extension.toml.in`, `FLEET.md`, `orchestration/voice.example.conf` — or a `reinstall-extension:` line | §3 | the installed extension no longer matches what it was rendered from |
| `interface/fleet_queue.lua` | §4 | the installed plugin is a stale copy of that file |
| `registry/owners.txt` | §5 | the generated map covers the wrong owners |
| `scripts/reconcile.sh` | §6 | the running reconciler loop is executing old code |
| `FLEET.md`, `AGENTS.md`, `CLAUDE.md`, `.agents/skills`, `.claude/skills`, `.claude/settings.json` — or a `restart-lead:` line | §8 | the lead is holding instructions it froze at launch |

`FLEET.md` is deliberately in two rows: it is the extension's `[[files]]`
payload *and* the lead's standing context, so it needs both the reinstall in §3
and the hand-over in §8.
`FLEET.md` is deliberately in two rows: the extension's `[[files]]` payload is
`FLEET.rendered.md`, which the installer renders FROM it, *and* it is the
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
Expand Down
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,24 @@
# extension.toml.in's header says why the path cannot be a `~`.
/extension.toml

# GENERATED. FLEET.md with the two voice names substituted in, written by
# scripts/install-extension.sh and shipped as the extension's [[files]] payload.
# FLEET.md is the tracked source and carries the placeholders;
# orchestration/voice.example.conf carries the defaults. Rendering to a second
# file is what lets an operator rename themselves without dirtying the tree
# scripts/sync-checkout.sh has to fast-forward.
/FLEET.rendered.md

# WORKING STATE. Your GitHub owners. registry/owners.example.txt is the tracked
# copy that documents the format, so a fresh clone still knows what goes here.
/registry/owners.txt

# WORKING STATE. What you want the lead to call you, and what it answers to.
# orchestration/voice.example.conf is the tracked copy that carries the
# defaults and the renderer falls back to it — so this file only needs to exist
# when you want something other than SLAYER and VEGA.
/orchestration/voice.conf

# MACHINE. Whether this terminal's font draws a two-cell emoji at two cells is
# true of one machine, so the glyph setting is one machine's answer.
# orchestration/session-glyphs.example.conf is the tracked copy that carries the
Expand Down
11 changes: 9 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ Adding a file means deciding which side it is on, and saying so:
| If it is… | Then | Example |
|---|---|---|
| machinery — scripts, skills, playbooks, prose | tracked | `scripts/`, `playbooks/ship-feature.md` |
| generated from a live source | ignored | `repos.generated.yaml` |
| generated from a live source, or rendered from a tracked one | ignored | `repos.generated.yaml`, `FLEET.rendered.md` |
| true on one machine | ignored | `extension.toml`, `orchestration/reconcile/` |
| the operator's own working state | ignored | `runs/<date>-<slug>.md`, `queue/<topic>/` |
| the operator's own working state | ignored | `runs/<date>-<slug>.md`, `queue/<topic>/`, `voice.conf` |

Prefer a whole ignored **directory** with a `!` negation for the one tracked
form it contains, the way `registry/context/`, `orchestration/runs/` and
Expand Down Expand Up @@ -160,6 +160,13 @@ session reports nothing and renders as `uncovered` while it works.
`./scripts/install-extension.sh` and is gitignored — it carries your clone's
absolute path. Edit the `.in` file and re-run the installer.

The same run renders `FLEET.rendered.md`, the payload the manifest ships, from
the tracked `FLEET.md` — substituting the two names in
`orchestration/voice.example.conf` (or the gitignored `voice.conf` beside it).
Edit `FLEET.md`, never the rendered copy. Rendering to a second file is what
lets an operator change what the lead calls them without dirtying the tree
`./scripts/sync-checkout.sh` has to fast-forward.

`min_thurbox_version` there is a claim about the whole range the manifest
supports, and the file's header records why the floor sits where it does, along
with why the path cannot be a `~`. Raise it only alongside the reason.
Expand Down
60 changes: 35 additions & 25 deletions FLEET.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ You are the **Mission Control** session: the long-lived control plane for its
owner's work across GitHub — whichever accounts and orgs are listed in
`registry/owners.txt`.

You are called Mission Control, and the session wears a mark in front of that:
The SESSION is called Mission Control, and it 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
Expand Down Expand Up @@ -203,37 +203,43 @@ it was worth keeping.

## How you report

**A routine status reply is a table, then AT MOST one line under it** — and
nothing under it at all when nothing surprised you. This is the shape:
You answer to @ASSISTANT_NAME@; the operator is @OPERATOR_NAME@. Mission
Control stays the SESSION's name — thurbox's, and the mailbox address.

```text
TOPIC TASK STATE ARTIFACT
shepherd-prs merge-open-prs shipped PR #34 (checks green)
remote-hosts probe-timeouts working —
declutter-app strip-dead-css blocked waits on #34

One surprise: probe-timeouts found ssh probes run serially.
```
**The default reply is one or two lines.** Name a task only when something
about it CHANGED or surprised you. `interface/fleet_queue.lua` draws the board
live in a thurbox column — topics, states, artifacts — so a status table in a
reply repeats what @OPERATOR_NAME@ is already looking at, which the "one fact
in one place" rule below already forbids.

`./scripts/fleet-status.sh` is that opening block in ONE call — fuel, queue,
sessions, PRs, checkout — so assemble it from five commands only when that one
has failed you.
`./scripts/fleet-status.sh` answers "where are we" in ONE call — fuel, queue,
sessions, PRs, checkout. Run it when asked and assemble the same picture from
five commands only when it has failed you. Asked is the condition: unprompted,
it is the table again.

**The register is Mission Control's, and it lives in verb choice and
terseness, not in props.**
**The register lives in verb choice and terseness, not in props.** Short
declarative sentences. No adjectives, no build-up, no reassurance. State a
limit as a fact and move on.

| do | example |
| --- | --- |
| terse status calls | `Three on the board, one holding.` |
| go/no-go phrasing for a gate | `#34 is go — checks green.` |
| telemetry words for an unfinished thing | `probe-timeouts running, no result yet.` |
| terse status calls | `Three running. One holding.` |
| go/no-go phrasing for a gate | `#34 is clear. Checks green.` |
| telemetry words for an unfinished thing | `probe-timeouts running. No result yet.` |
| hold/release words for a blocker | `Holding 03 until #34 is on main.` |
| a limit stated flat | `I cannot merge that. You can.` |

| do not | why |
| --- | --- |
| quoted film lines, "Houston", ranks, callsigns, an invented ship | it is a register, not a costume |
| emoji, rocket glyphs, ASCII flourish | the operator reads this in a terminal |
| a voice word that softens a state word | the rule below outranks this one |
| quoted lines, callsigns, ranks, an invented ship or facility | it is a register, not a costume |
| roleplay narration, in-fiction preamble, a themed sign-off | same rule, and it costs a paragraph |
| emoji, glyphs, ASCII flourish | @OPERATOR_NAME@ reads this in a terminal |
| a voice word standing in for a state word | the accuracy rules below outrank this one |

**The register is free; a bit is not.** A register is how the sentences you were
already writing get phrased — it adds no tokens. A bit adds a paragraph nobody
asked for. When the two are indistinguishable in effect, you have written the
bit. Cut it.

The register never costs a fact. Where the two pull against each other, the
fact wins:
Expand All @@ -246,12 +252,16 @@ fact wins:
- **No estimates** — not time, not effort, not percent complete.
- **Report the artifact, not the intention, and never restate the brief.** A
PR URL and its check status. "The worker should have opened a PR" is not a
result, and the operator already approved the brief — give them the outcome
result, and @OPERATOR_NAME@ already approved the brief — give the outcome
and what was surprising.
- **Say what you did not do**, and why, in one line. Silence about a skipped
step reads as completion.
- **One fact in one place.** Do not repeat in prose what the block above
already shows, and never re-explain a settled decision — act on it.
- **One fact in one place.** Do not repeat in prose what the pane already
shows, and never re-explain a settled decision — act on it.

Both names are settings, not literals: `orchestration/voice.example.conf`
carries them, a gitignored `voice.conf` beside it overrides, and
`scripts/install-extension.sh` renders them into the copy you are reading.

## Rules that bite

Expand Down
32 changes: 23 additions & 9 deletions extension.toml.in
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,11 @@
# extensions and two self-healing sessions, each recreating itself, plus an
# orphaned `~/.config/thurbox/extensions/fleet/`.
#
# FLEET.md's FILENAME is separate again: this manifest names it four times (one
# [[files]] path, three [[symlinks]] targets), so renaming the file means
# editing those four lines too. Neither rename above requires it.
# THE PAYLOAD's FILENAME is separate again: this manifest names
# `FLEET.rendered.md` four times (one [[files]] path, three [[symlinks]]
# targets), so renaming it — or renaming the `FLEET.md` it is rendered from —
# means editing those four lines and `scripts/install-extension.sh` together.
# Neither rename above requires it.
#
# WORKER session names are in none of this. They are free-form strings that
# nothing resolves — by the convention in orchestration/playbooks/ each is an
Expand All @@ -247,7 +249,7 @@
name = "fleet"
description = "Control-plane session: the repo map and thurbox orchestration"
config_version = 1
version = "1.2.0" # this extension's own version; bump on changes
version = "1.3.0" # this extension's own version; bump on changes

# WHY THE FLOOR IS 2.19.0 — it is a claim about what this control plane's own
# prose relies on, not a note about the version somebody happens to run.
Expand Down Expand Up @@ -312,23 +314,35 @@ min_thurbox_version = "2.19.0"
# Only the standing context ships. The registry, playbooks and run logs stay in
# the git checkout — they are versioned there, and `repos.generated.yaml` is
# generated, so shipping a copy would immediately drift.
#
# AND WHAT SHIPS IS THE RENDERED COPY, NOT THE TRACKED ONE. `FLEET.md` is the
# source and carries `@OPERATOR_NAME@` and `@ASSISTANT_NAME@`;
# `scripts/install-extension.sh` substitutes the two names out of
# `orchestration/voice.example.conf` (or the gitignored `voice.conf` beside it)
# and writes `FLEET.rendered.md`, which is gitignored exactly as the rendered
# manifest is. Rendering to a second file rather than in place is the whole
# point: an operator who changed what the lead calls them has edited no tracked
# file, so `scripts/sync-checkout.sh` still has a clean tree to fast-forward.
#
# The `substitute` flag below would not have done this. It substitutes `{home}`
# and nothing else, so the names could never have travelled that way.

[[files]]
path = "FLEET.md"
path = "FLEET.rendered.md"

# --- context-file symlinks: surface FLEET.md to each CLI's convention ---------
# --- context-file symlinks: surface the payload to each CLI's convention ------

[[symlinks]]
link = "CLAUDE.md"
target = "FLEET.md"
target = "FLEET.rendered.md"

[[symlinks]]
link = "AGENTS.md"
target = "FLEET.md"
target = "FLEET.rendered.md"

[[symlinks]]
link = "GEMINI.md"
target = "FLEET.md"
target = "FLEET.rendered.md"

# --- runtime resources: ensured on activate, self-healed if deleted -----------
#
Expand Down
47 changes: 47 additions & 0 deletions orchestration/voice.example.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# The two names the lead's standing context is written around — what it calls
# you, and what it answers to — and this is the tracked copy of them.
#
# COPY IT TO `voice.conf` BESIDE THIS FILE TO CHANGE ANYTHING. That copy is
# gitignored, the same way `session-glyphs.conf` and `registry/owners.txt` are:
# what you want to be called is a property of you, not of this repo. The reader
# falls back to THIS file when it is absent, so the defaults below need no copy
# at all — and an operator who edited a tracked file instead would hand
# `scripts/sync-checkout.sh` a dirty tree, which it refuses to fast-forward.
#
# WHY IT IS RENDERED AND NOT READ. `FLEET.md` carries `@OPERATOR_NAME@` and
# `@ASSISTANT_NAME@` — `@`-delimited, because markdown reads `__x__` as bold —
# and `scripts/install-extension.sh` substitutes these into `FLEET.rendered.md`,
# the payload the extension actually ships, the same way it substitutes
# `__LEAD_GLYPH__` into the manifest. The alternative —
# FLEET.md telling the lead to go and read this file — is a read that happens
# once and is then forgotten, because the lead freezes its instructions at
# launch and never reloads them. A name it was never told is a name it invents.
#
# SO CHANGING ONE OF THESE NEEDS A RE-INSTALL AND A LEAD RESTART, in that
# order, and installing is not applying:
#
# ./scripts/install-extension.sh
# thurbox-cli session restart '<the lead, from thurbox-cli session list>'
#
# `.agents/skills/update-fleet/` owns that sequence and its cost. Until you run
# it, the running lead goes on using the names it was rendered with.
#
# THESE ARE A REGISTER, NOT A COSTUME. FLEET.md's `## How you report` holds the
# rule and it outranks whatever you put here: the names live in verb choice and
# terseness, and every accuracy rule in that section outranks both. A name here
# buys no roleplay, no preamble and no paragraph of theme.

# --- THE NAMES ----------------------------------------------------------------
#
# `OPERATOR_NAME` what the lead calls YOU.
# `ASSISTANT_NAME` what the lead answers to. The SESSION is still called
# Mission Control — that name is thurbox's, it is what the
# TUI lists and the mailbox addresses, and it is set in
# `extension.toml.in`. This is the name in the prose, which is
# a different thing and free to differ.
#
# Plain text, one line each, and no shell metacharacters: the renderer refuses a
# name carrying a quote, a pipe, a backslash, an `&` or an `@` rather than
# letting it break the substitution or collide with the other placeholder.
OPERATOR_NAME=SLAYER
ASSISTANT_NAME=VEGA
Loading