- Build: base the agent image on Debian trixie. Replaces
debian:bookworm-slim(glibc 2.36) so binaries that require glibc 2.39+ run inside the container. - Build: install
unzipin the agent image. Available for setup scripts and agents that need to extract archives. - Feature: harvest tags a restore point before merging.
harvest.shcreates a localswarm-harvest-<date>-<time>tag on the pre-merge branch tip, so a harvest can be undone withgit reset --hard <tag>. Skipped on--dryand when nothing is new; never pushed. - Feature:
post_process.setupoverrides the post-process setup. A path runs a lighter setup,false/""skips setup so a heavy top-levelsetupis not redone, and omitting it inherits the top-level setup. - Dashboard (breaking): swap the post-process keys.
Pnow tails the post-process logs (matching theProw, like[1-9]for agent rows) and lowercasepstarts post-processing. The log hint reads[1-9/P]once the container exists. Previouslyptailed logs andPstarted the run. - Docs: document the dashboard effort letters. The Model
column's parenthesised suffix is
(h)high,(m)medium,(l)low,(x)xhigh,(n)none, and(M)max.
- Fix:
post-processpropagates the container's exit code.cmd_post_processharvests unconditionally (best-effort recovery of a crashed agent's in-flight commits) but now returns the post-process container's exit code, so CI workflows and daemons can gate publishing on it. A clean run still returns0; previously the failure was swallowed whenever harvest succeeded.
-
Feature: interactive agent containers for human-guided work.
launch.sh interactive,chat, andshellcan start one container from a namedagents[]profile, including profiles with omittedcountorcount: 0. The session reuses the same image, setup, driver, model, effort, auth, context, signing, Docker args, and submodule mirrors as numbered agents, but checks out a distinctswarm/<run>/interactive-*branch and pushes that branch on exit.harvest.shnow merges interactive branches alongsideagent-work, warns when interactive containers still have dirty worktrees, and the dashboard/status output shows interactive branches asI*rows with dirty, unharvested, or harvested state. Claude Code OAuth profiles now warn when the host token is missing instead of labeling the container as authenticated. The dashboard also shows configured numbered agents before their containers exist and labels the post-process row asP, matching the key that runs it. -
Fix: dashboard missing-container detection handles Docker errors. Failed
docker inspect -fcalls can emit a blank stdout line before the fallback state, especially when Docker is unavailable or denied. The dashboard now normalizes container states before comparing them, so configured numbered agents still render fromswarm.jsoninstead of falling back tounknown/not found. -
Fix: stop controls include every swarm container. The dashboard
skey andlaunch.sh stopnow stop numbered agents, interactive containers, and the post-process container. When a post-process container already exists, the footer shows only theplogs shortcut and hiddenPpresses do not replace the running post-process container. -
Fix: dashboard state summaries skip interactive-only profiles. The state file written by
launch.sh startnow filters out omitted and zero-count profiles when generating the dashboard model summary, matching the dashboard's direct config path and avoidingnullxor0xentries for manual profiles. -
Fix: manual interactive E2E harvest avoids setup-log conflicts. The generated manual fixture now writes setup output to a unique branch- or agent-specific log file, so following the runbook's broad
git add test-resultsstep does not make two interactive branches collide ontest-results/setup.logduring harvest. -
CI: remove the scheduled full matrix. The integration workflow now keeps the lightweight smoke job only; maintainers can still run
./tests/test.sh --allmanually when the full API-key-backed matrix is needed.
-
Fix: Docker project names are sanitized for uppercase repo basenames.
launch.shnow derives a lowercase Docker-safe project id for image names, container names, and/tmppaths, while preserving the raw repository basename in user-facing run context. Dashboard, harvest, progress, cost, and test helpers use the same sanitizer so they agree on the internal names. -
Fix: configured post-processing is visible before it starts. The dashboard now renders a synthetic
PProw frompost_processconfig when no post-process container exists yet, markedconfigured. Once the container exists, the row still uses the live Docker state, stats, and environment as before. -
Fix: dashboard post-process logs get a dedicated shortcut. Lowercase
pnow follows the existingPPcontainer logs and never starts post-processing. UppercasePstarts or replaces the post-process run only after an explicit confirmation prompt, so an accidental keypress does not stop agents and launch triage. -
Docs: clarify that
launch.sh waitdoes not start agents. Command help, quick-start docs, and post-processing docs now show the intended sequence: runstartfirst, thenwait.harvest.shis documented as a merge-only helper that does not triggerpost_process;post-processis the direct command for manual post-processing followed by harvest.
-
Test: runtime emergency-push verification under
--all.tests/runtime_signal_trap.shdrives the harness's SIGTERM / SIGINT / fatal-exit emergency-push paths inside a real container, using a synthetictest-committerdriver mounted via-vso no API key is required. Asserts the exit code (143 / 130 / 1), the harness's log markers (received SIG…,attempting emergency push,emergency shutdown complete), and that the in-flight commit lands onorigin/agent-work. Also asserts thecmd_stopbanner echoes the activeSWARM_STOP_TIMEOUT. The pure structural assertions intests/test_session_end_push.sh§6 / §7 pin the code shape but cannot prove the signal trap actually fires; this test does, end-to-end.Wired into
tests/test.shas Phase 1.5 of--allbetween unit and API-key integration, and exposed astests/test.sh --runtimefor standalone runs. Skips cleanly when Docker is unavailable so--allstays useful on hosts without it. Wall-clock ~25 s with the image cached; first run additionally pays the cacheddocker buildforclaude-swarm-runtime-test:latest(--build-arg SWARM_AGENTS=fake, so no CLI-install layers). -
Fix: agent commits abandoned on fatal-error exit and on
docker stop. The session-end push pipeline (in-place rebase -> scratch worktree -> agent-parked salvage) ran ONLY at the bottom of the per-iteration loop, AFTER theFATAL_MSGhandling block that exits with code 1 on non-retriable model errors, retry-budget exhaustion, and zero-token failures. In a real 45-agent swarm, 15 agents exited that exact path holding 3-154 commits each that died with the container. Separately,docker stop(default SIGTERM + 10 s grace + SIGKILL) hit the harness while it was parked in a foreground pipeline, so its push block never ran either -- 26 of the same 45 agents lost commits through that door.Fix in three layers:
-
Hoist the inline push pipeline into
_session_end_pushand call it before every fatalexit 1in theFATAL_MSGblock (with|| trueso a final push failure does not mask the original fatal cause). Tests:tests/test_session_end_push.sh§6 -- one definition, one inline call, three fatal-exit calls, an awk walk pins the ordering constraint that the push immediately precedes the exit, and the call-site count is locked at six. -
Wrap the agent pipeline in a backgrounded subshell (
( agent_run ... | /activity-filter.sh ) &; wait) so the harness'swaitbecomes signal-interruptible. Installtrap '_on_signal TERM' TERM(andINT) right before the main loop._on_signalkills the running pipeline, calls_session_end_push, then exits 143 / 130. Tests:tests/test_session_end_push.sh§7 pins both per-loop invocations using_run_agent_session, the absence of any bare foregroundagent_run | /activity-filter.shpipeline, the trap installation, and the handler's kill / push / exit behaviour. -
cmd_stopinlaunch.shnow passes-t "${SWARM_STOP_TIMEOUT:-60}"todocker stop. Docker's 10 s default cuts the emergency push mid-rebase on a busy bare repo; 60 s comfortably absorbs the three-try rebase plus the scratch-worktree cherry-pick plus the agent-parked salvage push. Override withSWARM_STOP_TIMEOUT=120 ./launch.sh stopfor larger swarms. Tests:tests/test_launch.sh§40 pins the flag, the default, and the env-var override via a fake-docker shim that captures the invocations.
-
-
Fix:
cmd_post_processreused a stale image after the driver set changed. A swarm built fromswarm-codex.jsonfollowed by./launch.sh post-processagainstswarm-claude-code.jsonran the post container on the codex-only image with noclaudebinary, exited 127 on first session, and the harness'scommand not foundretry path looped without recovery -- the operator had todocker buildthe image by hand. The post-process path never calleddocker buildat all, so the layer cache was bypassed and the wrong install layer was the one in effect.Fix: factor
compute_swarm_agents(driver-set union from a config) andbuild_image(docker buildwith derived build-args) out ofcmd_start, and callbuild_imagefromcmd_post_process. Build-args are derived from the same config the container will run, so the layer cache invalidates correctly when the driver set or pinned CLI versions change and is a no-op otherwise. Drive-by: switch the submodule mirror cleanup incmd_starttorm_docker_dirso it works when a prior container left UID-mismatched files behind.Tests:
tests/test_launch.sh§38 sourcescompute_swarm_agentsfromlaunch.shand pins the build-arg union on eight configs (default driver, codex-only top-level, mixed groups, dedup across groups, codex agents with a claude-code post-processor, post-process matching agents, post-process inheriting the top-level driver, and an empty per-group driver falling back to the default). §39 assertsbuild_imagehas exactly two call sites -- one incmd_start, one incmd_post_process-- and that its body still threadscompute_swarm_agents,SWARM_AGENTS,CLAUDE_CODE_VERSION, andCODEX_CLI_VERSIONas build-args. Closes #96. -
Fix:
harvest.shre-introduces commits dropped by an upstream force-push. When the bare repo'sagent-workno longer descends from the working tree's HEAD -- the classic shape after a rebase or force-push that orphans commits the bare still holds --harvest.shhappily 3-way-merged anyway and silently re-introduced the dropped ancestry into the operator's branch. The merge exited 0, so nothing in the workflow surfaced the regression until it landed in review or CI hours later.Fix: before merging, run
git merge-base --is-ancestor HEAD "$REMOTE_NAME/agent-work". On failure, print both short SHAs, name the bare path, and direct the operator to eitherrm -rfthe bare and re-launch or merge manually if the divergence is intentional. The temporary_agent-harvestremote is removed before exit so the script is re-runnable. The check is generic -- no hard-coded branch names -- so it coversmaster,workflow/master, and any custom branch.Tests:
tests/test_harvest.sh§9 builds a bare whoseagent-workdescends from a sibling of HEAD, runs the realharvest.sh, and asserts the guard exits non-zero, the diagnostic mentions the bare path and short HEAD, the working tree's HEAD is unchanged, and the temporary remote is cleaned up on the failure path. A happy-path assertion confirms that once the divergence is cleared the same invocation still succeeds. Closes #97.
-
Fix: codex-cli driver misclassifies "Selected model is at capacity" as non-retriable. OpenAI's fleet-saturation response is
{"message": "Selected model is at capacity. Please try a different model."}-- a genuine short-lived transient.agent_is_retriableinlib/drivers/codex-cli.shonly matched the rate-limit/quota class and v0.20.7's transient class (SSE drops, 5xx, connection layer), so the capacity message fell through to non-retriable and killed the agent withexiting due to unrecoverable error.Worst at quota-reset boundaries: when every quota-bound client retries simultaneously, the model fleet is briefly saturated and capacity errors land on the first post-reset session attempt -- exactly the moment the backoff loop should be re-entering, not exiting. Observed in production swarm runs as silent agent loss with no clean recovery path (container has to be
docker started by hand) and outcomes that depend on which retry happens to land a few seconds later when capacity frees up.Fix: extend the
_transientregex withat capacityandplease try a different model. Either substring alone classifies the response as transient, so an OpenAI wording tweak that drops one half doesn't reintroduce the regression. Genuinely fatal conditions (invalid auth, model not found, etc.) still return empty.Tests:
tests/test_harness.sh§14b adds three assertions matching the issue's reproduction: the full JSON message, theat capacitysubstring alone, and theplease try a different modelsubstring alone. Existing non-retriable counter-tests (auth error, model not found) still pass, so the change is additive. Closes #85.
-
Fix: codex agents commit unsigned even when
signing_keyis configured. The bind-mounted/etc/swarm/signing_keyinherits host perms (often0644for shared swarm-bot keys), andssh-keygen -Y signrefuses world-readable keys withUNPROTECTED PRIVATE KEY FILE. Claude Code surfaces the failure; Codex CLI silently retries the same commit with--no-gpg-sign(openai/codex#6199), so commits land without a signature and signing-required branches reject the push hours later. See openai/codex#6199.Fix:
lib/signing.shnowinstall -m 0600's the source key to/dev/shm/swarm-signing-key(tmpfs, RAM-backed, per-container in Docker -- private key bytes never hit disk) and pointsuser.signingkeyat the copy.installfailure short-circuits withreturn 1before anygit configruns, so a missing/dev/shmor full tmpfs surfaces immediately instead of leavinguser.signingkeypointing at a path that doesn't exist (which would silently fail every later commit). An optional second argument lets tests override the destination.Tests:
tests/test_harness.sh§12 adds six assertions: copy exists, perms are0600,user.signingkeypoints at the copy, nothing leaks under$HOME/.ssh/, install failure returns non-zero, and install failure does not poisonuser.signingkey. The pre-existingrun_signing_confighelper now threads an explicit sandboxdst_keyso unit tests stop leaking files into the host's/dev/shm. -
Codex CLI version pinning. New
codex_cli_versionfield in the swarmfile mirrorsclaude_code_version: forwarded tonpm install -g @openai/codex@<ver>via a Docker build-arg. Empty (or unset) keeps the default "latest published release" behavior.DockerfileaddsARG CODEX_CLI_VERSION=,launch.shreads the field withjqand forwards it as--build-argonly when set, andlib/drivers/codex-cli.sh'sagent_install_cmdheredoc matches. Tests:tests/test_launch.sh§29 pins the jq filter (present + absent), andtests/test_drivers.sh§30 pins the install snippet structurally and behaviorally (emptyCODEX_CLI_VERSION->@openai/codex, pinned ->@openai/codex@<ver>). -
Test infra: isolate scratch-repo unit tests from host gitconfig.
tests/test_launch.shandtests/test_session_end_push.shbuild scratch repos and rungit commit/commit-treeagainst them. When the host has SSH-SK signing or acommit.gpgsign=trueglobal, every internal commit prompts for a hardware-key touch. Both files nowexport GIT_CONFIG_GLOBAL=/dev/nullandGIT_CONFIG_SYSTEM=/dev/nullat the top, so the unit-test surface is independent of the developer's signing setup.
-
Fix:
launch.shbare-repo preflight sends the operator atharvest.sheven when local HEAD is strictly ahead of the bare. The divergence guard refused everyBARE_HEAD != LOCAL_HEADcase with the same message, but when local is ahead of the bareharvest.shhas nothing to collect and the only workable remediation isrm -rfon the bare. Post-harvest cherry-picks of recoveredagent-parked/*refs (routine under 0.20.10's retry park), squash/rebase of the harvested branch before the next phase, and topic branches forked from post-harvest state all land here.Fix: distinguish the two directions with
git merge-base --is-ancestor $BARE_HEAD HEADin the local repo. Running the check in the bare (as first drafted in the report) would fail to resolveLOCAL_HEADin the stale case -- local's new commit is not in the bare's object db -- and collapse stale into unharvested, leaving the bug in place. In local, ancestry resolves cleanly across all three cases:BARE_HEADin local and ancestor ofHEAD-> stale;BARE_HEADabsent from local -> unharvested (which also covers true divergence). Both messages name shortBARE_HEADandLOCAL_HEADso the operator can verify at a glance, and the stale branch leads withrm -rfas the primary remediation.Also switch the
rev-parse refs/heads/agent-workcall to--verify --quiet. Without it, a bare that exists but lacks theagent-workref captures the literal pathspec (refs/heads/agent-work) intoBARE_HEAD, the divergence test sees a non-empty value, and the guard fires on an otherwise-empty bare with a garbage SHA.Tests:
tests/test_launch.sh§37 mirrors the guard as a helper and drives it against six scenarios: equal (guard allows), unharvested (bare has a commit local doesn't), stale (local has a commit bare doesn't), divergent (each side has unique commits), bare absent (no-op), bare present withoutagent-workref (no-op). Each failure branch pins both the wording and the presence of both short SHAs in the output.
-
Fix: session-end
git pull --rebase && git pushfails every retry when the target repo installs worktree-touching hooks. The push path inlib/harness.shran the primary rebase and push with client-side hooks active. When the consumer repo installed apost-checkoutorpost-rewritehook that regenerates docs, stamps build artifacts, or otherwise modifies tracked files, those hooks fired during the rebase's internal checkouts under.git/rebase-merge/and re-dirtied the tree that the pre-rebase stash + submodule-sync had just cleaned. Every one of the three primary retries hit the same dirty-tree trap. The_scratch_worktree_pushfallback recovered because it already passed-c core.hooksPath=/dev/nullon itsgit worktree add, cherry-pick, and commit -- but the common case paid the cost of the rare case, and when/upstreamhad any other transient issue on top (the reporter hit a momentarily corrupt loose object from a concurrent unpack on the bare repo) the fallback also failed and the salvage-park push dropped the commit: scratch push rejected, park push rejected too, harness logged "commits remain in local repo", and the ephemeral container exited with the agent's work lost.Fix, part 1: pass
-c core.hooksPath=/dev/nullto bothgit pull --rebaseandgit pushon the primary session-end path, matching what the scratch fallback already does. Suppression is safe: the swarm's ownprepare-commit-msgandpost-rewritehooks are both no-ops on commits that already carry aModel:trailer, and every agent commit does becauseprepare-commit-msgruns at commit time. The override is client-side only, so server-side hooks on/upstream(pre-receive, update, post-receive) still run.Fix, part 2: wrap the salvage-park push inside
_scratch_worktree_pushin the same boundedfor _ptry in 1 2 3retry with 1..5s jitter that the primary path uses, and gate the "parking also failed" error on a_park_okflag so a successful attempt 2 or 3 no longer gets reported as a loss. A transient/upstreamhiccup now has three chances to drain before the container exits.Tests:
tests/test_session_end_push.sh§1 pins-c core.hooksPath=/dev/nullon both primary git invocations and on the scratch-fallbackworktree addstructurally; §2 pins the park retry loop's shape, backoff, logging, and_park_okgating against a regex-extracted slice oflib/harness.sh; §3 proves the hook-suppression mechanism at the elementary level (repo with apost-checkouthook fires twice without the flag, zero times with it); §4 reproduces the bug end-to-end by setting up two clones of a bare remote that diverge onagent-work, installing a hostilepost-checkouthook in the second clone that rewrites a tracked file, and asserting that without the fixgit pull --rebaseleaves the tree dirty or fails, whereas with the fix the rebase exits 0, the worktree is clean, HEAD is the local commit correctly rebased onto the remote tip, and the subsequent push lands; §5 drives the park retry idiom with a fakegitthat fails a configurable number of push attempts and asserts the loop survives 2 transient rejections withpark_ok=true, returns after 1 attempt when push succeeds immediately, and exhausts withpark_ok=falseafter 3 rejections.tests/test_harness.shpre-existing invariants updated to match the new shape.Reported by @BowTiedRadone (#82).
-
Fix:
dashboard.sh/costs.sh/lib/harness.shdie withprintf: <n>.<m>: invalid numberon hosts using,as the decimal separator. All three scripts format decimals the same way --printf '%.1f' "$(echo ... | bc -l)"in the shell tier,awk "BEGIN { printf \"%.6f\" ... }"for per-session cost accounting in the harness -- andbc/ mostawkimplementations always emit.regardless of locale. Bash's builtinprintfand GNU awk'sprintf, however, parse%farguments per LC_NUMERIC, so on a host with LC_ALL unset and LANG pointing at a locale that uses,as the decimal separator (de_DE, fr_FR, sv_SE, nl_NL, ...) the dashboard greeted the operator with a garbled error line instead of a tokens-per-second value, and the harness would silently miscount session cost on any container running gawk under an affected locale.Fix: export
LC_NUMERIC=Cat the top of each entry point (directly underset -euo pipefail, before any function is defined or anybc/awk/printfruns), so internal number parsing and formatting use.as the decimal separator regardless of the operator's locale. LC_ALL, LANG, LC_MESSAGES, LC_TIME, and LC_COLLATE are left untouched, so timestamps, error messages, and UI text still render in the operator's language.Tests:
tests/test_locale.sh§1 pins theexport LC_NUMERIC=Cliterally on all three scripts (structural); §2 bootstraps a comma-decimal locale either from the host's available locales or via unprivilegedlocaledefinto a per-testLOCPATH(glibc-only; skips gracefully on macOS); §3 reproduces the bug by sourcing the formatting helpers under the broken locale without the fix and asserts the subshell exits non-zero with aninvalid numbererror on stderr; §4 re-enables the fix in the same subshell and assertsformat_tps,format_tokens(both theMandkbranches), andformat_costall produce.-separated output.Reported by @friedger.
-
Fix:
harvest.shaborts mid-preview whenagent-workhas a large commit log.harvest.shruns underset -euo pipefailand line 55 emits a preview of the incoming commits withecho "$COMMIT_LOG" | head -20. Whenagent-workhas more than 20 new commits and the oneline log exceeds the 64 KiB kernel pipe buffer,headcloses stdin after its 20-line slice,echogets SIGPIPE (exit 141), pipefail propagates the failure to the pipeline, andset -eexits the script before the... and N moreoverflow line can print and before the actualgit mergeruns. A user harvesting a large swarm sees the first 20 commits printed and an unexplained non-zero exit with no merge performed.Fix: append
|| trueto the preview pipeline, matching thegrep -c . || trueidiom one line above. Short logs render unchanged; long logs now advance to the merge step.Tests:
tests/test_harvest.sh§6 pins the|| trueon the preview line structurally, §7 seeds 100 empty commits with ~1 KiB subject padding so the oneline log exceeds the pipe buffer and runs the realharvest.sh --dryagainst the fixture (asserts exit 0 and the... and 80 moreoverflow line), and §8 pipes a synthetic 250 KiB log through the guarded and unguarded forms of the idiom to document the class of bug.Credit: Fredrik (@fredrik0x) spotted and fixed the SIGPIPE.
-
Fix: codex-cli SSE stream drops treated as unrecoverable despite
max_retry_waitbeing set. Codex CLI's own reconnect budget is a hard-coded 5 attempts; after 2 of those are consumed on a transient OpenAI 5xx / SSE drop it emitsfatal: Reconnecting... 2/5 (stream disconnected before completion: An error occurred while processing your request... You can retry your request...)and exits. 0.20.6'sagent_is_retriablefor codex-cli only matched rate-limit / quota wording, so the harness classified this as fatal and exited the agent container immediately — bypassing the backoff loop entirely even when the operator setmax_retry_wait: 1800in the swarmfile. Observed in practice as a validator container dying at T+3h15m of a 5h production run on a transient network blip, leaving the swarm one role short for the rest of the run.Fix:
agent_is_retriableinlib/drivers/codex-cli.shnow recognises atransientclass alongsiderate_limited, covering SSE stream drops (stream disconnected,Reconnecting...), connection layer errors (connection reset|closed|refused,timed out), upstream 5xx gateway signals (bad gateway,service unavailable,50[234]), and OpenAI's generic retry hint (processing your request). Genuinely fatal conditions (invalid auth, model not found, etc.) still return empty so the harness exits fast on config bugs.Tests:
tests/test_harness.sh§14b sources the driver, feeds probe strings for each pattern class, and asserts the classifier returnsrate_limited/transient/""respectively. -
Fix: cherry-pick conflicts in
_scratch_worktree_pushdrop the agent's commits on the floor. 0.20.6's fallback aborts the cherry-pick on conflict, resets the scratch worktree, and returns 1. The local commits live on in the agent's working repo, but the next session's openinggit reset --hard origin/agent-workerases them — the hands-free recovery path assumed the next pull-rebase would succeed, and when a real conflict persists across sessions it does not. Observed as 3 lost commits across a 23-transplant 5h production run (all were low-value journal/claim markers, but the failure mode is indistinguishable from losing a finding).Fix: when the scratch transplant fails at any step (cherry-pick, commit, or final push)
_scratch_worktree_pushnow pushes the agent's local HEAD to a salvage ref on origin namedagent-parked/<agent-id>-<UTC-timestamp>before tearing down. The parked branch holds the agent's original SHAs (not the discarded cherry-pick replays), so harvest and manual inspection see the exact commits the agent made. The push step proper still returns 1 —agent-workis not advanced, which keeps dedup / fetch semantics unchanged — but the work is no longer lost. If the parking push itself fails (unreachable origin, auth, etc.) the commits remain in the agent's local repo, which is exactly where they started, and an error is logged so the operator can investigate.Tests:
tests/test_harness.sh§18e stages a genuine textual conflict (both-added file at identical path with incompatible content), runs the fallback against a bare repo, and asserts (a) the fallback returns 1, (b) exactly oneagent-parked/<agent>-*ref was created on origin, (c) its tip is the original local SHA, (d)agent-workwas not advanced. Structural pins againstlib/harness.shverify the parking code path exists in the production function, not just in the test rig.
-
Fix
scratch push: worktree add failedunder consumer- installed post-checkout hooks. 0.20.5's_scratch_worktree_pushpasses-c core.hooksPath=/dev/nullto thecherry-pickandcommitinvocations but not to the precedinggit worktree add. That omission was benign in most cases but fatal for any consumer that installs a post-checkout hook referencing another hook via a relative path: in a linked worktree.gitis a gitfile (not a directory), so.git/hooks/<anything>resolves to "Not a directory" at the syscall level and the entire worktree-add aborts before cherry-pick ever runs. Observed in practice as 100% fallback failure with the log linescratch push: worktree add failedimmediately followed bypush failed after 3 retries and scratch fallback-- the scratch path was effectively dead on arrival for affected consumers, reverting 0.20.5 to 0.20.4 behaviour (commit loss at session close).Fix: add
-c core.hooksPath=/dev/nullto thegit worktree addcall inlib/harness.sh. One-line code change; hooks were already irrelevant in the scratch worktree.Tests:
tests/test_harness.sh§18a pins the flag on the worktree-add site structurally and §18d exercises the failure mode end-to-end -- installs a hostile post-checkout hook, runs the fallback, asserts it still succeeds, then repeats with a flag-free control to confirm the hostile hook does break worktree-add when suppression is off. (See §18d's "negative control" assertion.)
-
Cherry-pick-onto-scratch fallback when session-end rebase exhausts its retries. On a 12-event dirty-tree bug-report run against an 0.20.4 swarm the in-place
git pull --rebase && git pushpath failed in 12/12 cases across three distinct patterns: (A) submodule pointer drift thatgit stashcannot capture (M <submodule>on the gitlink); (B) context-stripping hooks firing during the rebase's internal checkouts under.git/rebase-merge/re-dirtying the tree between the pre-apply clean state and the rebase's own integrity check; (C) "skipped previously applied commit" interactions with multi-agent swarms whose commit graphs overlap. Each ended with the three-attempt retry loop burning out and the next session's openinggit reset --hard origin/agent-workerasing the in-flight work. 0.20.5 adds_scratch_worktree_pushinlib/harness.sh: after the existing retry loop gives up, the harness fetchesorigin/agent-workfresh, spins up a detachedgit worktree addat that tip in/tmp/swarm-push-<agent>-<pid>-<rand>, cherry-picks each unpushed commit viacherry-pick -n+ redundancy check +commit --allow-empty-message -C, pushesHEAD:agent-workfrom the scratch, and tears the worktree down. Hooks are suppressed in the scratch viacore.hooksPath=/dev/nullso the context-stripping post- checkout hook that caused pattern B cannot re-delete files the cherry-pick is meant to bring back. Submodules are deliberately not initialised in the scratch -- the push only cares about the superproject's gitlinks, and a submodule-free worktree sidesteps pattern A entirely. The two-step cherry pick +commit -Cdance is a manual equivalent of git 2.45'scherry-pick --empty=dropdone by hand so it stays portable to the git 2.39 on Debian bookworm (the base image); the "dropping redundant commit" branch specifically handles pattern C. Behavioural coverage intests/test_harness.sh§18b sets up a bare + working clone with a three-shape dirty tree (tracked mod, tracked deletion, untracked scratch), drives the fallback, and verifies the commits land on origin while the main worktree's dirty state survives untouched; §18c simulates pattern A by publishing a patch-equivalent D' via a sibling clone and asserts the fallback drops the local D instead of stamping a duplicate on top. -
Activity watchdog inside
_run_reapedfor silent CLI hangs. On the same bug-report run one codex-cli agent went silent for 4h22m (SESSION_TIMEOUT=0 on that invocation) before the operator noticed and randocker stop; the CLI was alive but deadlocked internally (stuck model request or blocked MCP tool), sowait "$_cmd_pid"in_run_reapednever returned, the post-wait group-kill never fired, and the harness sat on the| teepipe indefinitely. 0.20.5 adds_reap_watchdoginlib/drivers/_common.sh: a sibling background process polls<logfile>'s mtime every$SWARM_ACTIVITY_POLLseconds (default 10), and if no advance is seen for$SWARM_ACTIVITY_TIMEOUTseconds it SIGTERMs the CLI's process group, polls for up to$SWARM_WATCHDOG_GRACEseconds (default 10), then SIGKILLs. The kill decision is tee'd into<logfile>.errso operators can distinguish a watchdog-killed exit from a crashed-on-its-own exit after the fact. Opt-in viaSWARM_ACTIVITY_TIMEOUT-- 0 (the default) disables the watchdog and preserves pre-0.20.5 behaviour; 300-600 is a sensible starting point for production codex-cli / claude-code swarms.SWARM_ACTIVITY_POLLandSWARM_WATCHDOG_GRACEare exposed primarily so the behavioural test suite can drive the full escalation in ~3s rather than the production 20s floor.tests/test_drivers.sh§40 adds 15 structural pins (mtime probe with BSD fallback, SIGTERM-before-SIGKILL escalation, poll-for-exit rather than fixed sleep, env-var validation, watchdog backgrounding + cleanup) plus 6 behavioural assertions (full-escalation path, pre-kill output preserved, dormant-when-disabled, non-numeric-degrades-safely). -
Test coverage growth.
tests/test_harness.shgoes from 124 to 147 assertions (+23 in §18);tests/test_drivers.shfrom 300 to 321 (+21 in §40). Full./tests/test.sh --unitruntime increases by roughly 3 s on Linux (mostly the §40b watchdog escalation rehearsal), unchanged on macOS (the setsid/stdbuf behavioural blocks skip cleanly).
- Close the remaining session-end rebase failures on dirty trees.
The
rebase.autoStash=truefix shipped in 0.20.2 closed the common unstaged-tracked-file case but left three adjacent failure modes unhandled, each empirically observed across an 11-event, 2-hour / 4-agent codex-cli run on a repo with a submodule: (1)git stashdefaults to not stashing untracked files, so?? <path>survives the autoStash and the rebase still refuses; (2)git stashdoes not capture submodule pointer drift (M <submodule>) regardless of flags -- the superproject gitlink diff is invisible to stash's default traversal, which causes everycargo build/git worktree add/ etc. that bumps a submodule's HEAD to block the next push; (3) when autoStash does create a stash, the auto-pop after a successful rebase is best-effort per git's own docs and was observed failing mid-rebase on "skipped previously applied commit" in multi-agent swarms where commit histories overlap. The push block now sidesteps all three: it runs an explicitgit stash push --include-untrackedto capture tracked + untracked state, thengit submodule update --init --recursive --forceto re-sync submodule HEADs to what the superproject expects (the one thing stash cannot reach), then a baregit pull --rebaseagainst a guaranteed-clean tree. The pre-push stash is intentionally not popped -- the next session's openinggit reset --hard origin/agent-workwipes whatever was in-flight anyway, and not popping removes the entire autoStash-pop conflict class. The stash stays in the reflog (git stash list/git stash show stash@{N}) for forensic recovery.tests/test_harness.sh§17 now pins seven invariants against the harness source: pre-stash with--include-untracked, submodule force-sync, bare rebase, absence ofgit -c rebase.autoStash, absence ofgit stash popinside the push block, porcelain status logging, and stash-ref logging. Credit to the operator who ran the 2h / 4-agent codex-cli smoke on a superproject-with-submodule repo and filed the bug report with raw harness logs and the empirical failure-mode breakdown that made this fix straightforward to scope.
- Tolerate missing
stdbuf/setsidon macOS CI runners. The_run_reapedhelper introduced in 0.20.2 unconditionally piped throughstdbuf -oL teeand launched the CLI undersetsid, which broke./tests/test.sh --uniton the macOS GitHub Action with "stdbuf: command not found" -- both are GNU utilities (coreutils / util-linux) shipped with the productiondebian:bookworm-slimcontainer but absent from stock macOS._run_reapednow probes for each tool: missingstdbuffalls back to baretee(matching the patternfake.shalready uses), missingsetsidruns the CLI in-line without the group-kill (the zombie-reaping protection is only meaningful inside the production container wheresetsidis always present, so the in-line fallback is unit-test scaffolding rather than a degraded production path). §39's behavioural block skips with aSKIPnotice when either tool is unavailable; the 7 structural grep pins still run on every host so the bug cannot silently regress in source.
-
Preserve agent work across session-end rebase. The push path in
lib/harness.shnow runsgit -c rebase.autoStash=true pull --rebaseinstead of the bare form. Without autoStash,git pull --rebaserefuses outright on a dirty working tree ("cannot pull with rebase: You have unstaged changes"), the three-attempt retry loop burns through all its tries without pushing, and the subsequent between-sessiongit reset --hard origin/agent-worksilently erases whatever in-flight edits, untracked scratch files, or dirty submodule pointers the agent left behind. autoStash stashes, rebases, and reapplies transparently, scoped viagit -cso no container-level config is touched. The push path also logsgit status --porcelain=v1once before the retry loop so operators can audit the exact uncommitted state at session end. -
Reap driver process groups so the agent pipeline can drain. Agent CLIs (codex, claude, gemini) routinely spawn helper subprocesses (MCP servers, reasoning workers, IPC brokers) that inherit stdout. When the CLI's main process exits without waiting for those children, the children keep the pipe to
teeopen,teenever sees EOF, and the downstream| /activity-filter.shpipeline wedges indefinitely — the harness blocks on the pipe and no progress is made until the container is externally killed. A new shared helper_run_reapedinlib/drivers/_common.shputs each CLI in its own process group viasetsidand SIGKILLs the group afterwait, so surviving descendants release their FDs and the pipeline observes EOF normally.claude-code.sh,codex-cli.shandgemini-cli.shnow route through the helper;fake.shemits synthetic JSONL inline and is intentionally exempt.
- Preserve environment across
sudoin setup hook. The container's setup script now runs viasudo -E bash, preserving the container environment across thesudoboundary. Previously default Debian sudoers'env_resetstripped everything exceptPATH, so any variable set in the container -- including vars passed viadocker_args -eand swarm-owned vars likeAGENT_ID,SWARM_MODEL, andMAX_IDLE-- was silently dropped beforesetup.shsaw it. Combined with docker's-e VARinheritance form (no=valueinherits from the caller's env, omits when unset), a single swarmfile can now be parameterized from host env at launch time:"docker_args": ["-e", "TARGET_REPO"]paired withTARGET_REPO=... ./launch.sh start.
- Optional SSH commit signing. New
git_user.signing_keyfield accepts a host-side path (literal,$VAR, or~/...) to an SSH private key. When set, the key is bind-mounted read-only into each agent and post-processor container at/etc/swarm/signing_keyand git is configured withgpg.format=ssh,user.signingkey=/etc/swarm/signing_key, andcommit.gpgsign=true. When absent -- or when the field resolves to empty via an unset$VAR-- signing is explicitly disabled inside the container to prevent a host signing config from leaking in.openssh-clientis now installed in the container image for thessh-keygen -Y signthat git invokes. Signing config is factored intolib/signing.shand shared betweenlib/harness.shand the harness test, so the production path and the regression test exercise the same code. - Per-post-processor
max_idle.post_process.max_idlecontrols the idle-session threshold for the post-processor independently of the top-level agent-facingmax_idle. Falls back to the top-level value when omitted, preserving the prior behaviour for configs that don't set it.
- No more blank
Think:lines on Opus 4.7. The activity filter (bothlib/activity-filter.shand the driver'sagent_activity_jq) now rendersThink: [encrypted]when thethinkingfield is empty butsignatureis present (the expected Opus 4.7display: "omitted"payload where the full reasoning ships encrypted server-side) andThink: [empty]when both are empty — so anomalous blocks are distinguishable from the expected encrypted-reasoning case. Opus 4.6 and earlier still render their summary text unchanged. - Forward-compatible
showThinkingSummariesopt-in. The claude-code driver's.claude/settings.local.jsonnow includes"showThinkingSummaries": true. On Claude Code ≤ 2.1.111 this is a no-op on Opus 4.7 headless mode: the CLI does not yet plumb the setting through to an explicitthinking.display: "summarized"on the Messages API request, so the API keeps returning empty thinking. The opt-in is left in place so that a future Claude Code release which wires the setting through will restore summaries with no further swarm change. See https://docs.anthropic.com/en/about-claude/models/whats-new-claude-4-7 and https://news.ycombinator.com/item?id=47664442.
- Retry on API 500 errors. Claude Code driver now treats
api_error,internal_error, and HTTP 500 responses as retriable, preventing agents from exiting on transient Anthropic outages. - Dashboard max-effort label.
format_modelnow displays(M)foreffort: "max"instead of(m), disambiguating it from medium. - Remove setup.sh wizard. Deleted the interactive
setup.shand its tests. Copying a config fromtests/configs/and editing is simpler and better documented. Not treated as a breaking change (would warrant 0.20.0) because no known users rely on it. - Weekly CI schedule. CI now runs on a Monday morning cron
(
0 7 * * 1) in addition to push/PR triggers.
- Codex CLI driver. New
codex-clidriver (lib/drivers/codex-cli.sh) implements the full interface for OpenAI's Codex CLI: headless mode withcodex exec --json, JSONL stats extraction (with cached-token deduplication for accurate cost), activity parsing, fatal/retriable error detection, and reasoning effort support. - ChatGPT subscription auth. Codex agents can authenticate
via
"auth": "chatgpt"(mounts~/.codex/auth.json) or"auth": "apikey"(usesOPENAI_API_KEY). Auto-detection when both are present. Usage-limit errors from ChatGPT subscriptions are retriable. - Bridge .claude/ conventions to Codex. When
AGENTS.mdis absent, copies.claude/CLAUDE.md(or rootCLAUDE.md) so Codex picks up project instructions. When.agents/skills/is absent, symlinks.claude/skills/so Codex discovers existing skills. Both are git-excluded to avoid committing bridged files. - Context stripping hooks. Git hooks (
post-merge,post-checkout,post-rewrite) re-strip.claude/aftergit pull --rebase, preventing agents from seeing files removed bycontext: slimorcontext: none. - Stale rebase cleanup. Push safety net cleans up stale
.git/rebase-mergeand.git/rebase-applybefore retries, preventing repeatedgit pull --rebasefailures. - Inline system prompt for Codex. System instructions are
prepended directly to the prompt text rather than relying on
project-level instruction files, ensuring rules are always
applied under
--skip-git-repo-check. - Document per-driver effort values. USAGE.md now lists valid effort values for each driver (Claude Code: low/medium/high/max; Codex CLI: none/low/medium/high/xhigh). Gemini CLI ignores the field.
- Tag and driver in setup wizard.
setup.shnow prompts for thetaganddriverfields under advanced settings. Tag supports$VARenv expansion; driver defaults toclaude-codeand is omitted from the config when unchanged. A tip after writing the config points users to USAGE.md for additional advanced fields.
- Fix bare repo UID mismatch for container pushes. The bare
repo is created by the host user, but the container's
agentuser may have a different UID. Setcore.sharedRepository=worldandchmod -R a+rwXso any UID can push. Fixes integration test failures (2-agents-sonnet,1-agent-effort,2-agents-effort) caused byunable to create temporary object directoryerrors on GitHub Actions runners.
- Create bare repo on demand in post-process. When
launch.sh post-processruns standalone (after harvest has cleaned up), the bare repo no longer exists. Instead of exiting with an error, create one from the local repo.
- Soften agent system prompt. Replace urgency and fear-based framing ("IMPORTANT", "will be lost") with calmer language that normalizes push failures and mentions the harness safety net. Motivated by Anthropic's interpretability research showing that desperation-associated representations causally increase reward hacking and misaligned behavior in Claude.
- Extra Docker arguments. New top-level
docker_argsarray in the swarmfile passes arbitrary flags to everydocker runinvocation. Each element is one shell token. Useful for mounting the host Docker socket (-v /var/run/docker.sock:...), adding--privileged,--network=host, or any other Docker flag the harness does not manage natively.
- Fix rate-limit retry for Pro subscriptions. Claude Code's
Pro rate limit uses
"rate_limit"and"rate_limit_event"in its output, which the retriable-error detector did not match. Additionally, when no pattern matched,agent_is_retriablereturned a non-zero exit code that crashed the harness underset -e, silently killing the agent instead of retrying or logging a fatal error. - Compact retry status in dashboard. Format retry wait/max
as short durations (e.g.
retry 0s/7hinstead of raw seconds) so the Status column doesn't cause line wrapping on narrow terminals. - Clear retry status when session resumes. The retry file was only removed after the session ended, so the dashboard showed stale "retry" status while the agent was actively working.
- Push safety net for concurrent agents. After each agent
session the harness checks for unpushed local commits and
retries
git pull --rebase && git pushup to three times with random jitter. Fixes the race condition where multiple agents competing for the bare-repo lock could leave commits stranded locally, causing idle-timeout exits in multi-agent swarms.
- Thinking/reasoning in activity stream. Agent logs now
display thinking content alongside tool calls. Claude Code
thinking blocks (
type:"thinking") and Gemini CLI thought events (type:"thought") both render asThink: <first 80 chars>in the live activity feed.
- Top-level tag with per-group override. New
tagfield in the swarmfile sets a default label for all agent groups. Per-grouptagoverrides the top-level value. Supports$VARenv expansion. Post-process inherits the top-level tag when none is set. (#41) - Documentation cleanup. Complete the driver interface list in USAGE.md (all 13 functions). Add missing test files to the unit-test listing. Remove stale claims, redundant paragraphs, and fix per-group fields list in README.md.
- Rate-limit retry with exponential backoff. New
max_retry_waitfield (seconds) in the swarmfile. When set, agents retry with exponential backoff (30 s initial, 30 min cap) on rate limits and zero-token exits instead of exiting. Default is 0 (exit immediately). Newagent_is_retriabledriver interface function distinguishes retriable errors from true fatals.
- Dependency version warnings.
check_depsnow warns when bash, git, jq, or docker are below the tested minimums. SetSWARM_SKIP_DEP_CHECK=1to silence. Never blocks execution. - Disable git commit signing in containers. Agents set
commit.gpgsign=falseglobally so FIDO/GPG-enforced hosts do not block agent commits. Tests do the same in temp repos. - jq 1.8 compatibility. Guard
split("/") | .[-1]with// ""fallback in model summary expressions. jq 1.8 returns null for.[-1]on empty arrays (from splitting an empty string), breakingrtrimstr(). Coerce pricing interpolation values to numbers with+ 0. Both changes are no-ops on jq 1.6. (#42)
- Swarmfile-only configuration. Environment variables and CLI
flags can no longer substitute for swarmfile fields. A swarmfile
is now always required. API credentials (
ANTHROPIC_API_KEY,CLAUDE_CODE_OAUTH_TOKEN,OPENROUTER_API_KEY, etc.) remain environment variables. - Optional top-level prompt. The top-level
promptfield is no longer required when every agent group specifies its ownprompt. - Dashboard column widening. Status column widened to 14 chars
(handles
idle N/Mwithout misalignment), In/Out column widened to 13 chars for large token counts. - Branch info in dashboard header. The header now shows the git
branch instead of the prompt path. Auto-detects branch and HEAD
from the repo the swarm runs in. The
titlefield in the swarmfile is respected and persists across dashboard refreshes. - Claude Code version pinning. New
claude_code_versionfield in the swarmfile passes a specific version to the install script, allowing reproducible Docker image builds. - MiniMax-M2.7 in kitchen-sink. Updated the showcase config to the newer model with per-model pricing.
- Rewrite dry-run documentation to use the swarmfile pattern. Expand cleanup section with full artifact inventory.
- Config pricing overrides driver cost. The
pricingmap inswarm.jsonnow always takes precedence when present. Previously it only applied when the driver reported $0 (Gemini CLI path). Claude Code reports costs at Anthropic rates even when routing to third-party endpoints (MiniMax, self-hosted models via litellm), so third-party runs showed wildly inflated costs.
- Push after every commit. System prompt now instructs agents to push immediately after each commit, not just at session end. Prevents silent commit accumulation inside containers that the harness and harvest cannot see.
- Task-complete stopping. Replaced "stop after pushing" with "keep working until your task is complete, then stop." Agents with multi-step or looping prompts now continue naturally instead of exiting after the first push. The harness idle counter still catches agents with nothing to do.
- Hardened smoke test. Split counting and reasoning into two
commits with distinct messages; add step-0 "already done" guard
so harness re-runs do not produce duplicate commits; pre-commit
hook now unstages
agent_logs/and.claude/settings.local.json; verification fails on missing reasoning files and garbage commits.
- Gemini CLI driver. New
gemini-clidriver (lib/drivers/gemini-cli.sh) implements the full interface for Google's Gemini CLI: headless mode with--output-format stream-json, JSONL stats extraction, tool-call activity parsing, and fatal error detection. - Auth abstraction. New
agent_docker_auth()interface function lets each driver resolve its own credentials and emit Docker-eflags. Claude Code handles ANTHROPIC_API_KEY / OAuth / auth-token; Gemini CLI handles GEMINI_API_KEY and OpenRouter. The ~50-line auth block that was duplicated inlaunch.sh(start + post-process) is now a single call per driver. - Default model per driver. New
agent_default_model()interface function so each driver declares its fallback model (claude-opus-4-6, gemini-2.5-pro, fake-model).harness.shcalls it whenSWARM_MODELis unset, removing the hardcoded Claude default. - Conditional Dockerfile.
SWARM_AGENTSbuild arg controls which CLIs are installed:claude-code(default),gemini-cli, or both. Node.js 22 is only installed when Gemini CLI is needed.launch.shderives the arg from config and passes it todocker build. - Dashboard driver label.
format_model()shows a[gem]or[fake]suffix when the agent's driver is not the defaultclaude-code, making mixed-driver swarms easy to identify at a glance. - Add 6 new test configs covering gemini-only, mixed-driver, OpenRouter, driver inheritance, post-process, and heterogeneous kitchen-sink scenarios. 87 new test assertions (762 total).
- Agent driver abstraction. The harness is no longer coupled to
Claude Code. A pluggable driver interface (
lib/drivers/*.sh) lets each agent CLI implementagent_run,agent_extract_stats,agent_activity_jq, and other role functions. Adding a new agent is now a single file inlib/drivers/. - Ship two drivers:
claude-code(production) andfake(test double that emits realistic JSONL without Docker or API keys). - Add
SWARM_DRIVERconfig field inswarm.json(top-level default and per-agent override) to select the agent driver. - Validate driver interface on startup: harness fails fast with a clear error if any required function is missing.
- Extract shared JSONL stats parser into
lib/drivers/_common.shto eliminate duplication across drivers. - Wire up
agent_docker_env()so drivers can map generic config (e.g. effort level) to CLI-specific Docker environment variables. - Dashboard reads generic
SWARM_MODELandSWARM_EFFORTenv vars instead of Claude-specificCLAUDE_MODELandCLAUDE_CODE_EFFORT_LEVEL. - Bridge the process boundary for activity filtering: the driver's
agent_activity_jqoutput is written to a temp file so the pipedactivity-filter.shsubprocess can read it. - Preflight driver validation in
launch.sh: unknown drivers are rejected before any containers are started. - Document the Dockerfile build-time limitation (CLI binary is
baked in,
SWARM_DRIVERis a runtime choice) with a TODO for build-arg support when a second production driver lands. - Add 40+ new unit test assertions covering driver fields in config
parsing,
agent_docker_env, shared stats helper, interface completeness, and activity filter process boundary. - Guard the dashboard post-process keybinding (
p) so it checks whetherpost_processis configured before stopping agents. - Document dry-run pattern using the
fakedriver inUSAGE.md.
- Set
CLAUDE_CODE_ATTRIBUTION_HEADER=0in workspace settings to prevent KV cache invalidation with local models (up to 10x faster inference via llama.cpp / other local backends). - Disable telemetry and nonessential traffic in agent containers
(
CLAUDE_CODE_ENABLE_TELEMETRY=0,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1). - Error out when
--agentsis used with a config file that defines agent groups, instead of silently ignoring the flag.
- Add CHANGELOG.md covering all releases (0.1.0 through 0.9.1).
- Block execution when required tools are missing. A shared
check_deps()guard inlib/check-deps.shchecks for bash, git, jq, bc, docker, and tput at startup. - Update README prerequisites with tput, whiptail (optional), and shellcheck (development).
- Show idle state (
idle N/M) in dashboard Status column via lightweight file-based approach (replacesdocker logsparsing). - Widen dashboard Model column to prevent row wrapping with long model names.
- Add README for test config fixture files.
- Remove unrelated rules from CLAUDE.md.
- Responsive dashboard columns that adapt to terminal width, hiding less-critical columns on narrow screens.
- Optional user-supplied tag column in dashboard.
- Show credential source in dashboard Auth column.
- Show test progress counter in dashboard title.
- Slim README to scannable landing page, move details to USAGE.md with fenced code blocks for syntax highlighting.
- Color full agent log line yellow, not just prefix.
- Add CLI flags to
launch.sh start(--prompt,--model,--agents,--max-idle,--effort,--setup,--no-inject-git-rules,--dashboard). - Flatten dashboard header into single line.
- Color activity filter prefix with ANSI yellow.
- Hide idle count from dashboard status column (later reintroduced in 0.9.1).
- Support
auth_tokenfor OpenRouter-style Bearer auth. - Expand
$VARenv references inapi_keyconfig fields. - Rebalance dashboard column widths for long model names.
- Show per-group prompt tags and tree-style agent summary in dashboard header.
- Add pre-commit hook to guard submodule pointers.
- Add post-rewrite hook to re-inject provenance trailers.
- Prevent submodule recurse on
git fetch. - Reclaim workspace ownership after sudo setup.
- Per-group prompt override in
swarm.jsonso each agent group can run a different task file. - Add per-group prompt question to setup wizard.
- Per-agent context mode (
full,slim,none) to control how much of.claude/each agent group sees. - Show context mode in dashboard Ctx column.
- Add context mode prompt to setup wizard.
- Add
hlog()helper for timestamped harness log lines. - Color harness lines green, errors red; align agent prefix with harness prefix in log output.
- Skip session when prompt file is missing.
- Activity streaming: real-time tool-call feed from
stream-jsonoutput, shown vialaunch.sh logs Nor dashboard[1-9]key. - Add
lib/activity-filter.shto parsestream-jsonevents.
swarm.jsonconfig file for per-agent model groups.- Always-on TUI dashboard with per-agent cost, tokens, cache, turns, throughput, and duration.
costs.shfor standalone cost and usage summary.- Interactive setup wizard (
setup.sh). - Wait and post-process commands.
- Per-agent auth source selection and Auth column in dashboard.
- Accept
CLAUDE_CODE_OAUTH_TOKENas alternative to API key. - Support effort level per agent and globally.
- Inject git coordination rules via system prompt.
- Add
--helpflag to all scripts. - Move model provenance from git author name to commit trailers.
- Add VERSION file and
Tools:trailer with claude-swarm branding. - Prefix project env vars with
SWARM_. - Move internal files into
lib/and tests intotests/. - Comprehensive unit test suite (500+ assertions).
- Initial release.
- N Claude Code instances in Docker, coordinating through git.
- Bare repo mirroring with per-agent workspace clones.
- Harness loop: reset to
origin/agent-work, run one session, push. harvest.shto merge agent work into the host branch.- Configurable git identity via
GIT_USER_NAMEandGIT_USER_EMAIL.