#Design: one Copilot agent, one repo, everyone in the room

#Problem

Two to five developers are on a call. One GitHub Copilot agent is working in a repository. Everyone should see what it is doing, anyone should be able to give it the next instruction, and the agent should keep one continuous context. GitHub Copilot CLI does not offer this natively: its remote control is limited to the account that started the session, and shared sessions are view-only.

Three architectures fit the phrase "multiplayer coding agent":

ShapeExamples
AMultiplayer terminal: users share the PTY that Copilot CLI runs inccshare, Coterm
BMultiplayer agent session: users share one SDK session through a room serverthis project
CMultiple agents in one workspace, coordinated to avoid collisionsOpenHands, Bothread

This project is B. GitHub's own SDK documentation describes it as "Pattern 3: shared sessions (collaborative)", "like a shared chat room with Copilot", and warns that the SDK provides no session locking, so access must be serialized by the application. See https://docs.github.com/en/copilot/how-tos/copilot-sdk/setup/scaling.

#Architecture

 Alice โ”€โ”                                      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 Bob   โ”€โ”ผโ”€ browser โ”€โ”€ WebSocket โ”€โ”€โ–บ Room server โ”‚ participants       โ”‚
 Carol โ”€โ”˜                                      โ”‚ strict prompt queue โ”‚
                                               โ”‚ permission router  โ”‚
                                               โ”‚ JSONL transcript   โ”‚
                                               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                                         โ”‚ Copilot SDK (JSON-RPC)
                                                         โ–ผ
                                               Copilot runtime, one session
                                                         โ”‚
                                                         โ–ผ
                                                  the repository

#Components

#Identity and roles

Identity { id, provider: github | entra | guest, login, displayName, role }
Role     host | member | viewer | pending
Actionpendingviewermemberhost
watchโœ“โœ“โœ“
submit promptโœ“โœ“
withdraw own promptโœ“โœ“
withdraw anyone's promptโœ“
answer permission promptif authorโœ“
abort running turnโœ“
admit, re-role, remove peopleโœ“

pending is a signed-in user parked at the door. They hold a WebSocket but receive nothing except the decision. Hosts see one card per waiting identity and choose viewer, participant, or reject; the decision is persisted and applied to every tab that identity has open. Hosts are configured (--hosts), never decided on.

Guests are viewer or member depending on --guests view|participate, never host. Guest identity is unverified and is badged as such in the UI and the transcript.

#Admission flow

sign-in โ”€โ”€โ–บ resolveRole โ”€โ”€โ–บ host / member โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ hello
                โ”‚
                โ”œโ”€โ–บ stored decision / allow list โ”€โ”€โ”€โ”€โ”€โ”€โ–บ hello (or 403)
                โ”‚
                โ””โ”€โ–บ policy: approve โ”€โ”€โ–บ admission.pending โ”€โ”€โ–บ host card
                                                              โ”‚
                                             admission.decide โ”˜
                                                              โ–ผ
                                       admission.decided + hello, or rejected

Host approval is the default for every sign-in type. On a server with no host online the automatic paths carry the load: org, team or group gates, the allow list, remembered decisions, and a policy of admit-as-viewer set from the UI. Anyone who still ends up waiting is shown to the next host who connects.

#Copilot authentication and seats

The Copilot runtime authenticates as one account: the host's CLI login in laptop mode, or a service account token (COPILOT_GITHUB_TOKEN) in server mode. All premium requests bill to that account. On Copilot Enterprise every participant should hold a seat, and the host pattern should be cleared with the org admin. Viewing-only guests are low risk; guests who steer the agent are people without seats driving a seat they do not own.

#What the SDK multi-tenancy guide says, and why most of it does not apply

The multi-tenancy guide targets strangers sharing one runtime: run in "empty" mode, per-user tokens, explicit tool allowlists. This room deliberately shares one coding agent on one repo among people who trust each other, so it keeps the default coding-agent mode and one token. What does carry over: the runtime executes shell commands with the host's privileges, so run it in a container or a dedicated worktree, and gate who can join.

#Network

#Runtime behaviour we had to work around

These are properties of the bundled Copilot runtime, not of this codebase. They are written down because each one cost an afternoon to find, and each workaround looks removable to someone who does not know why it is there.

The runtime does not apply a custom agent's authored prompt. An agent discovered from disk fails outright:

Standalone server does not support session effect 'custom_agent_prompt'

An agent supplied through customAgents is accepted, appears in agent.list(), and is dispatched under its own name โ€” but answers as the default agent. agent.select() is worse: it reports success and agent.getCurrent() confirms the selection, while the selected agent's prompt has no effect on the next turn at all. Because the catalog has already parsed the prompt out of the .md, the agent wrapper sends it as part of the subagent's task text. If a future runtime applies the prompt itself, the agent will receive it twice and this must be removed.

A subagent turn produces no session.idle. The host session goes quiet while the background task works, so the wrapper ends the turn on subagent.completed instead. Aborting clears the same flag, or the room would never return to idle.

Tool names are platform-specific. The shell tool is powershell on Windows and bash on Linux, so a tools: list in an agent definition silently strips the agent of a shell on the other platform. Names observed from this runtime: glob, powershell, read_agent, rg, skill, task, view.

Skills only run while the session is idle. commands.list reports allowDuringAgentExecution: false for them, which is why the room expands a /command in pump() rather than at submit time.

#Milestones

M1 (this skeleton, to be completed): one room, one repo, GitHub App and guest sign-in, org/team gate, strict queue with attribution, permission routing to the author with timeout, transcript with replay, session resume on restart, Docker image, npm publish.

Acceptance: two browsers on different GitHub accounts submit prompts alternately; both see identical streamed output; the second prompt waits for the first turn.

M2 (partly done): Microsoft Entra ID sign-in and host-approved public sign-in are in, as are markdown-rendered replies and the repository's own skills and custom agents behind / and @. Remaining: optional Copilot seat verification with an admin token, voting on destructive actions, per-kind auto-approval rules.

M3: multiple rooms per server and switching between sessions from the UI (today a room is one session; a second conversation means a second room on another port), Slack/Teams bridge, mid-turn steering.

#Non-goals

Not a replacement for Copilot's own session sharing, not a multi-agent orchestrator, not a general chat product.