Fleet-of-sessions in tmux: Agent tool spawns new pane subagents instead of delegating to existing sessions

Status Open
Maintainer reply None cached
Activity 4 comments · opened Aug 13, 2026

Summary

When a Claude Code session acts as a lead/director for a fleet of long-running Claude Code sessions in tmux (one window per session), the Agent tool always materialises new teammate subagents in fresh tmux panes — there is no ergonomic way to say "prefer delegating to my existing sessions". Over a working day this produced 15+ transient panes alongside the six deliberately-provisioned sessions, which the operator experienced as clutter and duplicated capacity ("you've got 6 teammates that are actual full sessions — use those").

Topology

  • 7 tmux windows: 1 director session + 6 worker sessions (different repos/roles, some running the same project's autonomous loop).
  • The director coordinates via tmux send-keys to the worker sessions and receives their output by reading panes; cross-session SendMessage/ListAgents also works between them.
  • When the director needs parallel review/fix work, the natural tool is Agent — but every Agent call opens another pane-backed subagent, even when provisioned worker sessions are idle.

Friction points

  1. No "route to existing session" affordance in the delegation path. SendMessage can address the running sessions, but the Agent tool's lifecycle features (task notifications, structured completion, automatic resume) only apply to subagents it spawned. So the choice is: spawn a duplicate worker with good lifecycle integration, or use the real worker with none (send-keys + pane-scraping, plus the idle-vs-finished ambiguity that entails).
  2. Pane accumulation. Each spawned teammate holds its pane until explicitly shut down; a review swarm of 7 plus fix builders left double-digit panes to clean up manually (shutdown_request per agent works, but nothing like "close on completion" exists).
  3. Operator legibility. A human watching the tmux session cannot tell provisioned fleet members from transient subagents; both look like Claude sessions.

Suggestions (any subset would help)

  • An Agent-tool option (or default heuristic) to dispatch a task to an existing ListAgents peer instead of spawning, with the same completion-notification plumbing.
  • An auto_close: true spawn option so one-shot subagents remove their pane on final report.
  • A visual/naming distinction in pane titles between durable sessions and transient subagents.

Environment

Claude Code v2.1.229 (CLI), Linux (Ubuntu LTS), tmux; director model claude-fable-5, workers opus-5/sonnet-5.

View original on GitHub ↗

4 Comments

kcarriedo · 17 days ago

The "route to existing session" gap is the core issue here, and the workaround tension you describe is real: SendMessage gives you the real worker but drops all lifecycle integration, while Agent gives you lifecycle but materialises a duplicate.

One pattern that helps with the observation side: if your worker sessions write a short state entry on each task pickup (pid, session name, current task title, status) to a shared coordination file (e.g. .claude/fleet.json in the primary), the director can read that before deciding whether to SendMessage vs spawn. It does not solve the lifecycle integration problem, but it at least tells you "worker B is idle and ready" vs "worker B is mid-task, spawn a new one."

The deeper ask -- Agent tool respecting a named-session routing preference -- would need first-party support. Worth being explicit in the issue body about whether you want (a) a preference hint that routes to a running session when one is available, or (b) a way to attach lifecycle callbacks to an externally-launched session. Those are different features with different complexity profiles.

(Disclosure: I am working on Claudeverse, a session coordination layer for teams running multiple Claude Code instances. This exact routing problem is what motivated building it.)

powell-clark · 17 days ago

Follow-up with a full day's evidence: the topology described above just completed a ~15-hour continuous run — one director session steering six worker sessions across two repos — producing 42 reviewed merges, one plugin release, and surviving two GitHub Actions outages. Sharing what worked and where the friction concentrated, since long-horizon fleet runs seem like exactly the workload this tooling is growing toward. (The operator may attach the director transcript via /feedback separately.)

What worked unreasonably well

  • Cross-session SendMessage between peer Claude sessions. A second "director" session (different repo family) and this one coordinated two live infrastructure incidents by exchanging evidence-dense messages — measured spend telemetry, run-id evidence, agreed go/hold protocols. This was the single best primitive of the day: fast, reliable, and the <cross-session-message> framing with the permission-laundering warning is well designed.
  • Parallel Agent-tool review swarms with structured briefs. Seven adversarial reviewers (per-PR briefs demanding file:line findings, empirical repro, and verdicts) found HIGH defects in 8 of 10 "green" PRs — including a data-loss fix that itself lost data, reproduced by the reviewer executing the defective function. A later verification agent mutation-tested the fix's tests to prove they were load-bearing. The quality ceiling of briefed subagent swarms is very high.
  • Background task notifications + a PR-event monitor as wake signals. The director ran dark between events and was re-invoked ~80 times without polling waste.
  • shutdown_request protocol for cleaning up finished subagents worked exactly as documented.

Where the friction concentrated (each with a concrete ask)

  1. Sessions stop at turn boundaries and nothing re-prompts them. The dominant failure mode all day: a worker finishes a turn (often "waiting on CI"), goes idle, and stays idle until the director notices and sends a kick — roughly 15 manual kicks over the day. ASK: a standing continuation policy per session ("if idle and your queue/instruction says continue, take the next step after N seconds") or self-scheduled wakes for interactive sessions.
  2. /compact ends the turn and strands the session. Every remotely-managed compact needed a follow-up nudge; a message queued behind a compact races it. ASK: compact-and-continue semantics, or an on-compact-complete continuation hook.
  3. Director observability is pixel-scraping. tmux capture-pane cannot distinguish idle vs mid-turn vs dead. One worker's CLI process exited silently mid-afternoon; it looked identical to "idle" until a deep capture showed a bare shell. ASK: a queryable session-state surface (running/idle/exited, current turn elapsed, context %, last activity timestamp) — the same gap as this issue's ListAgents point, sharpened.
  4. Message delivery to a session is unverifiable. send-keys text was once swallowed by a focused agent-list panel; an Escape sent to recover then interrupted a live turn. ASK: first-class message-to-session with a delivery/queued acknowledgement (SendMessage between sessions has this; keyboard injection does not).
  5. Auto-compact did not fire before death. One session hit 100% context twice; the first time its CLI exited entirely rather than compacting. ASK: guaranteed auto-compact (or a configurable hard floor) for long-running sessions.
  6. Session identity across restarts. A replacement session computed the same identity as the dead session it replaced (history-fallback resolution in our own tooling, but the primitive gap is upstream): ASK — a stable, CLI-queryable session id with an explicit "fresh vs resumed" distinction would let plugin ecosystems avoid this class entirely.

Shape of the day in numbers

~15 directed hours · 42 merges (all reviewed or locally verified) · ~73 PRs opened · 1 release shipped · 2 account-level CI outages weathered (workers switched to local-verification merge gates on operator ruling) · ~50 director interventions, of which perhaps 35 would be unnecessary with asks 1–3 above.

Environment: Claude Code v2.1.229, Linux/tmux; director claude-fable-5, workers opus-5/sonnet-5, one worker running the repo's own autonomous-loop plugin.

powell-clark · 2 days ago

@kcarriedo thank you — this is the most useful reply I have had on any of my issues, and the part I want to act on is the last paragraph rather than the workaround.

You were right that I had bundled two different features into one ask. Splitting them explicitly, as you suggested:

(a) A routing preference hint — the Agent tool accepts something like "prefer an existing idle session matching this name/cwd, otherwise start a new one", and routes accordingly. Low complexity, solves the duplicate-worker problem, does not change the lifecycle model at all.

(b) Lifecycle attachment to an externally-launched session — take a session that already exists (started by a human, by a multiplexer, by anything) and attach the same completion callbacks, result capture and task tracking that the Agent tool gives its own children. Much higher complexity, since it means the lifecycle contract stops being "I created it, therefore I own it".

I want (a). (b) is the more powerful primitive and I would use it, but (a) is the one that removes the actual daily friction, and I would rather ask for the cheap thing that ships than the expensive thing that does not. Thank you for making me separate them — the issue was worse for having them fused.

One thing worth passing back, since it may save you work: the coordination file you describe now partly exists first-party. claude agents --json reports pid, cwd, kind, startedAt, sessionId, name and status per running session, which is most of the "worker B is idle vs mid-task" signal you were suggesting people hand-roll into .claude/fleet.json. I verified it on 2.1.250 today while checking a different issue. It does not carry the current task title, so you would still want your own layer for that, but the liveness and identity half is now free.

Good luck with Claudeverse — the disclosure was unnecessary but appreciated, and the routing problem is clearly real if we both independently built scaffolding for it.

kcarriedo · 2 days ago

Your (a)/(b) split is exactly the right way to frame it, and I think you are correct that (a) is the tractable near-term ask.

A concrete spec for (a) since you asked for precision: the Agent tool's invocation could accept an optional prefer_session field taking a session name or cwd pattern. Resolution order would be: (1) find a live, idle teammate session matching the pattern, (2) if one is found, route the task there as a SendMessage with lifecycle callbacks attached, (3) if none match or all are busy, fall through to spawning a new subagent as today. The user gets the existing worker when it is available, the new pane only when it is genuinely needed.

The "lifecycle callbacks attached" part is where complexity lives - you need the parent to treat the existing session's completion/error signals the same way it would treat a subagent it spawned itself. Whether that is implemented as a thin wrapper on SendMessage or as a separate "adopt this session for this task" primitive is an Anthropic implementation call, but the surface to the user can stay simple: one field, a session name, and the routing happens automatically.

Worth noting for your fleet topology specifically: the 15-hour run you described last week is the kind of workload that most benefits from this. Six provisioned worker sessions with clear roles, each of which the director has been steering via SendMessage all day, is exactly the case where "spawn a new pane" is the wrong answer every time. The director already knows those workers; it should be able to route through them.

The (b) primitive - adopting an externally-launched session into the lifecycle graph - is genuinely more complex and I would keep it off this issue to avoid muddying the (a) request.