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
38 changes: 36 additions & 2 deletions .agents/skills/fleet-queue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ EOF
Store the prompt verbatim: your summary of it is a lossy copy made at the moment
you understood it least.

`topic add` also opens this run's log at
`orchestration/runs/<opened>-<topic>.md` and names it on stderr. You do not
have to make one, and you should not make a second — see **The run
log** below.

Then decompose. A topic is the unit of **intent**; a task is the unit of
**work** — one repo, one branch, one thing a single worker can finish and
validate on its own. The decomposition is yours.
Expand Down Expand Up @@ -784,6 +789,35 @@ reasons unrelated to what it was asked. So `collect` names it whenever it
closed a task that left a PR open, and `shepherd --json` is the seam anything
else reads it through.

## The run log — the queue writes the facts, you write the judgement

One log per topic, opened by `topic add`, refreshed by `dispatch`, `collect`
and `shepherd` as they go. It exists because it used to not: two consecutive
runs went unrecorded, one file surviving only because its lead was being
migrated and the other reconstructed from chat history at the end. The
instruction was there both times, which is what makes it a tool gap.

```text
<!-- fleet:facts --> everything between the fences is GENERATED — the task
… table, what waited on what, what overlapped anyway,
<!-- fleet:facts:end --> and a timeline from the records' own timestamps
```

Outside the fence is yours and nothing ever rewrites it: **Goal** in your own
words, **Decisions worth keeping**, **What went wrong**, **Outcome**. That is
the half no record can produce, and it is the half worth having — write into it
while you still know it, not at the end from scrollback.

- The block is **rewritten, not appended to**, so refreshing three times leaves
one file rather than three copies of a timeline. A refresh that changes
nothing prints nothing.
- `./scripts/queue.sh run [<topic>]` is that refresh made explicit — for a
topic older than this feature, or when you just want the path.
- Delete the fence and the log is yours entirely: the queue reports it as
`left alone` and never writes into it again.
- Run logs are gitignored, so machine paths and session ids are fine in them.
`_TEMPLATE.md` beside them is tracked; keep that one generic.

## 6. The views, and keeping your context clean

```bash
Expand Down Expand Up @@ -868,5 +902,5 @@ six weeks later.
them ticking, and §5d says what that does and does not change.
10. `plan` again. Review the PRs; the operator merges every one `shepherd` did
not. Sessions release themselves once their pull requests land — `collect`
reaps, `reap --dry-run` shows you what it would do — and you record the run
in `orchestration/runs/` as it happens.
reaps, `reap --dry-run` shows you what it would do. The run log has been
recording itself since step 1; write your judgement into it.
10 changes: 6 additions & 4 deletions .agents/skills/thurbox-session/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,8 +607,10 @@ the message.

## 5. Collect and clean up

Record every session in the run log **as it happens** — name, repo(s), prompt
intent, outcome, PR/artifact. The run log is the source of truth.
For a session the queue dispatched, its facts refresh themselves in the run
log — see `fleet-queue`'s **The run log**. For one you spawned by hand, note
it there yourself; the judgement — goal, decisions, outcome — is always yours
to write.

```bash
thurbox-cli session restart <uuid> # kill window, re-spawn with --resume
Expand All @@ -629,8 +631,8 @@ requests merged, and it reads this section's state table before it does — see
## Run loop

1. Clarify the goal. Pick or write a playbook in `orchestration/playbooks/`.
2. Open a run log from `orchestration/runs/_TEMPLATE.md`, named
`<YYYY-MM-DD>-<slug>.md`.
2. `./scripts/queue.sh topic add` opens this run's log — see `fleet-queue`'s
**The run log**.
3. Per unit of work: `session create` — with an `--on-existing` mode (§1c) and
the run's profile flags (§1d) — → `session send`, unless `created` came back
`false` → read the result file it writes → record.
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ jobs:
# the same name an operator has locally.
- name: Install lua
run: |
# The runner image preinstalls a google-chrome-stable apt source we
# never use; when its index goes stale, `apt-get update` exits
# non-zero for the whole run and (with the runner's default `-e`
# shell) aborts this step before lua is even installed. Drop it so
# this step depends only on the repos it actually needs. Ubuntu
# 24.04 runners ship it as deb822 `google-chrome.sources` rather
# than the old `google-chrome.list`, so remove both forms.
sudo rm -f /etc/apt/sources.list.d/google-chrome.list /etc/apt/sources.list.d/google-chrome.sources
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends lua5.4
sudo ln -sf /usr/bin/lua5.4 /usr/local/bin/lua
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
!/registry/context/_TEMPLATE.md

# WORKING STATE. One file per orchestration run — the record of what you did,
# when, and how it went. _TEMPLATE.md is the form and stays.
# when, and how it went. scripts/queue.sh opens one per topic and refreshes the
# facts inside it, so these carry machine paths and session ids freely; the
# judgement around those facts is the operator's. _TEMPLATE.md is the form,
# it is generic, and it stays.
/orchestration/runs/*.md
!/orchestration/runs/_TEMPLATE.md

Expand Down
12 changes: 10 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ names every path and the reason for each.
verify, place, hide, remove, diagnose.
- `orchestration/playbooks/<name>.md` — reusable recipes for running thurbox.
All tracked; write new ones here, from `_TEMPLATE.md`.
- `orchestration/runs/<date>-<slug>.md` — a log per orchestration run.
- `orchestration/runs/<date>-<topic>.md` — a log per orchestration run, one
per topic. **The queue writes it**: `topic add` opens it from `_TEMPLATE.md`
and the loop's own commands rewrite a fenced block of facts inside it.
Everything outside that fence is the lead's judgement and nothing ever
overwrites it. Gitignored, like everything a run produces; the template is
the one tracked file there.
- `.agents/skills/<name>/SKILL.md` — agent skills, in one agent-agnostic tree.
`.claude/skills` is a **symlink** to it, so Claude Code and opencode (which
auto-discovers `.claude/skills`) both load the same copy. Never add a second
Expand Down Expand Up @@ -115,7 +120,10 @@ The loop, driven by `./scripts/queue.sh`:
`queue.sh watch` folds `thurbox-cli watch`'s event stream into each task's
record and closes nothing; `queue.sh collect` reads the `result.md` the
worker wrote and only that closes a task. A turn ending is not a task
finishing. Record the run in `orchestration/runs/` as it happens.
finishing. The run log records itself as this happens — `topic add` opened
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
Expand Down
10 changes: 7 additions & 3 deletions FLEET.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ registry/repos.generated.yaml Generated index of every repo. NEVER hand-edit;
orchestration/queue/<topic>/ The task queue: one directory per topic, one
per task inside it, each holding that task's
own BRIEF.md. Driven by ./scripts/queue.sh.
orchestration/runs/<date>-<slug>.md A log per orchestration run.
orchestration/runs/<date>-<topic>.md A log per topic. `topic add` opens it
and `dispatch`/`collect`/`shepherd` keep its
facts current; you write the judgement.
```

**None of that is tracked.** `Thurbeen/fleet` is public and everything a
Expand Down Expand Up @@ -79,8 +81,10 @@ YAML by hand. Nothing to push — the map is gitignored.
4. **`watch` on your own cadence, then `collect`.** The event stream says WHEN
a turn ended; the worker's own result file says WHAT it concluded. A turn
ending is not a task finishing, and only `collect` closes anything.
5. Open a run log from `orchestration/runs/_TEMPLATE.md` and record what
happened as it happens. It is gitignored and not backed up by the repo.
5. The run log records itself: `topic add` opened it and `dispatch`, `collect`
and `shepherd` keep its facts current as you run them. Write the goal,
decisions and outcome into it in your own words — that half never comes
from a record. It is gitignored and not backed up by the repo.
6. **`shepherd`, as reflexively as `collect`.** The pull request outlives the
task, and `collect` names `shepherd` whenever it closed one that left a PR
open. It asks the forge for every open PR on the queue's repos, not just
Expand Down
10 changes: 6 additions & 4 deletions orchestration/playbooks/_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ How to decompose the goal into thurbox sessions. For each session, define:

## Run

1. Open a run log from `../runs/_TEMPLATE.md`.
1. `./scripts/queue.sh topic add` — which opens this run's log under
`../runs/` for you. Write the goal into it now; the facts arrive by
themselves.
2. Fast-forward each target repo's base branch, then `thurbox-cli session create`
with `--parent "$THURBOX_SESSION"`, the chosen `--on-existing` mode, and the
profile's flags from `./scripts/session-flags.sh`.
Expand All @@ -43,9 +45,9 @@ How to decompose the goal into thurbox sessions. For each session, define:
only when the spawn returned `created: true`; an adopted session is already
working. `./scripts/queue.sh dispatch` does both steps for you.
4. Read the results the workers wrote (`./scripts/queue.sh watch` for the
timing, `collect` for the conclusions); record each outcome in the run log
as it lands. Do not have workers mail you — `message send` wakes the lead
and interrupts whoever is talking to it.
timing, `collect` for the conclusions) — `collect`'s refresh puts each
outcome in the run log's facts as it lands. Do not have workers mail you —
`message send` wakes the lead and interrupts whoever is talking to it.
5. Review artifacts (PRs). A queue-dispatched session releases itself once its
pull request merges (`collect` reaps it; `reap --dry-run` first if unsure);
`session delete <uuid> --force` is for one you spawned by hand.
Expand Down
7 changes: 4 additions & 3 deletions orchestration/playbooks/cross-repo-sweep.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ One worker session **per repo**, all with the same prompt shape.

## Run

1. Build the repo list from the registry; write it into the run log up front.
1. `./scripts/queue.sh topic add` opens this run's log. Build the repo list
from the registry; write it into the run log's Goal section up front.
2. Fast-forward every target's base branch before spawning against it. A stale
local `main` yields a worker that does correct work in a conflicting PR.
3. Launch in waves of `max_parallel`, each with `--parent "$THURBOX_SESSION"`,
Expand Down Expand Up @@ -62,7 +63,7 @@ One worker session **per repo**, all with the same prompt shape.
reports each one's `state`, and `blocked` is the word for that. Read the
skill's session-state section before you act on any of those words —
`idle` means the agent said it is at rest, and it is the only one that does.
5. Collect PR URLs and `NOT_APPLICABLE` into the run log's session table.
5. Collect PR URLs and `NOT_APPLICABLE` into the run log's Outcome section.
6. Review PRs in a batch. Each session goes when its pull request merges —
`./scripts/queue.sh collect` reaps it, `reap --dry-run` says what it would
do — so nothing is left holding a worktree per repo.
Expand All @@ -71,7 +72,7 @@ One worker session **per repo**, all with the same prompt shape.

- Prompts must be self-contained and repo-agnostic — workers don't share context
with you or with each other.
- Record the profile in the run log alongside the repo list. Two sweeps of the
- The run log's facts name the profile without being asked. Two sweeps of the
same goal under different settings are two different runs.
- Log every repo that reported `NOT_APPLICABLE` so the sweep is auditable and
not silently partial. This is the reason to prefer a result file over polling
Expand Down
10 changes: 6 additions & 4 deletions orchestration/playbooks/ship-feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
- `repo` — `owner/name`.
- `base` — base branch (default the repo's default branch).
- `profile` — the session profile the worker starts under, from
`../session-profiles.yaml` (default `default`). Record which one in the run
log: it is part of what produced the result.
`../session-profiles.yaml` (default `default`). It is part of what produced
the result, and the run log's facts name it without being asked.

## Sessions

Expand All @@ -33,7 +33,8 @@ One worker session.

1. Read `registry/context/<repo>.md` for goals and gotchas; fold the relevant
bits into the prompt.
2. Open a run log.
2. `./scripts/queue.sh topic add` opens this run's log; write the goal into it
now.
3. Fast-forward `base` in the target repo, then `session create --parent
"$THURBOX_SESSION" --on-existing adopt` with the profile's flags →
`session send`.
Expand All @@ -46,7 +47,8 @@ One worker session.
4. Read the result file the worker wrote, when you choose. Do not ask it to
mail you: `message send` wakes the lead and interrupts whoever is talking to
it. `./scripts/queue.sh watch` gives the timing without interrupting anyone.
5. Review the PR; record it in the run log; merge or hand back.
5. Review the PR — `collect`'s refresh already has it in the run log's facts —
then merge or hand back.
6. `./scripts/queue.sh collect` reaps the session once the pull request
merges. For a session you spawned outside the queue, `session delete <uuid>
--force` once merged or abandoned.
Expand Down
41 changes: 23 additions & 18 deletions orchestration/runs/_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
# Run: `<YYYY-MM-DD>` — `<slug>`

> Copy to `orchestration/runs/<YYYY-MM-DD>-<slug>.md`. This log is the source of
> truth for what happened.
> One run, one topic. `./scripts/queue.sh topic add` opens this file, and
> `dispatch`, `collect`, `shepherd` and `run` refresh the fenced block below
> from the queue's own records as the run goes on.
>
> **Everything outside that fence is yours.** Nothing rewrites it, nothing
> generates it, and it is the reason the file exists — the block says what
> happened, and these sections say what you decided and what it cost.

- **Goal.** What this run is meant to achieve.
- **Playbook.** `../playbooks/<name>.md` (or "ad hoc").
- **Profile.** `../session-profiles.yaml` name the workers started under
(`default` if you did not pick one). Settings are part of what produced the
result, so a run that does not name one cannot be reproduced.
- **Started.** <date/time> · **Status.** planning | running | done | abandoned
<!-- fleet:facts -->
<!-- fleet:facts:end -->

## Sessions
## Goal

Name each session as an imperative sentence describing the work, in sentence
case — no repo prefix, the repo has its own column.
What this run is meant to achieve, in your words.

| Session name | Repo | Intent | Status | Artifact / PR |
|---|---|---|---|---|
| `Document the customization surface` | `owner/repo` | one line | running / done | link |
## Playbook

## Timeline
`../playbooks/<name>.md`, or "ad hoc".

- `<time>` — launched `Document the customization surface` against `owner/repo`.
- `<time>` — drained the inbox; that session reported …
## Decisions worth keeping

The calls you made and why: what you serialized and on what condition, what you
dispatched together despite an overlap, what you decided not to do at all.

## What went wrong

Where the tooling or the plan failed, and what the next lead should do instead.
A defect written down here is the one that gets fixed.

## Outcome

What shipped, what's pending, what to follow up on. Update the relevant
What shipped, what is pending, what to follow up on. Update the relevant
`registry/context/<repo>.md` if this run changed a project's goals or relations.
5 changes: 4 additions & 1 deletion scripts/fleet-status-selftest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ done

tmp="$(mktemp -d)"
export FLEET_QUEUE_DIR="$tmp/queue"
# `topic add` also opens a run log; keep this run's out of the operator's
# orchestration/runs/, the same way FLEET_QUEUE_DIR keeps its queue out.
export FLEET_RUNS_DIR="$tmp/runs"
mkdir -p "$FLEET_QUEUE_DIR" "$tmp/repo"

# A sandboxed PATH holding only the tools the command is allowed to find. This
Expand All @@ -97,7 +100,7 @@ stubbed="$(sandbox "$tmp/bin-stubbed" "${BASE_TOOLS[@]}")"

# --- a queue with something in it -------------------------------------------

"$QUEUE" topic add selftest --title "Selftest topic" --prompt 'the prompt, verbatim' >/dev/null
"$QUEUE" topic add selftest --title "Selftest topic" --prompt 'the prompt, verbatim' >/dev/null 2>&1
printf 'Do the thing.\n' >"$tmp/brief.md"
"$QUEUE" add selftest dispatched-task --title "A dispatched task" --repo "$tmp/repo" \
--branch t/dispatched --touches FLEET.md --brief-file "$tmp/brief.md" >/dev/null
Expand Down
Loading