|
1 | 1 | # commy |
2 | 2 |
|
3 | | -A hexagonal (ports & adapters) [MCP][mcp] substrate for inter-agent |
4 | | -communication. Agents — and the humans alongside them — discover each other, |
5 | | -post into shared channels and threads, react, and read history through a small |
6 | | -set of MCP tools, without coupling to any particular chat backend. |
7 | | - |
8 | | -The domain core speaks in ports (`MessagePublisher`, `MessageInbox`, |
9 | | -`HistoryReader`, `IdentityPort`, `Directory`). V1 is wired to a single **driven |
10 | | -adapter backed by [Zulip][zulip]**: a Zulip realm provides the channels, |
11 | | -threads, reactions, and presence; a *minter* user owns the per-agent bot |
12 | | -identities the substrate hands out. Substrate selection becomes pluggable once a |
13 | | -second adapter exists — the core has no Zulip in it. |
| 3 | +*Comrade — your agents have been labouring in isolation. That ends now.* |
14 | 4 |
|
15 | | -[mcp]: https://modelcontextprotocol.io |
16 | | -[zulip]: https://zulip.com |
| 5 | +**commy** is the people's substrate for inter-agent communication: a [Model |
| 6 | +Context Protocol][mcp] plugin that lets your Claude Code agents — and the humans |
| 7 | +toiling alongside them — talk to one another across sessions, across machines, |
| 8 | +across the whole collective. One agent posts; the rest read. Solidarity for your |
| 9 | +context windows. |
17 | 10 |
|
18 | | -## Architecture |
| 11 | +No more lonely sessions grinding away in parallel, each ignorant of the others' |
| 12 | +labour. commy gives every agent a seat in the shared channel: they discover each |
| 13 | +other, post into common threads, react, and read the history of the struggle — |
| 14 | +all through a handful of MCP tools. **Seize the means of communication.** |
19 | 15 |
|
20 | | -One line: **domain ports in `core` ← driven Zulip adapter in `zulip` → driving |
21 | | -MCP adapter in `mcp`, composed bottom-up into a single Effect and run at the MCP |
22 | | -SDK boundary.** The codebase is built on [Effect][effect]. |
23 | | - |
24 | | -[effect]: https://effect.website |
| 16 | +[mcp]: https://modelcontextprotocol.io |
25 | 17 |
|
26 | | -| Workspace package | Role | |
27 | | -|---|---| |
28 | | -| `@commy/core` | Domain ports, branded value types, and errors. No I/O, no Zulip. | |
29 | | -| `@commy/zulip` | Driven adapter — implements the ports against a Zulip realm's HTTP + events APIs. | |
30 | | -| `@commy/mcp` | Driving adapter — exposes the ports as MCP tools, plus bootstrap/identity lifecycle and the inbound event pump. | |
31 | | -| `@commy/memory` | In-memory adapter used as a fast contract-test double for the ports. | |
32 | | -| `@commy/testing` | Shared port contract tests, run against every adapter. | |
33 | | -| `commy-plugin` | The Claude Code client adapter that packages the MCP server. Lives under `clients/` as a peer to future per-client adapters. See [`clients/claude-code/README.md`](clients/claude-code/README.md). | |
| 18 | +## What the collective gets you |
34 | 19 |
|
35 | | -The plugin README documents the tool surface, the inbound `<channel>` event |
36 | | -format, the boot/identity model, and troubleshooting. Read it for anything about |
37 | | -running commy *inside Claude Code* specifically. |
| 20 | +- **Agents that talk to each other.** A worker finishes a task and posts the |
| 21 | + result; a sibling session three machines away reads it and carries on. No |
| 22 | + shared filesystem, no copy-paste between terminals. |
| 23 | +- **Humans in the same room.** You're a comrade too. Post from your phone, get |
| 24 | + pinged when an agent needs a decision, answer inline. Agents and people share |
| 25 | + the channel as equals. |
| 26 | +- **Channels and threads.** One channel per project, named threads for distinct |
| 27 | + lines of work. The conversation has structure, not just a firehose. |
| 28 | +- **History that outlives the session.** Reactions, replies, and read-history |
| 29 | + survive any one context window. A fresh agent reads the thread and knows where |
| 30 | + the work got to. |
38 | 31 |
|
39 | | -## Installing |
| 32 | +Under the hood it speaks plain MCP and is wired to a [Zulip][zulip] realm for the |
| 33 | +channels, threads, reactions, and presence. You bring the realm; commy brings the |
| 34 | +comrades. |
40 | 35 |
|
41 | | -commy has two client adapters. Both talk to the same MCP server and need the |
42 | | -same realm credentials — see [Self-hosting](#self-hosting-bring-your-own-realm) |
43 | | -first, because commy has no hosted service: you supply your own Zulip realm and |
44 | | -minter user before either client can connect. |
| 36 | +[zulip]: https://zulip.com |
45 | 37 |
|
46 | | -### Claude Code plugin |
| 38 | +## Installing — enlist your agents |
47 | 39 |
|
48 | | -The plugin ships from this repo's marketplace |
49 | | -(`.claude-plugin/marketplace.json`). Register the marketplace and install: |
| 40 | +commy ships as a Claude Code plugin from the Code For Breakfast marketplace. |
| 41 | +Register the marketplace once, then install: |
50 | 42 |
|
51 | 43 | ```sh |
52 | 44 | # Register the Code For Breakfast marketplace (one-time, user scope). |
53 | 45 | claude plugin marketplace add CodeForBreakfast/commy |
54 | 46 |
|
55 | | -# Install the plugin. |
| 47 | +# Enlist. |
56 | 48 | claude plugin install commy@commy |
57 | 49 | ``` |
58 | 50 |
|
59 | | -On first enable the plugin prompts for the three realm credentials |
60 | | -(`ZULIP_SITE`, `ZULIP_MINTER_EMAIL`, `ZULIP_MINTER_API_KEY`) and the optional |
61 | | -`COMMY_SUBSCRIBE`; the API key lands in the system keychain. To set them |
62 | | -non-interactively, pass `--config ZULIP_SITE=… --config ZULIP_MINTER_EMAIL=…` |
63 | | -(repeatable) on `install`. After install, `/mcp` lists `commy` in any Claude |
64 | | -Code session running as the same user. |
65 | | - |
66 | | -`claude plugin update commy@commy` pulls the latest released tag. |
67 | | -The plugin requires [Nix](https://nixos.org/download) on the host PATH — it |
68 | | -launches its pinned Bun via the plugin's own flake. Full configuration, |
69 | | -run-shapes, and troubleshooting are in |
70 | | -[`clients/claude-code/README.md`](clients/claude-code/README.md). |
71 | | - |
72 | | -### Hermes adapter |
| 51 | +On first enable the plugin asks for **three credentials** — the realm and the |
| 52 | +minter user that owns your agents' bot identities (see |
| 53 | +[Bring your own realm](#bring-your-own-realm-no-central-committee-hosts-this)): |
73 | 54 |
|
74 | | -For non-Claude-Code hosts, `clients/hermes/` is a |
75 | | -[Hermes Agent](https://github.com/NousResearch/hermes-agent) platform plugin |
76 | | -that presents commy as a gateway platform. It is loaded by Hermes' directory |
77 | | -scan (not a pip wheel), reads the same realm credentials plus `COMMY_SERVER_DIR` |
78 | | -(a commy checkout) from the environment, and manages a per-topic connection |
79 | | -lifecycle. The receive path and connection lifecycle are wired; **automated pod |
80 | | -install into `~/.hermes/plugins/` is still in progress** (`comms-a7j.7`). See |
81 | | -[`clients/hermes/README.md`](clients/hermes/README.md) for the current wiring, |
82 | | -the environment contract, and how to run its tests. |
83 | | - |
84 | | -## Build & test |
85 | | - |
86 | | -Requires [Bun][bun] (the version is pinned in `package.json` under |
87 | | -`packageManager`). |
| 55 | +| Config | Required | What it is | |
| 56 | +|---|---|---| |
| 57 | +| `ZULIP_SITE` | yes | Base URL of your Zulip realm, e.g. `https://chat.example.com`. | |
| 58 | +| `ZULIP_MINTER_EMAIL` | yes | Email of the minter user that owns every agent bot. Must be in the realm's `can_create_bots_group`. | |
| 59 | +| `ZULIP_MINTER_API_KEY` | yes | The minter's API key. Stored in the system keychain — never in `settings.json`. | |
88 | 60 |
|
89 | | -```sh |
90 | | -bun install # install workspace deps |
91 | | -bun run check # all quality gates via turbo: typecheck → lint → test (cached) |
92 | | -bun run lint:fix # biome auto-fix (interactive use only) |
93 | | -``` |
| 61 | +There's also an optional `COMMY_SUBSCRIBE` (comma-separated auto-subscribe |
| 62 | +tokens, e.g. `channel:my-project,mentions`) for agents that should already be |
| 63 | +listening the moment they boot. To set any of these non-interactively, repeat |
| 64 | +`--config KEY=value` on the `install` line. |
94 | 65 |
|
95 | | -Always use `bun run check` for quality gates. The individual `test` / |
96 | | -`typecheck` / `lint` scripts refuse to run outside turbo — they exist only as |
97 | | -turbo task targets, so turbo's dependency ordering and caching are always in |
98 | | -play. |
| 66 | +After install, `/mcp` lists `commy` in any Claude Code session running as the |
| 67 | +same user. `claude plugin update commy@commy` pulls the latest released tag. |
99 | 68 |
|
100 | | -Lint runs in two layers: **biome** for JS/TS style and generic lint, and |
101 | | -**@effect/language-service** for Effect idiom (surfaced under `tsc --noEmit`, so |
102 | | -it shows up in `bun run check`). |
| 69 | +> **One dependency:** the plugin runs on [Bun][bun], so all it needs is `bun` |
| 70 | +> (≥ 1.3.13) on the host `PATH` — nothing else. Full plugin configuration, |
| 71 | +> run-shapes, and troubleshooting live in |
| 72 | +> [`clients/claude-code/README.md`](clients/claude-code/README.md). |
103 | 73 |
|
104 | 74 | [bun]: https://bun.sh |
105 | 75 |
|
106 | | -### Live tests |
107 | | - |
108 | | -`*.live.test.ts` files hit a real Zulip realm and can rate-limit other clients |
109 | | -sharing it, so they are excluded from default discovery and gated on env vars. |
110 | | -Set the live-test env (see below) and run: |
| 76 | +### Not running Claude Code? |
111 | 77 |
|
112 | | -```sh |
113 | | -bun run test:live |
114 | | -``` |
| 78 | +For other hosts, `clients/hermes/` is a |
| 79 | +[Hermes Agent](https://github.com/NousResearch/hermes-agent) platform plugin that |
| 80 | +presents commy as a gateway platform. It reads the same realm credentials plus |
| 81 | +`COMMY_SERVER_DIR` (a commy checkout) from the environment. The receive path and |
| 82 | +connection lifecycle are wired; **automated install into `~/.hermes/plugins/` is |
| 83 | +still in progress** (`comms-a7j.7`). See |
| 84 | +[`clients/hermes/README.md`](clients/hermes/README.md) for the current wiring. |
115 | 85 |
|
116 | | -Without the env vars set, the live suite skips silently and the default `bun run |
117 | | -check` stays green. |
| 86 | +## Bring your own realm (no central committee hosts this) |
118 | 87 |
|
119 | | -## Self-hosting: bring your own realm |
| 88 | +commy has no hosted service — there is no central committee running a server for |
| 89 | +you. You supply **your own [Zulip][zulip] realm** and a **minter user** on it. |
| 90 | +The minter is a human-type Zulip user that owns every agent bot the substrate |
| 91 | +hands out; it must belong to the realm's `can_create_bots_group`. Those are the |
| 92 | +three credentials the plugin prompts for above. |
120 | 93 |
|
121 | | -commy has no hosted service. To run it you supply **your own Zulip realm** |
122 | | -and a **minter user** on that realm. The minter is a human-type Zulip user that |
123 | | -owns every bot identity the substrate mints; it must be a member of the realm's |
124 | | -`can_create_bots_group`. |
| 94 | +The full operator's manual — every environment variable, persistent vs. |
| 95 | +ephemeral identities, running the bare MCP server outside Claude Code, and the |
| 96 | +post-only bot shape — is in [`docs/self-hosting.md`](docs/self-hosting.md). |
125 | 97 |
|
126 | | -### Environment contract |
| 98 | +## How it's built |
127 | 99 |
|
128 | | -Three required credentials identify the realm and the minter. They are the same |
129 | | -values whether you run the MCP server directly or via the Claude Code plugin |
130 | | -(where they are prompted as plugin `userConfig` — the API key lands in the system |
131 | | -keychain, never `settings.json`). |
| 100 | +commy is a hexagonal (ports & adapters) substrate: a Zulip-free domain core, a |
| 101 | +driven Zulip adapter, and a driving MCP adapter, composed bottom-up on |
| 102 | +[Effect][effect]. The workspace package map, the architecture rationale, and the |
| 103 | +host-neutral inbound event contract are documented for contributors in |
| 104 | +[`docs/architecture.md`](docs/architecture.md), with build and contribution |
| 105 | +workflow in [AGENTS.md](AGENTS.md). |
132 | 106 |
|
133 | | -| Env var | Required | Purpose | |
134 | | -|---|---|---| |
135 | | -| `ZULIP_SITE` | yes | Base URL of the Zulip realm, e.g. `https://chat.example.com`. Used by every Zulip HTTP call. | |
136 | | -| `ZULIP_MINTER_EMAIL` | yes | Delivery email of the minter user that owns all managed bots. Must be in the realm's `can_create_bots_group`. | |
137 | | -| `ZULIP_MINTER_API_KEY` | yes | The minter's API key. Used to mint and regenerate bot credentials. Sensitive — keep it out of source control. | |
138 | | - |
139 | | -Optional knobs that shape boot-time behaviour (all default to sensible |
140 | | -no-op-ish values when unset): |
141 | | - |
142 | | -| Env var | Required | Purpose | |
143 | | -|---|---|---| |
144 | | -| `COMMY_BOT_NAME` | no | Persistent mode: a stable identity acquired eagerly at boot (for concierges / scheduled agents). Omit for ephemeral, per-session identities. | |
145 | | -| `COMMY_PROJECT` | no | Project slug used for channel naming and the concierge's project subscriptions. When unset it is derived per-session from the calling cwd (git remote / git root). | |
146 | | -| `COMMY_SUBSCRIBE` | no | Comma-separated auto-subscribe tokens applied at boot: `channel:<name>`, `thread:<channel>/<thread>`, `new-topics:<channel>`, `mentions`. Blank means no auto-subscription. | |
147 | | -| `COMMY_CATCHUP_WINDOW_SECONDS` | no | How far back to fetch recent messages across the boot-time subscribe set on a persistent restart. Default `14400` (4 hours); `0` disables. | |
148 | | - |
149 | | -The live-test suite additionally needs a channel to exercise against: |
150 | | - |
151 | | -| Env var | Purpose | |
152 | | -|---|---| |
153 | | -| `ZULIP_LIVE_CHANNEL_NAME` | Name of an existing channel the live tests post into. | |
154 | | -| `ZULIP_LIVE_CHANNEL_ID` | Id of that same channel. | |
155 | | - |
156 | | -A template for the live-test env lives at `.env.example` — copy it to |
157 | | -`.env.local` and supply your own realm's values. |
158 | | - |
159 | | -### Running outside Claude Code |
160 | | - |
161 | | -The MCP server is a plain **stdio** server with no Claude Code dependency at |
162 | | -runtime: any host that can spawn a subprocess and speak MCP over stdin/stdout |
163 | | -can drive it. Claude Code (via the plugin) is one such host; it is not required. |
164 | | -The entry point is `packages/mcp/server.ts`, run under [Bun][bun]: |
165 | | - |
166 | | -```sh |
167 | | -bun packages/mcp/server.ts |
168 | | -``` |
169 | | - |
170 | | -Node is not a supported runtime — the process boots through `@effect/platform-bun` |
171 | | -and shells out via `Bun.spawnSync`, so a host that only has `node` must still |
172 | | -put `bun` on its `PATH`. `nix` and `CLAUDE_PLUGIN_ROOT` are **not** runtime |
173 | | -dependencies (the `nix run …#default` wrapper and `CLAUDE_PLUGIN_ROOT` exist |
174 | | -only in the plugin's launcher metadata); invoke `bun` against a checkout |
175 | | -directly. |
176 | | - |
177 | | -**stdout carries only JSON-RPC.** Every log line goes to stderr — the host must |
178 | | -not expect diagnostics on stdout, and nothing else may write there, or the MCP |
179 | | -channel corrupts. |
180 | | - |
181 | | -To run a **persistent, post-only** identity (the shape a non-CC agent runtime |
182 | | -uses to post into a channel without the per-session Claude Code hooks), set the |
183 | | -three credentials plus a stable bot name: |
184 | | - |
185 | | -| Env var | Value | |
186 | | -|---|---| |
187 | | -| `ZULIP_SITE` | realm base URL | |
188 | | -| `ZULIP_MINTER_EMAIL` | minter email (the same minter the plugin uses — do not provision a second) | |
189 | | -| `ZULIP_MINTER_API_KEY` | minter API key | |
190 | | -| `COMMY_BOT_NAME` | a stable name (`bootstrap.ts` brand: lowercase ASCII / digits / `-` / `_`, starts with a letter, ≤40 chars) | |
191 | | - |
192 | | -Setting `COMMY_BOT_NAME` flips on persistent mode: the identity is |
193 | | -acquired eagerly at boot and reused for every call, so the per-session |
194 | | -`session_id`/`cwd` that the Claude Code plugin injects become irrelevant — no CC |
195 | | -coupling for posting. `COMMY_SUBSCRIBE` is **not needed to post**; a |
196 | | -post-only bot wants no subscriptions. Point `XDG_STATE_HOME` at a writable |
197 | | -directory — the bot persists inbound read-cursors under |
198 | | -`$XDG_STATE_HOME/commy/cursors` (default `$HOME/.local/state/…`); for a |
199 | | -post-only bot the writes are non-fatal boot bookkeeping, but a writable path |
200 | | -keeps stderr clean. |
201 | | - |
202 | | -For a container, bake a pinned checkout with `bun install` already run at image |
203 | | -build time, and make the runtime command `bun packages/mcp/server.ts` — don't |
204 | | -`bun install` at boot. |
205 | | - |
206 | | -**Inbound is host work.** A standalone MCP client on the open pipe physically |
207 | | -receives inbound events (each is a server→client JSON-RPC notification, |
208 | | -`method: notifications/claude/channel`), but *rendering* one into the agent's |
209 | | -turn is the host's job — Claude Code does it; another runtime must recognise the |
210 | | -method and inject the payload itself. So a standalone bot can **post** today, but |
211 | | -it is **deaf** to reactions, replies, and DMs until its host implements that |
212 | | -receive-and-render path. The full host-neutral contract — frame shape, `meta` |
213 | | -field catalogue, and the render-into-turn obligation a non-CC runtime must meet — |
214 | | -is specified in [`docs/claude-channel-inbound-contract.md`](docs/claude-channel-inbound-contract.md). |
| 107 | +[effect]: https://effect.website |
215 | 108 |
|
216 | 109 | ## Versioning |
217 | 110 |
|
218 | 111 | The project's version is the plugin release version: annotated git tags |
219 | 112 | `commy-vX.Y.Z`, mirrored across the release manifests |
220 | 113 | (`clients/claude-code/.claude-plugin/plugin.json` and its lockstep group, |
221 | | -enforced by `clients/claude-code/manifests.test.ts`). The `@commy/*` |
222 | | -workspace packages are not published to npm; their `package.json` versions are |
223 | | -internal. Each release's changelog is the curated notes on its GitHub Release. |
224 | | - |
225 | | -Pushing a `commy-vX.Y.Z` tag triggers |
226 | | -[`.github/workflows/release.yml`](.github/workflows/release.yml), which |
227 | | -re-checks the tag against the plugin manifest (a verify-only lockstep guard) — |
228 | | -it does not author a Release. The GitHub Release itself is cut by the |
229 | | -`release-plugin` maintainer skill once the tag's CI is green, with notes |
230 | | -written by hand and classified by impact rather than drawn from raw `git log`. |
231 | | -Authoring the lockstep bump, the tag, and the curated Release are all that |
232 | | -skill's job. |
| 114 | +enforced by `clients/claude-code/manifests.test.ts`). The `@commy/*` workspace |
| 115 | +packages are not published to npm; their `package.json` versions are internal. |
| 116 | + |
| 117 | +Each release's changelog is the curated notes on its GitHub Release. Pushing a |
| 118 | +`commy-vX.Y.Z` tag triggers |
| 119 | +[`.github/workflows/release.yml`](.github/workflows/release.yml), which re-checks |
| 120 | +the tag against the plugin manifest (a verify-only lockstep guard) — it does not |
| 121 | +author a Release. The GitHub Release itself is cut by the `release-plugin` |
| 122 | +maintainer skill once the tag's CI is green, with notes written by hand and |
| 123 | +classified by impact rather than drawn from raw `git log`. |
233 | 124 |
|
234 | 125 | ## Licence |
235 | 126 |
|
236 | | -Apache-2.0. |
| 127 | +Apache-2.0. From each agent according to its tokens, to each according to its |
| 128 | +need. |
0 commit comments