Przejdź do głównej zawartości

tmux dla flot agentów: sesje, send-keys, wait-for

tmux dla flot agentów to warstwa multipleksera terminala wokół agentów kodujących: utrzymuje sesje przy życiu przez SSH, pokazuje, który agent czeka na człowieka, i skryptuje wiadomości oraz bariery między agentami. Claude Code, Codex i Cursor same tworzą swoje worktree gita, więc izolacja plików nie jest już zadaniem tmuxa. Do tmuxa należą trwałość, widoczność i komunikacja, która działa między różnymi agentami.

Ta strona jest dla deweloperów i tech leadów, którzy uruchamiają naraz trzech lub więcej agentów terminalowych, często na zdalnej maszynie. Sytuacja, którą rozwiązuje: pięciu agentów pracuje, jeden od 40 minut stoi na pytaniu o uprawnienia, drugi skończył i czeka na migrację, która weszła godzinę temu, a ty zamknąłeś laptopa w drodze do domu.

  • Wbudowane funkcje, po które sięgasz najpierw: claude --worktree … --tmux, zespoły agentów w podzielonych panelach i wiadomości między sesjami.
  • Skrypt bin/fleet, który rozdziela N zadań bez ani jednego polecenia git worktree.
  • send-keys, capture-pane i wait-for jako wiadomość, odpytywanie i barierę, z flagami, które chronią każde z nich przed cichą porażką.
  • Hooki, które zamieniają „ten agent stoi” w dokładny sygnał, oraz bramkę weryfikacji, dzięki której „gotowe” znaczy „testy przechodzą”, a nie „agent przestał pisać”.

Co Claude Code i Codex robią same, zanim zaczniesz skryptować tmuxa?

Dział zatytułowany „Co Claude Code i Codex robią same, zanim zaczniesz skryptować tmuxa?”

Potrzebujesz tmuxa 3.4 lub nowszego; każde zachowanie tmuxa na tej stronie sprawdzono na 3.4.

Okno terminala
brew install tmux # macOS
sudo apt install tmux # Debian, Ubuntu
tmux -V # wypisuje: tmux 3.4 (lub nowszy)

Najpierw sprawdź funkcje wbudowane. Każdą flagę poniżej sprawdzono w claude --help (Claude Code 2.1.283 dnia 2026-09-26, ponownie na 2.1.285) i codex --help (codex-cli 0.157.1).

claude --worktree NAZWA --tmux tworzy worktree i otwiera je we własnej sesji tmuxa. Tekst pomocy: --tmux „Create a tmux session for the worktree (requires —worktree). Uses iTerm2 native panes when available; use --tmux=classic for traditional tmux.”

Okno terminala
# terminal, w katalogu głównym repozytorium
claude --worktree feature-auth --tmux # panele iTerm2, jeśli są dostępne, w przeciwnym razie tmux
claude --worktree feature-auth --tmux=classic # zawsze zwykła sesja tmuxa

Worktree trafia do .claude/worktrees/feature-auth/ na gałęzi worktree-feature-auth. Jeśli potrzebujesz tylko jednej sesji na zadanie, możesz tu skończyć: ta flaga zastępuje większość skryptów „worktree + tmux”.

Zespoły agentów w podzielonych panelach. Zespoły agentów (agent teams) są eksperymentalne i domyślnie wyłączone; włączasz je zmienną CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1. Domyślny tryb wyświetlania to in-process: każdy członek zespołu działa w twoim głównym terminalu. Żeby każdy dostał własny panel, ustaw teammateMode w ~/.claude/settings.json:

{
"teammateMode": "auto"
}

"auto" używa podzielonych paneli, gdy już jesteś w tmuxie albo w iTerm2 z zainstalowanym CLI it2, a w pozostałych przypadkach wraca do trybu in-process. "tmux" wymusza podzielone panele i wykrywa tmux lub iTerm2; "iterm2" wymusza panele iTerm2 i wymaga it2 oraz opcji iTerm2 → Settings → General → Magic → Enable Python API. Podzielone panele nie działają w zintegrowanym terminalu VS Code, w Windows Terminal ani w Ghostty (dokumentacja agent teams).

Wiadomości między sesjami. Od v2.1.224 na macOS, Linuksie i WSL 2 (v2.1.234 na natywnym Windowsie) sesje Claude Code na jednej maszynie mogą wysyłać sobie wiadomości, a od v2.1.236 jedna sesja może poprosić drugą o jednorazowe powiadomienie, gdy ta następnym razem przejdzie w bezczynność. We flocie złożonej tylko z Claude Code zastępuje to większość opisanego niżej mechanizmu opartego na send-keys i wait-for, a wiadomość trafia między wywołaniami narzędzi, a nie jako naciśnięcia klawiszy (dokumentacja cross-session messaging).

Codex uruchamia sesję w zarządzanym worktree przez codex --worktree (dostępne też w codex exec), a codex queue --thread WĄTEK --message TEKST kolejkuje wiadomość dla istniejącej sesji, gdzie WĄTEK to UUID sesji albo jej dokładna nazwa.

Pełne porównanie funkcji wbudowanych, aplikacji desktopowych i zewnętrznych orkiestratorów znajdziesz w równoległej pracy agentów.

ObszarWłaścicielDlaczego nie ten drugi
Izolacja plikówAgentclaude --worktree, codex --worktree, worktree w Cursorze. Własny skrypt dubluje wspieraną funkcję
Izolacja runtime’u (porty, baza deweloperska, nazwy kontenerów)TyNikt tego za ciebie nie zrobi. Worktree izoluje pliki i nic więcej
Widoczność (który agent mnie potrzebuje)tmux albo claude agents we flocie złożonej tylko z Claude CodePasek statusu i choose-tree widzą wszystkich agentów; każdy agent widzi tylko siebie
Trwałość (zamknięta klapa, ponowne podłączenie przez SSH)Serwer tmuxaAgent umiera razem z terminalem, chyba że przeżyje coś poza nim
Wiadomości między agentamiWiadomości Claude Code, codex queue; tmux we flotach mieszanychtmux to jedyny kanał, który dociera tak samo do Claude Code, Codeksa i każdego innego agenta CLI

Jedna sesja tmuxa na zadanie, nie jeden panel na zadanie. Panele sprawdzają się do mniej więcej trzech agentów; potem są za wąskie, żeby czytać diff, i gubisz się, który jest który. Sesje mają nazwy, więc przeskakujesz do nich po nazwie. W każdej sesji trzymaj jedno okno na agenta i jedno na serwer deweloperski albo testy.

Okno terminala
tmux switch-client -t =agent-auth-refresh # skok po dokładnej nazwie
tmux choose-tree -Zs # przeglądanie całej floty jako drzewa

Wstaw flotę do paska statusu, żeby na pytanie „który agent mnie potrzebuje” odpowiadać bez przełączania się:

~/.tmux.conf
set -g status-left-length 80
set -g status-left '#{?client_prefix,#[reverse],}[#S] '
set -g status-right '#(tmux list-sessions -F "##{session_name}##{?session_attached,*,}" | tr "\n" " ")'
set -g status-interval 5
set -g history-limit 50000

Wewnątrz #() podwajaj każdy wewnętrzny #. tmux rozwija formaty w poleceniu, zanim je uruchomi, więc pojedyncze #{session_name} zamienia się w nazwę bieżącej sesji, a pasek stanu powtarza jedną nazwę tyle razy, ile jest sesji.

Bazową warstwę tmuxa (prefiks, nawigacja po panelach, powiadomienia) opisuje mistrzostwo terminala.

Jak rozdzielić zadania agentów jednym skryptem tmuxa?

Dział zatytułowany „Jak rozdzielić zadania agentów jednym skryptem tmuxa?”

bin/fleet tworzy jedną odłączoną sesję na zadanie i uruchamia w niej agenta. Zwróć uwagę, czego brakuje: żadnego wywołania git worktree. To należy do agenta.

#!/usr/bin/env bash
# bin/fleet -- jedna sesja tmuxa na zadanie; agent sam zarządza swoim worktree
set -euo pipefail
[ $# -gt 0 ] || { echo "usage: FLEET_AGENT=claude|codex bin/fleet <slug> [slug...]" >&2; exit 1; }
agent="${FLEET_AGENT:-claude}"
for slug in "$@"; do
# send-keys pisze do powłoki, więc slug w rodzaju 'x;id' uruchomiłby `id`.
case "$slug" in
*[!a-zA-Z0-9._-]*|"") echo "invalid slug: $slug" >&2; exit 1 ;;
esac
case "$agent" in
claude) cmd="claude --worktree $slug --name $slug" ;;
codex) cmd="codex --worktree" ;;
*) echo "unknown FLEET_AGENT: $agent" >&2; exit 1 ;;
esac
session="agent-$slug"
# '=' wymusza dokładne dopasowanie; bez niego 'agent-api' trafia w 'agent-api-v2'
if tmux has-session -t "=$session" 2>/dev/null; then
echo "exists, skipping: $session" >&2
continue
fi
tmux new-session -d -s "$session" -c "$PWD" -n agent
tmux send-keys -t "=$session:agent" -l "$cmd"
tmux send-keys -t "=$session:agent" C-m
tmux new-window -d -t "=$session" -n run -c "$PWD"
done
first="=agent-$1"
if [ -n "${TMUX:-}" ]; then tmux switch-client -t "$first"; else tmux attach -t "$first"; fi
Okno terminala
bin/fleet auth-refresh rate-limit-headers stripe-webhook-idempotency

Co zobaczysz w innym terminalu:

Okno terminala
$ tmux ls
agent-auth-refresh: 2 windows (created Sat Sep 26 10:02:11 2026)
agent-rate-limit-headers: 2 windows (created Sat Sep 26 10:02:11 2026)
agent-stripe-webhook-idempotency: 2 windows (created Sat Sep 26 10:02:11 2026)

Trzy sesje, trzech agentów, trzy worktree w .claude/worktrees/. Sesja, do której się podłączyłeś, ma dodatkowo dopisek (attached). --name dodatkowo czyni każdą sesję Claude adresowalną jako @auth-refresh w wiadomościach między sesjami.

Jak wysłać wiadomość do innego agenta przez tmux send-keys?

Dział zatytułowany „Jak wysłać wiadomość do innego agenta przez tmux send-keys?”

send-keys pisze do panelu. Agent nie odróżni tego od twojego pisania i właśnie dlatego działa to jako kanał między różnymi agentami. Między dwiema sesjami Claude Code wybierz wiadomości między sesjami; send-keys stosuj, gdy po jednej stronie jest Codex, inny agent CLI albo skrypt.

Okno terminala
tmux send-keys -t =agent-api:agent -l "The migration in agent-auth-refresh landed on main. Rebase and re-run the integration tests."
tmux send-keys -t =agent-api:agent C-m
  • -l wysyła tekst dosłownie. Bez tej flagi tmux interpretuje słowa takie jak Enter czy C-c w twojej prozie jako nazwy klawiszy.
  • Zatwierdzaj osobnym wywołaniem. C-m to nazwa klawisza, więc nie może jechać w argumencie -l.
  • Podawaj w dokładnym celu okno. Dopasowanie dokładne = wymaga części okna (=nazwa: albo =nazwa:okno) w poleceniach panelu, takich jak send-keys i capture-pane; polecenia sesji (has-session, attach, switch-client) przyjmują samo =nazwa. Na tmux 3.4 samo -t =agent-api kończy się tu błędem can't find pane.
Okno terminala
tmux capture-pane -p -J -t =agent-api:agent -S -200

-p wypisuje na stdout. -J skleja zawinięte linie; bez tego długa ścieżka albo ramka stosu jest dzielona na szerokości terminala i twój grep jej nie znajdzie. -S -200 zaczyna 200 linii wstecz w historii przewijania.

Zapasowy nadzorca zbudowany na tym poleceniu:

#!/usr/bin/env bash
# bin/agent-watch <session> -- zgłasza, gdy agent potrzebuje człowieka
set -euo pipefail
session="$1"
while tmux has-session -t "=$session" 2>/dev/null; do
tail=$(tmux capture-pane -p -J -t "=$session:agent" -S -40)
case "$tail" in
*"Do you want"*|*"Allow "*|*"(y/n)"*) echo "BLOCKED $session"; break ;;
*"error:"*|*"FAIL"*) echo "ERROR $session"; break ;;
esac
sleep 15
done

To scrapowanie ekranu i nowe sformułowanie pytania w agencie je zepsuje. Właściwym mechanizmem są hooki, opisane niżej; ta pętla to rozwiązanie zapasowe dla agentów bez hooków.

Jak kazać jednemu agentowi czekać na drugiego przez wait-for?

Dział zatytułowany „Jak kazać jednemu agentowi czekać na drugiego przez wait-for?”

wait-for to bariera: czekający proces blokuje się, dopóki ktoś nie da sygnału na kanale. Nazwy kanałów żyją na serwerze tmuxa, więc dowolny panel może dać sygnał dowolnemu innemu bez współdzielonych plików.

Okno terminala
tmux wait-for -S migration-applied # w agencie roboczym, po udanym kroku
Okno terminala
tmux wait-for migration-applied # w orkiestratorze: blokuje do sygnału
tmux send-keys -t =agent-api:agent -l "Migration applied. Rebase onto main and re-run integration tests."
tmux send-keys -t =agent-api:agent C-m

W tmux 3.4 sygnał wysłany, zanim ktokolwiek czeka, jest zapamiętywany jeden raz, więc następne wait-for wraca natychmiast. To usuwa jeden wyścig, ale oznacza też, że nieaktualny sygnał z poprzedniego przebiegu zwolni czekającego za wcześnie.

Odpytywanie wnioskuje stan z wyrenderowanego tekstu. Hooki odwracają kierunek: agent mówi tmuxowi, co się stało, w chwili, gdy to się dzieje. W Claude Code hook Stop zmienia nazwę okna i daje sygnał na kanale nazwanym od własnej sesji; hook Notification oznacza „potrzebuję cię”. Wstaw to do .claude/settings.json (projekt) albo ~/.claude/settings.json (użytkownik):

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "S=$(tmux display-message -p -t \"$TMUX_PANE\" '#{session_name}' 2>/dev/null); tmux rename-window -t \"$TMUX_PANE\" done 2>/dev/null; [ -n \"$S\" ] && tmux wait-for -S \"done-$S\" 2>/dev/null; true"
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "tmux rename-window -t \"$TMUX_PANE\" NEEDS-YOU 2>/dev/null; tmux display-message -d 3000 'agent needs approval' 2>/dev/null; true"
}
]
}
]
}
}

$TMUX_PANE jest ustawiona w każdym panelu tmuxa, więc hook celuje w siebie. 2>/dev/null i końcowe true sprawiają, że poza tmuxem hook nic nie psuje. Nazwa kanału jest wyprowadzona z sesji, bo wait-for nie przenosi żadnej treści: jeden globalny kanał agent-done pozwoliłby pierwszemu agentowi, który się zatrzyma, obudzić wszystkich czekających. Codex też ma zdarzenie hooka Stop (12 zdarzeń w 0.157.1). Czyta ten sam kształt JSON z pliku hooks.json obok swojej konfiguracji (~/.codex/hooks.json albo .codex/hooks.json w zaufanym projekcie) lub z odpowiadających mu tabel w config.toml:

~/.codex/config.toml
[[hooks.Stop]]
[[hooks.Stop.hooks]]
type = "command"
command = "..." # to samo polecenie Stop co wyżej

Codex uruchamia nowy hook dopiero wtedy, gdy mu zaufasz; przejrzyj go w /hooks.

Jak zweryfikować wynik floty bez czytania każdego diffa?

Dział zatytułowany „Jak zweryfikować wynik floty bez czytania każdego diffa?”

tmux czyni rozdzielanie pracy tanim, więc łatwo przekroczyć własne możliwości przeglądu. Rozwiązaniem jest bramka, przez którą przechodzi każdy agent, zanim ktokolwiek przeczyta jego diff. Każde zadanie przepuść przez tę samą pętlę:

  1. Planuj z warunkiem zaliczenia. Każdy slug zadania dostaje jedno polecenie akceptacyjne przed startem agenta, na przykład npm run test:api. Zadanie bez niego nie trafia do floty.

  2. Buduj we własnej sesji. bin/fleet uruchamia agenta; okno run zostaje wolne na serwer deweloperski.

  3. Weryfikuj na sygnał, a nie na słowo agenta. Gdy odpali done-agent-<slug>, uruchom polecenie akceptacyjne w worktree, poza agentem:

    Okno terminala
    slug=api-rate-limit
    if ! timeout 3600 tmux wait-for "done-agent-$slug"; then
    echo "agent-$slug: no done signal within an hour; tests not run" >&2
    elif (cd ".claude/worktrees/$slug" && npm run test:api); then
    tmux wait-for -S "ok-$slug"
    else
    tmux send-keys -t "=agent-$slug:agent" -l "npm run test:api fails in your worktree. Fix it; do not edit the tests."
    tmux send-keys -t "=agent-$slug:agent" C-m
    fi

    Przekroczenie limitu czasu pomija testy, zamiast uruchamiać je na niedokończonym worktree. Gałąź else wpisuje prozę, co pasuje do interaktywnego agenta; agent Codex w trybie exec potrzebuje tam polecenia (zobacz zakładkę Codex niżej).

  4. Przegląd robi agent, a człowiek czyta podsumowanie. Uruchom /code-review w gotowym worktree (Claude Code) albo codex review (Codex) i czytaj ustalenia, a nie każdą linię.

  5. Wdrażaj przez CI. Pull request trafia do gałęzi głównej tylko przy zielonym CI; zatwierdza go człowiek odpowiedzialny za zadanie. Zadania zależne czekają na ok-<slug>, nigdy na done-agent-<slug>.

Trzymaj testy poza zasięgiem agenta, inaczej bramka niczego nie mierzy: zobacz ochronę wyroczni i jak silna jest twoja wyrocznia.

claude --worktree NAZWA tworzy .claude/worktrees/NAZWA/ na gałęzi worktree-NAZWA; --worktree "#1234" odgałęzia się od pull requesta. Przy wyjściu Claude automatycznie usuwa czyste worktree sesji bez nazwy; sesja nazwana (bin/fleet używa --name) i każde worktree z pracą dostają pytanie „zachować czy usunąć”. Po zachowaniu Claude wypisuje polecenie claude --worktree <nazwa> --resume, którym do niego wrócisz. worktree.baseRef ma domyślnie wartość "fresh" (domyślna gałąź zdalna); ustaw "head", gdy agenci muszą budować na twojej niewypchniętej pracy.

Dla agenta w trybie headless (bez interakcji) claude -p --worktree NAZWA "zadanie" pomija sprawdzenie zaufania, ale nie ma pytania przy wyjściu, więc nic nie usuwa worktree. Sprzątaj przez git worktree remove (jeśli git odmawia, najpierw git worktree unlock). We flocie złożonej tylko z Claude Code claude agents (agent view, research preview) pokazuje sesje w tle pogrupowane według stanu; zobacz agent view.

Jak utrzymać flotę przy życiu po zamknięciu laptopa?

Dział zatytułowany „Jak utrzymać flotę przy życiu po zamknięciu laptopa?”
  1. Odłączaj się zamiast wychodzić. Naciśnij prefiks, potem d. Agenci dalej pracują; tmux attach -t =agent-auth-refresh podłącza cię do nich z powrotem, także z innej maszyny.

  2. Uruchom serwer tam, gdzie może działać bez przerwy. Mały VPS albo zapasowy desktop sprawia, że zamknięcie laptopa niczego nie zatrzymuje: ssh -t box tmux attach -t =agent-auth-refresh. Dostęp do sesji Claude Code z telefonu porównuje strona klienci zdalni i mobilni.

  3. Przygotuj się na restart świadomie. tmux-resurrect przywraca układ sesji i okien, ale nie rozmowy agentów. Zainstaluj go przez TPM, dodając set -g @plugin 'tmux-plugins/tmux-resurrect' do ~/.tmux.conf; zapisujesz prefiksem i Ctrl-s, przywracasz prefiksem i Ctrl-r. Połącz go ze wznawianiem w samym agencie: claude --resume albo claude --worktree NAZWA --resume dla zachowanego worktree oraz codex resume.

  4. Najpierw zwiększ historię przewijania. Domyślny history-limit to 2000 linii; jeden refaktor go przepełnia, a capture-pane nie odczyta tego, co tmux już wyrzucił.

Zellij pokrywa połowę związaną z trwałością i wiadomościami: nazwane sesje, od których się odłączasz i do których wracasz, oraz zellij action write-chars i zellij action dump-screen, które robią to samo co send-keys i capture-pane. Dla flot liczą się dwie luki. Flaga --tmux w Claude Code i zespoły agentów w podzielonych panelach celują w tmux lub iTerm2, nie w Zellija. A definicja CLI Zellija na gałęzi main, odczytana 2026-09-26, nie ma odpowiednika wait-for, więc bariery wymagają plików albo gniazd. Wybierz Zellija, jeśli już w nim pracujesz, a flota jest mała; do skryptowanych flot wybierz tmux.

Na 2026-09-26 tmux ma 49 505 gwiazdek na GitHubie, a Zellij 35 546 (GitHub, odczyt 2026-09-26). Sam tmux nie dodaje nic do okna kontekstu agenta; każda wiadomość wysłana przez send-keys albo przez wiadomości między sesjami kosztuje tokeny jak wpisany prompt.

Dwa worktree na zadanie albo agent usuwa katalog, którego oczekuje twój skrypt. Skryptujesz worktree, którymi agent już zarządza. Usuń wywołania git worktree; właściciel jest jeden: agent.

Wiadomość trafiła do złej sesji. tmux dopasował prefiks: -t agent-api trafia w agent-api-v2, gdy agent-api nie istnieje (odtworzone na tmux 3.4). Używaj dokładnych celów: -t =agent-api w poleceniach sesji (has-session, attach, switch-client) i -t =agent-api:agent (albo =agent-api:) w poleceniach panelu (send-keys, capture-pane).

Twoja instrukcja odpowiedziała na pytanie o uprawnienia. send-keys wpisało tekst w okno tak/nie. Odzyskaj kontrolę: odczytaj panel, odrzuć albo cofnij to, co zrobił agent, i wyślij ponownie. Zapobiegaj przez capture-pane przed każdym zapisem albo przekazuj pracę na podstawie hooków.

Wiadomość dotarła zniekształcona albo zabiła agenta. Zabrakło -l i tmux odczytał Enter lub C-c w twojej prozie jako klawisze. Dodaj -l i wysyłaj C-m osobno.

grep na wyjściu capture-pane nie widzi oczywistego dopasowania. Zawinięte linie rozdzieliły ścieżkę. Dodaj -J.

Zadanie zależne ruszyło za wcześnie. Albo czekało na done-agent-* (które odpala się co turę), albo zwolnił je nieaktualny, zapamiętany sygnał. Uzależniaj od ok-* z kroku testowego i zaczynaj każdy przebieg ze świeżymi nazwami kanałów.

wait-for wisi w nieskończoność. Agent padł przed linią z sygnałem. Sygnalizuj obie ścieżki (&& tmux wait-for -S done-x || tmux wait-for -S fail-x) i opakowuj oczekiwania w timeout.

Podzielone panele się nie pojawiają. Pod "auto" jesteś poza tmuxem, w nieobsługiwanym terminalu (VS Code, Windows Terminal, Ghostty) albo brakuje it2. Uruchom sesję w tmuxie albo ustaw "tmux" i sprawdź which tmux.

Sesja tmuxa przeżywa swój zespół agentów. Uruchom tmux ls i tmux kill-session -t NAZWA dla sesji utworzonej przez zespół.

Wszyscy agenci stają naraz. Każdy panel zużywa limit tego samego konta; agent, który „zawisł”, często trafił na limit. Sprawdź panel, zanim zaczniesz debugować skrypt. Wpływ równoległej pracy na wydatki opisuje optymalizacja kosztów, a pułap na jednego dewelopera równoległość zespołowa.