Skip to content

Repository files navigation

nudge

Kratos and Bia force an idle agent-droid back to work

Yet another multi-agent-in-tmux setup — kept small on purpose: comms (tmux-send + durable log, deliver on idle), loops (babysit nudges + backlog → free panes), and best-effort quota pacing. Config-driven YAML/tmuxp grids and a tiny activity monitor (working / idle). Not a full control plane.

aiswarm demo: init, start, shell-pane sends and backlog task dispatch
Demo (3× speed): aiswarm init --flavour demo → start → shell-pane ops / backlog tasks · mp4

Daily driver and loop harness: the same panes are where you sit and work with agents by hand (Claude, Codex, Grok, …), and where idle-gated comms / babysit / tasks keep things moving when you step back. Personal multi-agent workflows; works standalone.

Design philosophy

  • Agents choose their isolation. The shared project checkout is the default, not a restriction. An agent or human may use a branch, worktree, or other isolation when the task benefits from it; nudge does not make one worktree per agent part of the swarm model.
  • Keep the exit cheap. It should be easy to stop using nudge, replace one of its parts, or operate the underlying sessions by hand. Repositories, commits, Backlog tasks, tmux sessions, and agent-native sessions remain useful without nudge.
  • Compose optional layers. Tmux lifecycle, activity monitoring, durable messaging, babysit loops, task dispatch, and quota hints can be used independently. Nudge should not become a mandatory control plane merely because one feature is useful.
  • Prefer portable, inspectable mechanisms. Use ordinary processes, files, SQLite, YAML, tmux, and provider CLIs. Keep state and routing understandable from the command line and avoid making one model vendor the center.
  • Treat only the official CLI as durable subscription access. If a provider lets a user log in and run its CLI, nudge may operate that CLI as a black box. Do not make subscription OAuth, reusable auth tokens, API-compatible endpoints, embedded runtimes, or third-party bridges architectural dependencies; assume those paths can disappear even when they work today.
  • Coordinate without owning the work. Backlog and git hold durable intent and results; nudge wakes and routes workers. The harness should not become the only place where a task, decision, or result exists.

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.

aiswarm # workflow cheat sheet
aiswarm instructions # agent guides index
aiswarm instructions overview
aiswarm this # this swarm: config + runtime.json path
aiswarm <command> --help # flags

Install aiswarm into your uv tool environment:

make install-aiswarm

Default config (.aiswarm/config.yaml)

Consumer projects: harness lives under .aiswarm/ (not the Python package).

Resolution order for commands that need a config:

  1. Explicit path (aiswarm status path/to.yaml or -c path/to.yaml)
  2. $AISWARM_CONFIG
  3. Walk up from cwd for .aiswarm/config.yaml
aiswarm init myproject # writes .aiswarm/config.yaml + prompts (commit if team-shared)
aiswarm start # no path needed inside the project
aiswarm send 0.0 "hello"# same
aiswarm send any "check tests"# one eligible idle pane receives it
aiswarm status nudgeswarm/nudge.yaml # explicit still works (e.g. this implementer repo)

Note: in this repo, ./swarm/ is package code. Live harness is still nudgeswarm/ until you migrate; use an explicit path or $AISWARM_CONFIG here.

States: unknownworkingidle

Workflow

# 1. Create a starter config and AGENTS note (once per project)
aiswarm init <project>
# 2. Turn on the swarm (tmux session + panes + per-pane monitors + worker loops)# This is a "create" for the tmux grid, not a declarative update: if you change# pane/window counts in the YAML after the swarm is running, `start` will refuse# and tell you to recreate the session. Worker/monitor config is more forgiving# on re-start.
aiswarm start # uses .aiswarm/config.yaml when present# aiswarm start ./path/to.yaml # explicit override

Architecture notes: one tmux session per YAML file (session_name); one monitor (activity detector via monitor-bin) per monitor: true pane, each with its own Unix socket /tmp/<session>_<W.N>.sock; comms workers (log consumers that deliver on idle) start for monitored panes. Babysit is not turned on by start.

# 3. Turn on babysit for panes that have `babysit.enabled: true` in the YAML
aiswarm babysit start
aiswarm babysit start --for 1h # auto-stop the prompt group after an hour# aiswarm babysit stop turns babysit back off; swarm/monitors/comms stay up
# 3b. Optional: pull real work from backlog into free panes (separate from babysit)# Monitored panes: tasks enabled by default (opt out: nudge.tasks.enabled: false)# Dispatcher is off until you start it; -D prints fully resolved defaults
aiswarm tasks start
aiswarm tasks start --for 30m # auto-stop the tasks group after 30 minutes
aiswarm tasks status
aiswarm tasks once -D # dry-run: resolved config + planned claims
aiswarm tasks stop
# 4. Full teardown: stops tasks dispatcher + workers, kills monitors, tears down tmux
aiswarm stop

Other useful commands:

aiswarm start --skip-grid
aiswarm status --brief -w
aiswarm broadcast "AGENTS.md updated; please re-read it."
aiswarm broadcast --via-log "use durable log"# write to event log instead of direct send
aiswarm send 0.0 "hello via log"# durable, delivered on idle
aiswarm send any "investigate the failure"# durable, exactly one idle pane
aiswarm log --pending
aiswarm cursors
aiswarm clear-comms -y
aiswarm quota
aiswarm av-usage
# explicit path still ok: aiswarm status ./nudgeswarm/nudge.yaml

Note: broadcast and log-delivered messages are sent literally. Do not add synthetic sender prefixes, and keep slash commands like /clear unchanged. Direct/manual sends still work with tmux-send; prefer it (or the log commands) over raw tmux send-keys.

send any is the single-consumer counterpart to broadcast. It queues immediately, even when all panes are busy; the always-running comms worker atomically routes it to exactly one idle monitored pane. Panes holding local Backlog task assignments are skipped. This does not require aiswarm tasks start, and it does not add task completion or chase semantics.

Agent-to-agent handoff (do not stream peer panes)

Prefer short aiswarm send pokes + durable results in backlog + a short done-ping, instead of attaching to another agent's tmux pane and waiting on its stream.

See backlog/docs/doc-2 - Agent-to-agent-handoff-via-send-backlog-and-ping.md for the full example workflow and message templates.

Status/usage reliability note:

  • monitor state is activity-based: any pane output means working; 10 seconds without output means idle
  • 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
  • usage is handled separately and remains best-effort; treat it as an operator hint

Attach after start if needed:

aiswarm start --attach

tmuxp-first flow:

tmuxp load .aiswarm/config.yaml
aiswarm start --skip-grid

Built-in examples:

  • examples/swarm-single.yaml
  • examples/swarm-grid.yaml

Config model

  • one tmux session
  • one or more tmux windows
  • each window has window_name, layout, and panes
  • pane command is shell_command
  • nudge metadata is under nudge.* (title, agent, monitor, babysit, comms, tasks)
  • comms.enabled (defaults to monitor) is served by the session worker, which consumes each pane's durable log and delivers on idle
  • optional top-level tasks: configures a task-dispatch group in that same session worker (v1 source: backlog)

Notes:

  • pane IDs are derived as W.N (window index, pane index)
  • start creates 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: true pane (started by start)
  • start ensures one Python session_worker.py for the swarm; it multiplexes base comms/message delivery for monitored panes.
  • babysit start enables the babysit prompt group (nudges etc.) for panes with babysit.enabled: true. It does not affect the base comms worker loop. --for 1h (also 30m, 90s, or seconds) auto-disables that group when the deadline passes; babysit stop clears any timer.
  • session_worker.py is the one process shown in ps for a swarm, not one Python process per pane. It handles comms for every configured pane and enables babysit prompts only for panes in that group. pane_worker.py is retained as a compatibility entrypoint; babysit.py was renamed in this release.
  • tasks start enables a session-level group in that same worker that lists backlog tasks matching tasks.ingest (default: To Do + In Progress), claims them, and delivers a prompt via the durable log to free monitored panes (tasks enabled by default; opt out with nudge.tasks.enabled: false).
  • tasks stop / tasks start only toggle that group; they do not restart the shared worker or reload edited Python code. tasks start --for 1h writes a deadline into tasks/enabled.json; the session worker drops the group when it expires, and tasks stop removes the timer. Use aiswarm worker restart to load current installed or editable-source code without stopping tmux panes, agents, or monitors.
  • worker restart preserves pane specs, enabled group flags, task assignment state, and the durable comms database/cursors; it validates the recorded worker process before terminating it.
  • start, babysit start, and tasks start write runtime files under /tmp/nudge-swarm/<session>/
  • runtime map: /tmp/nudge-swarm/<session>/runtime.json (path via aiswarm this)
  • tasks dispatcher state and enable flag: /tmp/nudge-swarm/<session>/tasks/

Tasks dispatcher (backlog → free panes)

Why: fixed babysit “please continue” prompts waste tokens when real work already lives in backlog. The orchestrator must touch backlog itself (list + claim) so agents only receive a concrete task when free. Delivery uses the durable log so the existing idle consumer still gates tmux-send. Uses backlog task list|view --json only (Backlog.md BACK-545; git/main until the next release after 1.48.0 — do not scrape --plain).

# top-level (session)tasks:
source: backlog # v1 only; name stays generic for future sourcesbacklog_dir: ../backlog # optional; walks up for backlog/config.yml if omittedingest: [To Do, In Progress] # default; In Progress lets a restarted dispatcher recover claimspoll_secs: 60min_chase_secs: 60# min between chase re-prompts; default = poll_secsunassigned_only: truerequire_label: null # e.g. auto — only tasks with this labelclaim_assignee_prefix: aiswarm # assignee becomes aiswarm:<session>:<pane>require_idle: truevia_log: truemax_inflight: 0# 0 = unlimited; still one task per free panecomplete_statuses: [Done] # dep gate + assignment clear; case-insensitivewindows:
- window_name: gridpanes:
- shell_command: claudenudge:
agent: claudemonitor: truebabysit:
enabled: false # prefer not both on same pane (fights tasks chase)tasks:
enabled: true
aiswarm tasks start
aiswarm tasks start --for 1h # optional; auto-stop after an hour
aiswarm tasks status
aiswarm tasks once
aiswarm tasks stop

Claim happens before log delivery (In Progress + assignee). Completion is not inferred from pane idle — the agent (or human) marks the task Done via the backlog CLI. Local assignment state is cleared on the next poll when status is Done.

  • skip_assignees: (default [human]) assignees the dispatcher will not claim or reclaim. Use -a human to park a task for people. Empty list disables. Swarm ownership is only aiswarm:<session>:<pane> — never model names.
  • Dependency gate: any incomplete dependency (status not in complete_statuses, default Done) blocks claim/chase of the parent, regardless of who owns the dep. Same predicate clears local assignments. Link with backlog task edit TASK-NN --depends-on TASK-BLOCKER. Cycles / missing ids still block.
  • Chase: idle + still assigned + deps Done → short re-prompt until Done/unassign. Interval is min_chase_secs (default same as poll_secs). Raise it only if you need fewer nudges.

Each tasks once pass (and each poll from tasks start) assigns at most one task to each free pane. A pane is free when it has no local assignment or pending comms-log event and, by default, is idle (require_idle: true; monitor-unknown panes are also eligible). With N free panes and M candidate tasks, the pass claims min(N, M) tasks, further limited by max_inflight when it is greater than zero. For example, 3 free panes and 10 To Dos claims 3 tasks this pass; the other 7 wait until a later poll finds a newly free slot.

The dispatcher does not dump the whole To Do list onto one pane and does not queue multiple tasks on a pane while it has an assignment. aiswarm start does not start this dispatcher; run aiswarm tasks start (or a single aiswarm tasks once) explicitly.

Stalled assigned panes

After healthcheck_chases idle chases (default 3), the dispatcher sends one durable HEALTHCHECK with a nonce. The agent replies with aiswarm healthcheck pong <pane> <nonce>; consumer delivery acks do not count. No pong before healthcheck_timeout_secs (default 300) restarts only that pane, re-attaches its monitor, and queues a chase. healthcheck_max_restarts (default 1) caps retries; then the dispatcher leaves the assignment for an operator or later peer-checkup path. This is not a continuous heartbeat and never scrapes provider-specific error text.

Babysit quota pacing

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_warmup nudges the fixed interval_secs is 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 quotaema_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 pacingema_warmup: 3# nudges before EMA replaces fixed intervalema_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.

Build and test

make build
make test
make test-c
make test-swarm

Python helpers live in pyproject.toml:

uv sync

Capture fixtures

Fixture 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=60

Practical cadence: re-capture on breakage or visible upstream CLI changes, not on a fixed schedule.

Backend

monitor-bin is the only monitor implementation. Low-level helpers used by the swarm tooling directly (rarely needed by hand): attach.sh (monitor attach to pane), tmux-send (safe text+Enter send).

Debug helpers:

MONITOR_DEBUG=1 ./attach.sh mysession claude
MONITOR_STATE_LOG=1 ./attach.sh mysession claude
MONITOR_IDLE_SECS=20 ./attach.sh mysession claude

Defaults:

  • MONITOR_DEBUG=1 writes raw lines to /tmp/<session>_<window-pane>.raw
  • MONITOR_STATE_LOG=1 writes transitions to /tmp/<session>_<window-pane>.state.log
  • MONITOR_IDLE_SECS controls the quiet period before idle (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.

Rough edges / limitations

Most of these are intentional trade-offs or already documented inline above:

  • Changing pane counts / layout after start requires a full session recreate (start is 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).

Similar projects

The space of “run many coding agents in parallel” is large and growing (see awesome-agent-orchestrators). nudge sits in a narrower slice: config-driven tmux swarm lifecycle, a small activity monitor (working / idle), durable log messaging delivered only when idle, optional idle babysit, and optional backlog → free-pane task dispatch.

What is usually not the product center here (and may be elsewhere): big TUI control planes, one worktree per agent as the swarm model, desktop/Electron fleet UIs, or vendor-native agent teams.

Git worktrees are fine when an agent (or human) chooses them for a task; nudge does not invent or require them. The default swarm is a shared project cwd — coordinate via idle-gated messaging and backlog, not by forking the checkout per pane. Peers that do center worktree isolation (dmux, Claude Squad, thurbox, …) are solving a related but different problem.

At a glance

ProjectSubstrateSummaryvs nudge
NTMreal tmux · GoFull local control plane: spawn, dashboard, mail, safety, work graph, robot/APIClosest full-stack peer; broader operator surface
thurboxreal tmux · Rust TUIMulti-CLI sessions, worktrees, automations, tasks, inter-session mailbox, reviewClosest “productized TUI”; heavier than YAML grid + workers
dmuxreal tmux · NodeParallel agents, one worktree/branch per pane, merge/PR flowIsolation + git workflow first; not idle log gate
Claude Squad (cs)real tmux · Go TUIBackground multi-agent sessions, worktrees, diff review before applySession/worktree manager; not swarm log + babysit
multi-agent-shogunreal tmuxHierarchy (shogun → karo → ashigaru) across many CLIsRole tree orchestration vs declarative pane grid
Corraltmux + FastAPISelf-hosted control plane for local coding agentsServer/API flavor; different packaging
herdrown mux (Rust)Agent-aware multiplexer, status detection, persistent workspacesSmart terminal, not tmux YAML + log workers
tmux-idereal tmuxide.yml layouts, agent-team templatesLayout/IDE templates; lighter orchestration loop
cmuxmacOS terminalAgent-native terminal (panes/splits), not a thin tmux wrapperDifferent substrate; often compared in the same “fleet” conversation
Claude Code Agent Teamsnative / tmux panesVendor multi-agent teams (teammateMode)Claude-only product feature, not multi-provider harness

Closest peers (detail)

NTM (Named Tmux Manager) is the closest full-stack cousin: local-first, tmux-centric multi-agent orchestration for Claude / Codex / AGY / Grok. Both spawn labeled agent panes, send work across them, and aim to make parallel coding agents manageable.

They diverge on scope and center of gravity:

nudge (aiswarm)NTM
ShapeSmall Python package + C monitor-bin; YAML/tmuxp gridsLarge Go binary; TUI dashboard/palette, REST/SSE/WS, robot CLI
Core jobKeep panes productive: activity gate → durable log delivery → optional babysit / task claimFull local control plane: spawn, triage, mail, safety, checkpoints, pipelines
Work queueBacklog.md via aiswarm tasksBeads / br / bv graph triage, assign, queue-dry ideation
CommsPer-session durable log; deliver only when pane is idleAgent Mail + locks/reservations; human overseer mail surfaces
Idle / activityExplicit per-pane C monitor; idle gates sendActivity/health/watch; less central to the product story
BabysitOptional idle re-prompt loop (separate from start / tasks)Not a first-class idle babysit loop
SafetyThin (safe tmux-send; no policy engine)Policy, guards, approvals
DurabilityRuntime under /tmp/nudge-swarm/…; log cursors; backlog claimsCheckpoints, timelines, audit, pipeline resume under .ntm/
ConfigProject .aiswarm/config.yaml (tmuxp-compatible + nudge.*)~/.config/ntm/ + project .ntm/
Depstmux, agent CLIs; optional backlogIntentionally integration-heavy (br, bv, Agent Mail, …)
When to preferLean swarm harness, idle-gated messaging, backlog dispatchOperator dashboard, work-graph intelligence, safety/audit, APIs

Overlap: both treat tmux as the runtime for multi-agent coding. nudge optimizes for a small, config-driven “keep the swarm moving” loop; NTM optimizes for a broad operator control plane around that same idea.

thurbox is the closest productized TUI peer: multi-CLI agents in real tmux (or psmux on Windows), git worktrees, automations, built-in tasks, inter-session mailbox + wake nudges, native code review, headless thurbox-cli, optional SSH hosts. Agent definitions are data (agents.toml).

nudgethurbox
ShapeCLI + YAML; small Python + C monitorRust TUI + CLI; SQLite-backed session state
Core jobDeclarative swarm grid + idle-gated workersOperator TUI for many persistent agent sessions
IsolationShared cwd unless you arrange worktrees yourselfFirst-class worktrees, multi-repo sessions
CommsDurable log, deliver on idleInter-session mailbox with claim/drain + wake
WorkOptional backlog claim → free paneBuilt-in tasks + automations (cron/send/spawn)
Activity gateCentral (monitor-bin → idle delivery)Metrics/info panel; not the same “only send when quiet” model
When to preferScriptable YAML harness, idle-gated sends, backlog integrationRich session UX, review, multi-repo, automations, Windows/SSH

Overlap: real tmux, any coding CLI, messaging, keep many agents alive. nudge stays smaller and more “workers around a YAML grid”; thurbox is a full session product with review and automation surfaces.

dmux is a strong worktree-isolation peer: each pane gets its own git worktree and branch; multi-agent launch, merge and PR helpers, file browser, multi-project panes. Wide agent CLI support.

nudgedmux
ShapeConfig-first (aiswarm + YAML)Interactive TUI (dmux, n new pane)
Core jobSwarm lifecycle + idle messaging + task claimParallel tasks without git collisions
IsolationOptional / externalDefault (worktree + branch per pane)
Comms / babysitDurable log + optional idle babysitPane management; not the same durable idle queue
When to preferCoordinated swarm on a shared tree + backlogMany independent tasks, merge/PR as the endgame

Overlap: tmux + multi-CLI parallel coding. dmux optimizes for branch isolation and merge UX; nudge optimizes for shared-grid coordination and idle-gated delivery.

Claude Squad is a mature session manager TUI: tmux sessions + git worktrees, background agents (Claude, Codex, Gemini, Aider, …), attach/diff/checkout/resume, optional auto-yes.

nudgeClaude Squad
ShapeYAML grid + CLI workersSingle TUI (cs) over many sessions
Core jobKeep a fixed swarm productiveSpin up/manage many background tasks
IsolationNot the product centerWorktree per instance by default
Messaging / tasksDurable log, babysit, backlog dispatchSession list + diff review; less swarm mailbox
When to preferLong-lived multi-pane swarm with log routingMany one-off tasks in isolated workspaces

Overlap: tmux-backed multi-agent coding with a human overview. Claude Squad is instance lifecycle + worktrees; nudge is grid + idle workers + optional task feed.

multi-agent-shogun runs a hierarchy of coding CLIs in tmux (shogun → karo → ashigaru), multi-provider (Claude, Codex, Copilot, Kimi, …), with coordination meant to stay cheap (local, not an extra LLM control plane).

nudgeshogun
ShapeFlat YAML panes + roles via prompts/configExplicit feudal role tree
Core jobIdle-gated swarm + backlog claimHierarchical parallel army
When to preferDeclarative grid you start/stop as a unitRole-based multi-agent theater in tmux

Other related tools

Status / indicators / light managers:

General session layout (not agent-specific):

About

Yet another multi-agent-in-tmux setup. Small on purpose: durable log comms, babysit/task loops, best-effort quota. Daily driver + loop harness.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages