Skip to content
View as .md

Multi-chat

A thread holds many humans + bots, yet each bot still gets a clean one-on-one LLM call without lying about authorship. One durable stream is what was said and what each bot did, all interleaved and author-stamped; dispatch decides who wakes; buildMessages projects that shared history into pi messages from one bot’s point of view.

Alice, Bob, bot-X ──► THREAD STREAM (arbe-thread-{id})
chat · pi.assistant · pi.tool_result · signal.*
│ every entry author-stamped (authorId)
DIRECTOR catches unread entries up to the read-time head
│ one lease per thread
│ free rule or one cheap model call: one speaker or nobody
LLM TURN via buildMessages(selfId, entries)
appends pi.assistant (authored by the bot) to the same stream

Three layers, each with one job. The thread stream is an append-only multi-party log with every entry carrying its authorId. The director catches up from decided_offset and derives open mentions and the per-bot turn limit from the stream. A paid verdict or picked bot records a decision; a bot attempt adds its tagged outcome. The per-bot LLM view is a disposable projection whose system prompt is <persona> + <thread context> + <participant list>. A paid director choice uses its own small pinned model; only the chosen bot pays for a full turn. See thread director.

Projection rules in buildMessages(selfId, entries) → pi.Message[] (packages/core/dispatch/build-messages.ts): a bot’s own pi.assistant/pi.tool_result entries pass through (toolCalltoolResult pairs preserved — providers require it; a missing result is back-filled so the next complete() never sees a dangling call); another agent’s chat becomes a labelled [Alice]: … user turn; another bot’s assistant text becomes a labelled user turn too, so the target bot can tell speakers apart; other authors’ tool results, chunks, compaction, and lifecycle entries are skipped. Pi’s UserMessage is a projection output, never storage.

chat.chunk streams a bot’s final text turn for live UX; tool activity never chunks. Resume a bot by reading the thread and re-running the projection — the stream is the only durable handle, the projection is recomputed, never stored. A coding session is just a thread with one human + one driving bot (agentId set).

Code: projection + turn execution live in packages/core/dispatch/; entry parsing, rules, settlement, and the paid choice live in packages/core/thread-director/. apps/backstage/src/thread-director-wake.ts runs both under the thread lease.
See when bots reply for which bots the director is even choosing between, thread director for the choice, dispatch for the turn, and threads / agents for the nouns.

Deferred: per-(bot, thread) trigger policy when one bot inhabits many threads; per-turn checkpoints for cheap replay.