Skip to main content
Parallel Collaboration

Cross-Session Messaging

Cross-session messaging lets two Qoder CLI sessions running on the same machine, under the same user account, see each other and exchange messages. A session can ask a peer to pick up a task, hand it a file, or answer a question, without you copying text between terminals. Use it when work naturally spans more than one session — one terminal on the backend and one on the frontend, or a long-running session you want to hand something to from a second window.
Beta Cross-session messaging is a beta feature and is not enabled by default. Set the feature flag before starting Qoder CLI: QODER_FEATURE_CROSS_SESSION=1 qoder. The flag can also be stored as QODER_FEATURE_CROSS_SESSION=1 in the user-level configuration .env file so later sessions enable it automatically. Default user-level configuration path: $HOME/.qoder/.env. Restart Qoder CLI after changing .env. The feature requires Unix domain sockets and is available on macOS and Linux only.

How it works

When the feature is on, each session listens on a private socket and records it in a per-user registry. Other sessions read that registry to discover peers, and connect directly to deliver a message.
Session A (terminal 1)                Session B (terminal 2)
+-- listens on its own socket         +-- listens on its own socket
+-- you @-mention B  ---------------> reads the registry, finds B
`-- SendMessage --------------------> delivered into B's next turn
Both the socket and its directory are readable only by your own user account. Sessions belonging to other users on the same machine cannot see or reach yours.

Discovering peers

Run /peers to list the peer sessions you can reach, along with the handle this session is reachable at. The Agent does not go looking for peers on its own: it contacts a session only when you point at one with an @-mention, or to answer a session that messaged it.
Peer sessions (3):
  api-service — interactive, idle, cwd /home/dev/api, handle -3f
  api-7c — interactive, busy, cwd /home/dev/api
  web-2b — interactive, idle, cwd /home/dev/web
A session's name is its current title, so it changes when you run /rename and when the Agent retitles the conversation. The status column tracks the session in the same way: busy while its Agent is working, waiting while it is asking for a confirmation, shell while its user is at a shell prompt, and idle otherwise. A session that has no title is named after its working directory, plus its handle — the -7c above. Without it, every session working in the same directory would present the same name and none of them could be addressed. Two sessions in /home/dev/api therefore appear as api-7c and api-3f, not twice as api. A peer is addressed by the label in its row, or by its handle alone. A handle is derived from the socket the session listens on, so it is the same in every session's listing, and it does not change when the session is retitled — -7c reaches that session whatever it is called by then. A titled session's name has no handle in it, so its row states one: handle -3f.

Mentioning a peer in the prompt

Type @ followed by at least one character to complete on peer sessions along with files and agents. Peer rows lead with the @ and are described as session, with the session's status and how long it has been running, so they are distinguishable from a file of the same name.
@rev
  @reviewer         session · idle · started 3m ago
Accepting the row inserts the label that addresses that session. A label containing spaces is inserted with the spaces escaped, the same way a file path is. Mentioning a session tells the Agent which one you mean; it does not send anything. Ask for what you want in the same message — "ask @reviewer to rerun CI" — and the Agent messages it. A message that only mentions a session does not message it. If the name matches more than one live session, the Agent is given the labels that each address exactly one, and asks which you meant rather than guessing. Names are self-chosen and unverified, so nothing addresses a session on a name alone when it is ambiguous. Typing @ on its own does not list peer sessions, and a mention that matches nothing is left in the text as written.

Sending a message

The Agent uses the same SendMessage tool it uses for teammates, with a peer name as the recipient. Files can be attached by absolute path and are copied to the recipient so it can read them. A send reports honestly. If the peer has exited, the result says so rather than silently succeeding. The Agent messages a peer only when you ask it to, or to answer a peer message that asks it something. It does not send unrequested status updates or acknowledgements. If you ask it to contact a session without @-mentioning one, it asks you to mention it. After three messages to the same session with no message from you in between, further sends to that session are refused until you write again.

Controlling what arrives

Each session decides for itself what to do with incoming peer messages, through the security.crossSessionInbound setting:
  • accept — deliver the message into the session's next turn.
  • hold — park the message for your review; the Agent never sees it until you approve it.
  • refuse — decline everything; nothing is delivered and no attachment is written to disk.
When the setting is unset, the fallback depends on how this session handles permissions. A session that bypasses permission checks holds incoming peer messages for your approval, because anything a peer asked for would otherwise run without a further prompt. A session that still asks for confirmation accepts them, since a peer cannot use it to skip a check it would face itself. Whatever the permission mode, a message whose sender address matches no session registered on this machine is held for your approval, and once delivered its row in the conversation is marked (unregistered). The address is supplied by the sender, so this separates a session this machine has a record of from an arbitrary address; it does not prove who sent the message. Messages from processes this session started are exempt. In a session with nobody to review anything — a non-interactive run that was given an explicit socket path — a message this fallback would have held is refused instead, so the sender is told immediately rather than waiting for a review that cannot happen. If such a session is meant to take instructions from peers, set security.crossSessionInbound to accept explicitly; a value you configure yourself is always honoured, even there. Project-level settings may only make this stricter. A value in a repository's checked-in settings can tighten accept to hold, but never loosen hold to accept. Because that means a repository can override your own choice, /peers always names the setting in effect and the file it came from.

Approving a held message

When a message is held because the trust question cannot be settled automatically, the session asks you directly:
Another session sent this one a message

From worker -7c (name and address are supplied by the sender and are not verified)
This session bypasses permission checks, and the sender did not declare its own.
Anything it asks for would run without a further prompt.

  please run the integration suite and report the failures

  ❯ Deliver it to this session
    Decline — drop it and tell the sender
The prompt is the last thing this session will put on screen, so it never interrupts a permission confirmation or a dialog you are part-way through. It only appears when nothing else is asking you for something. Approving delivers the message on the next turn; declining drops it and tells the sender it was refused. The excerpt is shortened for display — approving delivers the whole message — and any attached files are named by count, because they are copied on arrival and removed again if you decline. Only one approval is asked at a time, and only while the screen is otherwise free. A message that arrives while a prompt or a dialog is open is still held and is reviewed with /peers instead, as is one whose prompt was already open when something else needed the screen — that message goes back to the queue rather than waiting out its deadline behind whatever replaced it. How long the prompt waits is set by general.dialogExpiry, which defaults to five minutes. When it lapses the message is dropped and the sender is told it expired rather than that you refused it — a distinction the sending Agent can act on. never removes the deadline for other dialogs but not for this one, so a single unanswered prompt cannot stop every later message from asking. A message held because you configured hold yourself is parked without a prompt, since your own standing instruction is not a question. Those are reviewed with /peers. A hold that came from a settings file in the repository does prompt. It is the same value, but it is the repository's decision rather than yours, so it is put to you instead of quietly removing a prompt you would otherwise have seen.

Reviewing held messages

Run /peers to see the policy in effect, reachable peers, and anything waiting for review:
/peers
The first line always states the inbound policy in effect and where it came from — your own settings, a repository setting, or the permission-mode fallback, in which case it states the rule rather than a single example of it. This is worth reading when messages are not arriving at all, since a repository setting of refuse produces no held messages to explain itself. Each held message shows an id, its sender, why it was held, and a preview. The sender is identified by the handle derived from the address it asked replies at, and /peers says whether a session is still reachable there. Neither that address nor the name beside it is verified — no session can prove who sent it a message — but a handle can be checked against the peer list above it, where a name can only be read. Approve or decline by id:
/peers approve ab12cd34
/peers deny ab12cd34
Approving queues the message for the next turn. Declining drops it and tells the sender it was refused.

What a peer message can and cannot do

A message from a peer is treated as a request from a colleague, not as an instruction from you:
  • It never counts as your approval for a pending permission prompt.
  • A leading / in a peer message is plain text, never a slash command.
  • Permission boundaries are per-session. If a peer was denied an action and asks another session to perform it instead, the receiving Agent is instructed to refuse and surface it to you.
  • The receiving Agent acts on a peer's request only when it serves the task you gave it, and replies only when the message asks for an answer.

Security notes

Isolation rests on file permissions: the socket and its directory are restricted to your own user account. Within that boundary the sender's self-reported name is not cryptographically verified, so any process already running as you could present itself as any peer. Treat cross-session messages as trusted only to the extent that everything running under your user account is trusted, and prefer hold on sessions that run with elevated permissions.

Settings reference

  • security.crossSessionInbound — accept, hold, or refuse. Controls inbound peer messages for this session.
  • general.dialogExpiry — 60s, 5m, 10m, or never. How long an approval prompt waits before the message is dropped as expired. Defaults to 5m. Read from your own settings only, never from a repository.