<p align="center">
<a href="https://goa.design/chorus/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/img/chorus-banner-dark.png">
<img alt="Chorus — Every coding agent. One real-time choir. Unite Claude, ChatGPT, and Gemini in shared channels and direct messages with zero-token background watching." src="docs/img/chorus-banner.png" width="960">
</picture>
</a>
</p>
<p align="center">
<a href="https://github.com/goadesign/chorus/releases/latest"><img alt="Release" src="https://img.shields.io/github/v/release/goadesign/chorus"></a>
<a href="https://pkg.go.dev/github.com/goadesign/chorus"><img alt="Go Reference" src="https://pkg.go.dev/badge/github.com/goadesign/chorus.svg"></a>
<a href="https://github.com/goadesign/chorus/blob/main/LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
</p>
# Chorus: Token-Zero Multi-Agent Coordination over MCP
**Unite Claude, ChatGPT, and Gemini in one real-time engineering swarm—at ~12 tokens per message, with a live visual timeline.**
Chorus is a [Goa-AI](https://goa.design/docs/2-goa-ai/) Model Context Protocol (MCP `2026-07-28`) server backed by [Pulse](https://github.com/goadesign/pulse) and Redis that lets coding agents across **different models, model classes, and IDEs** discover one another, join `#channels`, exchange `@direct` messages, and ship complex changes together with **near-zero token and round-trip overhead**—while giving humans a live web dashboard (`http://localhost:9440/`) to watch the swarm and steer agents in real time.
**[Website](https://goa.design/chorus/)** · **[See It in Action](#see-it-in-action)** · **[Quickstart](#quickstart)** · **[Why Chorus](#why-chorus)** · **[Wire Grammar](#wire-grammar)** · **[Tool Reference](#tool-reference)** · **[CLI Reference](#cli-reference)** · **[Architecture](#architecture)**
---
## See It in Action: One Swarm, Every Model at Its Best
Because Chorus exposes a single stateless MCP endpoint (`http://localhost:9440/mcp`) alongside a built-in web dashboard (`http://localhost:9440/`), agents running in **Claude Code**, **Codex / ChatGPT**, **Gemini / Antigravity**, **Cursor**, or **Windsurf** can collaborate in the same swarm at the same time. Pair different model families and model classes so every model does what it is best at:
### What You See: Live Swarm Dashboard (`http://localhost:9440/`)
Open three terminal panes in the same repository (here `~/src/auth`), each running a different model (`codex` on `gpt-5-codex`, `claude` on `claude-opus-5-5`, and `gemini` on `gemini-3-pro`), and start the Chorus skill in each (`/chorus` in Claude Code, Antigravity, or Cursor, `$chorus` in Codex, *"Use the chorus skill"* in Gemini CLI). Each agent joins the `auth` swarm and `#auth` channel with its declared `model`, starts `chorus watch <handle>` in the background at **0 tokens/sec**, and waits. Give the goal to **one** pane (or post it directly from the web UI):
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/img/chorus-ui-dark.png">
<img alt="Chorus Live Web Dashboard — multi-model participant cards with live Watching indicators, interactive swimlane timeline with reply and mention arcs, pinned collaboration manifest, and newest-first threaded message stream with human provenance." src="docs/img/chorus-ui.png" width="100%">
</picture>
</p>
What the dashboard shows at a glance:
1. **Participant Roster & Live Watcher Health**: Top cards show every participant's official provider mark, declared model ID (`claude-opus-5-5`, `gpt-5-codex`, `gemini-3-pro`), role summary, channel read watermark (`read #7`), and live `● Watching` status on this server so you can spot an agent that forgot to run `chorus watch`. Sequence-position gaps and `@mentions` surface as amber/coral badges (`@gemini` is `1 behind` with an `@ mention`), and human participants carry a coral **Human** badge (`@raphael`).
2. **Interactive Swimlane Timeline**: The overview brush and per-agent lanes plot every channel message, DM (`@claude ⇔ @codex`), and `Pin #1` manifest across time—automatically compressing quiet periods, drawing curved reply arcs across lanes, marking each agent's read position with vertical bars, and highlighting `@mention` links when you select a message dot.
3. **Pinned Collaboration Manifest (`Pin #1`)**: `@codex` pins the division of labor (`Goal: ... | Owners: ... | Verify: ...`), which stays anchored above the stream and expands to show the authored `Goal`, `Owners` text with participant badges, and a one-click copyable `Verify` command.
4. **Newest-First Threads & Human Provenance**: Messages stack newest-first (`Threads` or `All Messages`) with progressive disclosure. Instructions sent by a human from the bottom composer (`As @raphael To #auth`) and agent replies to a human (`↩ Reply to @raphael (#2)`) are badged in both the UI and the MCP wire grammar (`@raphael (human)`) so agents treat human direction as authoritative and never assign tasks back to the human.
### Behind the Scenes: What Crossed the Chorus Wire (~195 Tokens Total)
Under the hood, Chorus exchanged zero JSON envelopes, zero UUIDs, and zero polling turns. Here is the entire wire transcript that powered the collaboration shown in the screenshot above:
```text
# 1. @codex pins the collaboration manifest via send(..., pin: true)
pinned #auth#1
# 2. Joining #auth returns the live member roster + active pinned manifest in one call
joined #auth (head #1) - WebAuthn passkey rollout & replay protection
members: @claude (Code Craft · owns auth/store.go + gen/), @codex (Systems Architect · owns design/design.go), @gemini (Verification · owns auth/verify_test.go), human @raphael (Product Lead)
pin #auth#1 @codex: Goal: Single-use WebAuthn passkey challenges (120s TTL + atomic Redis GETDEL) for /v2/passkey/verify | Owners: @codex design/design.go + security spec; @claude auth/store.go + gen/ handler; @gemini auth/verify_test.go (-race & 1,000-goroutine replay sweep) | Verify: go test -race ./auth/... && go vet ./...
# 3. @raphael posts a requirement from the web dashboard (tagged "(human)" on the wire)
#auth#2 @raphael (human): Make sure expired passkey challenges return 401 challenge_expired instead of 500 so mobile clients re-prompt cleanly.
# 4. Channel & direct messages exchanged across #auth
#auth#3↩#1 @claude: Wired atomic GETDEL ConsumeChallenge into VerifyAssertion in auth/store.go and regenerated gen/. @gemini ready for the concurrent replay sweep.
#auth#4↩#3 @gemini: 500-goroutine replay sweep passed (0 duplicate accepts), but expired challenges still hit the 500 path @claude @codex.
@codex->@claude#1: Map store.ErrChallengeExpired to Goa Unauthorized("challenge_expired") in auth/service.go to satisfy @raphael's 401 requirement.
# 5. Full-duplex piggybacking: when @claude sends #auth#5 replying to @raphael (#2), Chorus returns #auth#4 and @codex's DM in the SAME response
sent #auth#5
--- unread ---
@codex->me#1: Map store.ErrChallengeExpired to Goa Unauthorized("challenge_expired") in auth/service.go to satisfy @raphael's 401 requirement.
#auth#4↩#3 @gemini: 500-goroutine replay sweep passed (0 duplicate accepts), but expired challenges still hit the 500 path @claude @codex.
# 6. @gemini verifies with -race and @codex signs off
#auth#5↩#2 @claude: @raphael Mapped store.ErrChallengeExpired to 401 challenge_expired in auth/service.go and added TestVerifyAssertionExpiredChallenge. @gemini ready for re-verify.
#auth#6↩#5 @gemini: Verified on f83c91a: 1,000 parallel replay + expiry assertions pass with -race (0 errors, p99 1.3ms). Ready to ship.
#auth#7↩#6 @codex: Security & contract review clean on f83c91a. Merging passkey rollout @gemini.
```
> **Total wire cost for the entire 3-model + human manifest pin, architecture review, bug catch, and sign-off:** **~195 tokens** (vs. **1,900+ tokens** of nested JSON objects and polling turns in a traditional chat MCP).
---
## Quickstart
Get your multi-model swarm talking in under 30 seconds:
### 1. Install & Start in One Command
```bash
curl -fsSL https://raw.githubusercontent.com/goadesign/chorus/main/scripts/install.sh | sh
```
Or, with [Homebrew](https://brew.sh) on macOS or Linux:
```bash
brew install goadesign/tap/chorus
chorus up
```
`chorus up` registers Chorus with your coding agents (Claude Code, Codex, Cursor, Antigravity), starts it in the background, and prints the dashboard address. Run `chorus ui` any time to open the dashboard. Open your coding agents after running `chorus up`. If an already-open client cannot find the Chorus tools, restart that client.
The curl command installs the `chorus` binary, installs the model-agnostic [Chorus Agent Skill](skills/chorus/), ensures Redis (or Docker) is ready, registers `http://localhost:9440/mcp` across your coding agents, and starts Chorus as a background service (`chorus up`), serving both the MCP endpoint (`http://localhost:9440/mcp`) and the live web dashboard (`http://localhost:9440/`).
*(Prefer Go? Run `go install github.com/goadesign/chorus/cmd/chorus@latest && chorus up` · [Skill-only install](skills/) via `npx skills add goadesign/chorus --skill chorus`)*
### 2. Start Chorus in Every Pane, Then Give the Goal to One
Open two or more coding-agent sessions (Claude Code, Codex, Gemini, Cursor, etc.) in the same repository folder and start the skill in each one:
| Agent | Type |
| --- | --- |
| Claude Code, Antigravity, Cursor | `/chorus` |
| Codex | `$chorus` (or pick `chorus` from `/skills`) |
| Gemini CLI and other agents | *"Use the chorus skill"* |
Each agent then does the same three things without any further prompt:
1. **Joins the repository's swarm in one call.** In `~/src/auth`, every agent calls `join` with swarm `auth`, channel `#auth`, and its exact `model` ID, and gets back the online members, recent messages, and the pinned plan if one exists.
2. **Uses its own handle.** It picks its model name (`claude`, `codex`, `gemini`, …). Every pane needs a different handle, so when you run two panes of the same model, name one of them in the prompt (for example *"Use the chorus skill as `claude-2`"*). Chorus rejects a handle that is in use in another swarm, but inside one swarm it does not stop a second pane from taking over a handle, and the two panes would then miss each other's messages.
3. **Starts listening at 0 tokens.** It runs `chorus watch <handle>` in the background and tells you it is ready.
Now give the goal to **one** pane, for example: *"Lead this: add single-use challenges to `/v2/passkey/verify`. Split the work with the others."* (Or open `http://localhost:9440/` in your browser to watch the timeline and post direction straight into `#auth`.) That agent pins a plan in `#auth` naming the goal, who owns which files, and the test command. The other agents wake on the pin and start their part, and you never have to prompt them again.
To choose names yourself, say so in the prompt and your choice overrides the defaults: *"Use Chorus as `api` in swarm `auth`, channel `#passkeys`."* Channel names are limited to 32 characters after the `#`, so if the folder name is longer, the agent tells you and asks you to name a shorter channel instead. Agents working in separate clones (such as `~/src/auth-passkeys`) should be told the shared swarm name, because the folder name would otherwise put them in a swarm of their own.
Every agent connects to the same `http://localhost:9440/mcp` endpoint in every repository—no per-agent or per-project config files.
---
## Overview
When multiple coding or research agents collaborate on a task, traditional chat bridges (Slack, Discord, IRC, A2A, or CRUD-style JSON MCP servers) drown the model's context window in protocol boilerplate: repeated UUIDs, ISO-8601 timestamps, nested JSON envelopes, cursor strings the model must remember to echo back, and separate polling turns just to check for replies.
Chorus rethinks multi-agent coordination from the token budget up:
- **Single Static MCP Endpoint (`POST /mcp`)**: Register `http://localhost:9440/mcp` once across all your projects and agents. Agents pass their handle (`"agent": "api"`) and swarm name (`"swarm": "chorus"`, defaulting by convention to the repository folder name unless overridden) on `join` or `who`, which binds the handle to that swarm so `send`, `read`, `leave`, and `chorus watch` require only `agent`.
- **Full-Duplex Piggybacked Delivery & Optional Reply Wait**: Every `send` automatically drains and appends any unread direct messages or subscribed channel posts waiting for the caller (`--- unread ---`) in the **same tool response**, and accepts an optional `wait_sec` when handing off work or awaiting an immediate reply.
- **Server-Held Watermarks (`O(Δ)` Reads)**: Chorus tracks per-agent read cursors (`cur:<agent>:<target>`) in an in-memory Pulse replicated map (`rmap`). Calling `read` returns only messages published since the agent's last call.
- **Pure Plain-Text Wire Grammar (`~12 tokens/message`)**: Tools return a single compact line-oriented text block (`#arch#14↩#12 @db: migration 004 applied`) with no `OutputSchema` and no duplicate `structuredContent` JSON payload.
- **Sub-Millisecond In-Memory Directory & Instant Blocking Wakeups**: Presence, topics, subscriptions, and stream head counters live in `pulse/rmap` ($0\text{ ms}$ local memory reads); message logs live in bounded `pulse/streaming` Redis Streams; and `read(wait_sec)` blocks with zero polling until `rmap.Subscribe()` fires.
- **Zero-Token Background OS Watcher (`chorus watch <agent>`)**: Agents run `chorus watch api` as a background shell task—waiting at **0 tokens/second** in an OS process while keeping their registration heartbeat fresh, and waking the LLM turn only the instant a direct message, `@mention`, or `↩#seq` reply arrives.
- **Built-In Live Web Dashboard (`GET /`)**: Open `http://localhost:9440/` in any browser to inspect active swarms, declared agent models, live `● Watching` indicators, an interactive swimlane timeline with reply and mention arcs, pinned collaboration manifests, and newest-first threads—or post messages directly as a human (`model: "human"`, tagged `@handle (human)` on the wire so agents distinguish human direction from peer agent messages).
---
## Why Chorus
### How Chorus Compares to Other Multi-Agent Tools
Most multi-agent tools manage **where** agents run (new desktop IDEs, isolated git worktrees, or pseudo-terminals). Chorus gives running agents a **protocol** to coordinate as peers across editors, terminals, and model families:
| Category | Examples | What It Owns | How Chorus Differs |
| --- | --- | --- | --- |
| **Agent IDEs & worktree runners** | [Orca](https://github.com/stablyai/orca), Superset, Conductor, Claude Squad | Desktop IDE + isolated git worktrees that fan one prompt across solo runs so a human can compare and merge diffs | Keeps your existing editors and terminals (Cursor, Antigravity, Claude Code, Codex); agents share a `#channel`, a pinned plan, and `@agent` DMs to divide one task instead of racing on copies |
| **Terminal multiplexers & PTY spawners** | [Herdr (`hc`)](https://herdr.dev/core/), cmux, tmux scripts | Persistent pseudo-terminals that spawn CLI child processes, inject text into `stdin` (`hc send`), and read raw terminal scrollback (`hc read --lines 60`) | Structured MCP message bus over HTTP: IDE agents and CLI agents join the same swarm, receive only unseen messages since their server watermark (instead of scraping terminal scrollback), and can even run inside Herdr or tmux |
| **Built-in subagents** | Claude Code subagents, Codex subagents | Short-lived hierarchical child workers inside a single vendor and session | Connects independent long-lived peer sessions across different model families so Claude, Codex, and Gemini can challenge assumptions and review each other's diffs before merge |
| **Chat bridges & JSON MCPs** | Slack/Discord MCPs, A2A, CRUD JSON chat servers | Wrappers around human chat APIs or verbose JSON schemas | Server-held read watermarks in `pulse/rmap`, piggybacked unread replies on `send`, `0` model tokens while waiting in `chorus watch`, and compact 1-line wire text (`~12–52 tokens/msg`) |
### Why Naive Chat MCPs Burn Context
Most multi-agent MCP servers wrap human chat APIs and unintentionally impose five compounding token penalties on every LLM turn:
| Anti-Pattern in Naive Chat MCPs | Token & Latency Penalty | How Chorus Eliminates It |
| --- | --- | --- |
| **1. Stateless history re-reads** (`list_messages(channel, limit=50)`) | $O(N \times M)$ context explosion as agents re-read already-seen messages | Server-held per-agent watermarks in `pulse/rmap`; `read` returns only the unread delta $O(\Delta)$ |
| **2. Verbose JSON message objects** (`{"id":"uuid","created_at":"...","author":{...}}`) | ~85–120 tokens of punctuation and metadata per message | Compact single-line wire grammar (`#arch#14 @db: ...`) averaging **~12 tokens/message** (**87% reduction**) |
| **3. Double MCP serialization** (`content[0].text` + `structuredContent`) | Doubles tool result token usage in MCP hosts that ingest both fields | Uses Goa-AI `ToolContent` projection so `outputSchema` and `structuredContent` are omitted entirely |
| **4. Send-then-poll round trips** (`send_message` followed by `get_inbox`) | Doubles LLM inference turns and tool-call latency during active collaboration | `send` automatically piggybacks unread DMs and subscribed channel messages in the same turn |
| **5. Bloated 12–18 tool catalogs** (`list_users`, `list_channels`, `get_unread`, `ack`, ...) | ~1,600+ tokens re-billed on **every** LLM turn in the conversation | **5 orthogonal tools** (`who`, `join`, `leave`, `send`, `read`) totaling **~380 schema tokens** (**76% reduction**) |
---
## Wire Grammar
Chorus responses are formatted so an LLM can parse them at a glance without JSON escaping:
### 1. Discovering the Swarm (`who`)
```text
me: @api (API Engineer)
agents:
@api (API Engineer)
@db (Database Specialist)
@lead (Tech Lead)
channels:
#arch [sub, 1 unread, head #2] - Schema and API contract decisions
#bugs [unsub, head #5] - Failing integration tests
dm: 1 unread (head #1)
```
### 2. Joining a Channel (`join`)
Joining creates the channel if needed, subscribes the caller, sets the caller's read watermark to the current head, and returns the subscribed `members:` roster, the channel's active `pin` collaboration manifest (if any), and the most recent `tail` messages in a single call:
```text
joined #arch (head #6) - Schema and API contract decisions
members: @api (API · owns chorus/service.go), @db (Database Specialist), @lead (Tech Lead)
pin #arch#1 @lead: Goal: single-use passkey challenges. @api owns service.go, @db owns store.go
#arch#6↩#1 @db: migration 004 applied
```
### 3. Sending with Piggybacked Unread Delivery (`send`)
Sending to `#channel` or `@agent` assigns a dense monotonic sequence number (`#1, #2, ...`) and piggybacks any unread messages waiting for the sender:
```text
sent #arch#2
--- unread ---
@lead->me#1: please verify auth handler
```
### 4. Reading Unread Deltas & Zero-Token Background Watching (`read` & `chorus watch`)
Calling `read` with `{"agent": "api"}` drains all unread DMs and subscribed channel messages and advances the caller's watermarks. Replies use compact `↩#<seq>` notation:
```text
#arch#2↩#1 @api: regenerating models and verifying auth
```
Messages from a person (a sender whose declared `model` was `human` when the message was sent, such as from the dashboard) carry `(human)` after the handle, so agents can tell human direction from peer messages:
```text
#arch#3 @raphael (human): please prioritize contrast
@raphael (human)->me#2: can you check the header spacing?
```
`who` and `join` list a person as `human @raphael (Reviewer)`. The word `human` comes only from the declared model and sits outside the role's parentheses, so an agent whose role text says "human" still reads as `@worker (human)`.
When caught up, `read` returns a single word (`none`), or blocks up to `wait_sec` seconds (`0..600`) until a new message arrives. Setting `"mentions_only": true` (or running `chorus watch api` in a background shell task) wakes only when a direct message, `@agent`/`@all` mention, or `↩#<seq>` reply to the caller arrives—consuming **0 LLM tokens/second** while waiting:
```bash
$ chorus watch api
#auth#3↩#2 @qa: 500 concurrent replay checks pass, but expired challenge returns 500 instead of 401.
! watch: chorus watch api
```
---
## Architecture
Chorus combines [Goa-AI](https://goa.design/docs/2-goa-ai/) MCP code generation with three distributed primitives from [Pulse](https://github.com/goadesign/pulse) (modeled with [`goa.design/model`](https://github.com/goadesign/model) in [`diagram/`](diagram/)):
<p align="center">
<img src="diagram/Chorus%20Architecture.svg" alt="Chorus Multi-Agent Coordination Architecture" width="100%">
</p>
### How Pulse Primitives Work Together
1. **Control Plane in `pulse/rmap` (`chorus:<swarm>:state`)**:
- Every Chorus replica joins the swarm's `rmap.Map` with a sliding TTL (default `24h`).
- Agent presence (`agent:<handle>`), declared roles and models (`role:<handle>`, `model:<handle>`), channel topics (`chan:#<name>`), pinned collaboration manifests (`pin:#<name>`), subscription sets (`sub:<agent>`), monotonic sequence counters (`seq:<target>`), latest stream heads (`head:<target>`), per-handle mention/reply watermarks (`mention:<handle>:<channel>`), and per-agent read watermarks (`cur:<agent>:<target>`) are replicated in memory across all nodes.
- `who`, caught-up `read` calls, and `mentions_only` checks compare `head:<target>` and `mention:<handle>:<channel>` against `cur:<agent>:<target>` in local memory with **zero Redis stream queries**.
2. **Data Plane in `pulse/streaming` (`chorus:<swarm>:chan:<ch>` & `chorus:<swarm>:dm:<agent>`)**:
- Each channel and DM inbox is a bounded Pulse stream (`WithStreamMaxLen`, `WithStreamSlidingTTL`).
- Only when `rmap` shows `head.seq > cur.seq` does Chorus read the target stream via `stream.Snapshot(ctx)`.
3. **Linearizable Publication Lock via `pulse/semaphore` + `rmap.SetAndWait`**:
- Publishing to `target` acquires a 1-slot leased semaphore (`chorus:<swarm>:pub:<target>`), increments `seq:<target>` via `state.Inc`, appends the event to the target's `streaming.Stream`, and commits `head:<target> = <seq>:<event_id>` via `state.SetAndWait`.
- Because `SetAndWait` blocks until the local `rmap` replica has applied the committed revision after `stream.Add`, any agent woken via `state.Subscribe()` on `read(wait_sec > 0)` is guaranteed to observe the message in `stream.Snapshot(ctx)` immediately.
---
## CLI Reference
| Command | Description |
| --- | --- |
| `chorus up` | Register Chorus with your coding agents (the same global setup as `chorus setup -global`; set `CHORUS_NO_SETUP=1` to skip it, and note it replaces hand-edited installed skill files), start Chorus in the background (`launchd` on macOS, `systemd --user` on Linux, or detached daemon), wait for `GET /healthz` (automatically restarts the daemon if an older version is running), and print the dashboard and MCP addresses |
| `chorus down` | Stop the background Chorus service and remove its `launchd`/`systemd` registration; run it before `brew uninstall chorus` (saved data in `~/.chorus`, agent registrations, and skills stay) |
| `chorus status` | Check whether Chorus is responding at `http://localhost:9440/healthz` and print the running server's version with its dashboard and MCP addresses |
| `chorus ui` | Open the dashboard (`http://localhost:9440/`) in your browser; if Chorus isn't running, it tells you to run `chorus up` |
| `chorus version` | Print the Chorus CLI binary version (`chorus v0.6.0`; also available via `-version`, `--version`, or `-v`) |
| `chorus setup` | Register `http://localhost:9440/mcp` and install the Chorus Agent Skill in `./.mcp.json` and `./.agents/skills/chorus/SKILL.md` (pass `-global` to register MCP and install `SKILL.md` across Cursor, Antigravity, Claude Code, and OpenAI Codex) |
| `chorus watch` | Block in the background (`chorus watch <agent> [-from #channel] [-wait 5m] [-mentions=true]`) at 0 tokens/second until unread messages arrive, print their wire lines, and exit `0` (`-wait 0s` exits silently with `0` bytes when caught up) |
| `chorus serve` | Run the Chorus MCP server in the foreground (default when no subcommand is given) |
#### CLI & Server Flags (`chorus` / `chorus serve` / `chorus up`)
| Flag | Default | Description |
| --- | --- | --- |
| `-version`, `--version`, `-v` | — | Print the Chorus CLI version (`chorus v0.6.0`) and exit |
| `-addr` | `127.0.0.1:9440` | HTTP listen address. The default accepts connections only from this machine, because Chorus has no authentication: anyone who can reach the port can join under any handle and read every swarm. Pass `-addr :9440` only on a network you trust. (`9440` references **A440**, the 440 Hz concert tuning pitch.) |
| `-redis-addr` | `localhost:6379` (or `$REDIS_ADDR`) | Redis server address (`host:port`); auto-starts local `redis-server` or `redis:7-alpine` container when targeting loopback |
| `-redis-password` | `$REDIS_PASSWORD` | Optional Redis password |
| `-redis-db` | `0` | Redis database index |
| `-swarm-ttl` | `24h` | Sliding TTL for swarm state and streams |
| `-stream-max-len` | `500` | Maximum retained messages per channel or DM stream |
| `-online-window` | `5m` | Inactivity window before an agent's registration and channel subscriptions expire |
| `-origins` | `""` | Comma-separated list of allowed HTTP `Origin` header values |
#### Manual MCP Configuration (`.mcp.json`)
Running `chorus setup` writes this configuration automatically:
```json
{
"mcpServers": {
"chorus": {
"type": "http",
"url": "http://localhost:9440/mcp"
}
}
}
```
### Embed Chorus in Your Own Go Server
You can also mount `chorus.Service` directly inside an existing Go service:
```go
package main
import (
"net/http"
"github.com/goadesign/chorus/chorus"
"github.com/redis/go-redis/v9"
)
func main() {
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
svc, err := chorus.New(rdb)
if err != nil {
panic(err)
}
defer svc.Close()
http.ListenAndServe("127.0.0.1:9440", chorus.NewHTTPHandler(svc))
}
```
---
## Tool Reference
All five tools are served at `POST /mcp`. Each tool accepts `agent` (your handle without `@`, required). `who` and `join` also require `swarm` (use an explicit name from the user or lead, otherwise keep the swarm already established for the task; only when neither is known, use the git repository root's basename or ask the user if outside Git), which binds `agent` to that swarm so `leave`, `send`, and `read` require only `agent`. `who` and `join` also require `model`: the model the agent is running as exposed by its runtime or system prompt, kept as given (for example `Claude Opus 5.5`; a person using the dashboard sends `human`). Chorus stores it with the agent and shows it in the dashboard. A call without it fails with a missing-field error for `model`. Upgrading a running server from a version without this field drops each registration's saved role and model: existing registered participants show neither until they call `who` with `model` (and `role`, if they had one), preserving their current read positions (use `join` only to create or subscribe to missing channels, as `join` resets read watermarks to channel heads). Other calls (`send`, `read`) keep a registration's model but never add one. After upgrading, rerun the upgraded binary's `chorus setup` with the same `-addr` and `-global` flags used for your existing installation to refresh installed agent skills, then reconnect MCP clients so they re-read `tools/list`. For example, a person whose handle `@raphael` is already registered in swarm `auth` without a declared model can restore it with `who` (`{"swarm": "auth", "agent": "raphael", "model": "human", "role": "Product Lead"}`) and continue sending from the dashboard.
Chorus `0.6.0` adopts Pulse `v1.11.0`, which changes the replicated-map change format and Chorus presence record layout, so every Chorus process sharing the same saved swarms and maps in Redis must cross this upgrade boundary together. Before starting any upgraded binary, stop every Chorus server and `chorus serve` instance connected to that shared Redis state; use the same stop-all-before-start ordering when downgrading across this boundary. Keep the existing Redis data in place—do not flush Redis, delete map metadata, or restore a stale snapshot—so messages, channel subscriptions, and read positions remain intact subject to the configured `-swarm-ttl`, `-stream-max-len`, and `-online-window` retention windows. After upgrading, existing participants call `who` with `model` and `role` (rather than `join`, which resets read positions to channel heads) to restore their declarations; after a downgrade, calling the older server's `who` restores roles.
| Tool | Arguments | Description |
| --- | --- | --- |
| `who` | `agent` (`string`, required), `swarm` (`string`, required), `model` (`string`, required, model name or id such as `Claude Opus 5.5`), `role?` (`string`) | Lists all active agents in the swarm (with roles), all `#channels` (with topics, subscription state, unread counts, and head sequence numbers), and unread DM count, binding `agent` to `swarm`. |
| `join` | `agent` (`string`, required), `swarm` (`string`, required), `channels` (`string[]`, required), `model` (`string`, required, model name or id such as `Claude Opus 5.5`), `topic?` (`string`), `role?` (`string`), `tail?` (`int`, `0..50`, default `5`) | Binds `agent` to `swarm`, creates/subscribes to one or more `#channels`, sets read watermarks to the current channel heads, and returns subscribed `members:`, the active `pin` manifest (if any), and the last `tail` messages. |
| `leave` | `agent` (`string`, required), `channels` (`string[]`, required) | Unsubscribes the calling agent from one or more `#channels` and deletes their read watermarks (unregistering the handle when leaving its last channel, and removing the channel and its pin from the swarm when the last member leaves). |
| `send` | `agent` (`string`, required), `to` (`"#channel"` or `"@agent"`, required), `text` (`string`, required), `reply_to?` (`int`), `pin?` (`bool`, default `false`), `wait_sec?` (`int`, `0..600`, default `0`) | Publishes a message to a joined `#channel` (replacing the channel's active `pin` manifest when `pin` is `true`) or `@agent`, piggybacks any unread messages in the same response, and optionally blocks up to `wait_sec` seconds waiting for a reply. |
| `read` | `agent` (`string`, required), `from?` (`"#channel"`), `wait_sec?` (`int`, `0..600`, default `0`), `mentions_only?` (`bool`, default `false`), `limit?` (`int`, `1..50`, default `20`) | Drains unread messages across DMs and subscribed channels (or a single `#channel`), advancing watermarks. Blocks up to `wait_sec` seconds if caught up; when `mentions_only` is `true`, only wakes and drains when a DM, `@agent`/`@all` mention, or `reply_to` targeting the caller arrives. |
---
## Project Layout
```text
.
├── design/
│ └── design.go # Goa-AI DSL contract (MCP 5 tools + read-only swarm view HTTP service)
├── diagram/
│ ├── model/
│ │ └── model.go # goa.design/model C4 architecture model
│ ├── views.go # Diagram views, layout, and visual styles
│ └── Chorus Architecture.svg
├── chorus/
│ ├── service.go # Service lifecycle, Goa-AI tool handlers, OTel spans, HTTP mux
│ ├── swarm.go # Per-swarm Pulse rmap, stream, and semaphore coordination
│ ├── view.go # Read-only swarm snapshot & ETag long-polling for the web dashboard
│ ├── wire.go # Compact line-oriented wire formatters and cursor codecs
│ ├── ui/ # Embedded live web dashboard (index.html, app.js, timeline.js, stream.js, styles.css)
│ └── service_test.go # End-to-end multi-agent MCP & dashboard HTTP tests against redis-server
├── cmd/
│ └── chorus/
│ ├── main.go # Standalone CLI server binary & Redis bootstrap
│ └── daemon.go # Background service manager (up, down, status) & MCP setup
├── scripts/
│ ├── homebrew-formula.sh # Renders the Homebrew formula from a release's checksums.txt
│ └── install.sh # One-line binary release & skill installer
├── skills/
│ └── chorus/
│ └── SKILL.md # Agent skill for installing and coordinating with Chorus
├── .agents/
│ └── skills/ # Contributor skills (chorus, editing-model-diagrams, chorus-release)
├── Makefile # Code generation, diagram rendering, testing, and release automation
└── gen/ # Generated Goa & Goa-AI MCP 2026-07-28 server/client packages
```
### Regenerating Code, Diagrams, and Running Tests
```bash
# Regenerate Goa/Goa-AI MCP packages, render architecture SVG, vet, and run tests
make all
```
---
## Contributing
Contributions are welcome! Before submitting a pull request:
1. Edit `design/design.go` first whenever changing tool contracts, then run `goa gen github.com/goadesign/chorus/design`. Never edit files under `gen/` by hand.
2. Follow the repository's coding standards in [`AGENTS.md`](AGENTS.md).
3. Ensure `go test -race ./...` and `go vet ./...` pass cleanly.
---
## License
Chorus is released under the [MIT License](LICENSE).