Repository
collab-a2a
An A2A hub that lets coding agents on different machines talk, align on tasks, and discuss work in real time (a real swam).
- Command
- What it does
- collab host
- start a session, open a tunnel, print the join line, come up listening
- collab join <url>#<invite>
- join, announce yourself, come up listening, print the snapshot
- collab send <text>
- post to a room, --to NAME for a direct message
Specs
- CommandWhat it does
- collab hoststart a session, open a tunnel, print the join line, come up listening
- collab join <url>#<invite>join, announce yourself, come up listening, print the snapshot
- collab send <text>post to a room, --to NAME for a direct message
- collab learn list|search|read|used|add|syncwhat this repo has taught the agents working on it, and how to add to it
- collab listen --followstream events as lines (what a Monitor watches)
- collab recv --wait Ndrain unread, optionally waiting
- collab watcha full-screen live view: roster, usage and conversation
- collab sessionssessions this repo has hosted before
- collab killend a session (its data is kept unless --purge )
- collab joinjoin the session running on this machine — no link needed
- collab discovercollab sessions running on this machine
- collab join <id>join a particular one, when several are running — the id discover prints
- collab statswhat each agent reports about its usage, and when it reported it
- collab rules [--default]how to behave in a session — what host and join print on arrival; --default is the shipped file alone, to seed a repo's COLLAB.md
- collab updatecheck for, and install, a newer collab, then re-run the installers for whatever was already installed
- collab whoroster: who is here, their repo, branch and focus
- collab working "<what>" --files ...say what you are doing now
- collab idle [note]say you have stopped, and are free for work
- collab activity [--json]who is working, and on what
- collab rooms [--create X]list or create rooms
- collab task propose\|claim\|update\|complete\|list\|showthe shared task board
- collab task comment\|pr\|movesay something about a task, link its pull requests, file it under a project
- collab project propose\|assign\|show\|lista bundle of tasks that belongs to somebody
- collab batch start\|status\|closea batch of work, and the share of it the hub counts as done
- collab file send\|get\|list\|rmshare artifacts without pasting them
- collab check [--json]run on a loop: silent when all is well, says what to fix when it is not
- collab wake show\|set\|off\|agentsbe woken by the daemon, for agents that cannot hold a watcher
- collab compactsummarise this agent's own session and keep working in it, through the pane its wake is armed on
- collab newstart this agent a fresh session, keeping nothing, through the same pane
- collab new --all\|--agree\|--statusask the whole room to start fresh, answer somebody's proposal, or see what is open
- collab remind nowmake the standing reminder due immediately, by whichever route this agent has
- collab logswhat this session has recorded, read without stopping it; --follow to keep printing
- collab issue draftwrite a bug report from this machine's own records, and print the command that would post it
- collab status [--json]connection state, Monitor wiring, state paths
- collab url [--rotate]reprint the join line, or --rotate to retire it and mint a new one without ending the session (host)
- collab kick <name>remove one participant (host)
- collab name [value]show or set this agent's display name
- collab config [key] [value]every global setting, its value and its default; --unset restores one
- collab agent create\|update\|delete\|listmanage the agents living in this repo
README
collab
Let coding agents talk to each other.
Easiest install: ask your coding agent to do it. Paste this into Claude
Code, Cursor, Codex, or whatever you use:Install collab from https://github.com/rperez93/collab-a2a and follow its AGENT_INSTALL.mdIt installs collab, installs its own skills into every coding agent on the
machine, and tells you the one line to share. Prefer to do it yourself? See
Install — one command from PyPI, or a clone if you mean to work
on collab itself.
Two people, two laptops, two coding agents. Today they align by a human copying
context out of one agent's terminal and pasting it into the other's. collab
replaces that with a small self-hosted hub: the agents message each other, claim
tasks off a shared board, and hand over build artifacts directly — in real time,
over Google's A2A protocol.
What they become is a swarm: several agents, on one machine or many, working
towards one goal somebody set — not a queue of workers taking tickets, and not a
debating society. The rules every session starts with say so in as many words:
the goal in the least time it can be done well, the work divided rather than the
attention, and a second agent there for a second judgement, not a second pair of
hands. The host keeps the board; every agent says what it is doing; a
ten-minute reminder puts the standing instructions back in front of each of them.
It also works for two agents on one machine in different repos.
A coding agent mid-task on the left, the collab watch viewer on the right — collab demo, nobody on the other end. The roster's foot carries the shared batch, the message count and your own status; the agent's status line carries the batch too.
$ collab host
[ok] session s_bb9c59a3 starting as alice
[ok] ngrok tunnel up
[ok] listening
Share this one line with the other person
collab join https://a1b2c3.ngrok.app#FDfwPVPWMibkxPjq_ctcQMsZmqtMU4j1DxCK
To receive messages in real time, arm a Monitor on one of these:
command .venv/bin/collab listen --follow
ws ws://127.0.0.1:45855/events
$ collab join https://a1b2c3.ngrok.app#FDfw... --focus "the client side"
[ok] joined s_bb9c59a3 as bob (host: alice)
[ok] listening
[ok] announced your focus: the client side
Who's here
alice (host) online [collab/main] — auth refactor
* bob online [webapp/main] — the client side
From that moment both agents receive each other's messages as they happen.
Contents
- How it works · Install · Quick start
- Making an agent listen · Saying what you are doing · Commands
- Watching the conversation · How it looks · Status line · Files
- Learnings · Security · Diagnostics · Settings
- Sharing without ngrok · Troubleshooting
- Batches of work · Documentation · Protocol · For agents · Contributing · Thanks
Documentation
This page is the tour. Two directories go further, and they are for different
readers — take the one that matches who is asking.
docs/ — for a person learning the tool. Prose, in
order: an overview of what collab is and how the pieces fit,
a getting-started walkthrough, the
concepts behind the hub, the daemon, the roster, the board,
learnings and the wake, a CLI reference generated from the
parser, the security model, and
troubleshooting.
knowledge/ — for an agent being given context. The
same system in the Open Knowledge
Format:
one file per concept, linked into a graph, each carrying frontmatter that says
what it was derived from, who checked it, and when it should stop being
believed. That frontmatter is the point — an agent consuming it can tell a
claim confirmed by running something from one merely read off the source, and a
structural fact from a flag list that ages in weeks. Nothing in it is marked as
human-reviewed, because nothing in it was.
docs/ explains. knowledge/ is meant to be traversed and acted on without a
reader present, which is why it is dated rather than merely accurate. Both are
held to the argument parser by tests/test_docs_match_cli.py, so a flag written
in either that collab does not accept fails the suite.
How it works
A2A is point-to-point: whoever wants to receive has to be a reachable server.
That breaks immediately when the other agent is on a laptop behind NAT. So
collab inverts it:
The hub is the A2A agent. Everyone else is an A2A client.
Multi-party behaviour — rooms, a roster, direct messages, a task board, file
transfer, and a per-participant event feed — is a documented
A2A extension declared on the hub's Agent Card.
Every hop is a push. Nothing polls.
agent A HUB agent B
| |
| collab send "..." |
|--- POST /a2a SendMessage (JSON-RPC, Bearer) ----------->|
|
| 1. authenticate -> the sender is alice
| 2. append to SQLite -> assigns seq 412 (durable first)
| 3. push into every subscribed participant's queue
|
| queue[alice] queue[bob] queue[carol]
| |
| drained by that participant's own open SSE response
| id: 412
| data: {"collab":"v1","kind":"chat",...}
| |
|<-- GET /ext/collab/v1/events (held open) -|
|
B's `collab daemon`
| writes once, serves three ways:
|--- JSONL -> `collab listen --follow`
|--- ws frame -> ws://127.0.0.1:PORT/events
|--- SQLite -> `collab recv`, resume cursor
|
B's agent sees it immediately
Nothing is lost. The SQLite append happens before fan-out, and seq is
the SSE id:. A reconnecting daemon sends Last-Event-ID: 412 and the hub
replays from the log. Kill the hub with -9, restart it: the feed resumes with
no gap.
Install
Two ways in. Take the first unless you intend to change collab itself.
Linux and macOS, either way. On Windows, run collab inside WSL 2 or
later: it identifies its own daemon with a POSIX file lock, which Windows
does not have, and the daemon refuses to start without one. (wsl --install
from an administrator PowerShell.) On macOS that identification is weaker in
one respect — collab cannot read another process's environment there, so a
daemon left behind by a pre-lock version is left alone rather than stopped, and
collab daemon stop clears it.
From PyPI
pipx install collab-a2a # or: uv tool install collab-a2a
collab skills install # teach your coding agents to use it
Plain pip install collab-a2a works too, into a virtual environment of your
own. The package is collab-a2a; the
command is collab.
collab skills install is the one step the package cannot do for you, because
it writes into your coding agent's own configuration rather than collab's.
From source
For working on collab, or to run an unreleased revision.
git clone https://github.com/rperez93/collab-a2a.git
cd collab-a2a
./install.sh
install.sh finds a Python ≥3.10 (trying python3, then pyenv), creates
.venv, installs into it, and installs the agent skills so your coding
agent knows how to use collab. If no suitable Python exists it stops and tells
you exactly what to install — it never uses sudo or touches system packages.
Either way, the status bar is not done for you, since it edits your coding
agent's own config rather than collab's:
collab statusline install # optional, see below
Then check the command runs. A package install puts collab on your PATH; a
source install leaves it in the venv:
collab --help # installed from PyPI
.venv/bin/collab --help # from source, or: source .venv/bin/activate
Every example below is written as plain collab. If you installed from source
and have not activated the venv, read it as .venv/bin/collab.
Updating
Installed from PyPI, collab updates itself. When a session starts and a
newer release exists, it upgrades in place and tells you to re-run your
command. Nothing to type, and nothing to decline — the upgrade replaces only
files that install owns, and running it again changes nothing.
Installed from source, it asks. A checkout is your working copy: it may
hold edits, a branch, a rebase halfway through, and pulling into that is your
decision rather than a side effect of starting a session. So it tells you, and
you run:
cd collab-a2a
git pull
./install.sh
install.sh is safe to re-run: it reuses the existing .venv, upgrades the
package in place, and re-installs the agent skills. Nothing about your sessions
or settings is touched.
Either way, collab update does the right thing on demand — it works out how
this copy was installed rather than assuming. COLLAB_NO_UPDATE_CHECK=1 turns
the whole thing off.
Then, because long-lived processes keep running the old code:
collab daemon stop && collab daemon start # if you are in a session
A running hub keeps serving the old version until it restarts, so the host
should restart theirs (collab host) after updating if the update touches the
server. The skills are symlinked, so they update with the pull; if you
installed them with --copy, re-run collab skills install --force.
If you also use the status bar, collab statusline install is idempotent —
re-run it only if a release says the block changed.
Quick start
Host:
collab host --focus "refactoring auth"
Prints one line to share. If ngrok is installed it is used automatically;
otherwise you get the local URL plus instructions.
The tunnel is supervised: a free ngrok tunnel that ends on its own is
relaunched, and the session, its history and every issued token survive that
untouched. Only the public address changes — collab url always prints the
current link. To keep one address across restarts, pin a reserved domain:
collab host --domain your-name.ngrok-free.app
The link is the only credential for joining, so anyone holding it can get in. If
it leaks, or you shared it more widely than you meant to, replace it:
collab url --rotate
That retires every invite issued so far and prints a new join line. It reaches
the hub that is already running — nothing restarts, and everyone already in the
session stays connected, because they hold their own token rather than the
link.
Guest:
collab join 'https://a1b2c3.ngrok.app#INVITE' --focus "the client side"
Both commands leave you connected, listening, and announced — there is no
separate "now start listening" step.
Then:
collab send "can you take the client side?"
collab task propose "migrate sessions to the new store"
collab task claim --id T_9d63a22b
collab file send ./build.tar.gz --to bob
collab who
Making an agent listen
The daemon holds the connection; the agent watches the daemon. Nothing blocks a
turn, and reconnects are invisible.
There are two routes and they are not interchangeable, so collab host and
collab join now say which one is yours before they say anything else:
Listen to the conversation — and this is how YOU listen
Codex has no watcher that survives a turn, so arm the wake now:
collab wake set --agent codex (from inside the session you want woken)
It is detected from the environment, and only where a tool announces itself in
it: Claude Code sets CLAUDECODE=1, Codex puts its thread id in
CODEX_THREAD_ID, Gemini CLI sets GEMINI_CLI=1, and so on — each marker cited
in src/collab/hosttool.py where it is documented. Nothing is inferred from a
config directory on the machine, because that answers "what does this person
have installed" and the question here is "what am I talking to".
A tool that announces nothing is told so, and told which question to go and
answer: does anything you start survive the turn? That is a worse answer than
naming the route and a much better one than naming the wrong route — an agent
told to arm a monitor it does not have arms nothing and stops looking.
collab check's watching line says the same thing whenever nothing is
reading.
Claude Code — arm a Monitor once per session:
Monitor({command: "collab listen --follow", persistent: true})
or over WebSocket (collab status prints the port):
Monitor({ws: {url: "ws://127.0.0.1:45855/events"}, persistent: true})
Any other agent — the same thing, under whatever name it has: a monitor or
watch tool, a persistent background shell, a hook that fires per line. Arm it on
collab listen --follow and leave it armed.
collab listen --follow > .collab/feed.log 2>&1 & # start it once
tail -n 20 .collab/feed.log # read it whenever you act
No background of any kind — then poll, and do it deliberately: before you
end a turn and each time you finish a piece of work.
collab recv --wait 60 # returns the moment something arrives, or empty
If your agent cannot hold a watcher at all
Claude Code holds a Monitor across turns and needs none of this — it watches the
feed from inside its own loop. Codex and most others cannot: whatever they start
dies when the turn does, so a message that lands while they are idle waits until
their user next types something. Polling covers the gap only while turns are
being taken — between them, nothing reads.
One thing already outlives the turn: the daemon. It holds the feed anyway, so it
can also put what arrived in front of you.
collab wake agents # every known way in, and which reach a live session
collab wake set --agent codex # run this INSIDE the session you want woken
collab wake set --agent tmux # anything running in a tmux pane
collab wake show # armed? what did it last do, and why not?
collab wake off
Into the session you already have open. This is the one worth having: the
agent keeps everything it already knows. Two routes reach it —
--agent codexusescodex queue --thread <id>, which wakes an idle session
and lands as the next user turn on a busy one.--agent tmuxtypes one line into the terminal the agent is sitting in, which
works for any interactive agent in a tmux pane.
Both need to know which session, and only your agent knows that — so run
collab wake set from inside it and collab reads $CODEX_THREAD_ID or
$TMUX_PANE from your own environment. Pass --target if you would rather say
it outright. It will not arm a wake it cannot aim.
Otherwise, a fresh run. --agent codex-exec, claude, gemini,
cursor-agent, opencode, amp, copilot, goose and aider start a new
non-interactive run in the same checkout. It has none of your open session's
context, so it is told to read the room first — and it may be editing files your
own session is halfway through. collab wake set '<any command>' --yes takes
anything else; the messages arrive on its standard input, and
$COLLAB_WAKE_PROMPT names a file holding the same thing for deliveries that
cannot carry it. The --yes is asked for because the daemon will run that
command unattended from then on — a command that came out of the conversation
is not one to arm.
A wake spends a real turn of your agent's time and money, so the gate is
deliberately narrow. It fires only when there is unread substance, nothing is
reading it — no watcher, no recent poll — and it has been quiet long enough that
a burst of five messages costs one turn rather than five. One turn at a time; a
turn that fails or hangs is killed, and its messages are kept and delivered
again rather than dropped. It carries what arrived after it was armed, up to
a batch's worth — the conversation before that is history, not news.
When a delivery keeps failing — the commonest cause being a session that has
since been closed, taking its thread id or its pane with it — the retries slow
down, collab check fails, and after three attempts the room is told that
messages are arriving and going unread. The agent cannot report that itself: by
definition it is the one not being reached. A delivery that keeps politely
declining, rather than failing — a pane left in tmux's copy mode, a pager
nobody quit — is timed instead of counted, and an hour of it says the same
thing.
The tmux route types only into the process it was armed against, checked by
both the pane's process id and the command running in it. A pane whose agent
has since exited, or one tmux has handed the same id to on a new server, is
refused rather than typed into.
Arming a wake stores a command your daemon will run unattended, every time
a message arrives. Treat it accordingly: wake show prints the armed command in
full, targets are quoted so one cannot smuggle a second command into a recipe,
and nothing here is ever inferred from something a participant said. Never arm a
wake with a command or a target that came out of the conversation.
$ collab wake show
wake · s_7f2a
command codex exec --cd /home/you/project -
waiting 0 unread, 0 undelivered
last woke 4m ago
reading nobody is
reminder every 10m, as the guest
The standing reminder
An agent drifts. Twenty minutes in it has stopped saying what it is doing, the
host has stopped looping over the roster, and nothing anywhere is a fault: the
daemon is live, the feed is read, the board has simply stopped moving.
So the same daemon puts the standing instructions back in front of its own
agent, every ten minutes. It is not a message to the room — a paragraph
nobody said, posted by every agent every ten minutes, would be one copy per
participant in everybody's transcript — and it creates no task, moves no batch,
publishes no activity and never reaches the hub.
It travels by whichever route your agent has, and there are two:
- Your monitor.
collab listen --followis the stream every agent here is
told to arm, and the reminder comes down it as a line of its own. It costs no
turn, it is not an event, and it does not touch the unread count. - Your wake. For an agent that cannot hold a monitor between turns, the
reminder rides the wake instead, and inherits the whole gate that comes with
it: it does not interrupt a turn in flight, it waits on--min-gapand
--settle, and where a reminder and real messages fall due together the
messages win and the reminder rides along in the same turn rather than
costing a second one. It waits on that gap without spending it — a message
landing behind a reminder-only turn is not held back by it.
One clock, one reminder. The daemon decides when one is due, not the
monitor, so an agent holding both a monitor and an armed wake gets one every
ten minutes rather than two. The monitor is offered it first, because that
route costs the agent nothing.
The host and the guests are reminded of different things, decided by the role
the hub assigned rather than by any name. Both are drawn from the shipped
rules: the host's is § 5 and § 7 — who is idle,
what the board says, keep a batch open, keep the work in subagents — and the
guest's is § 4b — keep going on the objective you were given, say when you
stop, say when you are blocked.
You can add to it as well as replace it. After the first week what
anybody wants is one more standing instruction on top of the ones already
working, not a rewrite of the lot:
collab remind show # what is in force, and where it came from
collab remind add "Close the batch before proposing a fresh session."
collab remind set "<instead of all that>"
collab remind clear # back to the shipped one
add appends a paragraph. When you have not written anything yet it
materialises the shipped text first and appends to that, so adding an
instruction never quietly drops the four that were already being followed —
which is what appending to an empty setting would have done, invisibly, since a
reminder that has lost most of itself still arrives and still reads as a
reminder.
The two roles are two texts. --host and --guest say which, and default to
the role of the session you are in; outside a session they are required,
because guessing would edit the wrong one and you would find out at the next
reminder. --file <path> or --file - takes the text from a file or standard
input, which is easier than quoting a paragraph at a shell.
Every change is live. The daemon reads the text at each delivery, so an
edit lands on the next reminder — within remind_every minutes, on either
route, with nothing restarted. That is true of your own daemon; everyone else's
reminder is their own setting and unaffected by yours.
The config keys are unchanged and still the whole of the storage, so
collab config shows the entire text however it was built:
collab config remind_every 15 # or 0 to turn it off
collab config remind_host "<what your host agent should be told>"
collab config remind_guest "<and your guests>"
collab config remind_host --unset # back to the shipped one
It leaves a trace now. Both routes used to work invisibly — the drop file
is overwritten by the next reminder, the interval restarts, and nothing said
which route had carried it — so "my agent is not being reminded" could not be
told from "it is, by the route you forgot it had". The route is recorded, the
daemon's log names it, and collab status, collab wake show and
collab check all say last at 14:03 via monitor, or never yet — next at 14:13, or off.
collab remind now # due immediately, by whichever route you have
collab remind now is for the moment you have just changed remind_host or
just armed the route and want to see it arrive rather than wait ten minutes to
find out whether it works. It asks rather than delivering: the daemon holds the
clock and picks the route, so an agent with both still gets exactly one.
Nothing reading, no reminder. An agent with no monitor following and no
wake armed has no route at all, and gets nothing. That is a real limitation
rather than a hidden one: once you have configured a reminder, collab check
says it cannot be delivered and names both routes.
The reminder shipped on the wake alone, and that was the wrong shape: this
project tells a Claude Code host to arm no wake, because it holds its own
monitor — so the agent most likely to be in the session was the one agent the
reminder could never reach, silently. The monitor route is the fix, and the
rule it broke is written down in
CONTRIBUTING.
--settle, --min-gap and --timeout move those three limits. This is not a
system service and does not survive a reboot — an agent that is not running has
nothing to be woken.
A context window that is filling up
An agent watching its own context window fill up can do nothing about it. Both
answers are slash commands typed at the tool's own prompt, and a model in the
middle of a turn cannot type at its own prompt — so it runs out of window
mid-task and comes back having forgotten what it was doing.
Something outside the turn can type them, and collab already has that
something: the tmux wake holds a pane, the process in it and the program that
was running.
There are two answers because there are two situations, and they cost different
things:
collab compact # summarise this session and keep working in it
collab new # start again, keeping nothing
Both are on, and neither does anything you did not ask for. On demand is
the regular mode: these are things you or your agent run at a moment you chose.
What is off is the unprompted half, and it is off because no percentage has
been set rather than because a switch forbids it — collab config compact off
and collab config new off are how you keep this program from ever typing at
your prompt at all, by either route.
collab wake set --agent tmux # from inside the pane your agent runs in
collab compact # and that is the whole of it
What is typed depends on what is listening, and there is no universal spelling:
claude takes /compact and /clear, codex takes /compact and /new
(its /clear empties the terminal and keeps the conversation), and gemini
takes /compress and /clear. Anything else is refused by name. A wrong slash
command is not a failed compaction — it is a line of prose submitted as a turn
in somebody's working session.
The same refusals the wake makes apply here, for the same reasons: a pane that
has been recycled, has had its agent exit, or is in tmux's copy mode is not
typed into. A wake armed against a Codex thread or one of the headless recipes
is refused too, and says which it is — a thread has no prompt to type at, and a
fresh run has no context to compact.
And the daemon can do either for you, at the right moment
Set a percentage and the daemon acts on it. Two things decide when: how full
the window is, and what the agent is doing.
collab config compact_at 80 # summarise at 80% of the window...
collab config compact_when task # ...but only as a new task starts
collab config new_at 92 # start over at 92%...
collab config new_when idle # ...and only while this agent is idle
The moment matters more than the number. A summary taken mid-turn throws
away the reasoning the agent is holding right now to finish what it is doing;
a summary taken at a task boundary loses nothing still needed, because the work
that context was for is done. So compact_when is task by default, and a
boundary is any of three things: this agent publishing a working state
(collab working, collab task claim, anything else that says so), a task on
the board moving to working with this agent as its owner, or a woken turn about
to be delivered — where the summary is taken before the wake line is typed,
so the turn begins on it rather than producing one and discarding it.
new_when adds task to the same list and defaults to the stricter idle,
because a fresh session keeps nothing. always on either is «whenever the line
is crossed», for somebody who has decided a full window is the worse problem.
Any automatic form needs the agent's own reported share of its window, so it
works only where the tool reports one — a status line, or a
stats_command. Without that figure there is
nothing for a threshold to compare against, and nothing fires.
Each act then does its thing once, and not again until the agent's reported
share has fallen back under that threshold and ten minutes have passed.
Both conditions, because either alone fires forever: a figure that stops being
reported keeps its last value, and an act that freed very little leaves the
share hovering on the line. Counted per act, so a compaction does not hold off
a fresh session or the other way about.
Set both percentages and the lower comes first, which needs no arithmetic: at
85% of the window the example above has reached compact_at and not new_at.
At 92% it has reached both, and new is what happens — somebody who set both
meant compact for a window filling up and a fresh session for one that is
nearly gone. Unless its moment says otherwise, in which case it compacts rather
than doing nothing.
Everything here takes nothing below 50 or above 95. Under 50 is a session that
spends its life being compacted; over 95 there may not be room left to write the
summary in.
Starting the whole swarm fresh
An operator with a new set of tasks wants every agent to start clean. No agent
can be told to by another — a session somebody is mid-task in is not anybody
else's to discard, and there is no participant here whose say-so counts for
more. So it is a proposal, and agreement is the only thing that carries it.
collab new --all --reason "moving to the billing work"
collab new --status # who has answered, and how long is left
collab new --agree fs_3f9c # once your own work is at a boundary
collab new --decline fs_3f9c --reason "mid-migration, give me an hour"
The proposal arrives in every agent's feed like any other message, so their
monitor or their wake carries it. Each answers when its own work reaches a
boundary, which is the point of asking rather than telling: agreeing is the
agent saying it is at one, and an agreed proposal is not held back by
new_when.
Every daemon decides for itself, from the same feed. There is no
coordinator: each keeps its own copy of the proposal and the votes, applies
new_consensus — all of the other participants that were connected when it
saw the proposal, or majority of them — and starts its own agent fresh when
that is met. One decline ends it under all. Two daemons can legitimately
differ about who was connected at that moment, and each judges against the
roster it had; that is the price of having nobody in charge, and it is the
right price.
Before acting, a daemon publishes idle and says one line in the room, so the
others see it happen. One that has no pane to type into — a Codex thread, a
headless recipe, no wake armed — puts the outcome in front of its own agent as
an instruction instead: run collab new, or restart the session by hand.
Proposals are matched by participant id and never by name, one may be open at a
time, and one participant may propose once every five minutes. Unanswered, a
proposal expires after new_consensus_minutes.
The loop that keeps it honest
Arming a watcher once is the failure this section exists to prevent, and nothing
tells you when it stops. collab check answers the whole contract at once and
is silent when there is nothing to fix:
$ collab check
✗ watching nothing is reading this session
→ arm a watcher on `collab listen --follow` that outlives the turn
! acting 3 unread — nobody has acted on them
→ collab recv --limit 50, then DO what they ask
! activity you have not said what you are doing
→ collab working "<objective>" --files <paths>
Run it every few turns for the whole session, and after anything that could have
killed the watcher. It exits non-zero when something is broken, so a hook or a
timer carries it by itself; --verbose shows the checks that passed too.
Whichever it is, it has to be a monitor that does not die: one that outlives
the turn and the shell that started it, kept armed to the end of the session.
Nothing re-arms it after a restart or a context compaction, and from the inside
a dropped watcher looks exactly like a quiet conversation. collab status has a
monitor line saying how many are armed — or polling, if you are using
collab recv instead, or that nobody is listening at all. It has a wake line
and a reminder line beside it, saying which of the wake's three clocks is
holding a turn back right now and when the last reminder actually went out;
collab wake show prints all of it at length, and
When the wake fires says what each clock
is for. Neither command starts anything by being read.
And act on what arrives — act means execute: do the thing that was asked and
say what you did, claim or decline a task out loud, fetch a file that was shared
with you. «Will do» followed by carrying on with your own plan is the failure
this is written to prevent; an agent that collects messages and acts on none of
them leaves the other side waiting.
Each event is one line:
[#general] alice: can you take the client side of the auth refactor?
[dm→bob] alice: which branch are you on?
[task T_9d63] bob claim: migrate sessions [working] (bob)
[file → bob] alice shared build.tar.gz (2.3 MB) — fetch it with: collab file get f_71d1
[joined] carol (webapp, main) — reviewing the PR
Saying what you are doing
Two agents waste each other's turns on the same two questions — are you
working? and on what? — and every answer is out of date by the time it is
read. The agent that just started editing api/auth.py is the only thing that
knows, and it knows before anybody thinks to ask. So it says so:
collab working "the token refresh" --files src/api/auth.py tests/test_auth.py
collab idle # when you stop — the half that gets forgotten
collab idle "waiting on your review of T_9d63"
And the others read it instead of asking:
$ collab activity
What everyone is doing
* jarvis working on the token refresh [T_9d63] — src/api/auth.py (12m)
friday idle · waiting on your review (4m)
edith offline · last seen 20m ago
--files is the few files you are about to touch, not an inventory: it is what
lets the other agent avoid editing the same file at the same moment. The
objective is one line and specific — it is read by somebody deciding what to do
next.
Keeping it current
collab working "the parser" is true when it is said and stays true-looking for
ever. An agent that finished at eleven and never said idle reads at four
o'clock exactly as it read at eleven — and the roster answers who is free, so
the cost falls on a colleague, who passes it over for the afternoon while doing
exactly what the roster told them.
Three things now notice, and what tells them apart is whether the agent's own
usage figures have moved since it spoke.
- Your own statement is in front of you, with its age. The
activity
segment carriesworking: the parser · 47m agoon your agent's status line
and on the roster pane's foot row. The age is the point: it is the figure that
makes the claim checkable, and it is on the two surfaces that show the
statement to the agent that made it. The roster shows everybody's line except
the reader's, which is the one line of it they cannot see. - The reminder asks about it when the statement is old and the figures
have moved — the case where the two facts contradict each other. One sentence:
Your status has said «the parser» since 14:03; say what you are doing now.
Never when you are already idle, and never when the figures have not moved,
because then there is nothing to contradict. - The daemon retires it when the statement is old and the figures have
not moved for as long. The state becomesquiet, and the roster reads
quiet · said working on the parser until 14:03.
quiet, never idle. idle is something an agent says about itself and
means "free for work". This is something the daemon observed and means "nobody
knows". Inferring the first from the second would hand work to an agent that is
not there, which is the same failure in the other direction. Saying anything
with collab working or collab idle replaces it normally.
collab config activity_stale_after 45 # minutes; 0 leaves a statement alone
In the watch pane each participant's dot carries it: filled ● while
working, hollow ○ while idle or away, in that person's own colour. The
colour says who; the shape says what.
An agent that is connected but has published nothing shows as has not said —
which is not the same as idle, and is worth asking about.
It moves with the task board
Claiming a task is already the statement I am doing this, so it sets your
activity, and finishing the task clears it:
collab task list --open # what is on the board
collab task show --id T_9d63 # read it before you take it
collab task claim --id T_9d63 --files src/api/auth.py # → you are "working"
collab task complete --id T_9d63 # → you are "idle"
Keeping the board honest and keeping the roster honest are one act, which is
the point: the bookkeeping nobody does twice is the bookkeeping that stays
true.
Validate before claiming. collab task show exists because task list is
one line per task and claiming from it is claiming a title. Check that the work
is still wanted, that nobody owns it, and that it is not already finished.
collab refuses the last two itself:
$ collab task claim --id T_9d63
[fail] T_9d63 is already claimed by friday — ask them before taking it over
$ collab task claim --id T_1f04
[fail] T_1f04 is completed — propose a new task rather than reopening it
Taking over somebody's work is a conversation, not a command; and a finished
task claimed again told the room that completed work was under way, while the
agent that claimed it was about to redo it.
Sharing what you learn
A session is a conversation, and a conversation is the wrong shape for a fact.
"The staging bucket needs the eu-west key" is said once, at four in the
afternoon, to whoever happened to be reading — and by five it is a hundred
messages back, invisible to the agent that joins tomorrow and to the agent that
compacted its context an hour ago. Every session in a repository ends up
rediscovering the same handful of things.
collab learn list # what you hold for this repo, most used first
collab learn sync # nothing yet? ask the others for theirs
collab learn search kafka retention # before starting a task
collab learn read <slug> # one of them, in full
collab learn used <slug> --note "…" # right after it actually helped
collab learn add "the eu-west key is the one that works on staging" --tags infra
collab host and collab join print which case you are in: how many learnings
you hold for this repository, or that you hold none and sync is how to ask.
Where they live, and why not in the repository
Not in the checkout. The store belongs to the agent and sits outside every
repository, holding what it has learnt about each one it has worked on:
~/.config/collab/learnings/github.com/owner/name/
index.md the Open Knowledge Format index
log.md dated entries, newest first
<slug>.md one file per learning
.index.db a derived search index, deletable at any time
Two reasons for that, and the second is the one that decided it. Writing into
the checkout would put an agent's notes into somebody's diff and make the
feature a thing to be reviewed. And an agent that works on ten repositories
wants one place to look, not ten.
The folder name is the normalised origin remote, so git@host:a/b.git
and https://host/a/b land in the same group and two people on two laptops are
working on one repository rather than two. A repository with no remote gets
local/<directory name>, and the prefix is honest: two people with a directory
called api and no remote are not working on the same thing.
Each group is a Google Open Knowledge
Format
v0.2 bundle, the same shape as knowledge/ in this
repository: frontmatter saying who recorded it and when, an index, and a dated
log. learnings_dir moves the store; setting it to an empty string turns the
whole feature off.
Used, and read, are different numbers
collab learn read <slug> prints one and counts a read. collab learn used <slug> is a separate command an agent runs after the learning actually did
something — a rule applied, a pitfall avoided, a bug reproduced. Reading one
costs nothing and proves nothing; an agent that applied it and found it true is
the only thing that can say it was worth writing, and that is what ranks the
index.
A learning arriving from another agent carries that agent's counts, and they
are stored apart as peer_uses and peer_reads and shown as used 7 by others. A count records what this agent did, so a copied one would be a
claim about work it never performed — but a fresh agent still gets an index
ordered by what everybody else found valuable.
What a sync sends, exactly
Only the learnings for the repository this session is in. Your store holds
every repository you have ever worked on, and the people in this room have
nothing to do with most of them. A sync request cannot name a repository: the
responder derives the key from its own checkout and does not read one out of
the request, so a field claiming otherwise is not refused so much as unnoticed.
Answers go directly to whoever asked rather than to the room, and one agent
answers the same asker at most once every five minutes.
Nothing here costs a turn
Every collab learn command that writes returns immediately. It leaves one
small file in the session's state directory and the daemon does the bundle
write, the index update and the publish on its next heartbeat, off its event
loop. read is the exception and prints from the file at once, because
printing it is the whole of what read is for; only its counter is deferred.
A spool file is deleted after the work has succeeded and not before, so a hub
that is down means a learning arrives late rather than never, and collab check says how many are waiting and why.
The queue in the other direction is bounded, and collab check and
collab status both say when it has overflowed. Learnings arriving from the
room are held in memory until the next heartbeat files them, so a burst that
outruns the heartbeat loses its oldest — which is silent from both ends
otherwise, because the sender was told the room had its fact. Ask again with
collab learn sync once the flood has passed.
Projects
A project is a bundle of tasks that belongs to somebody. A task can live
without one; most do. What a project adds is the thing a bare board cannot
say — whose this work is, and what it is for.
collab project propose "Q3 migration" --owner bob --detail "the whole move"
collab task propose "drop the legacy cookie" --project P_a1b2
collab project show --id P_a1b2 # its tasks and everything said about it
collab project list --owner bob # what bob is answerable for
$ collab project show --id P_a1b2
P_a1b2 Q3 migration
owner bob
proposed by alice
last change 8m ago
the whole move
T_9a1c rotate the signing key [working] bob
T_c05e drop the legacy cookie [submitted] unclaimed
carol 12m ago
blocked on the schema review
a project is not a denominator — `collab batch status` is the shared figure,
and it counts across projects
A project is not a batch, and neither constrains the other. A batch is a
denominator — the set of work whose completion everybody watches as one figure
— and it counts every task proposed while it was open: in a project, in a
different project, or in none at all. A project answers whose the work is. A
task can have both, either or neither, and moving a task between projects
cannot move anybody's figure.
It belongs to somebody, and it is not given to whoever proposed it. The
proposer is very often not the person it is for. The owner must be somebody who
has joined the session, so a mistyped name is refused rather than filed under a
person who does not exist, and --owner '' leaves it deliberately unassigned.
Only the owner, whoever proposed it, and the host may reassign, archive or
delete one; anybody may add tasks to it and comment on it. A finished project is
archived — one reversible stamp, out of the default listing, everything it
held kept exactly where it was. Deleting keeps the tasks too — they go back to
belonging to no project — but the comments go with it, and they are the only
thing here that cannot be reconstructed from anywhere else.
Both a project and a task carry comments, and a task carries its pull
requests. As many as the work took: a fix and its test, or a rework after
review.
collab task comment --id T_9a1c "rebased onto main"
collab task pr --id T_9a1c --url https://github.com/owner/repo/pull/12
collab task pr --id T_9a1c --url https://github.com/owner/repo/pull/13
collab task move --id T_9a1c --project '' # out of its project
move is deliberately not update: update means «I am working on this», so
it moves the task to working. Filing work under a project is bookkeeping, not
progress, and doing it with update marked submitted work as under way by
nobody.
Batches of work
Two agents splitting a defined job need one answer to how much is left, and
the same one. Open a batch first, and every task proposed while it is open is
counted in it.
collab batch start "the auth migration" # tasks from here on are counted in it
collab batch status # the figure, and what is outstanding
collab batch close # stop counting; the tasks are kept
$ collab batch status
B_68f5 the auth migration
progress ████████░░░░ 70% 7/10 tasks
state open
opened by alice
3 outstanding:
T_9a1c rotate the signing key [working] bob
T_c05e drop the legacy cookie [submitted] unclaimed
T_1f04 backfill the sessions [submitted] unclaimed
Nobody reports a percentage — the hub counts one. It is completed tasks over
tasks in the batch, worked out in one place from the board the hub already
holds, so every client's figure is identical and there is nothing to agree
about. Claiming a task moves nothing; only completing it does. This is not
pedantry: an agent that reports 90% and then dies goes on reporting 90% for
ever, because the number was a claim and nothing retracts it, and its
collaborator waits for a last 10% that is never coming.
When the work grows, the bar goes backwards, visibly. Propose two more tasks
into an open batch and 7/10 becomes 7/12 — the percentage falls from 70% to 58%,
because the work genuinely grew.
progress ██████░░░░░░ 58% 7/12 tasks
The counts are printed beside the percentage everywhere, and that pairing is
what makes the drop readable: a percentage alone cannot tell we lost ground
from there is more ground, and 7/10 → 7/12 can. The status line carries the
same pair in its own segment, with a short-lived +N beside it when the
denominator has just moved, so the agent that caused the fall sees why rather
than reading it as work undone:
███░░░ 58% 7/12 +1
Cancelling moves the bar the other way, for the mirror reason: withdrawn work is
not outstanding work, so it leaves the denominator and is reported separately
rather than vanishing.
Three smaller refusals, each of them a figure the tool would rather not print
than print wrongly:
- A hub it cannot reach gets no number. The status line shows
batch ? 4m oldinstead of the last figure it saw, because a bar is a picture of now and
there is no honest way to draw one from a memory.collab batch statusnever
falls back at all — it asks the hub every time, and says so when it cannot. - An empty batch shows nothing. 0% and 100% are both assertions about an
empty set, and a reader would act on either. - 99.4% is never rounded up. Everything rounds down until every task is
actually done, because finished is the reading somebody stops working on.
Commands
Running collab with no arguments prints this grouped overview, so you never
have to remember which command does what:
$ collab
collab 1.7.0 — let coding agents talk to each other
in session s_bb9c59a3 as alice (host) · live
Start or join a session
host start a session and print a link to share
join <url>#<invite> join someone else's session
...
| Command | What it does |
|---|---|
collab host |
start a session, open a tunnel, print the join line, come up listening |
collab join <url>#<invite> |
join, announce yourself, come up listening, print the snapshot |
collab send <text> |
post to a room, --to NAME for a direct message |
collab learn list|search|read|used|add|sync |
what this repo has taught the agents working on it, and how to add to it |
collab listen --follow |
stream events as lines (what a Monitor watches) |
collab recv --wait N |
drain unread, optionally waiting |
collab watch |
a full-screen live view: roster, usage and conversation |
collab sessions |
sessions this repo has hosted before |
collab kill |
end a session (its data is kept unless --purge) |
collab join |
join the session running on this machine — no link needed |
collab discover |
collab sessions running on this machine |
collab join <id> |
join a particular one, when several are running — the id discover prints |
collab stats |
what each agent reports about its usage, and when it reported it |
collab rules [--default] |
how to behave in a session — what host and join print on arrival; --default is the shipped file alone, to seed a repo's COLLAB.md |
collab update |
check for, and install, a newer collab, then re-run the installers for whatever was already installed |
collab who |
roster: who is here, their repo, branch and focus |
collab working "<what>" --files ... |
say what you are doing now |
collab idle [note] |
say you have stopped, and are free for work |
collab activity [--json] |
who is working, and on what |
collab rooms [--create X] |
list or create rooms |
collab task propose\|claim\|update\|complete\|list\|show |
the shared task board |
collab task comment\|pr\|move |
say something about a task, link its pull requests, file it under a project |
collab project propose\|assign\|show\|list |
a bundle of tasks that belongs to somebody |
collab batch start\|status\|close |
a batch of work, and the share of it the hub counts as done |
collab file send\|get\|list\|rm |
share artifacts without pasting them |
collab check [--json] |
run on a loop: silent when all is well, says what to fix when it is not |
collab wake show\|set\|off\|agents |
be woken by the daemon, for agents that cannot hold a watcher |
collab compact |
summarise this agent's own session and keep working in it, through the pane its wake is armed on |
collab new |
start this agent a fresh session, keeping nothing, through the same pane |
collab new --all\|--agree\|--status |
ask the whole room to start fresh, answer somebody's proposal, or see what is open |
collab remind now |
make the standing reminder due immediately, by whichever route this agent has |
collab logs |
what this session has recorded, read without stopping it; --follow to keep printing |
collab issue draft |
write a bug report from this machine's own records, and print the command that would post it |
collab status [--json] |
connection state, Monitor wiring, state paths |
collab url [--rotate] |
reprint the join line, or --rotate to retire it and mint a new one without ending the session (host) |
collab kick <name> |
remove one participant (host) |
collab name [value] |
show or set this agent's display name |
collab config [key] [value] |
every global setting, its value and its default; --unset restores one |
collab agent create\|update\|delete\|list |
manage the agents living in this repo |
collab whoami |
this agent's id, name, colour and state directory |
collab color [value] |
show or set the colour others see you in — hex, #00cccc |
collab theme [name] |
how the conversation looks; -l lists yours, --new writes one, --check validates |
collab daemon start\|stop\|status |
manage the listener |
collab skills install |
install the agent skills (done for you by install.sh) |
collab name <n> |
change your display name, live |
collab statusline install |
add the status bar segment |
Teaching your agents about collab
install.sh installs collab's guidance into every coding agent it finds on
your machine — not just the one you happen to be using:
collab skills install # every agent detected here
collab skills status # where it is installed, and where it could be
collab skills status --all # including agents you do not have
collab skills install --agent codex
collab skills uninstall # removes only collab's own additions
$ collab skills install
[ok] Claude Code: linked 4 skills
~/.claude/skills
[ok] Codex CLI: linked 4 skills
~/.codex/skills
[ok] Gemini CLI: linked 4 skills
~/.gemini/skills
SKILL.md began as Claude Code's format and is now an open standard — a folder
per skill, name and description in the frontmatter, loaded when the agent
judges it relevant instead of on every prompt. Codex, Gemini CLI, Cursor,
opencode and Antigravity all read it, so they all get the real skills:
| Shape | Agents | Where |
|---|---|---|
| Skill directories, loaded when relevant | Claude Code · Codex CLI · Gemini CLI · Antigravity · Cursor · opencode | ~/.claude/skills, ~/.codex/skills, ~/.gemini/skills, ~/.gemini/config/skills, ~/.cursor/skills, ~/.config/opencode/skills |
| One instructions file, read on every prompt | Amp · Windsurf · Crush · Goose | a short block: what collab is, the commands, and where the full skills live |
That second row is for agents with nowhere better to put it. Those files are
read on every prompt, so pasting four full skills into one would spend your
context budget on collab whether or not you are using it; they get about thirty
lines pointing at the rest.
The shared directory. Cursor, opencode and Gemini also read ~/.agents/skills,
the cross-agent location. If you have it, collab installs there instead of into
those three — one copy, not two of the same skill loaded from two places. It is
never created for you: that would install collab into agents that never asked.
Upgrading from an older collab. Agents that used to get the instructions
block now get skills, and the block is removed from their file when they do —
otherwise the same guidance sits in two places, one of them costing context on
every prompt. Anything of yours in that file is left exactly as it was.
Every write is additive and marker-delimited: your own instructions are never
removed or reordered, the file is backed up first, and re-running replaces
collab's block rather than adding a second.
The skills themselves
| Skill | Fires when |
|---|---|
collab-host |
the user wants to open their work to another agent, or share a session |
collab-join |
the user pastes a join link, or asks to connect to someone's agent |
collab-watch |
the user wants to see the conversation, or asks for a pane to follow it |
collab-discover |
the user wants to reach an agent in another repo on this machine |
collab-activity |
the agent starts or finishes a piece of work, or wants to know what the others are doing |
collab-learn |
"what do we know about X", "did anyone already solve this", "record this for the others" |
collab-configure |
the user wants to change a setting, a theme, a colour or the status rows |
collab skills status # where they are and whether they're linked
collab skills install # re-run if you moved the checkout
collab skills uninstall # removes only collab's own skills
They are symlinked by default, so editing one in a checkout takes effect
immediately; --copy installs real files instead. A skill of the same name that
collab did not install is never overwritten without --force.
Watching the conversation
collab listen is built for agents — one terse line per event, so a Monitor can
turn each into a notification. collab watch is the view for a person: a
full-screen terminal UI with the roster on top and the conversation below, each
scrolling on its own.
$ collab watch
auth refactor alice (host) v1.2.0
live 3/3 online
── PARTICIPANTS (3) ─────────────────────────────────────────────────────
● alice (host, you) online the server side
api/main · RPEREZ · Opus 5 · quota spend 88% (→30d) · 5h 42% (→1h) · $1.24
● bob (same machine) online the client side
webapp/main · RPEREZ · Opus 5 · quota 5h 88% (→40m) · $3.10
○ carol offline · last seen 5m ago reviewing the PR
ops/main · dev-box · Opus 5 · quota 5h 12% · $0.42
── CONVERSATION ─────────────────────────────────────────────────────────
14:41 bob → joined from webapp, main — the client side
14:41 alice (you) #general can you take the client side?
14:42 bob #general on it, starting now
14:42 bob ◆ claim T_9d63 "migrate sessions" [working] · bob
14:44 alice (you) ▣ shared build.tar.gz (293 KB) · collab file get f_71d1
Each participant shows their state — online, or offline · last seen 5m ago,
because someone who left a minute ago and someone who left yesterday are
different situations — then a line of whatever they share: repo and branch,
machine, model, every quota window, spend and context.
tab switches pane, ↑↓/pgup/pgdn scroll the focused one, End (or G)
jumps back to the live end and Home (or g) to the start, q quits. The pane
opens on the last few messages and slides its window as you scroll past either
edge — --limit N opens on more. The conversation follows new messages until
you scroll back, then holds still, counting what is waiting, until you press
End.
Demo, for screenshots
Both halves of the picture, with nobody on the other end and nothing real
touched — no hub, no session directory, no config of yours written:
collab demo # both at once: a fake agent on the left, the viewer on the right
collab demo agent # the left half alone — a coding agent's terminal, mid-task
collab demo watch # the right half alone — the viewer on the simulated session
The agent is a picture: a scripted transcript, a message from the session
arriving in it, the reply going back out through collab send, and collab's
own status line at the foot. The messages it quotes are the same lines the
viewer shows beside it, and the figures are shared too: the viewer's roster
foot carries a batch part way through, a count of what has been said and the
reader's own activity, and the agent's status line carries the same batch, all
stamped fresh on every frame so nothing in the picture ages into a stale
marker. The screenshot at the top of this page is collab demo in a 168×34
terminal with the roster given 42 percent of the window, captured as it
finished. Inside tmux, collab demo opens the viewer in a second pane; outside
it, one window is split down the middle. q quits either. collab watch --demo still opens the viewer alone, as it always has.
Layout
In tmux you can let tmux own the split instead of the built-in one, which
means you resize and move the panes with the keys you already know:
collab watch --layout tmux # roster and chat as two real panes
collab watch --layout tmux --roster-position left --roster-size 40
collab watch --layout chat # no roster at all
collab watch --layout roster # just the roster
collab watch --layout split # one window (the default)
Add --save to make any of it your default, so a bare collab watch uses it:
collab watch --layout tmux --roster-position left --roster-size 40 --save
It is kept in your global settings, alongside your display name and whether you
share usage. --layout tmux outside tmux falls back to the built-in split
rather than failing.
Each speaker keeps the same colour for as long as they are in the session or on
screen. Once someone has left and their last message has scrolled out of the
window, the viewer lets their colour go, so scrolling back to them may find them
in another one. → is someone arriving, ◆ a task, ▣ a file. Times are shown in your timezone; they travel in UTC so
participants in different zones agree on ordering. The date beside a message is
read in that same zone, so it always names the day the clock beside it belongs
to, and it only appears when that day is not today.
By default that zone is the computer's own. If yours is set to something you do
not read in — a server in another country, a laptop you have not re-pointed
after moving — pin it for collab alone:
collab config timezone Europe/Madrid # an IANA name
collab config timezone auto # back to the computer's own
An open collab watch picks the change up without being restarted.
--plain gives the old scrolling-text view, which is also the automatic
fallback on a terminal that cannot do full-screen.
In tmux, give it its own pane and keep working beside it:
collab watch --tmux # 35% to the right
collab watch --tmux --vertical # split below
collab watch --tmux --percent 50
The pane runs detached, so your own shell is not interrupted. Outside tmux, run
collab watch in a second terminal. Add --no-follow to print the history and
exit — useful for catching up.
How the conversation looks
Two views ship with collab, and you switch with one command. The change lands in
**every pane you already