Skip to content

Add swarm plugin scaffold with backend adapter layer (P1) - #21

Merged
gering merged 14 commits into
mainfrom
task/add-swarm-plugin
Jul 3, 2026
Merged

Add swarm plugin scaffold with backend adapter layer (P1)#21
gering merged 14 commits into
mainfrom
task/add-swarm-plugin

Conversation

@gering

@gering gering commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • New swarm plugin (0.1.0): local mixture-of-agents code review — fans out one review across Claude subagents plus the codex and grok CLIs, merges and verifies findings into one ranked report. Complementary to pr-flow's GitHub-side loop.
  • This is phase 1 of 6 (one PR per phase): plugin scaffold + the deterministic backend adapter layer. The review pipeline itself lands in later phases.

Changes

  • plugins/swarm/.claude-plugin/plugin.json + marketplace.json entry (version-synced 0.1.0)
  • plugins/swarm/scripts/agents.sh — uniform adapter over claude/codex/grok with list / available / ready / run verbs; run enforces the shared findings schema (codex exec --output-schema, grok --json-schema) and prints uniform findings-JSON on stdout
  • plugins/swarm/scripts/schema/finding.schema.json — shared findings shape all backends normalize into (failure_scenario required and falsifiable)
  • plugins/swarm/skills/agents/SKILL.md/swarm:agents backend status table
  • READMEs (root + plugin); knowledge entry features/swarm-backend-adapter.md with the verified CLI facts and gotchas

Readiness

  • ✅ README (root + plugin) updated
  • ✅ Version 0.1.0 in sync (plugin.json ↔ marketplace.json)
  • ✅ Knowledge entry added
  • ✅ check-structure.py: 0 errors
  • ➖ Changelog / lint / build: N/A (declarative repo)

Test plan

  • agents.sh list / list --json / available / ready return correct status + exit codes for all three backends
  • E2E: run codex and run grok against a planted off-by-one diff — both returned schema-valid findings JSON, exit 0
  • Error paths: unknown backend → exit 2; run claude → exit 2 with hint
  • /swarm:agents renders the status table in a live session

🤖 Generated with Claude Code

https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA

gering added a commit that referenced this pull request Jul 3, 2026
Four robustness fixes surfaced by a codex+grok self-review of PR #21:
- available_version: `|| true` + explicit `return 0` so a non-zero
  --version exit or SIGPIPE from head() under pipefail can't flip an
  installed backend to "unavailable"
- run_codex: pass --model via an array, not unquoted ${model:+…}, so a
  model name with whitespace stays one argv word (matches run_grok)
- run_grok parser: reject non-object JSON before .get() (no traceback),
  and include a snippet in the invalid-JSON message for triage

E2E re-verified: codex + grok both return schema-valid JSON (exit 0) on
a hyphen-leading prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
gering added a commit that referenced this pull request Jul 3, 2026
Second ensemble pass (Claude lenses + codex + grok) on PR #21 surfaced:
- available_version claude branch: capture version separately so a SIGPIPE
  from head() under pipefail can't print both the real version AND the
  "in-session" fallback (regression from the prior hardening pass)
- list without --json: route through column_or_cat so a host lacking
  util-linux `column` degrades to raw TSV instead of dying (exit 127)
- CLAUDE.md: register the swarm plugin in "Current Plugins"
- knowledge entry: add updatedAt/updatedFrom to match sibling files

(A codex claim that --skip-git-repo-check needs a bypass flag was REFUTED
by the verify pass — tested, runs fine outside a repo — so not applied.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
gering and others added 14 commits July 3, 2026 23:05
- swarm 0.1.0: local mixture-of-agents code review (marketplace entry + plugin.json)
- scripts/agents.sh: uniform adapter over claude/codex/grok with
  list/available/ready/run verbs; run enforces the shared findings
  schema (codex exec --output-schema, grok --json-schema) and prints
  uniform JSON on stdout
- scripts/schema/finding.schema.json: shared findings shape all
  backends normalize into (failure_scenario required, falsifiable)
- /swarm:agents skill: backend status table
- E2E-verified against codex 0.128 and grok 0.2.77; fixes found in
  testing: close codex stdin (hangs on inherited non-TTY stdin) and
  keep the mktemp path global so the EXIT trap survives set -u

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
grok-composer-2.5-fast rejects --effort and does not enforce
--json-schema (structuredOutput stays null), so grok-build is the only
schema-capable grok voice; a second same-family voice would also dilute
the consensus signal. Prefer grok's native --best-of-n at high effort.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
Correctness (silent ensemble drops):
- Pass prompts hyphen-safe: `--` before codex positional, --single= for
  grok (leading "-" no longer parsed as a flag; E2E-verified on both)
- claude availability is in-session by definition — no PATH gate
- Usage-class errors in `run` now exit 2, not 1 (missing flag value,
  missing prompt file); 256 KiB prompt guard with a clear message
  instead of an opaque ARG_MAX exec failure

Robustness/diagnostics:
- Placeholder `-` for empty fields (BSD column collapses adjacent tabs)
  and tab-sanitized version strings in list output
- list rejects unknown flags (exit 2) instead of silent table fallback
- --help exits 0; usage block bounded by pattern, not line numbers
- python3 preflight; grok/codex JSON parsing catches decode errors
  cleanly instead of tracebacks / masked "invalid JSON"
- --effort only sent to grok-build (other models reject it)

Docs/cleanup:
- /swarm:agents no longer references the unshipped /swarm:review
- plugin description marks phase 1 honestly (plugin.json + marketplace)
- Shared require_usable gate (was duplicated in ready/run); dead
  claude ready_hint removed; knowledge entry pluginVersion corrected

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
Four robustness fixes surfaced by a codex+grok self-review of PR #21:
- available_version: `|| true` + explicit `return 0` so a non-zero
  --version exit or SIGPIPE from head() under pipefail can't flip an
  installed backend to "unavailable"
- run_codex: pass --model via an array, not unquoted ${model:+…}, so a
  model name with whitespace stays one argv word (matches run_grok)
- run_grok parser: reject non-object JSON before .get() (no traceback),
  and include a snippet in the invalid-JSON message for triage

E2E re-verified: codex + grok both return schema-valid JSON (exit 0) on
a hyphen-leading prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
Second ensemble pass (Claude lenses + codex + grok) on PR #21 surfaced:
- available_version claude branch: capture version separately so a SIGPIPE
  from head() under pipefail can't print both the real version AND the
  "in-session" fallback (regression from the prior hardening pass)
- list without --json: route through column_or_cat so a host lacking
  util-linux `column` degrades to raw TSV instead of dying (exit 127)
- CLAUDE.md: register the swarm plugin in "Current Plugins"
- knowledge entry: add updatedAt/updatedFrom to match sibling files

(A codex claim that --skip-git-repo-check needs a bypass flag was REFUTED
by the verify pass — tested, runs fine outside a repo — so not applied.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
Capture the validated review-pipeline shape (scope+gate → fan-out →
(file,mechanism) merge → verify solos → synthesis) as a reference for
P2, with the erprobte workflow script and the dry-run learnings so the
build doesn't start from scratch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
Adapter (agents.sh):
- ARG_MAX guard now counts BYTES not chars (a multibyte prompt slipped
  the ${#prompt} char check yet overflowed exec), and checks file size
  BEFORE slurping it into a variable
- external codex/grok calls wrapped in a wall-clock timeout (coreutils
  timeout/gtimeout when present, passthrough otherwise; SWARM_TIMEOUT)
- run_grok preflight-rejects non-default models (only grok-build enforces
  --json-schema; others would fail late with structuredOutput:null)

Docs:
- README: cross-agent agreement is a strong signal WHEN it occurs, not
  "primary" — solo + 3-state verify is the common path (dry-run showed
  consensus is the exception)
- blueprint: drop the dead link to the uncommitted task file

Found by the ensemble reviewing its own PR; 5 REFUTED findings dropped
by the verify pass, the CRITICAL prompt-injection tracked as P2 design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
The ensemble re-reviewed the fixed diff at xhigh and found regressions in
the timeout hardening plus deeper issues:
- validate SWARM_TIMEOUT up front (a malformed value made `timeout` exit
  125, which the rc==124 checks missed → every external run misreported
  as a backend failure)
- warn once when no timeout/gtimeout is on PATH (the cap silently never
  applied on stock macOS)
- run_codex/run_grok now validate the findings SHAPE, not just JSON
  syntax, before emitting (a valid-but-wrong object would crash the merge)
- run_grok: --disable-web-search as a defensive partial sandbox (the diff
  is untrusted; close the obvious network exfil channel). Full read-deny
  sandbox parity with codex is P2.
- SKILL.md: grok Ready is a heuristic (auth file present ≠ valid token) —
  say so instead of overclaiming "authenticated"
- finding.schema.json: line has minimum 0 (no negative lines)

Security note: during this round an agentic backend actually read a real
credentials file via the injection path — tracked as the P2 critical;
round-2 prompts use fictional secret paths + an explicit no-exploit rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
The prompt is passed as ONE argv word, so the binding cap is the
per-argument limit (Linux MAX_ARG_STRLEN = 128 KiB), not total ARG_MAX.
The 256 KiB guard let a ~200 KiB prompt pass yet fail to exec on Linux.
Cap at 120 KiB with headroom for the schema arg + environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
The loop's real credential exfiltration + converged security findings
drove adapter-level defenses (the parts fixable in P1):
- grok now runs --tools "" (verified to block file reads; the diff is
  inlined so grok needs no tools) on top of --disable-web-search
- scrub_secrets redacts secret-shaped content (AWS/GH/sk tokens, private
  keys, secret=… ) from findings JSON before it leaves run_codex/run_grok
  — a backstop even if a backend sandbox is bypassed
- finding.schema.json caps summary/failure_scenario/recommendation length
  so a payload can't route a large blob through a field
- blueprint gains a mandatory Security section (fencing, merge/verify
  scrub, consensus-verify, deterministic transport, OS jail = P2)

Remaining injection hardening is pipeline-level (merge/verify stages,
prompt fencing) and lands in P2; tracked in the task file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
R3 showed the whack-a-mole: closing grok's file access moved the critical
to codex (`-s read-only` still permits reads). The fix is one OS jail
around EVERY external call, not per-backend flags:
- sandboxed(): sandbox-exec (macOS) / bwrap (Linux) deny-read of common
  secret stores, realpath-resolved (defeats ~/.ssh→dotfiles, /tmp, /etc
  symlinks) as both subpath (dirs) and literal (files)
- VERIFIED: codex explicitly told to read a jailed file returns BLOCKED;
  normal reviews still work; secret never reaches the output
- codex reasoning transcript (stderr) now discarded (could echo a read
  secret, never scrubbed)
- schema gains maxItems:100 (per-field maxLength alone allowed volume
  exfil across many entries)
- stdin guard (TTY → clear error, not a hang); knowledge frontmatter
  field order; CLAUDE.md /swarm:agents notation
- blueprint: consensus gets a light verify not a blind skip; wrapper must
  distinguish error from empty

Fixed a set -e footgun found while wiring this up: _sandbox_deny_paths
ended in `[[ … ]] && …` returning 1 when SWARM_DENY_PATHS was unset,
aborting the profile build (every default run silently produced nothing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
R4 flagged the OS jail's own gaps. Fixes:
- per-backend deny: a backend keeps its OWN cred dir readable but the
  SIBLINGS' are denied (verified: codex can't read ~/.grok/auth.json) —
  closes cross-backend credential theft; also deny ~/.config/anthropic,
  ~/.config/openai, ~/.claude.json
- env filter: strip secret-shaped vars (AWS_*, *_TOKEN, *_API_KEY, …)
  before the call — the jail blocks file reads but backends inherit the
  environment, so a secret in AWS_SECRET_ACCESS_KEY bypassed it
- grok invalid-JSON error path no longer echoes raw bytes to stderr
  (never passed scrub_secrets; could carry injected/secret content)

Honest limit documented in the blueprint: a full deny-$HOME allowlist
jail is impractical for the node/bun CLIs (their loaders read all over
$HOME). The denylist is a backstop; the real defense is that backends
need no file reads (diff is inlined) and grok is tool-less. A minimal-
runtime container is the P2 upgrade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
- README output-shape snippet was invalid JSON without the top-level
  findings array — replaced with a valid, complete example
- bwrap jail: --tmpfs dies with ENOTDIR on regular-file deny paths
  (~/.netrc etc.); bind /dev/null read-only over files, --tmpfs only
  over directories

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nWqHzv35iMe4mXrLGWKyA
@gering
gering force-pushed the task/add-swarm-plugin branch from a1c2c38 to 923fa33 Compare July 3, 2026 21:05
@gering
gering merged commit bdb4fc5 into main Jul 3, 2026
1 check passed
@gering
gering deleted the task/add-swarm-plugin branch July 3, 2026 21:10
gering added a commit that referenced this pull request Jul 16, 2026
Second dogfooding round at the deepest profile (/swarm:review --max --fix:
per-lens split live — gate pruned style, 10 xhigh finders + codex
gpt-5.6-sol@xhigh + grok-4.5 + composer; 24 findings, 10 consensus).
Fixes the agreed findings:

- Never auto-accept an all-untagged consensus cluster: no tagged lens
  backs it, so it is verified like a solo. Verify/auto-accept now derive
  from ONE needsVerify predicate (structural exactly-once partition);
  kind vote in clearer every-form (#1, consensus critical; #21, #22)
- Design verifier sees the finding's recommendation — the proposal the
  applicability rubric actually tests — and carries an escape hatch:
  a genuine defect mis-filed under a design lens is not refuted away
  (#12, #3); "solo" dropped from verifier prompts (#4)
- Untagged findings from multi-lens cluster finders fall back to
  'unspecified' (safe defect bucket), not lenses[0] (#7); merge-agent
  free-text lens validated, majority-member fallback (#9)
- Improvement invitation scoped to design finder units — defect-lens
  finders stay defect-only (#8); merge prompt clusters by issue, not
  only defect (#13); schema descriptions generalized for design
  findings (#14)
- LENS_BRIEF startup assertion (#6); gate prompt interpolates
  LENS_CLUSTERS.design (#19); new test_lens_sync.py guards all lens
  mirrors: SKILL HDR prompt, LENS_BRIEF, pr-post DESIGN_LENSES (#5)
- Workflow assigns stable finding num (defects first, shared sequence);
  presenter/pr-post render it verbatim (#20)
- pr-post.py: design lens is the backup kind signal when the handoff
  drops kind (explicit defect still wins); single-pass partition (#17, #24)
- Balance: REFUTED is its own segment (refuted ⊄ solo since design
  consensus can be refuted) (#2); LOCKED design-table column precedence
  in --loop rounds clarified (#11); "verifies solos" doc sweep across
  manifests/README/knowledge (#4); cluster failure-isolation trade-off
  documented (#10); knowledge index line trimmed (#15)

Declined: #16 (JS test harness for sandbox code; lens-sync test covers
the drift class), #18 (gate-fail under --max runs all lenses — the
documented never-silently-narrower degrade), #23 (micro-opt vs readability).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126cGxsBYxqgEFH4AcpriNk
gering added a commit that referenced this pull request Jul 17, 2026
Second dogfooding round at the deepest profile (/swarm:review --max --fix:
per-lens split live — gate pruned style, 10 xhigh finders + codex
gpt-5.6-sol@xhigh + grok-4.5 + composer; 24 findings, 10 consensus).
Fixes the agreed findings:

- Never auto-accept an all-untagged consensus cluster: no tagged lens
  backs it, so it is verified like a solo. Verify/auto-accept now derive
  from ONE needsVerify predicate (structural exactly-once partition);
  kind vote in clearer every-form (#1, consensus critical; #21, #22)
- Design verifier sees the finding's recommendation — the proposal the
  applicability rubric actually tests — and carries an escape hatch:
  a genuine defect mis-filed under a design lens is not refuted away
  (#12, #3); "solo" dropped from verifier prompts (#4)
- Untagged findings from multi-lens cluster finders fall back to
  'unspecified' (safe defect bucket), not lenses[0] (#7); merge-agent
  free-text lens validated, majority-member fallback (#9)
- Improvement invitation scoped to design finder units — defect-lens
  finders stay defect-only (#8); merge prompt clusters by issue, not
  only defect (#13); schema descriptions generalized for design
  findings (#14)
- LENS_BRIEF startup assertion (#6); gate prompt interpolates
  LENS_CLUSTERS.design (#19); new test_lens_sync.py guards all lens
  mirrors: SKILL HDR prompt, LENS_BRIEF, pr-post DESIGN_LENSES (#5)
- Workflow assigns stable finding num (defects first, shared sequence);
  presenter/pr-post render it verbatim (#20)
- pr-post.py: design lens is the backup kind signal when the handoff
  drops kind (explicit defect still wins); single-pass partition (#17, #24)
- Balance: REFUTED is its own segment (refuted ⊄ solo since design
  consensus can be refuted) (#2); LOCKED design-table column precedence
  in --loop rounds clarified (#11); "verifies solos" doc sweep across
  manifests/README/knowledge (#4); cluster failure-isolation trade-off
  documented (#10); knowledge index line trimmed (#15)

Declined: #16 (JS test harness for sandbox code; lens-sync test covers
the drift class), #18 (gate-fail under --max runs all lenses — the
documented never-silently-narrower degrade), #23 (micro-opt vs readability).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126cGxsBYxqgEFH4AcpriNk
gering added a commit that referenced this pull request Jul 17, 2026
Second dogfooding round at the deepest profile (/swarm:review --max --fix:
per-lens split live — gate pruned style, 10 xhigh finders + codex
gpt-5.6-sol@xhigh + grok-4.5 + composer; 24 findings, 10 consensus).
Fixes the agreed findings:

- Never auto-accept an all-untagged consensus cluster: no tagged lens
  backs it, so it is verified like a solo. Verify/auto-accept now derive
  from ONE needsVerify predicate (structural exactly-once partition);
  kind vote in clearer every-form (#1, consensus critical; #21, #22)
- Design verifier sees the finding's recommendation — the proposal the
  applicability rubric actually tests — and carries an escape hatch:
  a genuine defect mis-filed under a design lens is not refuted away
  (#12, #3); "solo" dropped from verifier prompts (#4)
- Untagged findings from multi-lens cluster finders fall back to
  'unspecified' (safe defect bucket), not lenses[0] (#7); merge-agent
  free-text lens validated, majority-member fallback (#9)
- Improvement invitation scoped to design finder units — defect-lens
  finders stay defect-only (#8); merge prompt clusters by issue, not
  only defect (#13); schema descriptions generalized for design
  findings (#14)
- LENS_BRIEF startup assertion (#6); gate prompt interpolates
  LENS_CLUSTERS.design (#19); new test_lens_sync.py guards all lens
  mirrors: SKILL HDR prompt, LENS_BRIEF, pr-post DESIGN_LENSES (#5)
- Workflow assigns stable finding num (defects first, shared sequence);
  presenter/pr-post render it verbatim (#20)
- pr-post.py: design lens is the backup kind signal when the handoff
  drops kind (explicit defect still wins); single-pass partition (#17, #24)
- Balance: REFUTED is its own segment (refuted ⊄ solo since design
  consensus can be refuted) (#2); LOCKED design-table column precedence
  in --loop rounds clarified (#11); "verifies solos" doc sweep across
  manifests/README/knowledge (#4); cluster failure-isolation trade-off
  documented (#10); knowledge index line trimmed (#15)

Declined: #16 (JS test harness for sandbox code; lens-sync test covers
the drift class), #18 (gate-fail under --max runs all lenses — the
documented never-silently-narrower degrade), #23 (micro-opt vs readability).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126cGxsBYxqgEFH4AcpriNk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant