Skip to main content
Parallel collaboration

Cross-Session Messaging

Cross-session messaging lets two Qoder CLI CN 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 CN: QODERCN_FEATURE_CROSS_SESSION=1 qodercn. The flag can also be stored as QODERCN_FEATURE_CROSS_SESSION=1 in the user-level configuration .env file so later sessions enable it automatically. Default user-level configuration path: $HOME/.qoder-cn/.env. Restart Qoder CLI CN 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
+-- ListAgents  --------------------> 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

Ask the Agent to list what it can reach, or let it call ListAgents on its own. The listing has two groups: agents running inside the current session, and peer sessions elsewhere on the machine.
Agents in this session (address by name):
- Reviewer

Peer sessions (other Qoder CLI CN sessions on this machine):
- api-service — interactive, idle, started 12m ago, cwd /home/dev/api, handle -3f
- api-7c — interactive, busy, started 3m ago, cwd /home/dev/api
- web-2b — interactive, idle, started 40m ago, 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. Run /peers to see the same list along with the handle this session is reachable at.

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 exited between the listing and the send, the result says so and suggests listing again rather than silently succeeding.

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. 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.

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.crossSessionInboundaccept, hold, or refuse. Controls inbound peer messages for this session.
  • general.dialogExpiry60s, 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.