agentainer 0.1.0 → 0.1.2
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 +40 -40
- package/{swarm.sh → agentainer} +9 -9
- package/agents.example.yaml +11 -11
- package/bin/agentainer.js +1 -1
- package/examples/bug-hunt.yaml +2 -2
- package/examples/existing-repo.yaml +3 -3
- package/examples/research-swarm.yaml +8 -8
- package/examples/software-company.yaml +2 -2
- package/hooks/claude_stop.sh +2 -2
- package/hooks/codex_notify.sh +1 -1
- package/lib/config.py +6 -6
- package/lib/minyaml.py +2 -2
- package/lib/swarm.py +13 -12
- package/llms.txt +21 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ Run a team of coding agents — **Claude Code, Codex, Gemini CLI, Hermes** — s
|
|
|
4
4
|
side in tmux, each in its own directory, each able to message the others only if
|
|
5
5
|
your YAML file says it may.
|
|
6
6
|
|
|
7
|
-
> Formerly **AgentSwarm**. Installed globally
|
|
8
|
-
>
|
|
7
|
+
> Formerly **AgentSwarm**. Installed globally the command is `agentainer`; from a
|
|
8
|
+
> clone, the `./agentainer` script in the repo root is the same thing.
|
|
9
9
|
|
|
10
10
|
```
|
|
11
11
|
agents.yaml tmux
|
|
@@ -31,7 +31,7 @@ the agent's CLI, and types each agent's first prompt into it.
|
|
|
31
31
|
|
|
32
32
|
- `tmux` (3.0+)
|
|
33
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 `./
|
|
34
|
+
- `node` (16+) — only for the global `agentainer` command; not needed if you run `./agentainer` from a clone
|
|
35
35
|
- whichever agent CLIs you reference: `claude`, `codex`, `gemini`, `hermes` — install only the one(s) you actually use
|
|
36
36
|
|
|
37
37
|
## Install
|
|
@@ -51,7 +51,7 @@ Or from a clone (no npm needed):
|
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
git clone https://github.com/mehmetcanfarsak/AgentSwarm.git && cd AgentSwarm
|
|
54
|
-
./
|
|
54
|
+
./agentainer --help # same commands as the global `agentainer`, straight from the repo
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
## Quickstart
|
|
@@ -69,15 +69,15 @@ agentainer down # stop everything
|
|
|
69
69
|
Give the swarm its actual work:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
|
|
72
|
+
agentainer send --to orchestrator "Build a CLI that converts CSV to Parquet."
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
Watch the traffic between agents:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
agentainer logs -f # whole swarm, live
|
|
79
|
+
agentainer logs reviewer -n 20 # one agent
|
|
80
|
+
agentainer inbox developer # messages an agent received
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
---
|
|
@@ -135,15 +135,15 @@ Getting that to work reliably against a live TUI took more than a sleep:
|
|
|
135
135
|
- **Claude Code silently discards keystrokes for several seconds partway through
|
|
136
136
|
startup.** Measured on v2.1.205: input at t=2s landed, t=6s and t=12s vanished,
|
|
137
137
|
t=20s landed. A fixed `boot_delay_ms` is therefore a coin flip. Before typing,
|
|
138
|
-
|
|
138
|
+
Agentainer types a throwaway token and waits for the input box to echo it back,
|
|
139
139
|
then erases it (`ready_probe`). Enter is never sent, so nothing is submitted.
|
|
140
|
-
- **Readiness is not monotonic**, so after pasting,
|
|
140
|
+
- **Readiness is not monotonic**, so after pasting, Agentainer checks that the
|
|
141
141
|
text actually appeared on screen before pressing Enter, and retries if it did
|
|
142
142
|
not. If delivery cannot be confirmed it refuses to press Enter, rather than
|
|
143
143
|
submitting a half-delivered prompt.
|
|
144
144
|
- **Both CLIs open a "do you trust this folder?" modal** on first run in a new
|
|
145
145
|
directory, which would eat the first prompt (Enter answers the dialog). Claude
|
|
146
|
-
does this even under `--dangerously-skip-permissions`.
|
|
146
|
+
does this even under `--dangerously-skip-permissions`. Agentainer pre-trusts each
|
|
147
147
|
agent's workdir: for codex in its generated `config.toml`, for claude by adding
|
|
148
148
|
`hasTrustDialogAccepted` for that path in `~/.claude.json`.
|
|
149
149
|
- **Both collapse a long paste into a chip** rather than showing the text —
|
|
@@ -199,7 +199,7 @@ tmux send-keys -t reviewer -l "your message" && tmux send-keys -t reviewer Enter
|
|
|
199
199
|
**Agents get reminded when their answer goes nowhere.** A model that was asked a
|
|
200
200
|
question will often just *write the answer as prose* and end its turn — and that
|
|
201
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,
|
|
202
|
+
agent owes a reply and finishes a turn without sending one, Agentainer messages it:
|
|
203
203
|
|
|
204
204
|
```
|
|
205
205
|
Your last turn sent no message to anyone, and lead is waiting on your answer to
|
|
@@ -214,7 +214,7 @@ If instead the agent *tried* to send but the block was malformed, it gets the
|
|
|
214
214
|
specific diagnosis — unclosed tag, missing `to`, unknown recipient, permission
|
|
215
215
|
denied — so it can correct itself rather than lose the message silently.
|
|
216
216
|
|
|
217
|
-
It is reminded at most `max_reply_reminders` times (default **1**), then
|
|
217
|
+
It is reminded at most `max_reply_reminders` times (default **1**), then Agentainer
|
|
218
218
|
gives up and stops nagging. An agent that auto-forwards via `forward_responses_to`
|
|
219
219
|
is never reminded, since its words did reach someone. Turn it off per agent with
|
|
220
220
|
`reply_reminder: false`.
|
|
@@ -236,8 +236,8 @@ conversation instead of starting a fresh one — it does not re-send the first
|
|
|
236
236
|
prompt, and it keeps any mail still queued for that agent:
|
|
237
237
|
|
|
238
238
|
```bash
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
agentainer sessions # what is recorded, and the command that would resume it
|
|
240
|
+
agentainer up --resume # reattach; agents without a recorded id start fresh
|
|
241
241
|
```
|
|
242
242
|
|
|
243
243
|
Claude is resumed with `--resume <id>`, codex with `resume <id>`. Set
|
|
@@ -262,7 +262,7 @@ agent sees on its own terminal. Use `can_talk_to: "*"` for "everyone else".
|
|
|
262
262
|
|
|
263
263
|
## Capturing what an agent says
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
Agentainer needs to know when an agent finishes a turn — both to log it and to
|
|
266
266
|
support auto-forwarding. How it finds out depends on the CLI, and the two
|
|
267
267
|
mechanisms are **not** equally good:
|
|
268
268
|
|
|
@@ -276,7 +276,7 @@ mechanisms are **not** equally good:
|
|
|
276
276
|
with **no `matcher` key** (`Stop` is not a tool event, and supplying one stops the
|
|
277
277
|
interactive TUI from ever running the hook). It reads the session transcript.
|
|
278
278
|
Claude fires the hook *before* flushing the assistant message to that transcript,
|
|
279
|
-
so
|
|
279
|
+
so Agentainer polls it briefly, and only reads text written after the last user
|
|
280
280
|
message — otherwise a turn would silently capture nothing, or re-relay the
|
|
281
281
|
previous turn's reply.
|
|
282
282
|
- **codex** → the agent gets a private `CODEX_HOME` at `<agent-dir>/.codex/`
|
|
@@ -379,7 +379,7 @@ Some honest limits:
|
|
|
379
379
|
busy forever. After `busy_timeout_ms` (default 15 minutes) it is treated as idle
|
|
380
380
|
again, with a warning. `swarm idle <agent>` clears it immediately. Any mail queued
|
|
381
381
|
for such an agent is not lost either: whenever some *other* agent finishes a turn,
|
|
382
|
-
|
|
382
|
+
Agentainer sweeps the now-idle agent's queue and delivers what was stranded — so
|
|
383
383
|
one missed turn-completion cannot wedge a queue permanently.
|
|
384
384
|
- A capture only fires if the agent's `type` matches the CLI its `command` actually
|
|
385
385
|
runs. If you point a `type: codex` agent at a `claude` command (e.g. through an
|
|
@@ -478,7 +478,7 @@ agent_types:
|
|
|
478
478
|
|
|
479
479
|
`defaults:` supplies any agent key for agents that don't set it, including
|
|
480
480
|
`workdir` — useful for putting a whole swarm in one repository. `templates:`
|
|
481
|
-
overrides the text
|
|
481
|
+
overrides the text Agentainer generates — `comms` and `task_notice` (appended to
|
|
482
482
|
first prompts), plus `reply_reminder` and `send_failed` (the nudges) —
|
|
483
483
|
with `{agent} {swarm} {peers} {prefix} {inbox} {workdir}` available as
|
|
484
484
|
placeholders.
|
|
@@ -497,9 +497,9 @@ Ready-to-run swarms in [`examples/`](examples/):
|
|
|
497
497
|
| [`existing-repo.yaml`](examples/existing-repo.yaml) | Pairing | Two agents in one **existing** checkout, with `create_workdirs: false` |
|
|
498
498
|
|
|
499
499
|
```bash
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
500
|
+
agentainer validate -c examples/research-swarm.yaml # look before you leap
|
|
501
|
+
agentainer up -c examples/research-swarm.yaml
|
|
502
|
+
agentainer send --to lead "Research the state of WebGPU compute shaders."
|
|
503
503
|
```
|
|
504
504
|
|
|
505
505
|
`existing-repo.yaml` intentionally refuses to start until you point `workdir` at
|
|
@@ -509,28 +509,28 @@ a repository that exists.
|
|
|
509
509
|
|
|
510
510
|
| Command | Purpose |
|
|
511
511
|
|---|---|
|
|
512
|
-
| `
|
|
513
|
-
| `
|
|
514
|
-
| `
|
|
515
|
-
| `
|
|
516
|
-
| `
|
|
517
|
-
| `
|
|
518
|
-
| `
|
|
519
|
-
| `
|
|
520
|
-
| `
|
|
521
|
-
| `
|
|
522
|
-
| `
|
|
523
|
-
| `
|
|
524
|
-
| `
|
|
525
|
-
|
|
526
|
-
|
|
512
|
+
| `agentainer up` | Start the swarm. `--only a,b`, `--restart`, `--resume`, `--no-prompt`, `--attach` |
|
|
513
|
+
| `agentainer down` | Kill sessions and watchers. `--only a,b` |
|
|
514
|
+
| `agentainer restart` | `down` then `up` |
|
|
515
|
+
| `agentainer status` | Table of agents, sessions, capture mode, permissions |
|
|
516
|
+
| `agentainer attach <agent>` | Attach to an agent's tmux session |
|
|
517
|
+
| `agentainer send --to <agent> "msg"` | Deliver a message (`--from`, `--file`, `--queue`, `--wait`, `--ignore-busy`, `--force`) |
|
|
518
|
+
| `agentainer broadcast "msg"` | Message everyone the sender may talk to |
|
|
519
|
+
| `agentainer sessions` | Show each agent's recorded conversation id (`--raw`) |
|
|
520
|
+
| `agentainer queue <agent>` | Show what is waiting for a busy agent (`--clear`) |
|
|
521
|
+
| `agentainer idle <agent>` | Force an agent back to idle, then drain its queue |
|
|
522
|
+
| `agentainer inbox <agent>` | Print archived messages |
|
|
523
|
+
| `agentainer logs [agent] [-f]` | Event log: prompts, responses, messages |
|
|
524
|
+
| `agentainer validate` | Parse the config. `--show-prompts` renders final prompts |
|
|
525
|
+
|
|
526
|
+
`agentainer my-swarm.yaml` is shorthand for `agentainer up -c my-swarm.yaml`.
|
|
527
527
|
`-c` and `$SWARM_CONFIG` both select a config; `-c` wins.
|
|
528
528
|
|
|
529
529
|
## Layout
|
|
530
530
|
|
|
531
531
|
```
|
|
532
532
|
AgentSwarm/
|
|
533
|
-
├──
|
|
533
|
+
├── agentainer # entrypoint
|
|
534
534
|
├── agents.example.yaml # annotated config
|
|
535
535
|
├── llms.txt # reference for agents configuring this tool
|
|
536
536
|
├── hooks/
|
|
@@ -568,7 +568,7 @@ being skipped, and a transcript read before Claude has flushed it.
|
|
|
568
568
|
## Troubleshooting
|
|
569
569
|
|
|
570
570
|
**"could not confirm the text arrived; NOT pressing Enter".** The agent's input
|
|
571
|
-
box never echoed the prompt, so
|
|
571
|
+
box never echoed the prompt, so Agentainer refused to submit it. Attach to the
|
|
572
572
|
session to see what state the CLI is in -- usually a modal (login, trust,
|
|
573
573
|
onboarding) is holding focus. Raise `ready_timeout_ms` if the CLI is merely slow.
|
|
574
574
|
|
|
@@ -587,7 +587,7 @@ exists and `.swarm/logs/hooks.log` for errors. If the agent also looks busy fore
|
|
|
587
587
|
its `type` probably does not match the CLI its `command` runs (see the capture note
|
|
588
588
|
under [Busy agents](#busy-agents-and-backpressure)).
|
|
589
589
|
|
|
590
|
-
**Can't scroll up in an attached session.**
|
|
590
|
+
**Can't scroll up in an attached session.** Agentainer raises tmux's scrollback to
|
|
591
591
|
`tmux_history_limit` (50000 lines) and turns on `tmux_mouse`, so the wheel scrolls
|
|
592
592
|
the backlog; press `q` to leave copy mode. If your terminal grabs the wheel itself,
|
|
593
593
|
use `Ctrl-b [` then PageUp. Both options are set on the tmux server before sessions
|
package/{swarm.sh → agentainer}
RENAMED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
#
|
|
3
|
+
# Agentainer -- launch a configurable swarm of coding agents in tmux.
|
|
4
4
|
#
|
|
5
|
-
# ./
|
|
6
|
-
# ./
|
|
7
|
-
# ./
|
|
8
|
-
# ./
|
|
9
|
-
# ./
|
|
10
|
-
# ./
|
|
11
|
-
# ./
|
|
5
|
+
# ./agentainer up start every agent in agents.yaml
|
|
6
|
+
# ./agentainer up -c my-swarm.yaml ...from a different config
|
|
7
|
+
# ./agentainer agents.yaml shorthand for `up -c agents.yaml`
|
|
8
|
+
# ./agentainer status see who is running
|
|
9
|
+
# ./agentainer send --to dev "hi" message an agent
|
|
10
|
+
# ./agentainer attach dev jump into an agent's tmux session
|
|
11
|
+
# ./agentainer down stop everything
|
|
12
12
|
#
|
|
13
13
|
# See README.md for the full reference, or llms.txt if you are an LLM.
|
|
14
14
|
set -euo pipefail
|
|
@@ -27,7 +27,7 @@ if [[ -z "$PYTHON" ]]; then
|
|
|
27
27
|
fi
|
|
28
28
|
|
|
29
29
|
if [[ -z "$PYTHON" ]]; then
|
|
30
|
-
echo "xx
|
|
30
|
+
echo "xx Agentainer needs python3 on PATH (or set SWARM_PYTHON)" >&2
|
|
31
31
|
exit 1
|
|
32
32
|
fi
|
|
33
33
|
|
package/agents.example.yaml
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
|
-
#
|
|
2
|
+
# Agentainer example configuration
|
|
3
3
|
#
|
|
4
4
|
# cp agents.example.yaml agents.yaml
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# agentainer validate --show-prompts # check it without launching anything
|
|
6
|
+
# agentainer up # launch the swarm
|
|
7
7
|
#
|
|
8
8
|
# Every key is documented inline. Anything marked (optional) can be omitted.
|
|
9
9
|
# =============================================================================
|
|
@@ -32,7 +32,7 @@ swarm:
|
|
|
32
32
|
# (optional) How long to keep probing an agent's input box for a response
|
|
33
33
|
# before giving up and typing the first prompt anyway. Some CLIs (Claude Code)
|
|
34
34
|
# silently discard keystrokes for several seconds partway through startup, so
|
|
35
|
-
#
|
|
35
|
+
# Agentainer waits for the input box to echo a throwaway token before typing.
|
|
36
36
|
ready_timeout_ms: 60000
|
|
37
37
|
|
|
38
38
|
# (optional) An agent is "busy" from when a message is submitted to it until its
|
|
@@ -50,7 +50,7 @@ swarm:
|
|
|
50
50
|
|
|
51
51
|
# (optional) An agent that answers a question as plain prose sends nothing: only a
|
|
52
52
|
# <swarm-send> block is delivered. When it owes a reply and its turn ends without
|
|
53
|
-
# one,
|
|
53
|
+
# one, Agentainer messages it explaining how to send. This is how many times.
|
|
54
54
|
max_reply_reminders: 1
|
|
55
55
|
|
|
56
56
|
# (optional) Reattach agents to their previous conversations on `up`, using the ids
|
|
@@ -63,9 +63,9 @@ swarm:
|
|
|
63
63
|
pane_poll_ms: 700 # how often the watcher samples the pane
|
|
64
64
|
pane_scrollback: 400 # lines of scrollback to diff
|
|
65
65
|
|
|
66
|
-
# (optional) tmux comfort settings for when you `
|
|
66
|
+
# (optional) tmux comfort settings for when you `agentainer attach <agent>`.
|
|
67
67
|
# The default tmux scrollback (2000 lines) is too small to hold a long
|
|
68
|
-
# multi-agent conversation, so
|
|
68
|
+
# multi-agent conversation, so Agentainer raises it before creating sessions.
|
|
69
69
|
# mouse mode lets you wheel-scroll the backlog; press q to leave copy mode.
|
|
70
70
|
# Both are applied to the tmux server, so they also affect your other sessions.
|
|
71
71
|
tmux_history_limit: 50000 # lines of scrollback per agent pane (0 = leave tmux's default)
|
|
@@ -85,13 +85,13 @@ defaults:
|
|
|
85
85
|
# agent_types (optional) -- override the built-in launch commands, or define
|
|
86
86
|
# entirely new agent types. Built-ins are: claude, codex, gemini, hermes.
|
|
87
87
|
#
|
|
88
|
-
# capture: how
|
|
88
|
+
# capture: how Agentainer learns that an agent finished a turn.
|
|
89
89
|
# hook -- the CLI can run an external program on turn completion.
|
|
90
90
|
# Supported for claude (Stop hook) and codex (notify program).
|
|
91
91
|
# pane -- no such facility: poll the tmux pane and diff it. Heuristic.
|
|
92
92
|
# none -- do not capture at all.
|
|
93
93
|
#
|
|
94
|
-
# boot_delay_ms: a grace period before
|
|
94
|
+
# boot_delay_ms: a grace period before Agentainer starts probing the input
|
|
95
95
|
# box. It does NOT need to cover the full startup time -- delivery of the
|
|
96
96
|
# first prompt is verified and retried, so a small value is fine.
|
|
97
97
|
# -----------------------------------------------------------------------------
|
|
@@ -214,7 +214,7 @@ agents:
|
|
|
214
214
|
# single `defaults.workdir` can serve every agent, e.g. "{root}/{name}-wt".
|
|
215
215
|
#
|
|
216
216
|
# Several agents may share one directory (they then see each other's edits;
|
|
217
|
-
#
|
|
217
|
+
# Agentainer warns you). Pair `create_workdir: false` with an existing
|
|
218
218
|
# project so a bad path is an error, not a new empty folder.
|
|
219
219
|
#
|
|
220
220
|
# workdir: ~/projects/acme-api
|
|
@@ -245,7 +245,7 @@ agents:
|
|
|
245
245
|
You are the SCRIBE. Keep a running changelog of the project in NOTES.md.
|
|
246
246
|
|
|
247
247
|
# -----------------------------------------------------------------------------
|
|
248
|
-
# templates (optional) -- override the text
|
|
248
|
+
# templates (optional) -- override the text Agentainer appends to first prompts.
|
|
249
249
|
# Available placeholders: {agent} {swarm} {peers} {prefix} {inbox} {workdir}
|
|
250
250
|
# -----------------------------------------------------------------------------
|
|
251
251
|
#
|
package/bin/agentainer.js
CHANGED
|
@@ -21,7 +21,7 @@ if (process.argv[2] === "doctor") {
|
|
|
21
21
|
process.exit(r.status === null ? 1 : r.status);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// Locate a Python interpreter, mirroring
|
|
24
|
+
// Locate a Python interpreter, mirroring ./agentainer (SWARM_PYTHON, then python3,
|
|
25
25
|
// then python).
|
|
26
26
|
function findPython() {
|
|
27
27
|
const candidates = process.env.SWARM_PYTHON
|
package/examples/bug-hunt.yaml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
2
|
# Bug hunt -- an automatic pipeline: reproduce -> diagnose -> fix -> verify.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# agentainer up -c examples/bug-hunt.yaml
|
|
5
|
+
# agentainer send --to reproducer "Uploads over 2MB fail with a 500 in prod."
|
|
6
6
|
#
|
|
7
7
|
# This is the one shape where `forward_responses_to` earns its keep: each stage
|
|
8
8
|
# hands its finished turn to the next stage automatically, with no human in the
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
# writes the code and a navigator who reads it as it lands.
|
|
7
7
|
#
|
|
8
8
|
# 1. Point `workdir` below at a real repository.
|
|
9
|
-
# 2.
|
|
10
|
-
# 3.
|
|
11
|
-
# 4.
|
|
9
|
+
# 2. agentainer validate -c examples/existing-repo.yaml
|
|
10
|
+
# 3. agentainer up -c examples/existing-repo.yaml
|
|
11
|
+
# 4. agentainer send --to driver "Add retry-with-backoff to the HTTP client."
|
|
12
12
|
#
|
|
13
13
|
# Until you edit the paths, `validate` will refuse to run and tell you so --
|
|
14
14
|
# that is the point of `create_workdir: false`.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
2
|
# Research swarm -- a small team that investigates a topic and writes it up.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# agentainer up -c examples/research-swarm.yaml
|
|
5
|
+
# agentainer send --to lead "Research the state of WebGPU compute shaders."
|
|
6
6
|
#
|
|
7
7
|
# Shape: hub and spoke. The lead hands out work and assembles the result; the
|
|
8
8
|
# specialists only ever report back to the lead (and to each other where it
|
|
@@ -21,7 +21,7 @@ agents:
|
|
|
21
21
|
|
|
22
22
|
- name: lead
|
|
23
23
|
type: claude
|
|
24
|
-
command: "
|
|
24
|
+
command: "claude --dangerously-skip-permissions"
|
|
25
25
|
can_talk_to: ["scout", "analyst", "writer"]
|
|
26
26
|
|
|
27
27
|
# The lead waits for the human to supply the research question.
|
|
@@ -51,8 +51,8 @@ agents:
|
|
|
51
51
|
Never accept "it depends" as a final answer. Force a recommendation.
|
|
52
52
|
|
|
53
53
|
- name: scout
|
|
54
|
-
type:
|
|
55
|
-
command: "
|
|
54
|
+
type: gemini
|
|
55
|
+
command: "gemini --yolo"
|
|
56
56
|
can_talk_to: ["lead", "analyst"]
|
|
57
57
|
|
|
58
58
|
# Gemini has no turn-completion hook, so its output would have to be scraped
|
|
@@ -79,8 +79,8 @@ agents:
|
|
|
79
79
|
of guessing. Say plainly when you could not find something.
|
|
80
80
|
|
|
81
81
|
- name: analyst
|
|
82
|
-
type:
|
|
83
|
-
command: "
|
|
82
|
+
type: codex
|
|
83
|
+
command: "codex --yolo"
|
|
84
84
|
can_talk_to: ["lead", "scout"]
|
|
85
85
|
|
|
86
86
|
first_prompt: |
|
|
@@ -101,7 +101,7 @@ agents:
|
|
|
101
101
|
|
|
102
102
|
- name: writer
|
|
103
103
|
type: claude
|
|
104
|
-
command: "
|
|
104
|
+
command: "claude --dangerously-skip-permissions"
|
|
105
105
|
can_talk_to: ["lead"]
|
|
106
106
|
|
|
107
107
|
# A custom folder: the report lands somewhere predictable, outside the
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
# Software company -- a product team with a CTO, an architect, two developers,
|
|
3
3
|
# a QA reviewer and a technical writer.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# agentainer up -c examples/software-company.yaml
|
|
6
|
+
# agentainer send --to cto "Build a URL shortener with an API and a web UI."
|
|
7
7
|
#
|
|
8
8
|
# The communication graph is deliberately not a free-for-all: developers talk to
|
|
9
9
|
# their architect and to QA, but not to each other, so design decisions go
|
package/hooks/claude_stop.sh
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Claude Code `Stop` hook: fires when Claude finishes responding.
|
|
3
3
|
# Claude passes a JSON payload on stdin containing `transcript_path`.
|
|
4
|
-
# Installed automatically into <agent-workdir>/.claude/settings.json by `
|
|
4
|
+
# Installed automatically into <agent-workdir>/.claude/settings.json by `agentainer up`.
|
|
5
5
|
#
|
|
6
6
|
# A hook must never break the agent it is attached to, so every failure here is
|
|
7
7
|
# swallowed and the script always exits 0.
|
|
@@ -13,5 +13,5 @@ if [[ -n "${SWARM_ROOT:-}" ]] && mkdir -p "$SWARM_ROOT/.swarm/logs" 2>/dev/null;
|
|
|
13
13
|
log="$SWARM_ROOT/.swarm/logs/hooks.log"
|
|
14
14
|
fi
|
|
15
15
|
|
|
16
|
-
"$HERE/
|
|
16
|
+
"$HERE/agentainer" hook claude >>"$log" 2>&1
|
|
17
17
|
exit 0
|
package/hooks/codex_notify.sh
CHANGED
package/lib/config.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Load, normalise and validate an
|
|
1
|
+
"""Load, normalise and validate an Agentainer YAML config."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -70,9 +70,9 @@ NAME_RE = re.compile(r"^[A-Za-z0-9_][A-Za-z0-9_-]*$")
|
|
|
70
70
|
|
|
71
71
|
DEFAULT_COMMS_TEMPLATE = """\
|
|
72
72
|
---
|
|
73
|
-
##
|
|
73
|
+
## Inter-agent communication protocol
|
|
74
74
|
|
|
75
|
-
You are the agent **{agent}**
|
|
75
|
+
You are the agent **{agent}** on the **{swarm}** team.
|
|
76
76
|
Agents you are allowed to message: {peers}
|
|
77
77
|
|
|
78
78
|
### Receiving
|
|
@@ -108,7 +108,7 @@ literally, across as many lines as you need:
|
|
|
108
108
|
To reach everyone you are allowed to talk to, use `<swarm-broadcast>` with no `to`.
|
|
109
109
|
|
|
110
110
|
The `reply-to` attribute is optional; drop it when you are starting a new thread.
|
|
111
|
-
Quoting it also
|
|
111
|
+
Quoting it also marks your message as an answer, so the other agent is not
|
|
112
112
|
chased for a reply to it. For an announcement that needs no answer, either use
|
|
113
113
|
`<swarm-broadcast>` or add `expects-reply="false"`.
|
|
114
114
|
|
|
@@ -312,7 +312,7 @@ def load(path: str | os.PathLike) -> SwarmConfig:
|
|
|
312
312
|
raise ConfigError(
|
|
313
313
|
f"config file not found: {cfg_path}\n"
|
|
314
314
|
" Create one with: cp agents.example.yaml agents.yaml\n"
|
|
315
|
-
" Or point at it:
|
|
315
|
+
" Or point at it: agentainer -c /path/to/swarm.yaml up"
|
|
316
316
|
)
|
|
317
317
|
|
|
318
318
|
try:
|
|
@@ -577,7 +577,7 @@ def load(path: str | os.PathLike) -> SwarmConfig:
|
|
|
577
577
|
if not agent.workdir.exists() and not agent.create_workdir:
|
|
578
578
|
raise ConfigError(
|
|
579
579
|
f"agent {agent.name!r}: workdir does not exist: {agent.workdir}\n"
|
|
580
|
-
" Create it yourself, or allow
|
|
580
|
+
" Create it yourself, or allow Agentainer to: create_workdir: true"
|
|
581
581
|
)
|
|
582
582
|
|
|
583
583
|
shared: dict[Path, list[str]] = {}
|
package/lib/minyaml.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""A small YAML-subset parser.
|
|
2
2
|
|
|
3
|
-
Used only when PyYAML is not importable, so that
|
|
4
|
-
bare Python 3 install. Supports the subset
|
|
3
|
+
Used only when PyYAML is not importable, so that Agentainer keeps working on a
|
|
4
|
+
bare Python 3 install. Supports the subset Agentainer configs actually need:
|
|
5
5
|
|
|
6
6
|
* nested block mappings and block sequences
|
|
7
7
|
* scalars: strings, ints, floats, booleans, null
|
package/lib/swarm.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""Agentainer -- run a swarm of coding agents in tmux and let them talk.
|
|
3
3
|
|
|
4
|
-
Invoked through ``
|
|
4
|
+
Invoked through ``agentainer`` (or ``./agentainer`` from a clone); see
|
|
5
|
+
``agentainer --help`` and README.md.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from __future__ import annotations
|
|
@@ -550,8 +551,8 @@ def read_sessions(cfg: SwarmConfig) -> dict:
|
|
|
550
551
|
def write_sessions(cfg: SwarmConfig, agents: dict) -> None:
|
|
551
552
|
cfg.runtime.mkdir(parents=True, exist_ok=True)
|
|
552
553
|
header = (
|
|
553
|
-
"#
|
|
554
|
-
"# `
|
|
554
|
+
"# Agentainer session state -- written automatically as agents work.\n"
|
|
555
|
+
"# `agentainer up --resume` reads this to reattach each agent to its own\n"
|
|
555
556
|
"# conversation after a restart. Safe to delete; you then start fresh.\n"
|
|
556
557
|
)
|
|
557
558
|
body = yaml_dump(
|
|
@@ -847,7 +848,7 @@ def deliver(
|
|
|
847
848
|
if not session_exists(target.session):
|
|
848
849
|
raise SwarmError(
|
|
849
850
|
f"agent {recipient!r} is not running (tmux session {target.session!r} missing). "
|
|
850
|
-
"Start it with:
|
|
851
|
+
"Start it with: agentainer up"
|
|
851
852
|
)
|
|
852
853
|
|
|
853
854
|
msg_id = new_message_id()
|
|
@@ -872,7 +873,7 @@ def deliver(
|
|
|
872
873
|
if not _paste_locked(cfg, target.session, body, enter=True, needle=needle):
|
|
873
874
|
raise SwarmError(
|
|
874
875
|
f"could not confirm the message reached {recipient!r}; "
|
|
875
|
-
f"inspect it with:
|
|
876
|
+
f"inspect it with: agentainer attach {recipient}"
|
|
876
877
|
)
|
|
877
878
|
|
|
878
879
|
with file_lock(cfg, recipient, "turn.lock"):
|
|
@@ -1231,7 +1232,7 @@ def install_codex_hook(agent: Agent) -> Path:
|
|
|
1231
1232
|
# to that table. `notify` must therefore come before anything else, or codex
|
|
1232
1233
|
# reads it as projects.<dir>.notify and never calls it.
|
|
1233
1234
|
chunks = [
|
|
1234
|
-
"# installed by
|
|
1235
|
+
"# installed by Agentainer -- fires when codex finishes a turn.",
|
|
1235
1236
|
"# Keep `notify` above every [table] header: TOML is order-sensitive.",
|
|
1236
1237
|
f"notify = [{notify}]",
|
|
1237
1238
|
"",
|
|
@@ -1609,8 +1610,8 @@ def write_shim(cfg: SwarmConfig) -> None:
|
|
|
1609
1610
|
shim = cfg.bin_dir / "swarm"
|
|
1610
1611
|
shim.write_text(
|
|
1611
1612
|
"#!/usr/bin/env bash\n"
|
|
1612
|
-
"# Generated by
|
|
1613
|
-
f'exec {shlex.quote(str(SWARM_HOME / "
|
|
1613
|
+
"# Generated by Agentainer. Lets an agent run `swarm send ...` from its shell.\n"
|
|
1614
|
+
f'exec {shlex.quote(str(SWARM_HOME / "agentainer"))} "$@"\n'
|
|
1614
1615
|
)
|
|
1615
1616
|
shim.chmod(0o755)
|
|
1616
1617
|
|
|
@@ -1803,7 +1804,7 @@ def cmd_up(args) -> int:
|
|
|
1803
1804
|
print()
|
|
1804
1805
|
info(f"swarm {cfg.name!r} is up with {len(started)} agent(s)")
|
|
1805
1806
|
info(f"attach with: tmux attach -t {started[0].session}")
|
|
1806
|
-
info(f"or: {SWARM_HOME / '
|
|
1807
|
+
info(f"or: {SWARM_HOME / 'agentainer'} attach {started[0].name}")
|
|
1807
1808
|
|
|
1808
1809
|
if args.attach:
|
|
1809
1810
|
os.execvp("tmux", ["tmux", "attach", "-t", started[0].session])
|
|
@@ -2042,7 +2043,7 @@ def cmd_inbox(args) -> int:
|
|
|
2042
2043
|
cfg = cfgmod.load(args.config)
|
|
2043
2044
|
name = args.agent or os.environ.get("SWARM_AGENT")
|
|
2044
2045
|
if not name:
|
|
2045
|
-
die("specify an agent:
|
|
2046
|
+
die("specify an agent: agentainer inbox <agent>")
|
|
2046
2047
|
cfg.get(name)
|
|
2047
2048
|
|
|
2048
2049
|
box = cfg.inbox_dir / name
|
|
@@ -2129,7 +2130,7 @@ def select_agents(cfg: SwarmConfig, only: str | None) -> list[Agent]:
|
|
|
2129
2130
|
|
|
2130
2131
|
|
|
2131
2132
|
def default_config() -> str:
|
|
2132
|
-
"""SWARM_CONFIG, else ./agents.yaml, else the agents.yaml beside
|
|
2133
|
+
"""SWARM_CONFIG, else ./agents.yaml, else the agents.yaml beside agentainer."""
|
|
2133
2134
|
from_env = os.environ.get("SWARM_CONFIG")
|
|
2134
2135
|
if from_env:
|
|
2135
2136
|
return from_env
|
package/llms.txt
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agentainer
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Agentainer runs several coding-agent CLIs (Claude Code, Codex, Gemini, Hermes) at once.
|
|
4
4
|
> Each agent gets its own directory and its own tmux session. A single YAML file defines
|
|
5
5
|
> the agents, the command that launches each one, its first prompt, and — as a strict
|
|
6
6
|
> whitelist — which other agents it is allowed to message.
|
|
7
7
|
>
|
|
8
|
-
> This file is the reference for LLMs asked to write or repair an
|
|
8
|
+
> This file is the reference for LLMs asked to write or repair an Agentainer config.
|
|
9
9
|
> If you are configuring a swarm, everything you need is below. Verify your work with
|
|
10
|
-
>
|
|
10
|
+
> `agentainer validate --show-prompts`, which parses the config and launches nothing.
|
|
11
11
|
|
|
12
12
|
## Mental model
|
|
13
13
|
|
|
14
|
-
- One YAML file describes the whole swarm.
|
|
14
|
+
- One YAML file describes the whole swarm. `agentainer up` reads it and, for each agent:
|
|
15
15
|
creates `<root>/<name>/`, installs a turn-completion hook in that folder, opens a tmux
|
|
16
16
|
session named `<session_prefix><name>`, runs the agent's `command` inside the folder,
|
|
17
17
|
waits for its input box to become responsive, then types the assembled first prompt in.
|
|
@@ -82,7 +82,7 @@ agents:
|
|
|
82
82
|
append_agents_that_you_can_talk_to_prompt: true
|
|
83
83
|
in_first_prompt_append_your_task_will_be_sent_in_the_next_prompt: false
|
|
84
84
|
|
|
85
|
-
templates: # override the text
|
|
85
|
+
templates: # override the text Agentainer generates
|
|
86
86
|
comms: | # placeholders: {agent} {swarm} {peers} {prefix} {inbox} {workdir}
|
|
87
87
|
You are {agent}. You may message: {peers}.
|
|
88
88
|
task_notice: |
|
|
@@ -169,7 +169,7 @@ prose reaches nobody -- only a `<swarm-send>` block is delivered. So:
|
|
|
169
169
|
to write.
|
|
170
170
|
- If B *tried* to send but the block was malformed -- unclosed, missing `to`, unknown
|
|
171
171
|
recipient, permission denied -- it gets the `send_failed` template naming each fault.
|
|
172
|
-
- At most `max_reply_reminders` nudges (default 1), then
|
|
172
|
+
- At most `max_reply_reminders` nudges (default 1), then Agentainer gives up silently.
|
|
173
173
|
- Anything B delivered counts, including an auto-forward via `forward_responses_to`.
|
|
174
174
|
Reminders are sent from `swarm`, so they never create a reply obligation themselves.
|
|
175
175
|
- `reply_reminder: false` disables it per agent; it is forced off when the agent's
|
|
@@ -223,23 +223,23 @@ Final prompt order: `first_prompt`, then the comms block, then the task notice.
|
|
|
223
223
|
## Commands
|
|
224
224
|
|
|
225
225
|
```
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
agentainer up [--only a,b] [--restart] [--no-prompt] [--attach]
|
|
227
|
+
agentainer down [--only a,b]
|
|
228
|
+
agentainer restart
|
|
229
|
+
agentainer status
|
|
230
|
+
agentainer attach <agent>
|
|
231
|
+
agentainer send --to <agent> [--from <name>] [--file F] [--queue] [--wait]
|
|
232
232
|
[--wait-timeout S] [--ignore-busy] [--force] "message"
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
233
|
+
agentainer broadcast "message"
|
|
234
|
+
agentainer queue <agent> [--clear]
|
|
235
|
+
agentainer idle <agent> [--no-drain]
|
|
236
|
+
agentainer inbox <agent> [-n N]
|
|
237
|
+
agentainer logs [agent] [-n N] [-f]
|
|
238
|
+
agentainer validate [--show-prompts]
|
|
239
239
|
```
|
|
240
240
|
|
|
241
241
|
Config resolution: `-c PATH`, else `$SWARM_CONFIG`, else `./agents.yaml`, else the
|
|
242
|
-
`agents.yaml` beside `
|
|
242
|
+
`agents.yaml` beside `agentainer`. `agentainer x.yaml` is shorthand for `up -c x.yaml`.
|
|
243
243
|
|
|
244
244
|
## What an agent sees at runtime
|
|
245
245
|
|
|
@@ -387,7 +387,7 @@ agents:
|
|
|
387
387
|
limit stops them. Prefer one-directional forwarding, or none.
|
|
388
388
|
- `--dangerously-skip-permissions` / `--yolo` let agents run tools unsupervised. Point
|
|
389
389
|
`root` at a disposable directory.
|
|
390
|
-
- The first prompt is typed into a live TUI. If
|
|
390
|
+
- The first prompt is typed into a live TUI. If Agentainer reports "could not confirm
|
|
391
391
|
the text arrived", the CLI is usually stuck behind a modal (login, trust, onboarding);
|
|
392
392
|
attach to the session and look. Raise `ready_timeout_ms` if it is merely slow.
|
|
393
393
|
- PyYAML is used when installed; otherwise a bundled subset parser handles the config.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentainer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Run a configurable swarm of coding-agent CLIs (Claude Code, Codex, Gemini) in tmux and let them talk to each other.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"lib/*.py",
|
|
33
33
|
"hooks/*.sh",
|
|
34
34
|
"scripts/check-deps.js",
|
|
35
|
-
"
|
|
35
|
+
"agentainer",
|
|
36
36
|
"examples/*.yaml",
|
|
37
37
|
"agents.example.yaml",
|
|
38
38
|
"README.md",
|