agentainer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,603 @@
1
+ # Agentainer
2
+
3
+ Run a team of coding agents — **Claude Code, Codex, Gemini CLI, Hermes** — side by
4
+ side in tmux, each in its own directory, each able to message the others only if
5
+ your YAML file says it may.
6
+
7
+ > Formerly **AgentSwarm**. Installed globally, the command is `agentainer`; the
8
+ > repo's `./swarm.sh` remains a drop-in equivalent for local development.
9
+
10
+ ```
11
+ agents.yaml tmux
12
+ ┌────────────┐ ┌──────────────────┐
13
+ │ orchestr. │───────▶│ session: orchestr│──┐
14
+ │ researcher │ │ session: research│ │ swarm send --to developer "..."
15
+ │ developer │ │ session: develop │◀─┘
16
+ │ reviewer │ │ session: reviewer│
17
+ └────────────┘ └──────────────────┘
18
+ │ │
19
+ │ workspace/<agent>/ │ hooks capture each finished turn
20
+ ▼ ▼
21
+ one folder per agent messages routed + logged
22
+ ```
23
+
24
+ One command starts the swarm: it creates a folder per agent, installs a
25
+ completion hook inside each folder, opens a tmux session per agent, launches
26
+ the agent's CLI, and types each agent's first prompt into it.
27
+
28
+ ---
29
+
30
+ ## Requirements
31
+
32
+ - `tmux` (3.0+)
33
+ - `python3` — PyYAML is used if present, otherwise a bundled parser handles the config
34
+ - `node` (16+) — only for the global `agentainer` command; not needed if you run `./swarm.sh` from a clone
35
+ - whichever agent CLIs you reference: `claude`, `codex`, `gemini`, `hermes` — install only the one(s) you actually use
36
+
37
+ ## Install
38
+
39
+ Global, via npm:
40
+
41
+ ```bash
42
+ npm install -g agentainer
43
+ agentainer doctor # check tmux/python3 are present; report which agent CLIs it found
44
+ ```
45
+
46
+ `agentainer doctor` verifies the required tools (`tmux`, `python3`) and reports
47
+ which agent CLIs are available — it never fails on a missing *agent* CLI, since
48
+ you may only use one of them.
49
+
50
+ Or from a clone (no npm needed):
51
+
52
+ ```bash
53
+ git clone https://github.com/mehmetcanfarsak/AgentSwarm.git && cd AgentSwarm
54
+ ./swarm.sh --help # same commands as `agentainer`, straight from the repo
55
+ ```
56
+
57
+ ## Quickstart
58
+
59
+ ```bash
60
+ cp agents.example.yaml agents.yaml
61
+
62
+ agentainer validate # parse the config, print the resolved swarm, launch nothing
63
+ agentainer up # create dirs, install hooks, start tmux, send first prompts
64
+ agentainer status # who is running
65
+ agentainer attach developer
66
+ agentainer down # stop everything
67
+ ```
68
+
69
+ Give the swarm its actual work:
70
+
71
+ ```bash
72
+ ./swarm.sh send --to orchestrator "Build a CLI that converts CSV to Parquet."
73
+ ```
74
+
75
+ Watch the traffic between agents:
76
+
77
+ ```bash
78
+ ./swarm.sh logs -f # whole swarm, live
79
+ ./swarm.sh logs reviewer -n 20 # one agent
80
+ ./swarm.sh inbox developer # messages an agent received
81
+ ```
82
+
83
+ ---
84
+
85
+ ## How it works
86
+
87
+ **One folder + one tmux session per agent.** Agent `developer` gets
88
+ `workspace/developer/` and a tmux session named `developer` (plus any
89
+ `session_prefix`). The agent's CLI is launched inside that folder, so its file
90
+ operations are naturally scoped to it.
91
+
92
+ ### Working directories
93
+
94
+ By default every agent gets a fresh folder under `root`, created for you. You can
95
+ override that per agent, or for the whole swarm via `defaults`:
96
+
97
+ ```yaml
98
+ swarm:
99
+ root: ./workspace
100
+ create_workdirs: true # auto-create missing folders (default)
101
+
102
+ agents:
103
+ - name: developer # -> ./workspace/developer (created)
104
+
105
+ - name: reviewer
106
+ workdir: ~/projects/acme-api # -> an existing checkout
107
+ create_workdir: false # ...and fail loudly if it is not there
108
+
109
+ - name: scribe
110
+ workdir: "{root}/{name}-notes" # {name} {root} {swarm} {type}
111
+ ```
112
+
113
+ - **`workdir`** may be absolute, relative to the config file, or use `~`.
114
+ - **`create_workdir: false`** turns a missing folder into an error instead of a
115
+ new empty directory — the right setting when you are pointing agents at real
116
+ repositories, where a typo should not silently create `~/projcets/acme-api`.
117
+ - **`defaults.workdir`** applies to every agent that does not override it. With
118
+ a `{name}` placeholder it lays out a folder each; without one, every agent
119
+ shares a single directory.
120
+ - **Sharing is allowed and sometimes the point** (a driver and a navigator in one
121
+ checkout), but agents then overwrite each other's files and interleave commits,
122
+ so `validate` and `up` warn when it happens. See
123
+ [`examples/existing-repo.yaml`](examples/existing-repo.yaml).
124
+
125
+ `root` is still used even when every agent lives elsewhere: it holds `.swarm/`
126
+ with the logs, inboxes and the `swarm` shim.
127
+
128
+ **Prompts are typed in, not piped.** `swarm up` drops each first prompt into the
129
+ agent's input box with a tmux bracketed paste, as one block, then presses Enter.
130
+ That is why multi-line prompts survive intact instead of being submitted line by
131
+ line.
132
+
133
+ Getting that to work reliably against a live TUI took more than a sleep:
134
+
135
+ - **Claude Code silently discards keystrokes for several seconds partway through
136
+ startup.** Measured on v2.1.205: input at t=2s landed, t=6s and t=12s vanished,
137
+ t=20s landed. A fixed `boot_delay_ms` is therefore a coin flip. Before typing,
138
+ AgentSwarm types a throwaway token and waits for the input box to echo it back,
139
+ then erases it (`ready_probe`). Enter is never sent, so nothing is submitted.
140
+ - **Readiness is not monotonic**, so after pasting, AgentSwarm checks that the
141
+ text actually appeared on screen before pressing Enter, and retries if it did
142
+ not. If delivery cannot be confirmed it refuses to press Enter, rather than
143
+ submitting a half-delivered prompt.
144
+ - **Both CLIs open a "do you trust this folder?" modal** on first run in a new
145
+ directory, which would eat the first prompt (Enter answers the dialog). Claude
146
+ does this even under `--dangerously-skip-permissions`. AgentSwarm pre-trusts each
147
+ agent's workdir: for codex in its generated `config.toml`, for claude by adding
148
+ `hasTrustDialogAccepted` for that path in `~/.claude.json`.
149
+ - **Both collapse a long paste into a chip** rather than showing the text —
150
+ `[Pasted text #1 +36 lines]` for claude, `[Pasted Content 2580 chars]` for codex.
151
+ Delivery verification recognises both.
152
+
153
+ **Agents talk in tagged messages.** A message arrives inside an envelope, so the
154
+ agent always knows where it starts, where it ends, and who sent it:
155
+
156
+ ```
157
+ <swarm-message from="lead" to="reviewer" id="m-eb4105" reply-to="m-3f9a1c">
158
+ Review finding for ./parse: tokenize() mishandles a trailing backslash.
159
+
160
+ printf 'a\' | ./parse
161
+ </swarm-message>
162
+ ```
163
+
164
+ To send one, the agent simply **writes a block in its reply**. The capture hook
165
+ reads it when the turn ends and delivers it — no shell, no quoting, so multi-line
166
+ bodies, code blocks and backslashes survive intact:
167
+
168
+ ```
169
+ <swarm-send to="reviewer" reply-to="m-3f9a1c">
170
+ Please review src/parse.py.
171
+
172
+ printf 'a\' | ./parse
173
+ </swarm-send>
174
+ ```
175
+
176
+ `<swarm-broadcast>` (no `to`) reaches everyone the sender may talk to. The `id`
177
+ and `reply-to` attributes let agents thread a conversation instead of guessing
178
+ which question an answer belongs to. Set `message_format: plain` to go back to the
179
+ old `[swarm] message from <sender>:` header, and `parse_outbound_tags: false` to
180
+ stop reading tags out of replies.
181
+
182
+ Agents can also send from their shell, which is useful mid-task rather than at the
183
+ end of a turn (but then they must quote the text themselves):
184
+
185
+ ```bash
186
+ swarm send --to reviewer "I finished the parser, please review src/parse.py"
187
+ swarm broadcast "heads up: I renamed the config module"
188
+ ```
189
+
190
+ Either way the message is permission-checked against `can_talk_to`, archived under
191
+ `.swarm/inbox/<recipient>/`, and written to the event log.
192
+
193
+ The raw tmux equivalent also works, and bypasses permissions and logging:
194
+
195
+ ```bash
196
+ tmux send-keys -t reviewer -l "your message" && tmux send-keys -t reviewer Enter
197
+ ```
198
+
199
+ **Agents get reminded when their answer goes nowhere.** A model that was asked a
200
+ question will often just *write the answer as prose* and end its turn — and that
201
+ prose reaches nobody, because only a `<swarm-send>` block is delivered. So when an
202
+ agent owes a reply and finishes a turn without sending one, AgentSwarm messages it:
203
+
204
+ ```
205
+ Your last turn sent no message to anyone, and lead is waiting on your answer to
206
+ message m-c73724.
207
+ ...
208
+ <swarm-send to="lead" reply-to="m-c73724">
209
+ your answer here, over as many lines as you need
210
+ </swarm-send>
211
+ ```
212
+
213
+ If instead the agent *tried* to send but the block was malformed, it gets the
214
+ specific diagnosis — unclosed tag, missing `to`, unknown recipient, permission
215
+ denied — so it can correct itself rather than lose the message silently.
216
+
217
+ It is reminded at most `max_reply_reminders` times (default **1**), then AgentSwarm
218
+ gives up and stops nagging. An agent that auto-forwards via `forward_responses_to`
219
+ is never reminded, since its words did reach someone. Turn it off per agent with
220
+ `reply_reminder: false`.
221
+
222
+ **Conversations survive a restart.** Each time an agent finishes a turn, its
223
+ conversation id is written to `<root>/.swarm/sessions.yaml`:
224
+
225
+ ```yaml
226
+ agents:
227
+ lead:
228
+ session_id: "0c2e47e2-5110-4e69-ae45-69d8492d2084"
229
+ type: "claude"
230
+ transcript: "/root/.claude/projects/.../0c2e47e2-....jsonl"
231
+ updated_at: "2026-07-09T21:00:41+00:00"
232
+ ```
233
+
234
+ If the machine dies, `swarm up --resume` reattaches every agent to its own
235
+ conversation instead of starting a fresh one — it does not re-send the first
236
+ prompt, and it keeps any mail still queued for that agent:
237
+
238
+ ```bash
239
+ ./swarm.sh sessions # what is recorded, and the command that would resume it
240
+ ./swarm.sh up --resume # reattach; agents without a recorded id start fresh
241
+ ```
242
+
243
+ Claude is resumed with `--resume <id>`, codex with `resume <id>`. Set
244
+ `swarm.resume: true` to make it the default, and `--no-resume` to override.
245
+ Gemini and hermes have no recoverable session id (their turns are scraped from
246
+ the terminal), so they always start a fresh conversation, with a warning.
247
+
248
+ If your command runs the CLI through an alias or wrapper, flags cannot simply be
249
+ appended — give the full recipe:
250
+
251
+ ```yaml
252
+ - name: lead
253
+ command: "bash -ic chy3"
254
+ resume_command: "bash -ic 'chy3 --resume {session_id}'"
255
+ ```
256
+
257
+ **Permissions are a whitelist.** An agent may only message the agents in its
258
+ `can_talk_to` list. Anything else is refused with an explanatory error that the
259
+ agent sees on its own terminal. Use `can_talk_to: "*"` for "everyone else".
260
+
261
+ ---
262
+
263
+ ## Capturing what an agent says
264
+
265
+ AgentSwarm needs to know when an agent finishes a turn — both to log it and to
266
+ support auto-forwarding. How it finds out depends on the CLI, and the two
267
+ mechanisms are **not** equally good:
268
+
269
+ | `capture` | Used by | Mechanism | Reliability |
270
+ |---|---|---|---|
271
+ | `hook` | `claude`, `codex` | The CLI runs a program when a turn completes | Exact — the model's final message |
272
+ | `pane` | `gemini`, `hermes` | Poll the tmux pane, diff it once it stops changing | Heuristic — sees rendered text |
273
+ | `none` | any | Nothing is captured | — |
274
+
275
+ - **claude** → a `Stop` hook is written into `<agent-dir>/.claude/settings.json`,
276
+ with **no `matcher` key** (`Stop` is not a tool event, and supplying one stops the
277
+ interactive TUI from ever running the hook). It reads the session transcript.
278
+ Claude fires the hook *before* flushing the assistant message to that transcript,
279
+ so AgentSwarm polls it briefly, and only reads text written after the last user
280
+ message — otherwise a turn would silently capture nothing, or re-relay the
281
+ previous turn's reply.
282
+ - **codex** → the agent gets a private `CODEX_HOME` at `<agent-dir>/.codex/`
283
+ with a `notify` program wired up (your `~/.codex/auth.json` is symlinked in, so
284
+ it stays logged in, and your existing `config.toml` is carried over). The
285
+ generated file keeps `notify` above every `[table]` header -- TOML is
286
+ order-sensitive, and a `notify` written after one silently becomes
287
+ `projects.<dir>.notify`, which codex never calls.
288
+ - **gemini / hermes** → no turn-completion hook exists, so a background watcher
289
+ samples the pane and emits the new text once it has been quiet for
290
+ `pane_idle_ms`. It filters out the terminal's echo of incoming messages, but
291
+ it is still terminal scraping: spinners and redraws can leak in. Prefer having
292
+ these agents call `swarm send` explicitly.
293
+
294
+ Set `capture:` per agent to override the default for its type.
295
+
296
+ ### Subagents, parallel work, and busy agents
297
+
298
+ Coding agents spawn subagents and background tasks. Four things follow from that.
299
+
300
+ **A subagent that calls `swarm send` speaks as its parent.** Subagents inherit the
301
+ agent's environment, so `SWARM_AGENT` still names the parent and `can_talk_to` is
302
+ enforced against the parent. That is almost always what you want: the swarm sees
303
+ one `developer`, not five anonymous workers.
304
+
305
+ **Parallel subagents cannot garble each other.** A paste and the Enter that submits
306
+ it are two separate tmux calls, so two senders racing on one pane used to produce
307
+ one Enter submitting two concatenated messages and another submitting nothing.
308
+ Everything that types into a pane now takes a per-recipient lock, so concurrent
309
+ sends queue up instead of interleaving. Different recipients are still messaged in
310
+ parallel.
311
+
312
+ **A message that arrives while an agent is busy is queued, not lost.** The CLIs
313
+ hold it in their input box and process it when the current tool call finishes —
314
+ codex says so out loud (`Messages to be submitted after next tool call`).
315
+
316
+ **An agent that ends its turn saying "I'll respond when the subagent finishes"
317
+ gets captured twice.** With `capture: hook`, the Stop/notify hook fires when the
318
+ *agent's* turn ends. Claude's `Task` subagents run inside the turn, so the hook
319
+ waits for them. But work the agent genuinely backgrounds lets the turn end early,
320
+ and then:
321
+
322
+ - that interim message is captured, and forwarded if `forward_responses_to` is set;
323
+ - when the agent is re-invoked and finishes for real, its answer is captured and
324
+ forwarded too. The hop counter records the hop at which the agent *received* its
325
+ last message, so a second response does not consume an extra hop — the real
326
+ answer is never suppressed by the loop guard.
327
+
328
+ Subagent chatter never leaks: Claude writes subagent turns into the same transcript
329
+ marked `isSidechain: true`, and the hook skips them, so what gets relayed is the
330
+ agent's own final message rather than whatever a subagent happened to say last.
331
+
332
+ With `capture: pane` (gemini, hermes) this breaks down. A quiet pane is the only
333
+ "turn finished" signal there is, so a pause while a subagent works looks exactly
334
+ like a completed turn: the interim "I'll respond when it finishes" is captured and
335
+ forwarded, then the real answer is captured separately. Raise `pane_idle_ms` above
336
+ the longest silence you expect, or set `capture: none` and have the agent call
337
+ `swarm send` when it actually has something to say.
338
+
339
+ ### Busy agents and backpressure
340
+
341
+ If `b` gives `a` a task, `a` is mid-turn. When `c` then tries to task `a` as well,
342
+ the message is refused rather than dropped into a working agent's input box:
343
+
344
+ ```
345
+ $ swarm send --to a "please review my diff"
346
+ xx a is busy right now (working for 42s on a task from b). Please try again after
347
+ some time, or put your message in the queue and wait for the answer:
348
+ swarm send --to a --queue "..." # delivered automatically when a is free
349
+ swarm send --to a --wait "..." # block here until a is free
350
+ Meanwhile you are free to do other work.
351
+ ```
352
+
353
+ So `c` chooses: come back later, `--queue` it and carry on with other work, or
354
+ `--wait` and block. A queued message is delivered by the next capture hook that
355
+ fires for `a`, the moment it goes idle. Agents are told all of this in the
356
+ communication block appended to their first prompt.
357
+
358
+ ```bash
359
+ swarm status # TURN column: idle / busy 42s / untracked, plus QUEUE depth
360
+ swarm queue a # what is waiting for a, and who sent it
361
+ swarm queue a --clear # drop it all
362
+ swarm idle a # force a back to idle, then drain -- if a capture never fired
363
+ ```
364
+
365
+ **This is safe against parallel senders.** The busy check and the "now busy" write
366
+ happen inside the same per-recipient lock as the paste, so two subagents racing to
367
+ message an idle agent cannot both pass the check — one delivers, the other is told
368
+ it is busy. A flag checked and set separately would let both through.
369
+
370
+ Some honest limits:
371
+
372
+ - Busy tracking needs a "turn finished" signal, so it only works for agents with
373
+ `capture: hook` or `capture: pane`. A `capture: none` agent reports `untracked`
374
+ and always accepts mail.
375
+ - With `capture: pane`, "idle" means "the pane went quiet", which a thinking agent
376
+ can also look like. Backpressure there is a hint, not a guarantee.
377
+ - A turn started by a human typing directly into the pane is not tracked.
378
+ - If a capture never fires (crashed CLI, misconfigured hook), the agent would look
379
+ busy forever. After `busy_timeout_ms` (default 15 minutes) it is treated as idle
380
+ again, with a warning. `swarm idle <agent>` clears it immediately. Any mail queued
381
+ for such an agent is not lost either: whenever some *other* agent finishes a turn,
382
+ AgentSwarm sweeps the now-idle agent's queue and delivers what was stranded — so
383
+ one missed turn-completion cannot wedge a queue permanently.
384
+ - A capture only fires if the agent's `type` matches the CLI its `command` actually
385
+ runs. If you point a `type: codex` agent at a `claude` command (e.g. through an
386
+ alias), it gets codex's `notify` hook, which claude never calls — its turns are
387
+ never detected and it looks busy forever. Set `type` to whatever the command runs.
388
+ - `--force` and `--ignore-busy` deliver anyway. The agent's CLI will queue the
389
+ message and handle it after the current tool call, so nothing is lost — you just
390
+ give up the backpressure.
391
+
392
+ ### Auto-forwarding
393
+
394
+ `forward_responses_to` relays an agent's finished turn to other agents without
395
+ it having to ask:
396
+
397
+ ```yaml
398
+ - name: researcher
399
+ can_talk_to: [orchestrator, developer]
400
+ forward_responses_to: [orchestrator] # must be a subset of can_talk_to
401
+ ```
402
+
403
+ Two agents forwarding to each other would ping-pong forever, so every forwarded
404
+ message carries a hop count, and forwarding stops at `max_forward_hops`
405
+ (default 3). A fresh message from you resets the count. Auto-forwarding is
406
+ powerful but chatty — for most swarms it is better to let agents decide when to
407
+ speak, and leave `forward_responses_to` unset.
408
+
409
+ ---
410
+
411
+ ## Configuration reference
412
+
413
+ Full annotated example: [`agents.example.yaml`](agents.example.yaml).
414
+ Machine-readable summary for agents: [`llms.txt`](llms.txt).
415
+
416
+ ### `swarm:`
417
+
418
+ | Key | Default | Meaning |
419
+ |---|---|---|
420
+ | `name` | config filename | Label used in prompts and logs |
421
+ | `root` | `./workspace` | Where per-agent folders are created |
422
+ | `create_workdirs` | `true` | Auto-create missing agent folders |
423
+ | `session_prefix` | `""` | Prepended to every tmux session name |
424
+ | `send_delay_ms` | `150` | Pause before pasting into a pane |
425
+ | `enter_delay_ms` | `250` | Pause between pasting and pressing Enter |
426
+ | `max_forward_hops` | `3` | Auto-forward loop guard |
427
+ | `ready_timeout_ms` | `60000` | How long to wait for an agent's input box to respond |
428
+ | `busy_timeout_ms` | `900000` | After this, a stuck "busy" agent is treated as idle |
429
+ | `message_format` | `tagged` | `tagged` XML-ish envelopes, or `plain` text headers |
430
+ | `max_reply_reminders` | `1` | How often to remind an agent that its reply reached nobody |
431
+ | `resume` | `false` | Make `up` reattach to recorded conversations by default |
432
+ | `pane_idle_ms` | `2500` | Quiet time before a `pane` turn counts as done |
433
+ | `pane_poll_ms` | `700` | Pane sampling interval |
434
+ | `pane_scrollback` | `400` | Lines of scrollback the watcher diffs |
435
+ | `tmux_history_limit` | `50000` | Scrollback kept per agent pane so you can scroll up (`0` = tmux default) |
436
+ | `tmux_mouse` | `true` | Enable mouse-wheel scrolling in the panes |
437
+
438
+ ### `agents:`
439
+
440
+ | Key | Default | Meaning |
441
+ |---|---|---|
442
+ | `name` | *required* | Folder name **and** tmux session name |
443
+ | `type` | `claude` | `claude`, `codex`, `gemini`, `hermes`, or one you define |
444
+ | `command` | from type | Exact CLI to run, e.g. `claude --dangerously-skip-permissions` |
445
+ | `can_talk_to` | `[]` | Whitelist of agents it may message; `"*"` for all others |
446
+ | `first_prompt` | `""` | Prompt typed in after the CLI boots |
447
+ | `first_prompt_file` | — | Read the prompt from a file instead |
448
+ | `append_agents_that_you_can_talk_to_prompt` | `true` | Append the "here's who you can message and how" block |
449
+ | `in_first_prompt_append_your_task_will_be_sent_in_the_next_prompt` | `false` | Append "stand by, your task is coming next" |
450
+ | `forward_responses_to` | `[]` | Auto-relay finished turns to these agents |
451
+ | `capture` | from type | `hook`, `pane`, `none`, or `auto` |
452
+ | `boot_delay_ms` | from type | Grace period before probing the input box (not a delivery guarantee) |
453
+ | `ready_probe` | `true` | Wait for the input box to echo a token before typing |
454
+ | `busy_check` | `true` | Refuse incoming messages while this agent is mid-turn |
455
+ | `parse_outbound_tags` | `true` | Route `<swarm-send>` blocks the agent writes in its reply |
456
+ | `reply_reminder` | `true` | Remind it when it owes a reply but sent nothing |
457
+ | `resume_args` | from type | Appended to `command` to resume, e.g. `--resume {session_id}` |
458
+ | `resume_command` | — | Full replacement command when flags can't be appended |
459
+ | `workdir` | `<root>/<name>` | Override the agent's directory (`~`, `{name}`, `{root}`, `{swarm}`, `{type}`) |
460
+ | `create_workdir` | from `create_workdirs` | Create the folder if missing, else error |
461
+ | `env` | `{}` | Extra environment variables for its tmux session |
462
+
463
+ ### `agent_types:`
464
+
465
+ Override a built-in launch command, or define a new agent type:
466
+
467
+ ```yaml
468
+ agent_types:
469
+ claude:
470
+ command: "claude --dangerously-skip-permissions --model opus"
471
+ aider: # a type of your own
472
+ command: "aider --yes"
473
+ capture: pane # only claude/codex support `hook`
474
+ boot_delay_ms: 4000
475
+ ```
476
+
477
+ ### `defaults:` and `templates:`
478
+
479
+ `defaults:` supplies any agent key for agents that don't set it, including
480
+ `workdir` — useful for putting a whole swarm in one repository. `templates:`
481
+ overrides the text AgentSwarm generates — `comms` and `task_notice` (appended to
482
+ first prompts), plus `reply_reminder` and `send_failed` (the nudges) —
483
+ with `{agent} {swarm} {peers} {prefix} {inbox} {workdir}` available as
484
+ placeholders.
485
+
486
+ ---
487
+
488
+ ## Examples
489
+
490
+ Ready-to-run swarms in [`examples/`](examples/):
491
+
492
+ | File | Shape | Shows off |
493
+ |---|---|---|
494
+ | [`research-swarm.yaml`](examples/research-swarm.yaml) | Hub and spoke | A lead delegating to a scout, an analyst and a writer; a custom output folder |
495
+ | [`software-company.yaml`](examples/software-company.yaml) | Org chart | Six agents across all four CLIs, with a deliberately restricted comms graph |
496
+ | [`bug-hunt.yaml`](examples/bug-hunt.yaml) | Pipeline | `forward_responses_to` chaining reproduce → diagnose → fix → verify, hands-free |
497
+ | [`existing-repo.yaml`](examples/existing-repo.yaml) | Pairing | Two agents in one **existing** checkout, with `create_workdirs: false` |
498
+
499
+ ```bash
500
+ ./swarm.sh validate -c examples/research-swarm.yaml # look before you leap
501
+ ./swarm.sh up -c examples/research-swarm.yaml
502
+ ./swarm.sh send --to lead "Research the state of WebGPU compute shaders."
503
+ ```
504
+
505
+ `existing-repo.yaml` intentionally refuses to start until you point `workdir` at
506
+ a repository that exists.
507
+
508
+ ## Commands
509
+
510
+ | Command | Purpose |
511
+ |---|---|
512
+ | `swarm.sh up` | Start the swarm. `--only a,b`, `--restart`, `--resume`, `--no-prompt`, `--attach` |
513
+ | `swarm.sh down` | Kill sessions and watchers. `--only a,b` |
514
+ | `swarm.sh restart` | `down` then `up` |
515
+ | `swarm.sh status` | Table of agents, sessions, capture mode, permissions |
516
+ | `swarm.sh attach <agent>` | Attach to an agent's tmux session |
517
+ | `swarm.sh send --to <agent> "msg"` | Deliver a message (`--from`, `--file`, `--queue`, `--wait`, `--ignore-busy`, `--force`) |
518
+ | `swarm.sh broadcast "msg"` | Message everyone the sender may talk to |
519
+ | `swarm.sh sessions` | Show each agent's recorded conversation id (`--raw`) |
520
+ | `swarm.sh queue <agent>` | Show what is waiting for a busy agent (`--clear`) |
521
+ | `swarm.sh idle <agent>` | Force an agent back to idle, then drain its queue |
522
+ | `swarm.sh inbox <agent>` | Print archived messages |
523
+ | `swarm.sh logs [agent] [-f]` | Event log: prompts, responses, messages |
524
+ | `swarm.sh validate` | Parse the config. `--show-prompts` renders final prompts |
525
+
526
+ `./swarm.sh my-swarm.yaml` is shorthand for `./swarm.sh up -c my-swarm.yaml`.
527
+ `-c` and `$SWARM_CONFIG` both select a config; `-c` wins.
528
+
529
+ ## Layout
530
+
531
+ ```
532
+ AgentSwarm/
533
+ ├── swarm.sh # entrypoint
534
+ ├── agents.example.yaml # annotated config
535
+ ├── llms.txt # reference for agents configuring this tool
536
+ ├── hooks/
537
+ │ ├── claude_stop.sh # Claude Code Stop hook
538
+ │ └── codex_notify.sh # Codex notify program
539
+ ├── lib/
540
+ │ ├── swarm.py # tmux orchestration, routing, capture
541
+ │ ├── config.py # schema, defaults, validation
542
+ │ └── minyaml.py # YAML subset parser, used when PyYAML is absent
543
+ ├── tests/validate.sh # full suite: mock agents, no model calls
544
+ ├── examples/ # research swarm, software company, bug hunt, pairing
545
+ └── workspace/ # created by `up`
546
+ ├── <agent>/ # one folder per agent
547
+ └── .swarm/
548
+ ├── state.json # what `up` started
549
+ ├── sessions.yaml # each agent's conversation id, for `up --resume`
550
+ ├── bin/swarm # the `swarm` command agents call
551
+ ├── logs/ # <agent>.jsonl + swarm.jsonl
552
+ ├── inbox/<agent>/ # archived messages
553
+ └── run/ # watcher pids, hop counters
554
+ ```
555
+
556
+ ## Tests
557
+
558
+ ```bash
559
+ tests/validate.sh
560
+ ```
561
+
562
+ 48 checks over the real code paths — tmux, hooks, locks, queues, the tag parser,
563
+ sessions and resume — driven by mock agents, so it needs no API key and costs
564
+ nothing. It covers the awkward cases: the check-and-set race between concurrent
565
+ senders, a queued message beating a reply reminder, a subagent's sidechain turn
566
+ being skipped, and a transcript read before Claude has flushed it.
567
+
568
+ ## Troubleshooting
569
+
570
+ **"could not confirm the text arrived; NOT pressing Enter".** The agent's input
571
+ box never echoed the prompt, so AgentSwarm refused to submit it. Attach to the
572
+ session to see what state the CLI is in -- usually a modal (login, trust,
573
+ onboarding) is holding focus. Raise `ready_timeout_ms` if the CLI is merely slow.
574
+
575
+ **An agent says it "cannot message" another.** That is the permission check
576
+ doing its job — add the recipient to the sender's `can_talk_to`.
577
+
578
+ **A `pane` agent forwards garbage.** Terminal scraping picked up a redraw. Raise
579
+ `pane_idle_ms`, or set `capture: none` and instruct the agent to call
580
+ `swarm send` itself.
581
+
582
+ **Forwarding stopped with a hop-limit warning.** Two agents were relaying to each
583
+ other. Raise `max_forward_hops`, or break the cycle in `forward_responses_to`.
584
+
585
+ **Nothing captured from a claude agent.** Check `<agent-dir>/.claude/settings.json`
586
+ exists and `.swarm/logs/hooks.log` for errors. If the agent also looks busy forever,
587
+ its `type` probably does not match the CLI its `command` runs (see the capture note
588
+ under [Busy agents](#busy-agents-and-backpressure)).
589
+
590
+ **Can't scroll up in an attached session.** AgentSwarm raises tmux's scrollback to
591
+ `tmux_history_limit` (50000 lines) and turns on `tmux_mouse`, so the wheel scrolls
592
+ the backlog; press `q` to leave copy mode. If your terminal grabs the wheel itself,
593
+ use `Ctrl-b [` then PageUp. Both options are set on the tmux server before sessions
594
+ are created, so a server that was already running keeps its old panes' smaller
595
+ buffer — restart the swarm (or that pane) to pick up the larger one.
596
+
597
+ ## A note on flags
598
+
599
+ `claude --dangerously-skip-permissions`, `codex --yolo` and `gemini --yolo` let
600
+ agents act without asking for confirmation. That is usually what you want for an
601
+ unattended swarm, and it means several models are running tools unsupervised in
602
+ these directories. Point `root` somewhere disposable, and don't run a swarm over
603
+ a directory you can't afford to lose.