agentainer 0.1.1 → 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 +11 -11
- package/agents.example.yaml +9 -9
- package/lib/config.py +5 -5
- package/lib/minyaml.py +2 -2
- package/lib/swarm.py +6 -6
- package/llms.txt +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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`.
|
|
@@ -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.
|
|
@@ -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/agents.example.yaml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
|
-
#
|
|
2
|
+
# Agentainer example configuration
|
|
3
3
|
#
|
|
4
4
|
# cp agents.example.yaml agents.yaml
|
|
5
5
|
# agentainer validate --show-prompts # check it without launching anything
|
|
@@ -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/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
|
|
|
@@ -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
|
@@ -551,8 +551,8 @@ def read_sessions(cfg: SwarmConfig) -> dict:
|
|
|
551
551
|
def write_sessions(cfg: SwarmConfig, agents: dict) -> None:
|
|
552
552
|
cfg.runtime.mkdir(parents=True, exist_ok=True)
|
|
553
553
|
header = (
|
|
554
|
-
"#
|
|
555
|
-
"# `
|
|
554
|
+
"# Agentainer session state -- written automatically as agents work.\n"
|
|
555
|
+
"# `agentainer up --resume` reads this to reattach each agent to its own\n"
|
|
556
556
|
"# conversation after a restart. Safe to delete; you then start fresh.\n"
|
|
557
557
|
)
|
|
558
558
|
body = yaml_dump(
|
|
@@ -848,7 +848,7 @@ def deliver(
|
|
|
848
848
|
if not session_exists(target.session):
|
|
849
849
|
raise SwarmError(
|
|
850
850
|
f"agent {recipient!r} is not running (tmux session {target.session!r} missing). "
|
|
851
|
-
"Start it with:
|
|
851
|
+
"Start it with: agentainer up"
|
|
852
852
|
)
|
|
853
853
|
|
|
854
854
|
msg_id = new_message_id()
|
|
@@ -873,7 +873,7 @@ def deliver(
|
|
|
873
873
|
if not _paste_locked(cfg, target.session, body, enter=True, needle=needle):
|
|
874
874
|
raise SwarmError(
|
|
875
875
|
f"could not confirm the message reached {recipient!r}; "
|
|
876
|
-
f"inspect it with:
|
|
876
|
+
f"inspect it with: agentainer attach {recipient}"
|
|
877
877
|
)
|
|
878
878
|
|
|
879
879
|
with file_lock(cfg, recipient, "turn.lock"):
|
|
@@ -1232,7 +1232,7 @@ def install_codex_hook(agent: Agent) -> Path:
|
|
|
1232
1232
|
# to that table. `notify` must therefore come before anything else, or codex
|
|
1233
1233
|
# reads it as projects.<dir>.notify and never calls it.
|
|
1234
1234
|
chunks = [
|
|
1235
|
-
"# installed by
|
|
1235
|
+
"# installed by Agentainer -- fires when codex finishes a turn.",
|
|
1236
1236
|
"# Keep `notify` above every [table] header: TOML is order-sensitive.",
|
|
1237
1237
|
f"notify = [{notify}]",
|
|
1238
1238
|
"",
|
|
@@ -2043,7 +2043,7 @@ def cmd_inbox(args) -> int:
|
|
|
2043
2043
|
cfg = cfgmod.load(args.config)
|
|
2044
2044
|
name = args.agent or os.environ.get("SWARM_AGENT")
|
|
2045
2045
|
if not name:
|
|
2046
|
-
die("specify an agent:
|
|
2046
|
+
die("specify an agent: agentainer inbox <agent>")
|
|
2047
2047
|
cfg.get(name)
|
|
2048
2048
|
|
|
2049
2049
|
box = cfg.inbox_dir / name
|
package/llms.txt
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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
|
|
|
@@ -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
|
|
@@ -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.
|