Config-driven tmux orchestration for local AI coding-agent swarms, with activity monitoring, durable log comms, and quota-aware babysitting.
Primary use: keep multiple LLM agents (Claude, Codex, Grok, etc.) productive in tmux panes without constant manual intervention. Designed for personal multi-agent workflows; works standalone.
Primary workflow is the installed aiswarm command. From a repo checkout,
python -m swarm.cli or python swarm/cli.py also works.
aiswarm must be on PATH; from this repo, run make install-aiswarm.
Install aiswarm into your uv tool environment:
make install-aiswarmStates: unknown working idle
# 1. Turn on the swarm (tmux session + panes + per-pane monitors + worker loops)
# Note: this is mostly a "create" for the tmux grid. Changing pane counts later
# requires stopping and re-starting (see limitations below).
aiswarm start ./swarm/<project>.yaml
# Architecture notes:
# - One tmux *session* per YAML file (named by `session_name`)
# - One *monitor* (activity detector via monitor-bin) per `monitor: true` pane
# (creates a Unix socket /tmp/<session>_<W.N>.sock per pane)
# - Comms workers (log consumers that deliver on idle) are started for monitored panes
# - **Babysit (automatic nudging / prompt loops) is NOT turned on yet**
#
# Important: `start` is **not** a full declarative "update" for the tmux grid.
# If you change the number of panes/windows in the YAML after the swarm is running,
# re-running `start` will refuse and tell you to recreate the session first.
# Worker configuration (comms/babysit) and monitor setup are more forgiving on re-start.# 2. Turn on babysit for panes that have `babysit.enabled: true` in the YAML
aiswarm babysit start ./swarm/<project>.yaml# 3. Turn off babysit only (swarm / monitors / comms stay up)
aiswarm babysit stop ./swarm/<project>.yaml# 4. Full teardown
# - stops all workers (if running)
# - kills per-pane monitors
# - tears down the tmux session
aiswarm stop ./swarm/<project>.yamlCreate a starter config and AGENTS note:
aiswarm init <project>View/edit ./swarm/<project>.yaml.
See the Basic operational flow section above for the recommended sequence (start, babysit start, babysit stop, stop).
Other useful commands:
aiswarm start --skip-grid ./swarm/<project>.yaml
aiswarm status ./swarm/<project>.yaml --brief -w
aiswarm broadcast ./swarm/<project>.yaml "AGENTS.md updated; please re-read it."
aiswarm broadcast --via-log ./swarm/<project>.yaml "use durable log"
aiswarm send ./swarm/<project>.yaml 0.0 "hello via log"
aiswarm log ./swarm/<project>.yaml --pending
aiswarm clear-comms ./swarm/<project>.yaml -y
aiswarm quota ./swarm/<project>.yaml
aiswarm av-usage ./swarm/<project>.yamlNote: broadcast and log-delivered messages are sent literally. Do not add synthetic sender prefixes, and keep slash commands like /clear unchanged.
Status/usage reliability note:
- monitor state is activity-based: any pane output means
working; 10 seconds without output meansidle - output content is not classified, so agent UI text changes do not affect state detection
- quiet long-running commands can appear idle, while continuous idle-screen redraws can appear working
usageis handled separately and remains best-effort; treat it as an operator hint
Attach after start if needed:
aiswarm start ./swarm/<project>.yaml --attachtmuxp-first flow:
tmuxp load ./swarm/<project>.yaml
aiswarm start --skip-grid ./swarm/<project>.yamlBuilt-in examples:
examples/swarm-single.yamlexamples/swarm-grid.yaml
- one tmux session
- one or more tmux windows
- each window has
window_name,layout, andpanes - pane command is
shell_command - nudge metadata is under
nudge.*(title,agent,monitor,babysit,comms) comms.enabled(defaults tomonitor) starts a per-pane worker that consumes the durable log and delivers on idle
Notes:
- pane IDs are derived as
W.N(window index, pane index) startcreates the tmux grid (session/windows/panes) according to the YAML. It is not safe to re-run after changing pane counts or layout on a live session (you'll be told to recreate the session).- One monitor per
monitor: truepane (started bystart) startensures the base worker loop (comms/message delivery) for monitored panes.babysit startenables the babysit prompt group (nudges etc.) for panes withbabysit.enabled: true. It does not affect the base comms worker loop.startandbabysit startwrite runtime files under/tmp/nudge-swarm/<session>/- runtime map:
/tmp/nudge-swarm/<session>/runtime.json - self-awareness note:
/tmp/nudge-swarm/<session>/self-awareness.txt
These are low-level helpers used by swarm tooling and power users:
attach.sh(monitor attach to pane)tmux-send(safe text+Enter send)
Use the built-in log for reliable agent-to-agent messages (durable, replayable, with cursors):
# direct to pane via log (buffered until consumer delivers on idle)
aiswarm send ./swarm/<project>.yaml 0.2 "review this"
# broadcast via log
aiswarm broadcast --via-log ./swarm/<project>.yaml "new plan"
# inspect
aiswarm log ./swarm/<project>.yaml --pending
aiswarm cursors ./swarm/<project>.yaml
aiswarm clear-comms ./swarm/<project>.yaml -yThe worker loop (started automatically by start for monitor: true panes) consumes the log and delivers via tmux-send when the pane is idle. babysit start additionally enables the prompt-nudge logic on top for configured panes.
Direct/manual still works with tmux-send.
When messaging panes manually, prefer tmux-send (or the log commands) over raw tmux send-keys.
When babysit.enabled: true and agent is one of claude, codex, or agy, the
babysitter samples remaining quota every quota_probe_secs seconds (default 300) and
uses an exponential moving average (EMA) to pace nudge intervals so quota is spread
evenly until the provider reset.
How it works:
- After each nudge the babysitter measures how much quota was consumed (
C = pct_before - pct_after) - EMA tracks the mean (
μ) and variance (σ) of consumption per nudge - Nudge interval:
τ = (time_to_reset × (μ + k_var × σ)) / (quota_remaining × safety) - For the first
ema_warmupnudges the fixedinterval_secsis used while the EMA warms up - The quota cache is pre-warmed in a background thread so probes never block the main loop
YAML knobs (all optional, defaults shown):
babysit:
quota_probe_secs: 300 # how often to sample quota
ema_alpha: 0.30 # smoothing factor (higher = reacts faster)
ema_safety: 0.92 # target fraction of quota (leaves ~8% buffer)
ema_k_var: 0.0 # variance weight; raise to 0.5–1.0 for conservative pacing
ema_warmup: 3 # nudges before EMA replaces fixed interval
ema_min_wait: 30 # hard floor (seconds)
ema_max_wait: 1200 # hard ceiling (seconds)The EMA is noisy when multiple swarms share the same provider quota — each instance independently estimates its own consumption rate. This is intentional: overestimation biases toward slower nudging, which is the right direction when quota is shared.
make build
make test
make test-c
make test-swarmPython helpers live in pyproject.toml:
uv syncFixture replay tests depend on real captured agent output in fixtures/*_capture.txt.
Fixtures now exercise real terminal byte streams rather than expected UI patterns. Re-capture when replay tests expose an input-handling issue or fixtures become stale.
Commands:
make capture AGENT=claude DUR=60
make capture_codex DUR=60
make capture_copilot DUR=60
make capture_gemini DUR=60
make capture_vibe DUR=60
make capture_qwen DUR=60
make capture_grok DUR=60
make capture_all DUR=60Practical cadence: re-capture on breakage or visible upstream CLI changes, not on a fixed schedule.
monitor-bin is the only monitor implementation.
Debug helpers:
MONITOR_DEBUG=1 ./attach.sh mysession claude
MONITOR_STATE_LOG=1 ./attach.sh mysession claude
MONITOR_IDLE_SECS=20 ./attach.sh mysession claudeDefaults:
MONITOR_DEBUG=1writes raw lines to/tmp/<session>_<window-pane>.rawMONITOR_STATE_LOG=1writes transitions to/tmp/<session>_<window-pane>.state.logMONITOR_IDLE_SECScontrols the quiet period beforeidle(default: 10)
The monitor deliberately reports activity, not semantic agent status: for all
agents except grok, any pane output means working until the quiet timeout,
while grok still relies on parsing OSC terminal-title updates where title
grok means idle and any other title means working.
Most of these are intentional trade-offs or already documented inline above:
- Changing pane counts / layout after
startrequires a full session recreate (startis create-oriented, not a declarative grid update) - Monitor is activity-based only (not semantic agent status); quiet long jobs can look idle, busy idle-screen redraws can look working
- Quota EMA pacing is noisy when multiple swarms share one provider quota (overestimates → slower nudges; usually the safe direction)
usage/ quota reporting is best-effort operator hint, not a hard scheduler
See backlog/tasks/ for planned work. Contributions welcome via issues or PRs
(see AGENTS.md).
Many of these are status indicators, session launchers, or general tmux managers. nudge's focus is swarm lifecycle (YAML start/stop), durable log-based pane comms, and quota-paced babysitting on top of activity monitors.