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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
{
"name": "swarm",
"source": "./plugins/swarm",
"description": "Local mixture-of-agents code review for Claude Code. Fans a diff across Claude lenses plus the codex and grok CLIs (grok-4.5 + composer models), merges by mechanism with cross-family consensus, verifies solo findings, and presents one ranked report. Optional --fix / --loop applies the findings you agreed with; --pr reviews a GitHub PR diff and posts the result. Skills: /swarm:review, /swarm:agents.",
"version": "0.4.2"
"description": "Local mixture-of-agents code review for Claude Code. Fans a diff across Claude lenses plus the codex and grok CLIs (grok-4.5), merges by mechanism with cross-family consensus, verifies solo findings, and presents one ranked report. Optional --fix / --loop applies the findings you agreed with; --pr reviews a GitHub PR diff and posts the result. Skills: /swarm:review, /swarm:agents.",
"version": "0.4.3"
}
]
}
2 changes: 1 addition & 1 deletion .claude/knowledge/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- `features/herdr-close-automation.md` — `/close` in herdr: cwd-tab teardown, plugin SessionEnd hook, the one TUI-exit primitive, detached self-exit onto idle
- `features/herdr-tab-glyphs.md` — Task-state glyphs (`○ ● ◇ ◆ ✓`) on herdr tab names: `states` mode inside the self-contained renderer (no sibling), sync-vs-`--cached` PR refresh per caller, exact-cwd rename rules, soft pr-flow shim
- `features/task-archiving-on-close.md` — `/close` archives (not deletes) the task file; adaptive commit + ff-push to main
- `features/swarm-backend-adapter.md` — Verified codex/grok CLI facts (schema-enforced JSON, effort mapping, stdin hang, composer defensive-parse voice) behind `swarm`'s adapter script
- `features/swarm-backend-adapter.md` — Verified codex/grok CLI facts (schema-enforced JSON, effort mapping, stdin hang, model-aware grok readiness) behind `swarm`'s adapter script
- `features/swarm-review-pipeline.md` — `/swarm:review` P2 pipeline (skill↔Workflow wiring, family-consensus, minimal security) + P5 `--fix`/`--loop` (in-session orchestration between workflow runs; deterministic termination + close-out box in `loop-closeout.py`) + `--pr` (review a PR diff, post via `gh` under the user's identity so pr-flow's `claude`-author poll ignores it; publish path is deterministic `pr-post.py`: per-cell sanitizer + stale-head gate + post)

## Deployment
Expand Down
117 changes: 93 additions & 24 deletions .claude/knowledge/features/swarm-backend-adapter.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: "Swarm Backend Adapter Layer"
createdAt: 2026-07-03
updatedAt: 2026-07-15
updatedAt: 2026-07-17
createdFrom: "PR #21"
updatedFrom: "session: 2026-07-15"
updatedFrom: "PR #37"
pluginVersion: 1.8.2
prime: false
reindexedAt: 2026-07-12
Expand Down Expand Up @@ -32,11 +32,13 @@ debugging round.
`--output-last-message <file>` (stdout carries the agent transcript,
stderr the progress log); grok prints a response **envelope** on stdout —
the validated object is its `.structuredOutput` field.
- **The adapter pins `-m grok-4.5`** — the schema-capable model. grok 0.2.101
renamed it from `grok-build` (same upstream pin-rename class as codex's
`gpt-5.6-terra`; verified drop-in: identical envelope/`structuredOutput`
shape, `--single` unchanged). `grok-composer-2.5-fast` still rejects
`--effort` ("does not support parameter reasoningEffort").
- **The adapter pins `-m grok-4.5`** — the schema-capable model, and since
swarm 0.4.3 the *only* grok model it supports. grok 0.2.101 renamed it from
`grok-build` (same upstream pin-rename class as codex's `gpt-5.6-terra`;
verified drop-in: identical envelope/`structuredOutput` shape, `--single`
unchanged). Any other `--model` is preflight-rejected with a usage error —
only grok-4.5 enforces `--json-schema`, and an unlisted model fails late with
`structuredOutput: null` after burning a full review.
- **Effort ladders**: grok is `low|medium|high` since 0.2.101 (the `max` tier
is gone) → the adapter maps `xhigh`/`max`→`high`; codex has no `max` tier →
map `max`→`xhigh` (`-c model_reasoning_effort=…`). Both mappings degrade a
Expand All @@ -47,23 +49,90 @@ debugging round.
The pipeline runs codex at `high` normally; the `--max` profile overrides both
model and effort (`gpt-5.6-sol` @ `xhigh`) — see
[swarm-review-pipeline](swarm-review-pipeline.md).
- **`grok-composer-2.5-fast` does not enforce `--json-schema`** — but it is
still usable as a second grok voice. Given a strict-JSON *prompt* (the adapter
appends the schema text and drops `--json-schema`/`--effort`), it emits **pure
`{"findings":[...]}` directly on stdout — no response envelope, no
`structuredOutput`** (verified P2, grok 0.2.82; simpler than grok-4.5's
envelope). The adapter routes `--model grok-composer-2.5-fast` to a separate
`run_grok_composer` path that parses the answer **defensively**: collect ALL
balanced `{...}` objects (whole string, fenced blocks, every `{` run), pick the
first **non-empty** `findings` object (a leading `{"note":…}`/`{"findings":[]}`
would otherwise mask the real one), and **validate every item against
`finding.schema.json`** (composer, unlike codex/grok-4.5, is not CLI-schema-
enforced — a malformed item must ERROR, not reach merge/verify). Both the
first-object bug and the missing per-item validation were caught by swarm
reviewing its own diff. composer is
~2× faster than grok-4.5. It is same-family-correlated, so consumers must
count consensus by **model family**, not backend (composer + grok-4.5
agreeing is one grok vote — see [swarm-review-pipeline](swarm-review-pipeline.md)).
- **Model-aware readiness beats an auth-only check** (swarm 0.4.3). grok drops
and renames models between releases — 0.2.101 removed
`grok-composer-2.5-fast`, which swarm had shipped as a second grok voice; the
auth-only probe kept reporting it Ready until it failed mid-review with
`Invalid params: "unknown model id"`. `ready`/`list` now also require
grok-4.5 in `grok models` (grok is the one backend with a usable model-list
command; codex has none, so its model is trusted). The gotchas, all live-
verified:
- **Parse the bullet list by SHAPE, not position — and match the id
SUBSTRING, not the field.** Lines read ` * grok-4.5 (default)`, but keying
on `$2` turns a reworded line (` * default: grok-4.5`) into garbage tokens
that read as "the model is gone" and fail closed. Matching whole
whitespace-fields starting `grok-` fixes that but still drags glued-on
punctuation along (`grok-4.5,`, `` `grok-4.5` ``, ANSI codes), which breaks
the exact-match the same way. Extract with a pattern ending on
alphanumerics (`grok-[A-Za-z0-9]+([._-][A-Za-z0-9]+)*`); a line with no
id-shaped token then contributes nothing, landing in the trust-auth degrade.
- **Match without a pipe to `grep -q`**: an early-exiting `grep -q` can
SIGPIPE the writer, and under `set -o pipefail` a *hit* would then report
failure. Newline-fence the list and use a `case` substring match.
- **An empty model list must NOT fail closed.** Offline, a timeout, or a
future CLI renaming the subcommand would otherwise silently drop grok from
every fan-out. Empty/unparseable → trust auth and let `run_grok` surface the
explicit error; a non-empty list *without* grok-4.5 → an honest "not ready"
plus an update-the-CLI hint.
- **A probe added to a local path must not make it hang — and must not lie
when it can't run.** `ready`/`list` were purely local (stat the auth file)
before this; the probe puts a network call in every `/swarm:agents` and
review start. With no coreutils `timeout`/`gtimeout` to bound it (stock
macOS), the probe is **skipped** rather than run uncapped, degrading to
trust-auth in ~25ms — but it **warns on stderr**, because a silent skip
would make the documented model-aware guarantee false on that host: the
same "promise that doesn't hold at runtime" bug the composer removal exists
to fix.
- **Route every degrade through ONE audible exit.** This one spot was fixed
across FIVE consecutive swarm rounds, each catching the previous round's
miss: (1) the no-timeout branch ran uncapped; (2) it was capped but skipped
*silently*, while the docs were tightened to "never silently"; (3) `|| true`
still swallowed a *failed* probe, so the strengthened promise was false on
two of three routes; (4) with warnings finally on every route, `rc` was only
read **when the list came back empty** — so a probe killed mid-stream with
partial output skipped the degrade entirely and its truncated list read as
"model gone" (update an already-current CLI); (5) the probe was routed
through the review jail, which dragged `_init_sandbox`'s python3
profile-build into the local `ready`/`list` paths — where a missing python3
then misreported as "grok models failed". The invariants that survive:
- If N routes end in the same degrade, they need **one shared exit**
(`_probe_degraded`), not N hand-written warnings.
- **Check `rc` before the output, and discard partial output.** Only a
clean exit is an answer; anything else is a degrade.
- "The probe answered honestly" (model genuinely gone → `not ready` +
update-the-CLI hint) must stay distinguishable from "the probe never
answered" (→ trust auth + warning).
The meta-lesson (the one that actually ended the loop): **a fix that keeps
coming back is a shape problem, not a patch problem.** Rounds 1–4 patched a
probe that had a security jail bolted on; round 5 deleted the jail instead,
and the findings stopped. The composer removal itself — the PR's actual
subject — drew zero findings across all five rounds. When a *feature you
added to be safe* generates every round's bugs, cutting it beats hardening
it.
- **A readiness check is not a review — don't jail it.** The probe was first
built to go through `sandboxed()` (env-secret filter + sandbox-exec
read-deny jail), by analogy to the `run` calls. Wrong analogy: `sandboxed()`
exists because a *review* feeds grok the untrusted diff; a readiness check
passes **no** untrusted input, exactly like the sibling `codex login status`
a few lines away, which is also unjailed. Routing it through the jail bought
nothing and cost a hard python3 dependency on the formerly-local `ready`/
`list` paths (plus a shared-warning bug and a cross-backend memo bug — all
three vanished when the jail came back out). The probe runs grok directly.
- **Bound it with `timeout -k`, its own knob.** `SWARM_TIMEOUT` caps a
*review* (600s, `0` disables) — useless for a probe that `list` blocks on;
`SWARM_PROBE_TIMEOUT` (10s) is separate and a malformed/`0` value falls back
to 10, never uncapped. Plain `timeout` only SIGTERMs, so a grok that ignores
SIGTERM (or forks a stdout-inheriting child) keeps the `$(...)` substitution
blocking past the deadline — the "must never hang" hole. `-k <grace>` sends
SIGKILL after the grace period; treat both rc 124 (SIGTERM) and 137
(SIGKILL) as "timed out".
- **Memoize by call convention, not by wishing.** `list="$(grok_model_list)"`
runs the function in a *subshell*, so its cache-global assignments vanish
and every caller silently re-pays the network call. The cache only works if
the fetch is called directly and callers read the global
(`grok_model_fetch; local list="$_grok_models"`).
This mirrors work-system's `agent-registry.sh`, which learned the same lesson
at task-launch time.
- **Headless tool execution**: both CLIs run read-only commands (e.g.
`git diff`) without extra approval flags — codex inside `-s read-only`
sandbox, grok headless `-p` auto-approves read-only tools. So lens prompts
Expand Down
36 changes: 23 additions & 13 deletions .claude/knowledge/features/swarm-review-pipeline.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: "Swarm Review Pipeline (/swarm:review)"
createdAt: 2026-07-08
updatedAt: 2026-07-15
updatedAt: 2026-07-17
createdFrom: "PR #24"
updatedFrom: "session: 2026-07-15"
updatedFrom: "PR #37"
pluginVersion: 1.8.2
prime: false
reindexedAt: 2026-07-12
Expand All @@ -13,9 +13,11 @@ reindexedAt: 2026-07-12

P2 turns the blueprint into a working review: a **Workflow-tool script**
(`plugins/swarm/workflows/swarm-review.js`) launched by the `/swarm:review`
skill. Shape: `scope+gate → fan-out (4 voices) → merge (file,mechanism) →
verify solos → output-gated synthesis`. Four voices: Claude lenses ∥ codex ∥
grok-4.5 ∥ composer (see [swarm-backend-adapter](swarm-backend-adapter.md)).
skill. Shape: `scope+gate → fan-out (3 voices) → merge (file,mechanism) →
verify solos → output-gated synthesis`. Three voices: Claude lenses ∥ codex ∥
grok-4.5 (see [swarm-backend-adapter](swarm-backend-adapter.md)). A fourth,
`grok-composer-2.5-fast`, was removed in swarm 0.4.3 — the grok CLI dropped the
model.

## The skill ↔ workflow wiring (the non-obvious parts)

Expand All @@ -38,10 +40,16 @@ grok-4.5 ∥ composer (see [swarm-backend-adapter](swarm-backend-adapter.md)).

## Design decisions

- **Consensus counts model *families*, not backends.** A cross-family cluster
- **Consensus counts model *families*, not voices.** A cross-family cluster
(≥2 of claude / openai / grok) is CONFIRMED without extra verify; everything
else is solo and goes through the adversarial 3-state verifier. composer +
grok-4.5 agreeing is one grok vote — they cannot alone mint consensus.
else is solo and goes through the adversarial 3-state verifier. The `FAMILY`
map (`workflows/swarm-review.js`) survives the composer removal deliberately:
with composer gone the backend→family mapping is 1:1, so *for consensus
counting* it is currently a no-op — but the fan-out is many-voices-per-family
(one Claude finder per gated lens), so the rule "same vendor agreeing with
itself is one vote, not a cross-check" is still the load-bearing invariant.
Collapsing consensus onto backends/voices would silently re-introduce
correlated self-agreement the day a second same-vendor voice returns.
- **Security is intentionally minimal** (user directive: no cannons-at-sparrows).
The P1 adapter floor stays (sandbox, tool-less grok, secret scrub, env filter,
caps); P2 adds only three cheap things — **fencing** the diff as data
Expand All @@ -60,8 +68,8 @@ grok-4.5 ∥ composer (see [swarm-backend-adapter](swarm-backend-adapter.md)).
the delimiter); the workflow only collision-checks it against the returned
findings and extends it deterministically (`nonce-1`, `-2`…) on collision.

- **`args.claude: false`** runs an **external-only control** (codex + grok-4.5
+ composer, no Claude finder lenses, no gate; merge/verify still in-session).
- **`args.claude: false`** runs an **external-only control** (codex + grok-4.5,
no Claude finder lenses, no gate; merge/verify still in-session).
Proven useful: a control run found real bugs the with-Claude run missed (an
`aws_secret_access_key` scrub-list drift, `git diff` omitting untracked files)
— the "different models catch different defects" premise, live.
Expand Down Expand Up @@ -95,9 +103,9 @@ the diff out of the script, above). Claude applies edits between rounds.
the report table contract this entry defines above (P2 reserved them).
- **`--max` profile** (`INPUT.max` in the workflow): lifts every voice to its
ceiling — codex `gpt-5.6-sol`@`xhigh` (codex has NO `max` tier, xhigh is its
top), Claude finder lenses + verifier `xhigh`; gate/merge, grok (`high` is
top), Claude finder lenses + verifier `xhigh`; gate/merge and grok (`high` is
its ceiling since grok 0.2.101 dropped `max` — it runs there on both
profiles) and composer (no effort control) unchanged. Orthogonal to `--fix`/`--loop`,
profiles) unchanged. Orthogonal to `--fix`/`--loop`,
composes with both. The profile's live settings are verified end-to-end
(`gpt-5.6-sol`@`xhigh` at wiring time; grok re-verified at `--effort high` on
0.2.101) — the "no silent fail on a non-existent model/effort" rule.
Expand Down Expand Up @@ -170,7 +178,9 @@ call, more thinking) over N calls. Not a default.

Real background runs on this branch: a Claude-only smoke run proved the wiring
(6 agents, correct return shape); the review **found a real bug in its own
composer parser** (first-object-vs-findings-object), which was then fixed. Only
grok-composer parser** (first-object-vs-findings-object), which was then fixed
— that parser has since been removed with the composer backend (0.4.3), but it
stands as the proof that swarm can catch a genuine bug in its own diff. Only
`REFUTED` solos are dropped; consensus/solo/refuted counts + per-lens raw→
surviving ship in the `balance` block the skill renders. Iterating a
`/swarm:review` loop over the branch caught several fix-induced regressions
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ entries are grouped per plugin, newest first.

## swarm

### 0.4.3 — 2026-07-17
- Remove the `grok-composer-2.5-fast` backend: grok CLI 0.2.101 dropped the model, so the composer voice (adapter path, defensive parser, workflow voice, docs) failed at runtime. `grok-4.5` is now the only grok model, and the ensemble is three voices (Claude lenses + codex + grok-4.5).
- Make grok readiness model-aware: `ready`/`list` now require `grok-4.5` to appear in `grok models`, not just auth — so a dropped or renamed model reads as "not ready" with an actionable hint instead of failing mid-review. The probe runs unjailed like the sibling `codex login status` check (a readiness check passes no untrusted diff, so it needs no sandbox and stays free of the review path's python3 profile-build), bounded by `SWARM_PROBE_TIMEOUT` (10s, `timeout -k` so a SIGTERM-ignoring CLI can't hang it; separate from the review-length `SWARM_TIMEOUT`). It falls back to the auth check — with a warning on stderr, never silently — whenever it can't produce a clean answer (no coreutils `timeout`, non-zero exit, timeout, or an empty/unparseable list), rather than dropping grok from the fan-out.

### 0.4.2 — 2026-07-15
- Fix grok CLI 0.2.101 compat: pin `grok-4.5` (upstream renamed `grok-build`), cap grok effort at `high` (the `max` tier is gone; the adapter maps `xhigh`/`max` → `high` so stale callers degrade instead of erroring).

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ This is a **Claude Code plugin marketplace** (monorepo) containing plugins that
- **knowledge-system** (v1.9.x) — Knowledge management with three layers: Rules, Knowledge, Memory. Skills: `/init`, `/query`, `/curate`, `/reindex`, `/backfill-knowledge`, `/migrate`, `/statusline`
- **work-system** (v1.8.x) — Task and worktree workflow. Skills: `/define`, `/kickoff`, `/adopt`, `/continue`, `/status`, `/close`, `/list`, `/statusline`
- **pr-flow** (v1.3.x) — PR review feedback loop. Skills: `/open`, `/cycle`, `/check`, `/fix`, `/rebase`, `/merge`
- **swarm** (v0.4.x) — Local mixture-of-agents code review (external `codex`/`grok` CLIs — grok-4.5 + composer — plus Claude lenses). P2: `/swarm:review` pipeline (scope→fan-out→merge→verify); P5: `--fix`/`--loop` apply the findings you agreed with. Skills: `/swarm:review`, `/swarm:agents`
- **swarm** (v0.4.x) — Local mixture-of-agents code review (external `codex`/`grok` CLIs — grok-4.5 — plus Claude lenses). P2: `/swarm:review` pipeline (scope→fan-out→merge→verify); P5: `--fix`/`--loop` apply the findings you agreed with. Skills: `/swarm:review`, `/swarm:agents`

## Plugin Anatomy

Expand Down
Loading
Loading