Skip to content

feat(swarm): one task, a herd of agents, one answer (PRD 0015) - #502

Merged
ralyodio merged 3 commits into
mainfrom
feat/0015-swarm
Sep 13, 2026
Merged

ralyodio merged 3 commits into
mainfrom
feat/0015-swarm

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

What

What Claude Code calls ultracode, on any engine moshcode can start. The herd already had every part (sessions that outlive the terminal, herd prompt --wait, a task ledger holding each session's output), and moshscript could wire a fan-out by hand. moshcode swarm "<task>" is the verb that composes them:

phase what happens
plan one headless call splits the task into at most --agents pieces that do not touch the same files (JSON in, JSON out; a plan that does not parse runs the task as one piece)
fan out one herd session per piece, --agents at a time (default 4, the same cap the claude engine's defaults put on Claude's own workflows), prompted and waited on so every piece is a ledger task
verify --verify: one skeptic per piece, prompted to refute; its verdict goes to the synthesis and never drops a piece by itself
synthesise one more call folds the outputs into the answer the operator reads

Sessions are ended when it is done unless --keep. --plan-only shows the split and starts nothing; --json is the whole run as data. Works from the CLI, the pit (/swarm) and moshscript (swarm(...)). PRD 0015, README section and command table, help page.

Herd fixes it needed (second commit)

Running it live against Claude Code 2.1.270 turned up four things no unit test could see:

  • Pane title overwritten. The pane index is keyed by title and Claude sets its terminal title the moment it is up, so a member read gone while waiting for a prompt. tmuxStartPlan pins it with allow-set-title off (tmux 3.4+, version-guarded).
  • 80-column panes truncate the footer the idle rule reads. Sessions start 200x50.
  • Idle footer changed in 2.1 with permissions bypassed ("bypass permissions on (shift+tab to cycle)", placeholder ❯ Try "…"). Both are idle now. The trust dialog is blocked, and the claude engine's new boot spec answers it with Down, Enter, because its default is "No, exit".
  • Enter swallowed by paste. A long prompt from send-keys is a paste, and the Enter behind it landed inside it as "+1 lines". sendPrompt settles 250ms before Enter.

In the swarm: members start with the engine's agentArgs, not --agent (for Claude that opens the agents overview, where a typed prompt starts a background job elsewhere); the prompt wait insists on seeing the engine work before accepting idle; the headless calls are told to think, not act.

Verified

  • node --test: 2967 tests, 0 failures (340 environment-gated skips). New: test/swarm.test.mjs (32), test/herd-pinned-title.test.mjs, test/herd-send-prompt.test.mjs.
  • Live, two agents on a scratch task: both ledger tasks carry the agents' SUMMARY sections, the synthesis reads them, both files exist, sessions ended, exit 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_014HMV1QDcVY1xiaWWrZQM4S

ralyodio and others added 3 commits September 13, 2026 05:16
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014HMV1QDcVY1xiaWWrZQM4S
What Claude Code calls ultracode, on any engine moshcode can start. The
herd already had every part — sessions that outlive the terminal, `herd
prompt --wait`, a task ledger holding each session's output — and moshscript
could wire a fan-out by hand. `moshcode swarm "<task>"` is the verb that
composes them:

  plan        one headless call splits the task into at most --agents
              pieces that do not touch the same files (JSON in, JSON out;
              a plan that does not parse runs the task as one piece)
  fan out     one herd session per piece, --agents at a time (default 4,
              the same cap the claude engine's defaults put on Claude's own
              workflows), prompted and waited on as `herd prompt --wait` is,
              so every piece is a task in the ledger
  verify      --verify: one skeptic per piece, prompted to refute; its
              verdict goes to the synthesis, never drops a piece by itself
  synthesise  one more call folds the outputs into the answer

Sessions are ended when it is done unless --keep. --plan-only shows the
split and starts nothing; --json is the whole run as data. A blocked engine
at boot (a trust dialog on a new directory) gets Enter once, then the
prompt; anything still blocked is reported, not answered.

Everything that touches an engine or a pty goes through an injectable deps
object, so test/swarm.test.mjs runs the four phases against fakes. Works
from the CLI, the pit (/swarm) and moshscript (swarm(...)). README section
and command table, help page, PRD 0015 and index.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014HMV1QDcVY1xiaWWrZQM4S
… prompts land

Four things a live `moshcode swarm` against Claude Code 2.1.270 turned up,
none of them visible to a unit test because they are all about what the real
engine paints:

- The pane title is the herd's handle, and Claude overwrites it through OSC
  the moment it is up ("1 awaiting input · claude agents"). The member fell
  out of the pane index and read `gone` while it sat waiting for a prompt.
  tmuxStartPlan now pins it with `allow-set-title off` (tmux 3.4+, guarded
  by tmuxCanPinTitle so an older tmux keeps today's plan).
- A detached session is 80x24 and truncates the status line the idle rule
  reads ("? for shortc…"). Sessions start 200x50; a client that attaches
  resizes as before.
- Claude 2.1 with permissions bypassed no longer prints "? for shortcuts";
  its footer is "bypass permissions on (shift+tab to cycle)" and an empty
  composer shows '❯ Try "…"'. Both are idle now, checked after the shared
  working rules. The workspace trust dialog is `blocked`, and the claude
  engine carries a `boot` spec that answers it with Down, Enter — its default
  is "No, exit", so the reflex Enter ends the engine.
- A long prompt typed by send-keys is a paste to the composer, and the Enter
  on its heels landed inside it as "+1 lines"; nothing was ever submitted.
  sendPrompt settles for a quarter second between the text and Enter.

In the swarm itself: members start with the engine's `agentArgs` rather than
`--agent`, because for Claude `--agent` opens the agents overview, where a
typed prompt starts a background job somewhere else instead of working here;
the prompt wait will not take an idle screen as finished until it has seen
the engine work; and the headless plan/verify/synthesis calls are told to
think, not act — a synthesis run in the working directory did the task itself.

Verified live: two agents, two files, both ledger tasks carry the agents'
SUMMARY sections, the synthesis reads them, sessions ended, exit 0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014HMV1QDcVY1xiaWWrZQM4S
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

4 finding(s) in the 13 file(s) this pull request changes.

MEDIUM: 4

Severity Rule Location
MEDIUM sql-string-concatenation src/cli-schema.mjs:189
MEDIUM sql-string-concatenation src/cli-schema.mjs:561
MEDIUM sql-string-concatenation src/cli-schema.mjs:594
MEDIUM sql-string-concatenation src/cli-schema.mjs:701
89 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 8 | **MEDIUM**: 71 | **LOW**: 10

Not introduced by this pull request. The full set is in the Security tab.

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:86
HIGH sh-remote-script-execution install.sh:90
HIGH sh-remote-script-execution install.sh:202
HIGH sh-remote-script-execution install.sh:213
HIGH sh-remote-script-execution install.sh:219
HIGH tls-verification-disabled src/dns.mjs:766
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:139
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:153
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:179
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:373
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:377
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:422
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:671
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:867
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:869
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:928
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:974
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1044

…and 69 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 87e6cdc into main Sep 13, 2026
4 of 6 checks passed
@ralyodio
ralyodio deleted the feat/0015-swarm branch September 13, 2026 06:07
ralyodio added a commit that referenced this pull request Sep 13, 2026
…ts a retry (#503)

CI on ubuntu-latest (tmux 3.4) answered `invalid option: allow-set-title`
to every session start after #502, so no herd member could start there and
the tmux-backed tests went red. The option arrived in 3.5, not 3.4.

tmuxCanPinTitle now requires 3.5. And because a version string is a guess
about a binary, startSession also learns the answer the hard way: when tmux
rejects the pin it kills the half-made session (tmux stops at the command it
rejects, after the session exists), remembers "no" for the rest of the
process, and starts the member with the plan an older tmux understands. A
wrong guess costs one retry, never a member.

Verified: the full suite locally (tmux 3.6); the survival, tile and pinned-
title suites in a node:22 container with the fake-runner fallback; and, as
a baseline, main's own herd.mjs fails the same three tmux tests on Debian's
3.3a, so that older crash is pre-existing and separate.


Claude-Session: https://claude.ai/code/session_014HMV1QDcVY1xiaWWrZQM4S

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@ralyodio ralyodio mentioned this pull request Sep 13, 2026
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