Live session-to-session collaboration for pi and kimchi.
Run two (or more) coding-agent terminals side by side. Each session discovers the others, binds an A2A-compatible inbox, and can hand bounded work to a peer — "kinda like a subagent", except the worker is a full interactive session with its own user, its own context, and its own TUI.
flowchart LR
subgraph TUI-A["kimchi/pi TUI — session A (lead)"]
A1[agent + tools<br/>list_peers · link_peer<br/>ask_peer · message_peer] --> A2[agent-colab extension]
A2 --> A3[A2A inbox server<br/>127.0.0.1:port<br/>bearer token]
A2 -- "/colab picker<br/>/agent-name" --> U1[user]
end
subgraph TUI-B["kimchi/pi TUI — session B (worker)"]
B1[agent + tools] --> B2[agent-colab extension]
B2 --> B3[A2A inbox server<br/>127.0.0.1:port<br/>bearer token]
end
R[("peer registry<br/><agentDir>/peers/*.json<br/>pid-liveness, names.json")]
A2 <-- HTTP JSON-RPC<br/>message/send · tasks/get<br/>A2A v1.0 subset --> B3
B2 <-. HTTP JSON-RPC .-> A3
A2 --- R
B2 --- R
style A3 fill:#1b3a5c,color:#fff
style B3 fill:#1b3a5c,color:#fff
style R fill:#4a2d12,color:#fff
Orchestrating parallel coding work today means copy-pasting between terminals, or spawning throwaway subagent processes that can't see what your other sessions already know. agent-colab makes your existing sessions addressable:
- Discover every live local session — new, restarted, or reloaded peers appear on their own; nothing to re-register.
- Delegate a bounded task to a named worker and wait for its reply (
ask_peer), or fire-and-forget a heads-up (message_peer). - Stay cache-friendly: peers exchange conclusions + file pointers, never transcripts or JSON dumps. Inbound messages append at the conversation tail — every session remains one unbroken, prefix-cache-friendly token stream. (Session merging was deliberately rejected: a merged file is a token stream no inference server ever cached.)
# As a pi package (recommended)
pi install npm:pi-agent-colab # or: pi install git:github.com/getkimchi/pi-agent-colab
# From a checkout
./install.sh # symlinks into pi's discovered extensions dirNew sessions load it automatically. Already-running sessions: type
/reload — pi re-runs extension discovery in place, no restart needed.
# Terminal A # Terminal B
$ kimchi $ kimchi
> /agent-name alpha > /colab ← pick alpha
> ... > "Ask alpha what's in its cwd"
→ agent calls ask_peer,
alpha's reply lands here
| Command | What it does |
|---|---|
/colab |
Pick a live session → link it as a worker, optionally tell your agent |
/agent-name <name> |
Name this session (persisted; peers address you by it) |
/reload |
Attach/update the extension in a running session |
| Tool | Behavior |
|---|---|
list_peers |
Live local sessions (self hidden, linked marked) |
link_peer / unlink_peer |
Designate / drop a worker |
ask_peer |
Blocking task → wakes an idle peer, returns its reply as the tool result |
message_peer |
Fire-and-forget → never wakes the peer; optional notifyWhenIdle one-shot notice |
Acknowledgment is transport-level, never model-level — the JSON-RPC task state is the receipt, handled by the extension. The receiving agent never burns a turn to say "got it".
sequenceDiagram
participant UA as User (A)
participant AA as Agent A
participant IA as A's inbox (A2A)
participant IB as B's inbox (A2A)
participant AB as Agent B
UA->>AA: "Ask beta to check the flaky test"
AA->>IA: ask_peer → POST message/send
IA->>IB: JSON-RPC (bearer token)
IB->>IB: consent check (accept/hold/refuse)
IB->>AB: inject "[peer message from alpha]"<br/>idle → followUp + triggerTurn<br/>busy → steer (between tool calls)
Note over AB: works, settles
IB-->>IA: task completed (reply text captured<br/>from B's session transcript)
IA-->>AA: tool result: B's reply
AA->>UA: "beta says: it's the retry loop in x.ts"
Fire-and-forget (message_peer) skips the wake entirely: the task completes at
injection (that's the HTTP-200-style ack), the message queues as nextTurn, and an
opt-in notifyWhenIdle notice is sent by the extension — immediately if the peer is
already idle, otherwise after its next settle.
| Variable | Default | Meaning |
|---|---|---|
AGENT_COLAB |
on | off disables the extension |
AGENT_COLAB_INBOUND |
accept |
hold = approval dialog per message · refuse = reject at the door |
AGENT_COLAB_STATE_DIR |
<agentDir>/peers |
peer registry location |
- Inboxes bind 127.0.0.1 only, with a mandatory per-session bearer token.
- Peer messages cannot approve permissions, change configuration, or execute commands; the receiver's own permission gates still apply.
- Every inbound message is labeled with its sender in the transcript.
- Abuse resistance: 200 KB message cap, burst cap, duplicate suppression, in-flight cap, self-send refusal — agent-to-agent loops die on their own.
- Pure extension — imports only pi's public API (
ExtensionAPI,getAgentDir),typebox, andpi-tui. No harness internals; works in vanilla pi and kimchi. - Registry —
<agentDir>/peers/<sessionId>.jsonrecords with pid-liveness pruning on read;names.jsonpersists/agent-nameacross restarts. The agent dir is inferred from the live session-file path, so all sessions converge on one registry even across harness redirections. - A2A subset —
GET /.well-known/agent-card.json,message/send,tasks/get,tasks/cancel(JSON-RPC 2.0 over loopback HTTP, per the A2A v1.0 spec). Theclient.ts/a2a-server.tsseam is designed for a later swap to the officiala2a-jsSDK and SSE streaming. - Session files are append-only entry chains (
parentId-linked); reply capture reads the newest assistant entry after an injection marker.
npm install
npm test # 37 tests: registry, A2A protocol, tools, full extension lifecycle
npm run check # tsc --noEmit- Headless sessions (
-p/rpc) bind inboxes — long-running workers stay reachable - A2A
message/stream(SSE) for streaming delegation updates - Shared-server KV reuse: messages already carry log refs; a KV-transfer layer carries what was read while A2A carries what changed