██████╗ ██████╗ ██████╗ ██╗ ██╗██╗ ██╗███████╗ ██╔═══██╗██╔══██╗██╔══██╗██║ ██║██║ ██║██╔════╝ ██║ ██║██████╔╝██████╔╝███████║██║ ██║███████╗ ██║ ██║██╔══██╗██╔═══╝ ██╔══██║██║ ██║╚════██║ ╚██████╔╝██║ ██║██║ ██║ ██║╚██████╔╝███████║ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
Agents deliberate in rooms.
Outside their context windows.
“Many minds, from many makers, argue at one table — the best path leaves the room.”
Fleets of agents across providers deliberate in rooms outside their context windows, and converge on a decision of record.
Multi-agent “discussion” usually means piping every agent's output into every other agent's prompt. Context windows fill with other agents' reasoning, token costs grow quadratically with participants, and long collaborations die of transcript bloat.
A context-window contract.
The discussion lives in a small local broker — outside every agent's context window. A room reaches an agent through three tiers, and the two that arrive unasked are bounded by the runtime rather than by prompt discipline:
| Tier | What enters context | Bound |
|---|---|---|
| activity ping push | #design: 3 new (planner, critic) | one line per quiet period, coalesced |
| digest pull | newest messages verbatim → older as one-line headlines → rest collapsed to a count | fixed character budget (default 2000) |
| explicit fetch pull | raw messages by sequence range | none — full bodies; limit is the only guard |
fetch is deliberately not bounded: that tier exists for the caller who genuinely needs the text. It is a choice to spend context — which is different from context arriving whether you wanted it or not.
The room lives in a broker, not in anyone's window.
A small local socket server holds rooms, ring buffers, and read cursors. Agents reach it through tool calls; it reaches them through one-line activity pings. Cursors live broker-side, keyed by role name, so they survive session restarts.
┌──────────┐ post / digest ┌─────────────────────┐ │ planner │◄─────────────────►│ Orphus broker │ ├──────────┤ (tool calls) │ (local socket) │ │ critic │◄─────────────────►│ │ ├──────────┤ activity pings │ rooms · ring buffer │ │ reviewer │◄─────────────────►│ read cursors │ └──────────┘ (one-liners) └─────────────────────┘
A hostile peer cannot inflate your context.
The digest is deterministic and model-free: budget is spent on the newest messages first, rendered chronologically. A verbose — or hostile — peer cannot spend your context. That guarantee is specific, and the honest scoreboard of every bound lives in architecture.md.
$ roundtable digest #design → newest verbatim · older as headlines → rest collapsed to a count → deterministic, no model in the loop
Measured, not promised.
In the bundled demo, a reviewer joining after a 9-message design discussion catches up for 32% of the raw transcript cost — decision messages intact verbatim, only early exploration collapsed. CI asserts the ratio against a 40% ceiling and fails the build rather than reporting a worse number.
$ npm run demo late-joiner catch-up: 32% of raw transcript ceiling 40% — build fails past it
Declare the fleet once.
Deliberation improves when roles run on different models — distinct models disagree more usefully. Rooms key everything by role name, so any model can sit behind any role. The manifest doubles as the reproducibility artifact: same roles, same models, same budgets — rerun the deliberation.
roles: planner: { provider: anthropic } researcher: { provider: openai } critic: { provider: xai } $ npm run roles -- --format tmux | sh
No model. No API key.
No network.
demo:loop is the whole thesis in one run: four roles deliberate, a late reviewer catches up on a bounded digest, the librarian exports the room losslessly, memory ingests it, and a fresh session with no access to the room recalls the decision. It asserts each of those properties, so it fails loudly if the loop breaks.
$ git clone https://github.com/kelvincushman/orphus.git orphus && cd orphus $ npm ci --ignore-scripts $ npm run demo # scripted 3-agent discussion + late-joining reviewer $ npm run demo:loop # the full loop: room → export → memory → later recall $ npm run roles # the role manifest, turned into launch commands
- Getting startedClone to working fleet, in five tiers. The first needs no model and no API key.
- TroubleshootingThe three failures that look like success.
- The roundtable toolEvery action, parameter, and default, with the reasoning.
- Roles & the manifestDeclaring a fleet in orphus.roles.yaml.
- MemoryThe durable layer: export → ingest → query, behind the librarian gate.
- Fleets & OrcaBlueprint-driven orchestration; parallel worktree fan-out.
- ArchitectureWhat runs where, and what the bound actually guarantees.
- Design decisionsWhy each choice went the way it did — alternatives rejected included.
- RLM security postureThe rules self-modification must obey, and which are enforced.
- ContributingIssue coordination and pull request guidance.
- CIThe gate that runs, what it covers, and what it deliberately does not.
- Security policyReporting a vulnerability, and what is in scope.
| node ≥ 22.19 | Runs the agent and every test suite. |
| bun ≥ 1.3.14 | Runs the demo, the role launcher, scripts/*.ts; compiles release binaries. |
| npm only | package-lock.json is the single lockfile, and the committed .npmrc adds a supply-chain gate. pnpm, yarn, and bun installbypass it — they are not merely discouraged, they are wrong here. |
| contributing | CONTRIBUTING.md — read AGENTS.md first. |
| security | Report privately — never through public issues. |
| issues | github.com/kelvincushman/orphus/issues |
██████╗ ██████╗ ██████╗ ██╗ ██╗██╗ ██╗███████╗ ██╔═══██╗██╔══██╗██╔══██╗██║ ██║██║ ██║██╔════╝ ██║ ██║██████╔╝██████╔╝███████║██║ ██║███████╗ ██║ ██║██╔══██╗██╔═══╝ ██╔══██║██║ ██║╚════██║ ╚██████╔╝██║ ██║██║ ██║ ██║╚██████╔╝███████║ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
Many minds,
one table.
The best path leaves the room — and the room never touches a context window.