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/llms.txt ADDED
@@ -0,0 +1,405 @@
1
+ # AgentSwarm
2
+
3
+ > AgentSwarm runs several coding-agent CLIs (Claude Code, Codex, Gemini, Hermes) at once.
4
+ > Each agent gets its own directory and its own tmux session. A single YAML file defines
5
+ > the agents, the command that launches each one, its first prompt, and — as a strict
6
+ > whitelist — which other agents it is allowed to message.
7
+ >
8
+ > This file is the reference for LLMs asked to write or repair an AgentSwarm config.
9
+ > If you are configuring a swarm, everything you need is below. Verify your work with
10
+ > `./swarm.sh validate --show-prompts`, which parses the config and launches nothing.
11
+
12
+ ## Mental model
13
+
14
+ - One YAML file describes the whole swarm. `./swarm.sh up` reads it and, for each agent:
15
+ creates `<root>/<name>/`, installs a turn-completion hook in that folder, opens a tmux
16
+ session named `<session_prefix><name>`, runs the agent's `command` inside the folder,
17
+ waits for its input box to become responsive, then types the assembled first prompt in.
18
+ - Agents message each other with `swarm send --to <agent> "text"`. The `swarm` command is
19
+ on their `PATH` and `SWARM_AGENT` identifies them, so no `--from` is needed. Delivery is
20
+ refused unless the recipient appears in the sender's `can_talk_to`.
21
+ - A delivered message is pasted into the recipient's pane as
22
+ `[swarm] message from <sender>:\n<text>`, archived under `.swarm/inbox/<recipient>/`,
23
+ and written to `.swarm/logs/`.
24
+
25
+ ## Config file
26
+
27
+ Top level keys: `swarm`, `defaults`, `agent_types`, `agents`, `templates`.
28
+ Only `agents` is required. Relative paths resolve against the config file's directory.
29
+
30
+ ```yaml
31
+ swarm:
32
+ name: dev-swarm # label used in prompts/logs; default = config filename
33
+ root: ./workspace # parent of the per-agent folders; default ./workspace
34
+ create_workdirs: true # auto-create missing agent folders; default true
35
+ session_prefix: "" # prepended to tmux session names; default ""
36
+ send_delay_ms: 150 # pause before pasting into a pane
37
+ enter_delay_ms: 250 # pause between pasting and pressing Enter
38
+ max_forward_hops: 3 # auto-forward loop guard
39
+ ready_timeout_ms: 60000 # how long to wait for an agent's input box to respond
40
+ busy_timeout_ms: 900000 # after this, an agent stuck "busy" is treated as idle
41
+ message_format: tagged # tagged | plain; tagged is the default
42
+ max_reply_reminders: 1 # how often to nudge an agent whose reply reached nobody
43
+ resume: false # make `up` reattach to recorded conversations by default
44
+ pane_idle_ms: 2500 # quiet time before a `pane` capture counts as a finished turn
45
+ pane_poll_ms: 700 # pane sampling interval
46
+ pane_scrollback: 400 # lines of scrollback the pane watcher diffs
47
+ tmux_history_limit: 50000 # scrollback per agent pane so you can scroll up when attached; 0 = tmux default
48
+ tmux_mouse: true # enable mouse-wheel scrolling in the panes
49
+
50
+ defaults: # any agent key; applied to agents that omit it
51
+ type: claude
52
+ append_agents_that_you_can_talk_to_prompt: true
53
+ workdir: "{root}/{name}" # optional; see "Working directories" below
54
+
55
+ agent_types: # override built-ins, or define new types
56
+ claude:
57
+ command: "claude --dangerously-skip-permissions"
58
+ capture: hook
59
+ boot_delay_ms: 3000
60
+
61
+ agents:
62
+ - name: developer # REQUIRED. Used as the folder name AND the tmux session name.
63
+ # Must match ^[A-Za-z0-9_][A-Za-z0-9_-]*$
64
+ type: codex # claude | codex | gemini | hermes | any key in agent_types
65
+ command: "codex --yolo" # exact CLI invocation; defaults to the type's command
66
+ can_talk_to: [reviewer] # whitelist. "*" means every other agent. Default: []
67
+ first_prompt: | # typed in after the CLI boots
68
+ You are the DEVELOPER...
69
+ first_prompt_file: ./p.md # alternative to first_prompt (mutually exclusive)
70
+ forward_responses_to: [reviewer] # auto-relay finished turns; must ⊆ can_talk_to
71
+ capture: hook # hook | pane | none | auto (default: from type)
72
+ boot_delay_ms: 3000 # grace period before probing the input box
73
+ ready_probe: true # wait for the input box to echo a token first
74
+ busy_check: true # refuse incoming mail while this agent is mid-turn
75
+ parse_outbound_tags: true # route <swarm-send> blocks written in its replies
76
+ reply_reminder: true # nudge it when it owes a reply but sent nothing
77
+ resume_args: "--resume {session_id}" # appended to command by `up --resume`
78
+ resume_command: "bash -ic 'chy3 --resume {session_id}'" # or replace it entirely
79
+ workdir: ./somewhere # default <root>/<name>; see below
80
+ create_workdir: true # false => a missing workdir is an error
81
+ env: {KEY: value} # extra env vars for its tmux session
82
+ append_agents_that_you_can_talk_to_prompt: true
83
+ in_first_prompt_append_your_task_will_be_sent_in_the_next_prompt: false
84
+
85
+ templates: # override the text AgentSwarm generates
86
+ comms: | # placeholders: {agent} {swarm} {peers} {prefix} {inbox} {workdir}
87
+ You are {agent}. You may message: {peers}.
88
+ task_notice: |
89
+ Stand by. Your real task arrives in the next message.
90
+ reply_reminder: | # placeholders: {agent} {sender} {id} {peers} {problems}
91
+ You owe {sender} an answer to {id}. Send it inside <swarm-send to="{sender}">.
92
+ send_failed: | # placeholders: {agent} {peers} {problems}
93
+ Your message could not be delivered. {problems}
94
+ ```
95
+
96
+ ## Working directories
97
+
98
+ - Default: each agent gets `<root>/<name>`, created on `up`.
99
+ - `workdir` overrides it. Absolute, relative to the config file, or `~`-prefixed.
100
+ Placeholders: `{name}` `{root}` `{swarm}` `{type}`.
101
+ - `defaults.workdir` applies to every agent that omits it. Include `{name}` to give
102
+ each agent its own folder; omit it to put the whole swarm in one directory.
103
+ - `create_workdirs` (swarm-level) and `create_workdir` (per-agent) control creation.
104
+ Set them to `false` when pointing at real repositories, so a mistyped path is an
105
+ error rather than a new empty folder.
106
+ - Several agents may share one workdir. That is legitimate (pair programming in one
107
+ checkout) but they will overwrite each other's files and interleave git commits,
108
+ so `validate` and `up` print a warning.
109
+ - `root` always holds `.swarm/` (logs, inboxes, the `swarm` shim), even when every
110
+ agent works elsewhere.
111
+
112
+ ## Tagged messages (the default wire format)
113
+
114
+ Inbound, an agent sees an envelope it can parse unambiguously:
115
+
116
+ <swarm-message from="lead" to="reviewer" id="m-eb4105" reply-to="m-3f9a1c">
117
+ any number of lines, code blocks, backslashes -- all verbatim
118
+ </swarm-message>
119
+
120
+ Outbound, the agent writes a block **in its reply**; the capture hook routes it when
121
+ the turn ends. There is no shell involved, hence no quoting or escaping to get wrong:
122
+
123
+ <swarm-send to="reviewer" reply-to="m-eb4105">
124
+ multi-line body
125
+ </swarm-send>
126
+
127
+ <swarm-broadcast>
128
+ goes to everyone in the sender's can_talk_to
129
+ </swarm-broadcast>
130
+
131
+ - `id` is generated per message; `reply-to` is optional and used for threading.
132
+ - Tagged sends are permission-checked exactly like `swarm send`, and are queued
133
+ automatically if the recipient is busy.
134
+ - A `<swarm-send to="...">` naming something that is not an agent is discarded with a
135
+ warning (this catches an agent echoing the `AGENT_NAME` placeholder from its prompt).
136
+ - Blocks are removed from the text before `forward_responses_to` runs, so a message
137
+ addressed to one peer is not also auto-forwarded to everybody.
138
+ - `message_format: plain` restores the `[swarm] message from <sender>:` header.
139
+ `parse_outbound_tags: false` stops reading tags out of replies. Both are forced off
140
+ when `capture: none`, since there is nothing to read.
141
+ - With `capture: pane` the tags are recovered from scraped terminal text, so they are
142
+ only as reliable as the scrape. Prefer hook-captured agents for tag routing.
143
+
144
+ ## Resuming after a restart
145
+
146
+ - Every captured turn records the agent's conversation id in `<root>/.swarm/sessions.yaml`
147
+ (claude hands it to the Stop hook; codex's is read from the newest rollout file under
148
+ that agent's private `CODEX_HOME/sessions`).
149
+ - `swarm up --resume` rebuilds each agent's command from that id, skips re-sending the
150
+ first prompt, and preserves the agent's queued mail and any reply it still owes.
151
+ - Resume recipes: claude `--resume {session_id}`, codex `resume {session_id}`. Neither
152
+ gemini nor hermes exposes a session id, so they always start fresh (with a warning).
153
+ - `resume_args` is appended to `command`. If the command invokes the CLI through an
154
+ alias or wrapper (`bash -ic chy3`), appending is wrong -- set `resume_command` to the
155
+ full replacement, using `{session_id}` (and optionally `{command}`).
156
+ - Starting an agent fresh removes its stale entry, so a later `--resume` cannot reattach
157
+ to a conversation that agent never ran.
158
+ - `swarm sessions` prints what is recorded and the exact command that would resume it.
159
+
160
+ ## Reply reminders
161
+
162
+ A model asked a question will often write the answer as prose and end its turn. That
163
+ prose reaches nobody -- only a `<swarm-send>` block is delivered. So:
164
+
165
+ - When agent B receives a message from agent A (and B may message A back), B is
166
+ recorded as owing a reply.
167
+ - If B's turn ends having sent nothing, it is messaged with the `reply_reminder`
168
+ template: who is waiting, the message id to quote as `reply-to`, and the exact block
169
+ to write.
170
+ - If B *tried* to send but the block was malformed -- unclosed, missing `to`, unknown
171
+ recipient, permission denied -- it gets the `send_failed` template naming each fault.
172
+ - At most `max_reply_reminders` nudges (default 1), then AgentSwarm gives up silently.
173
+ - Anything B delivered counts, including an auto-forward via `forward_responses_to`.
174
+ Reminders are sent from `swarm`, so they never create a reply obligation themselves.
175
+ - `reply_reminder: false` disables it per agent; it is forced off when the agent's
176
+ tags are not parsed (`capture: none` or `message_format: plain`).
177
+
178
+ ## The two prompt switches
179
+
180
+ Both are per-agent booleans, both default as shown, and both append to `first_prompt`:
181
+
182
+ - `append_agents_that_you_can_talk_to_prompt` (default `true`) — appends a block naming
183
+ the agents this one may message and showing the `swarm send` / `swarm broadcast` / raw
184
+ `tmux send-keys` syntax. Set it to `false` for an agent with no peers, or when you want
185
+ to write the communication instructions yourself.
186
+ - `in_first_prompt_append_your_task_will_be_sent_in_the_next_prompt` (default `false`) —
187
+ appends a "do not start yet, your task arrives in the next prompt" block. Use it for
188
+ the agent you will drive manually with `swarm send`, so it acknowledges and waits
189
+ instead of inventing work.
190
+
191
+ Final prompt order: `first_prompt`, then the comms block, then the task notice.
192
+
193
+ ## capture: how a finished turn is detected
194
+
195
+ | value | works with | how |
196
+ |---|---|---|
197
+ | `hook` | `claude`, `codex` **only** | the CLI runs a program on turn completion; exact |
198
+ | `pane` | anything | poll the tmux pane and diff it after `pane_idle_ms`; heuristic |
199
+ | `none` | anything | no capture |
200
+ | `auto` | anything | use the agent type's default (this is the default) |
201
+
202
+ - `claude` → a `Stop` hook in `<agent-dir>/.claude/settings.json`; reads the transcript.
203
+ - `codex` → a private `CODEX_HOME` at `<agent-dir>/.codex/` with a `notify` program.
204
+ - `gemini`, `hermes` → no such facility, so they fall back to `pane`. Setting
205
+ `capture: hook` on them (or on a custom type) prints a warning and uses `pane`.
206
+ - `pane` capture is terminal scraping. It filters the echo of incoming messages, but
207
+ spinners and redraws can leak through. When output quality matters, prefer
208
+ `capture: none` and tell the agent in its prompt to call `swarm send` itself.
209
+
210
+ `forward_responses_to` requires `capture` to be `hook` or `pane`; combining it with
211
+ `capture: none` is a config error.
212
+
213
+ ## Rules the validator enforces
214
+
215
+ - agent names are unique and match `^[A-Za-z0-9_][A-Za-z0-9_-]*$`
216
+ - every name in `can_talk_to` and `forward_responses_to` refers to a real agent
217
+ - an agent may not list itself in `can_talk_to`
218
+ - `forward_responses_to` ⊆ `can_talk_to`
219
+ - `forward_responses_to` is empty unless capture is enabled
220
+ - `type` is a built-in or defined under `agent_types`
221
+ - `first_prompt` and `first_prompt_file` are not both set
222
+
223
+ ## Commands
224
+
225
+ ```
226
+ ./swarm.sh up [--only a,b] [--restart] [--no-prompt] [--attach]
227
+ ./swarm.sh down [--only a,b]
228
+ ./swarm.sh restart
229
+ ./swarm.sh status
230
+ ./swarm.sh attach <agent>
231
+ ./swarm.sh send --to <agent> [--from <name>] [--file F] [--queue] [--wait]
232
+ [--wait-timeout S] [--ignore-busy] [--force] "message"
233
+ ./swarm.sh broadcast "message"
234
+ ./swarm.sh queue <agent> [--clear]
235
+ ./swarm.sh idle <agent> [--no-drain]
236
+ ./swarm.sh inbox <agent> [-n N]
237
+ ./swarm.sh logs [agent] [-n N] [-f]
238
+ ./swarm.sh validate [--show-prompts]
239
+ ```
240
+
241
+ Config resolution: `-c PATH`, else `$SWARM_CONFIG`, else `./agents.yaml`, else the
242
+ `agents.yaml` beside `swarm.sh`. `./swarm.sh x.yaml` is shorthand for `up -c x.yaml`.
243
+
244
+ ## What an agent sees at runtime
245
+
246
+ Environment inside every agent's tmux session:
247
+
248
+ ```
249
+ SWARM_AGENT this agent's name SWARM_CONFIG absolute path to the YAML
250
+ SWARM_PEERS comma-separated can_talk_to SWARM_ROOT the swarm root directory
251
+ SWARM_SESSION its tmux session name SWARM_NAME the swarm's name
252
+ PATH has <root>/.swarm/bin first, providing `swarm`
253
+ ```
254
+
255
+ Sending, from inside an agent:
256
+
257
+ ```bash
258
+ swarm send --to reviewer "src/parse.py is ready for review"
259
+ swarm broadcast "I renamed the config module"
260
+ tmux send-keys -t reviewer -l "raw fallback, skips permissions and logging" \
261
+ && tmux send-keys -t reviewer Enter
262
+ ```
263
+
264
+ Loop guard: a forwarded message carries a hop count that increments on each relay and
265
+ stops at `max_forward_hops`. A message you send with `swarm send` resets it to zero.
266
+
267
+ ## Worked examples in this repo
268
+
269
+ - `examples/research-swarm.yaml` — hub and spoke: a lead delegates to a scout
270
+ (gemini), an analyst (codex) and a writer; the writer uses a custom `workdir`.
271
+ - `examples/software-company.yaml` — six agents across all four CLIs with a
272
+ restricted comms graph (developers talk to the architect and QA, never to
273
+ each other).
274
+ - `examples/bug-hunt.yaml` — a hands-free pipeline built on `forward_responses_to`:
275
+ reproduce → diagnose → fix → verify.
276
+ - `examples/existing-repo.yaml` — two agents pairing inside one existing checkout,
277
+ using `create_workdirs: false` so a wrong path fails instead of being created.
278
+
279
+ ## Recipes
280
+
281
+ **Hub and spoke** — one orchestrator drives specialists, which reply only to it:
282
+
283
+ ```yaml
284
+ agents:
285
+ - {name: orchestrator, type: claude, can_talk_to: "*",
286
+ in_first_prompt_append_your_task_will_be_sent_in_the_next_prompt: true}
287
+ - {name: researcher, type: gemini, can_talk_to: [orchestrator]}
288
+ - {name: developer, type: codex, can_talk_to: [orchestrator]}
289
+ ```
290
+
291
+ **Pipeline** — each stage hands off to the next, automatically:
292
+
293
+ ```yaml
294
+ agents:
295
+ - {name: spec, type: claude, can_talk_to: [build], forward_responses_to: [build]}
296
+ - {name: build, type: codex, can_talk_to: [review], forward_responses_to: [review]}
297
+ - {name: review, type: claude, can_talk_to: []}
298
+ ```
299
+
300
+ **Isolated worker** — runs alone, talks to nobody:
301
+
302
+ ```yaml
303
+ agents:
304
+ - {name: scribe, type: hermes, can_talk_to: [], capture: none,
305
+ append_agents_that_you_can_talk_to_prompt: false}
306
+ ```
307
+
308
+ ## Busy agents (backpressure)
309
+
310
+ - An agent is "busy" from the moment a message is submitted to it until its next
311
+ captured turn end. Sending to a busy agent is refused with a message telling the
312
+ sender to retry later, `--queue`, or `--wait`.
313
+ - `--queue` appends to `<root>/.swarm/run/<agent>.queue.jsonl`; the capture hook that
314
+ ends the agent's turn drains one message from it. `--wait` blocks until deliverable.
315
+ `--queue --wait` queues, then blocks until the agent picks the message up.
316
+ - The busy check and the "now busy" write happen inside the recipient's pane lock, so
317
+ two parallel senders can never both see an idle agent and both deliver.
318
+ - Turn state is two counters (`delivered`, `completed`), not a boolean: a hook that
319
+ ends turn N may land after a message delivered during turn N. `completed` is clamped
320
+ to `delivered` on each turn end, because codex folds a queued message into the turn
321
+ already running, and an incrementing counter would drift and wedge the agent "busy".
322
+ - Only works when the agent is captured. `capture: none` => `busy_check` is forced off
323
+ and the agent always accepts mail.
324
+ - `busy_timeout_ms` fails open if a capture never fires; `swarm idle <agent>` clears it.
325
+ - Auto-forwarded responses to a busy peer are queued rather than dropped.
326
+ - Queues self-heal: every turn end sweeps other agents and drains any that have gone
327
+ idle (or gone stale-busy past `busy_timeout_ms`), so a single missed turn-completion
328
+ cannot strand queued mail forever. A capture only fires when `type` matches the CLI
329
+ the `command` actually launches — a `type: codex` agent running `claude` never reports
330
+ turn ends and looks busy forever.
331
+
332
+ ## Subagents and concurrency
333
+
334
+ - A subagent inherits `SWARM_AGENT`, so `swarm send` from inside one is attributed to
335
+ the parent agent and checked against the parent's `can_talk_to`.
336
+ - Every write into a pane takes a per-recipient lock (`<root>/.swarm/run/<session>.lock`).
337
+ Without it, parallel senders interleave: a paste and its Enter are two tmux calls, so
338
+ one Enter can submit two concatenated messages and another submit nothing.
339
+ - A message delivered to a busy agent is queued by the CLI and processed after the
340
+ current tool call. It is not lost.
341
+ - `capture: hook` fires when the *agent's* turn ends. Claude `Task` subagents run inside
342
+ the turn, so the hook waits for them. Backgrounded work ends the turn early: the
343
+ interim "I will respond when the subagent finishes" is captured (and forwarded), then
344
+ the real answer is captured when the agent finishes for real. The hop counter stores
345
+ the hop at which the agent last *received* a message, so a second response reuses the
346
+ same hop and the loop guard never suppresses the real answer.
347
+ - Claude records subagent turns in the same transcript with `isSidechain: true`. The
348
+ Stop hook skips them, so subagent chatter is never relayed as the agent's answer.
349
+ - `capture: pane` cannot tell "waiting on a subagent" from "finished": a quiet pane is
350
+ its only signal, so it forwards the interim message and then the real one. Raise
351
+ `pane_idle_ms`, or use `capture: none`.
352
+
353
+ ## Verified CLI behaviours (do not "simplify" these away)
354
+
355
+ - The claude `Stop` hook entry must have **no `matcher` key**. With one, the interactive
356
+ TUI never runs the hook (headless `-p` still does, which makes this easy to miss).
357
+ - Claude fires `Stop` *before* flushing the assistant message to the transcript. Read it
358
+ with a short poll, and only consider text after the last user record -- otherwise the
359
+ hook captures nothing, or re-relays the previous turn's reply.
360
+ - Claude asks "do you trust this folder?" in any new directory, even under
361
+ `--dangerously-skip-permissions`. Pre-trust it by setting `hasTrustDialogAccepted`
362
+ for that path in `~/.claude.json` before launching.
363
+ - Long pastes are collapsed into a chip: `[Pasted text #1 +N lines]` (claude),
364
+ `[Pasted Content N chars]` (codex). Delivery checks must recognise both.
365
+
366
+ - Claude Code discards keystrokes for several seconds partway through startup
367
+ (measured on v2.1.205: t=2s landed, t=6s and t=12s were dropped, t=20s landed).
368
+ A fixed `boot_delay_ms` cannot fix this, which is why `ready_probe` types a
369
+ throwaway token until the input box echoes it, and why a paste is verified on
370
+ screen before Enter is pressed.
371
+ - Codex shows a "do you trust this directory?" modal on first run in a new folder;
372
+ Enter answers the modal instead of submitting the prompt. The generated
373
+ `<agent>/.codex/config.toml` pre-trusts the agent's workdir to avoid it.
374
+ - TOML is order-sensitive. In that generated config, `notify` must appear above
375
+ every `[table]` header, or it becomes `projects.<dir>.notify` and codex never
376
+ calls it.
377
+ - Claude collapses a long paste into a `[Pasted text #1 +N lines]` chip, so the
378
+ pasted text never appears verbatim on screen. Delivery checks look for the chip
379
+ as well as for the tail of the pasted text (the head scrolls out of view).
380
+
381
+ ## Gotchas
382
+
383
+ - A folder and a tmux session are named after each agent; renaming an agent orphans both.
384
+ - Pointing several agents at one `workdir` is allowed and warned about; they share a
385
+ filesystem and a git index, so they can clobber each other.
386
+ - Two agents with `forward_responses_to` pointing at each other will relay until the hop
387
+ limit stops them. Prefer one-directional forwarding, or none.
388
+ - `--dangerously-skip-permissions` / `--yolo` let agents run tools unsupervised. Point
389
+ `root` at a disposable directory.
390
+ - The first prompt is typed into a live TUI. If AgentSwarm reports "could not confirm
391
+ the text arrived", the CLI is usually stuck behind a modal (login, trust, onboarding);
392
+ attach to the session and look. Raise `ready_timeout_ms` if it is merely slow.
393
+ - PyYAML is used when installed; otherwise a bundled subset parser handles the config.
394
+ It supports block maps/sequences, `|` and `>` scalars, flow `[a, b]` / `{a: 1}`,
395
+ comments and quoting — but not anchors, aliases, tags or multi-document files.
396
+
397
+ ## Files
398
+
399
+ - [tests/validate.sh](tests/validate.sh): the full suite. Run it after any change; it
400
+ uses mock agents, so it needs no API key. 48 checks, all must pass.
401
+ - [agents.example.yaml](agents.example.yaml): every option, documented inline.
402
+ - [README.md](README.md): human-facing guide, architecture and troubleshooting.
403
+ - [lib/config.py](lib/config.py): schema, defaults and validation — the source of truth.
404
+ - [lib/swarm.py](lib/swarm.py): tmux orchestration, message routing, capture.
405
+ - [hooks/](hooks/): the Claude `Stop` hook and the Codex `notify` program.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "agentainer",
3
+ "version": "0.1.0",
4
+ "description": "Run a configurable swarm of coding-agent CLIs (Claude Code, Codex, Gemini) in tmux and let them talk to each other.",
5
+ "keywords": [
6
+ "agents",
7
+ "ai",
8
+ "claude",
9
+ "codex",
10
+ "gemini",
11
+ "tmux",
12
+ "orchestration",
13
+ "multi-agent",
14
+ "swarm"
15
+ ],
16
+ "homepage": "https://github.com/mehmetcanfarsak/AgentSwarm#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/mehmetcanfarsak/AgentSwarm/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/mehmetcanfarsak/AgentSwarm.git"
23
+ },
24
+ "license": "MIT",
25
+ "author": "mehmetcanfarsak",
26
+ "type": "commonjs",
27
+ "bin": {
28
+ "agentainer": "bin/agentainer.js"
29
+ },
30
+ "files": [
31
+ "bin/agentainer.js",
32
+ "lib/*.py",
33
+ "hooks/*.sh",
34
+ "scripts/check-deps.js",
35
+ "swarm.sh",
36
+ "examples/*.yaml",
37
+ "agents.example.yaml",
38
+ "README.md",
39
+ "llms.txt"
40
+ ],
41
+ "scripts": {
42
+ "postinstall": "node scripts/check-deps.js",
43
+ "doctor": "node scripts/check-deps.js"
44
+ },
45
+ "engines": {
46
+ "node": ">=16"
47
+ },
48
+ "os": [
49
+ "linux",
50
+ "darwin"
51
+ ]
52
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ // Dependency doctor for Agentainer.
3
+ //
4
+ // Runs on `npm install` (postinstall) and on demand via `agentainer doctor`.
5
+ // It only checks the things Agentainer itself needs -- python3 and tmux -- and
6
+ // reports on optional agent CLIs (claude, codex, gemini, ...) without ever
7
+ // treating their absence as an error: a user may only ever run one of them.
8
+ //
9
+ // This script NEVER fails the install. Missing tools are reported with hints;
10
+ // the exit code stays 0 so `npm install -g agentainer` always succeeds.
11
+ "use strict";
12
+
13
+ const { spawnSync } = require("child_process");
14
+ const os = require("os");
15
+
16
+ function has(cmd, args) {
17
+ const probe = spawnSync(cmd, args, { stdio: "ignore" });
18
+ return !probe.error && probe.status === 0;
19
+ }
20
+
21
+ const platform = os.platform();
22
+ function installHint(pkg) {
23
+ if (platform === "darwin") return `brew install ${pkg}`;
24
+ if (platform === "linux")
25
+ return `sudo apt install ${pkg} (or your distro's package manager)`;
26
+ if (platform === "win32")
27
+ return `${pkg} is not natively supported on Windows; use WSL2`;
28
+ return `install ${pkg} with your package manager`;
29
+ }
30
+
31
+ // --- Required: Agentainer cannot run without these -------------------------
32
+ const required = [
33
+ { name: "python3", ok: has("python3", ["--version"]) || has("python", ["--version"]), hint: installHint("python3") },
34
+ { name: "tmux", ok: has("tmux", ["-V"]), hint: installHint("tmux") },
35
+ ];
36
+
37
+ // --- Optional: at least one agent CLI, but which one is up to the user ------
38
+ const optional = [
39
+ { name: "claude", label: "Claude Code", ok: has("claude", ["--version"]) },
40
+ { name: "codex", label: "Codex CLI", ok: has("codex", ["--version"]) },
41
+ { name: "gemini", label: "Gemini CLI", ok: has("gemini", ["--version"]) },
42
+ ];
43
+
44
+ const missingRequired = required.filter((r) => !r.ok);
45
+
46
+ process.stdout.write("\nAgentainer -- checking dependencies\n");
47
+ process.stdout.write("-----------------------------------\n");
48
+
49
+ for (const r of required) {
50
+ process.stdout.write(` [${r.ok ? "ok" : "--"}] ${r.name}${r.ok ? "" : ` -> ${r.hint}`}\n`);
51
+ }
52
+
53
+ process.stdout.write("\n Agent CLIs (install whichever you'll actually use):\n");
54
+ for (const o of optional) {
55
+ process.stdout.write(` [${o.ok ? "ok" : " "}] ${o.name.padEnd(8)} ${o.label}\n`);
56
+ }
57
+
58
+ if (missingRequired.length) {
59
+ process.stdout.write(
60
+ "\n!! Missing required tools: " +
61
+ missingRequired.map((r) => r.name).join(", ") +
62
+ "\n Install them, then re-check with: agentainer doctor\n"
63
+ );
64
+ } else {
65
+ const anyAgent = optional.some((o) => o.ok);
66
+ process.stdout.write(
67
+ "\nok Core dependencies satisfied." +
68
+ (anyAgent ? "" : " (No agent CLI detected yet -- install one to start a swarm.)") +
69
+ "\n"
70
+ );
71
+ }
72
+ process.stdout.write("\n");
73
+
74
+ // Always succeed: never abort an npm install over a missing optional (or even
75
+ // required) tool. The launcher re-checks at runtime and fails clearly there.
76
+ process.exit(0);
package/swarm.sh ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # AgentSwarm -- launch a configurable swarm of coding agents in tmux.
4
+ #
5
+ # ./swarm.sh up start every agent in agents.yaml
6
+ # ./swarm.sh up -c my-swarm.yaml ...from a different config
7
+ # ./swarm.sh agents.yaml shorthand for `up -c agents.yaml`
8
+ # ./swarm.sh status see who is running
9
+ # ./swarm.sh send --to dev "hi" message an agent
10
+ # ./swarm.sh attach dev jump into an agent's tmux session
11
+ # ./swarm.sh down stop everything
12
+ #
13
+ # See README.md for the full reference, or llms.txt if you are an LLM.
14
+ set -euo pipefail
15
+
16
+ SWARM_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ export SWARM_HOME
18
+
19
+ PYTHON="${SWARM_PYTHON:-}"
20
+ if [[ -z "$PYTHON" ]]; then
21
+ for candidate in python3 python; do
22
+ if command -v "$candidate" >/dev/null 2>&1; then
23
+ PYTHON="$candidate"
24
+ break
25
+ fi
26
+ done
27
+ fi
28
+
29
+ if [[ -z "$PYTHON" ]]; then
30
+ echo "xx AgentSwarm needs python3 on PATH (or set SWARM_PYTHON)" >&2
31
+ exit 1
32
+ fi
33
+
34
+ if ! command -v tmux >/dev/null 2>&1; then
35
+ echo "!! tmux was not found on PATH; every command except 'validate' will fail" >&2
36
+ fi
37
+
38
+ # The config is resolved in lib/swarm.py: -c, then $SWARM_CONFIG, then
39
+ # ./agents.yaml, then the agents.yaml next to this script. Deliberately NOT
40
+ # exported here -- a stale SWARM_CONFIG would shadow the config that a hook
41
+ # discovers from the agent's working directory.
42
+
43
+ exec "$PYTHON" "$SWARM_HOME/lib/swarm.py" "$@"