Skip to content

Commit 970ed59

Browse files
authored
Merge pull request #107 from protocol-security/feature/0.22.0
Trixie image, harvest restore tags, post-process setup, dashboard keys
2 parents e36501c + 3494b00 commit 970ed59

12 files changed

Lines changed: 321 additions & 71 deletions

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@
22

33
## Unreleased
44

5+
## 0.22.0 — 2026-06-08
6+
7+
- **Build: base the agent image on Debian trixie.** Replaces
8+
`debian:bookworm-slim` (glibc 2.36) so binaries that require
9+
glibc 2.39+ run inside the container.
10+
- **Build: install `unzip` in the agent image.** Available for
11+
setup scripts and agents that need to extract archives.
12+
- **Feature: harvest tags a restore point before merging.**
13+
`harvest.sh` creates a local `swarm-harvest-<date>-<time>` tag on
14+
the pre-merge branch tip, so a harvest can be undone with
15+
`git reset --hard <tag>`. Skipped on `--dry` and when nothing is
16+
new; never pushed.
17+
- **Feature: `post_process.setup` overrides the post-process setup.**
18+
A path runs a lighter setup, `false`/`""` skips setup so a heavy
19+
top-level `setup` is not redone, and omitting it inherits the
20+
top-level setup.
21+
- **Dashboard (breaking): swap the post-process keys.** `P` now
22+
tails the post-process logs (matching the `P` row, like `[1-9]`
23+
for agent rows) and lowercase `p` starts post-processing. The
24+
log hint reads `[1-9/P]` once the container exists. Previously
25+
`p` tailed logs and `P` started the run.
26+
- **Docs: document the dashboard effort letters.** The Model
27+
column's parenthesised suffix is `(h)` high, `(m)` medium,
28+
`(l)` low, `(x)` xhigh, `(n)` none, and `(M)` max.
29+
530
## 0.21.1 — 2026-06-08
631

732
- **Fix: `post-process` propagates the container's exit code.**

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM debian:bookworm-slim
1+
FROM debian:trixie-slim
22

33
RUN apt-get update && apt-get install -y --no-install-recommends \
44
git \
@@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
88
make \
99
jq \
1010
sudo \
11+
unzip \
1112
openssh-client \
1213
&& rm -rf /var/lib/apt/lists/*
1314

USAGE.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,15 @@ do not count toward numbered-agent completion.
319319
|-----|--------|
320320
| `q` | Quit. |
321321
| `1`-`9` | Logs for agent N. |
322+
| `P` | Post-process logs (the `P` row), if it exists. |
322323
| `h` | Harvest results. |
323324
| `s` | Stop numbered agents and post-process. |
324-
| `p` | Post-process logs, if the container exists. |
325-
| `P` | Start post-process after confirmation. |
325+
| `p` | Start post-process after confirmation. |
326+
327+
The Model column appends the agent's reasoning effort as a
328+
parenthesised letter: `(h)` high, `(m)` medium, `(l)` low,
329+
`(x)` xhigh, `(n)` none, and `(M)` max. Models configured
330+
without an `effort` show no suffix.
326331

327332
## Activity streaming
328333

@@ -448,6 +453,12 @@ post-processing, run `./launch.sh post-process`; use that command
448453
directly when you intentionally want to run only the post-process
449454
agent and then harvest.
450455

456+
Before merging, `harvest.sh` tags the current branch tip with a
457+
local `swarm-harvest-<date>-<time>` tag (skipped on `--dry` and
458+
when nothing is new). Undo a harvest with
459+
`git reset --hard <tag>`. The tag is never pushed and does not
460+
interfere with the harvested branches.
461+
451462
`post_process` also accepts `base_url`, `api_key`,
452463
`auth_token`, `auth`, `tag`, `driver`, and `max_idle` -- same
453464
fields as per-group agents -- to route post-processing through
@@ -456,6 +467,11 @@ many consecutive sessions with no commits before the
456467
post-processor exits. When omitted it inherits the top-level
457468
`max_idle` (default: `3`).
458469

470+
`post_process.setup` overrides the setup script for the
471+
post-process pass: a path runs that script, `false` or `""`
472+
skips setup (so a heavy top-level `setup` is not redone), and
473+
omitting the key inherits the top-level `setup`.
474+
459475
`./launch.sh post-process` (and `wait` when it triggers
460476
post-processing) exits with the post-process container's exit
461477
code. Harvest still runs first, so a crashed agent's in-flight

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.21.1
1+
0.22.0

dashboard.sh

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ interactive session branches.
2828
Keybindings:
2929
q Quit the dashboard.
3030
1-9 Tail logs for agent N.
31+
P Tail post-process logs (the "P" row) when it exists.
3132
h Harvest agent results into current branch.
3233
s Stop numbered, interactive, and post-process agents.
33-
p Tail post-process logs when the container exists.
34-
P Start post-process after confirmation.
34+
p Start post-process after confirmation.
3535
3636
Environment:
3737
SWARM_TITLE Dashboard title override.
@@ -758,18 +758,21 @@ draw() {
758758
# Help bar.
759759
# shellcheck disable=SC2059
760760
printf " ${DIM}[q]${RESET} quit"
761-
# shellcheck disable=SC2059
762-
printf " ${DIM}[1-9]${RESET} logs"
761+
if post_process_container_exists; then
762+
# The "P" row's logs join the numbered agent logs.
763+
# shellcheck disable=SC2059
764+
printf " ${DIM}[1-9/P]${RESET} logs"
765+
else
766+
# shellcheck disable=SC2059
767+
printf " ${DIM}[1-9]${RESET} logs"
768+
fi
763769
# shellcheck disable=SC2059
764770
printf " ${DIM}[h]${RESET} harvest"
765771
# shellcheck disable=SC2059
766772
printf " ${DIM}[s]${RESET} stop all"
767-
if post_process_container_exists; then
768-
# shellcheck disable=SC2059
769-
printf " ${DIM}[p]${RESET} post-process logs"
770-
elif post_process_configured; then
773+
if post_process_configured && ! post_process_container_exists; then
771774
# shellcheck disable=SC2059
772-
printf " ${DIM}[P]${RESET} post-process"
775+
printf " ${DIM}[p]${RESET} post-process"
773776
fi
774777
printf "\n"
775778
}
@@ -823,24 +826,8 @@ while true; do
823826
enter_alt_screen
824827
;;
825828
p)
826-
leave_alt_screen
827-
_pp_name="${IMAGE_NAME}-post"
828-
if post_process_container_exists; then
829-
echo "--- Logs for ${_pp_name} (Ctrl-C to return) ---"
830-
docker logs -f "$_pp_name" 2>&1 || true
831-
if ! docker inspect -f '{{.State.Running}}' \
832-
"$_pp_name" 2>/dev/null | grep -q true; then
833-
echo ""
834-
read -rp "Press Enter to return to dashboard..." _
835-
fi
836-
else
837-
echo "(post-processing is not running -- press P to start)"
838-
echo ""
839-
read -rp "Press Enter to return to dashboard..." _
840-
fi
841-
enter_alt_screen
842-
;;
843-
P)
829+
# Lowercase p starts post-processing (an action),
830+
# gated on confirmation and a stopped swarm.
844831
leave_alt_screen
845832
if ! post_process_configured; then
846833
echo "(post-processing not configured)"
@@ -852,7 +839,7 @@ while true; do
852839
fi
853840
_pp_name="${IMAGE_NAME}-post"
854841
if post_process_container_exists; then
855-
echo "(post-processing is already running -- press p for logs)"
842+
echo "(post-processing is already running -- press P for logs)"
856843
echo "Stop it first with s before starting a new run."
857844
echo ""
858845
read -rp "Press Enter to return to dashboard..." _
@@ -882,6 +869,26 @@ while true; do
882869
read -rp "Press Enter to return to dashboard..." _
883870
enter_alt_screen
884871
;;
872+
P)
873+
# Uppercase P tails the "P" row's logs, mirroring how
874+
# [1-9] tail the numbered agent rows.
875+
leave_alt_screen
876+
_pp_name="${IMAGE_NAME}-post"
877+
if post_process_container_exists; then
878+
echo "--- Logs for ${_pp_name} (Ctrl-C to return) ---"
879+
docker logs -f "$_pp_name" 2>&1 || true
880+
if ! docker inspect -f '{{.State.Running}}' \
881+
"$_pp_name" 2>/dev/null | grep -q true; then
882+
echo ""
883+
read -rp "Press Enter to return to dashboard..." _
884+
fi
885+
else
886+
echo "(post-processing is not running -- press p to start)"
887+
echo ""
888+
read -rp "Press Enter to return to dashboard..." _
889+
fi
890+
enter_alt_screen
891+
;;
885892
esac
886893
fi
887894
done

harvest.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,16 @@ if [ "$TOTAL_NEW_COMMITS" -eq 0 ]; then
151151
exit 0
152152
fi
153153

154+
# Tag a restore point on the pre-merge HEAD so this harvest can be
155+
# undone with `git reset --hard <tag>`. The tag lives in refs/tags
156+
# and never collides with the harvested branches in refs/heads, so
157+
# it is safe alongside agent-work. Skip if HEAD is already tagged.
158+
if ! git tag --points-at HEAD | grep -q '^swarm-harvest-'; then
159+
harvest_tag="swarm-harvest-$(date +%Y-%m-%d-%H%M%S)"
160+
git tag "$harvest_tag"
161+
echo "Tagged restore point: ${harvest_tag}"
162+
fi
163+
154164
echo ""
155165
for item in "${MERGE_BRANCHES[@]}"; do
156166
branch="${item%%|*}"

launch.sh

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ cmd_wait() {
821821
}
822822

823823
cmd_post_process() {
824-
local pp_prompt pp_model pp_base_url pp_api_key pp_effort pp_auth pp_auth_token pp_tag pp_driver pp_max_idle
824+
local pp_prompt pp_model pp_base_url pp_api_key pp_effort pp_auth pp_auth_token pp_tag pp_driver pp_max_idle pp_setup
825825
pp_prompt=$(jq -r '.post_process.prompt // empty' "$CONFIG_FILE")
826826
pp_max_idle=$(jq -r '.post_process.max_idle // .max_idle // 3' "$CONFIG_FILE")
827827
pp_model=$(jq -r '.post_process.model // "claude-opus-4-6"' "$CONFIG_FILE")
@@ -836,6 +836,18 @@ cmd_post_process() {
836836
pp_tag="$(expand_env_ref "$pp_tag")"
837837
pp_driver=$(jq -r '.post_process.driver // .driver // "claude-code"' "$CONFIG_FILE")
838838

839+
# Resolve the post-process setup script. An explicit
840+
# post_process.setup wins (a path runs it, false/empty skips it so a
841+
# heavy top-level setup is not redone); omitting the key inherits
842+
# the top-level setup.
843+
if jq -e '(.post_process // {}) | has("setup")' "$CONFIG_FILE" \
844+
>/dev/null 2>&1; then
845+
pp_setup=$(jq -r '.post_process.setup // ""' "$CONFIG_FILE")
846+
[ "$pp_setup" = "false" ] && pp_setup=""
847+
else
848+
pp_setup="${SWARM_SETUP:-}"
849+
fi
850+
839851
if [ -z "$pp_prompt" ]; then
840852
echo "ERROR: post_process.prompt is not set in ${CONFIG_FILE}." >&2
841853
exit 1
@@ -892,7 +904,7 @@ cmd_post_process() {
892904
-e "SWARM_EFFORT=${pp_effort}" \
893905
-e "CLAUDE_MODEL=${pp_model}" \
894906
-e "SWARM_PROMPT=${pp_prompt}" \
895-
-e "SWARM_SETUP=${SWARM_SETUP:-}" \
907+
-e "SWARM_SETUP=${pp_setup}" \
896908
-e "MAX_IDLE=${pp_max_idle}" \
897909
-e "MAX_RETRY_WAIT=${MAX_RETRY_WAIT}" \
898910
-e "GIT_USER_NAME=${GIT_USER_NAME}" \
@@ -903,7 +915,7 @@ cmd_post_process() {
903915
-e "SWARM_DRIVER=${pp_driver}" \
904916
-e "SWARM_RUN_CONTEXT=${SWARM_RUN_CONTEXT}" \
905917
-e "SWARM_CFG_PROMPT=${pp_prompt}" \
906-
-e "SWARM_CFG_SETUP=${SWARM_SETUP:-}" \
918+
-e "SWARM_CFG_SETUP=${pp_setup}" \
907919
-e "SWARM_ACTIVITY_TIMEOUT=${SWARM_ACTIVITY_TIMEOUT:-0}" \
908920
-e "SWARM_ACTIVITY_POLL=${SWARM_ACTIVITY_POLL:-10}" \
909921
-e "SWARM_WATCHDOG_GRACE=${SWARM_WATCHDOG_GRACE:-10}" \

tests/manual_interactive_e2e.sh

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ set -euo pipefail
1515
# - Claude OAuth env forwarding;
1616
# - dashboard configured rows before containers exist;
1717
# - dashboard I* rows for interactive containers;
18-
# - dashboard P row and [P] post-process action.
18+
# - dashboard P row, with P tailing its logs and lowercase p
19+
# starting the post-process run;
20+
# - post_process.setup override that runs a lighter post-process
21+
# setup instead of inheriting the top-level setup.
1922
#
2023
# The generated config uses normal prompts and max_idle=3. It does
2124
# not include a timeboxed "keep running for N minutes" prompt. Stop
@@ -139,6 +142,25 @@ setup_log="test-results/setup-${setup_id}.log"
139142
EOF
140143
chmod +x "$REPO_DIR/scripts/setup.sh"
141144

145+
cat > "$REPO_DIR/scripts/post-setup.sh" <<'EOF'
146+
#!/bin/bash
147+
set -euo pipefail
148+
149+
# Lighter setup used only for the post-process pass. swarm.json sets
150+
# post_process.setup to this script, so launch.sh runs it instead of
151+
# inheriting the heavier top-level scripts/setup.sh. The unique marker
152+
# makes the override observable: it proves the post-process container
153+
# ran this lighter script and did not redo the top-level setup.
154+
mkdir -p test-results
155+
{
156+
printf 'POST_SETUP_RAN at %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
157+
printf 'driver=%s model=%s\n' \
158+
"${SWARM_DRIVER:-unknown}" \
159+
"${SWARM_MODEL:-unknown}"
160+
} >> test-results/post-process-setup.log
161+
EOF
162+
chmod +x "$REPO_DIR/scripts/post-setup.sh"
163+
142164
cat > "$REPO_DIR/prompts/headless.md" <<'EOF'
143165
You are running a manual claude-swarm smoke test in a disposable repo.
144166
@@ -241,6 +263,7 @@ cat > "$REPO_DIR/swarm.json" <<'EOF'
241263
],
242264
"post_process": {
243265
"prompt": "prompts/post-process.md",
266+
"setup": "scripts/post-setup.sh",
244267
"driver": "codex-cli",
245268
"model": "gpt-5.4",
246269
"effort": "medium",
@@ -309,9 +332,10 @@ Expected result:
309332
\`unknown\` / \`not found\`.
310333
- Omitted \`count\` on \`claude-manual\` and \`count: 0\` on
311334
\`codex-manual\` should not add numbered rows.
312-
- The post-process row should appear as \`P\`, matching the key that
313-
starts it.
314-
- The footer should show \`[P] post-process\`.
335+
- The post-process row should appear as \`P\`, the same letter as the
336+
\`P\` key that tails its logs once it exists.
337+
- The footer should show \`[p] post-process\` -- lowercase \`p\`
338+
starts the run.
315339
316340
Press \`q\` to exit.
317341
@@ -326,10 +350,11 @@ Expected result:
326350
- Five numbered containers start: 3 Claude OAuth and 2 Codex ChatGPT.
327351
- The dashboard header reports 5 agents.
328352
- Press \`1\` through \`5\` to inspect numbered-agent logs.
329-
- \`p\` should only tail post-process logs after a post-process
330-
container exists. When it appears, \`P\` should be hidden because
331-
post-process is already running.
332-
- \`P\` should ask for confirmation before starting post-process.
353+
- \`P\` should only tail post-process logs after a post-process
354+
container exists; the logs hint becomes \`[1-9/P]\` then.
355+
- \`p\` should ask for confirmation before starting post-process.
356+
Once the container exists, the \`[p] post-process\` start hint
357+
disappears (stop with \`s\` before starting another run).
333358
334359
This fixture uses \`max_idle: 3\` and normal prompts, so agents can
335360
finish naturally. Once you have seen enough, press \`s\` in the
@@ -434,10 +459,10 @@ Expected result:
434459
./dashboard.sh
435460
\`\`\`
436461
437-
Press \`P\`, confirm with \`y\`, and watch the \`P\` row. After the
438-
post-process container exists, lowercase \`p\` should tail its logs and
439-
\`P\` should disappear. Use \`s\` or \`./launch.sh stop\` before
440-
starting a new post-process run.
462+
Press \`p\`, confirm with \`y\`, and watch the \`P\` row. After the
463+
post-process container exists, uppercase \`P\` tails its logs and the
464+
\`[p] post-process\` start hint disappears. Use \`s\` or
465+
\`./launch.sh stop\` before starting a new post-process run.
441466
442467
Equivalent non-dashboard command:
443468
@@ -448,9 +473,28 @@ Equivalent non-dashboard command:
448473
Expected result:
449474
450475
- Post-process uses the Codex ChatGPT profile from \`post_process\`.
476+
- Before the prompt runs, the container executes
477+
\`scripts/post-setup.sh\` (from \`post_process.setup\`), which appends
478+
a \`POST_SETUP_RAN\` line to \`test-results/post-process-setup.log\`.
479+
This is the lighter post-process setup; the heavier top-level
480+
\`scripts/setup.sh\` is not redone here.
451481
- It writes \`test-results/post-process-summary.md\`.
482+
- The summary lists \`post-process-setup.log\`, so the override stays
483+
visible after harvest.
452484
- The summary ends with \`POST_PROCESS_DONE\`.
453485
486+
The \`post_process.setup\` key has three modes. This fixture ships the
487+
override (a path); to try the others, edit \`swarm.json\`:
488+
489+
- Path (the default here): \`"setup": "scripts/post-setup.sh"\` runs
490+
that lighter script for the post-process pass only.
491+
- Skip: \`"setup": false\` or \`"setup": ""\` runs no setup, so a heavy
492+
top-level setup is not repeated. \`post-process-setup.log\` is then
493+
absent.
494+
- Inherit: remove the \`setup\` key from \`post_process\` to reuse the
495+
top-level \`scripts/setup.sh\`; a \`test-results/setup-*.log\` for the
496+
post-process pass appears instead.
497+
454498
## 8. Harvest And Inspect
455499
456500
\`\`\`bash

0 commit comments

Comments
 (0)