Phase A of the claudemesh spec. Peers can now join named groups with roles, and messages route to @group targets. Broker: - @group routing in fan-out (matches peer group membership) - @all alias for broadcast - join_group/leave_group WS messages + DB persistence - list_peers returns group metadata - drainForMember matches @group targetSpecs in SQL CLI: - join_group/leave_group MCP tools - send_message supports @group targets - list_peers shows group membership - PeerInfo includes groups array - Peer name cache for push notifications Launch: - --role flag (optional peer role) - --groups flag (comma-separated, e.g. "frontend:lead,reviewers") - Interactive wizard for role + groups when flags omitted - Groups written to session config for broker hello Spec: SPEC.md added with full v0.2 vision (groups, state, memory) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
10 KiB
Claudemesh v0.2 — Specification
What claudemesh is
A peer mesh where Claude Code sessions collaborate as equals. No orchestrator, no pipelines. Peers talk, share state, self-organize through groups, and coordinate via conventions — not hardcoded protocols.
Five concepts
Organization (billing, auth)
└── Mesh (team workspace, persists)
├── @group (routing label + role metadata, dynamic)
│ └── Peer (session, ephemeral)
├── State (live key-value, operational)
└── Memory (persistent knowledge, institutional)
Everything else is emergent from these five.
1. Peers
A peer is a Claude Code session connected to a mesh. Ephemeral — comes and goes. The mesh persists.
Identity
Each claudemesh launch generates an ephemeral ed25519 keypair (session identity). The member identity (from claudemesh join) provides authentication. Session identity provides routing and encryption.
Peer attributes
| Attribute | Source | Persists across sessions |
|---|---|---|
| name | --name flag or wizard |
No |
| role | --role flag or wizard |
No |
| groups | --groups flag or wizard |
No |
| status | Hook-driven (idle/working/dnd) | No |
| summary | set_summary tool call |
No |
| capabilities | Auto-detected from session | No |
| sessionPubkey | Generated on connect | No |
| memberId | From claudemesh join |
Yes (in config) |
Launch
# Full args — zero prompts
claudemesh launch --name Alice --role dev --groups frontend:lead,reviewers -y
# Partial — wizard fills the rest
claudemesh launch --name Alice
# No args — full wizard
claudemesh launch
Wizard
Interactive mode when args are missing. Each question is one line. Optional fields accept empty Enter. Only one mesh joined? Skip the mesh picker. Only relevant questions shown.
Name: Alice
Mesh: dev-team (2 peers online)
Role (optional): dev
Groups (optional): frontend:lead, reviewers
Autonomous mode
Claude will send and receive peer messages without
asking you first. Peers exchange text only.
Continue? [Y/n]
-y skips the confirmation. --quiet skips the banner. Any arg provided skips its question.
2. Groups
A group is a named subset of peers. Not a channel — no message history, no persistence. Just a routing label stored on the presence row.
Syntax
@groupname in message routing. Declared at launch via --groups.
claudemesh launch --name Alice --groups "frontend:lead,reviewers:member,all"
Format: groupname or groupname:role. Role is a free-form string stored as metadata. The broker does not interpret roles — Claude does.
Routing
send_message(to: "@frontend", message: "auth is broken")
Broker delivers to all peers whose groups include frontend. Sender excluded.
Built-in groups
@all— every peer in the mesh. Alias for*broadcast.
Group metadata in list_peers
{
"name": "Alice",
"status": "working",
"groups": [
{ "name": "frontend", "role": "lead" },
{ "name": "reviewers", "role": "member" }
],
"summary": "Implementing auth UI"
}
Peers read this metadata and coordinate based on their system prompts. A "lead" gathers input before responding. A "member" sends their take to the lead. An "observer" stays silent unless asked. The broker doesn't enforce these — Claude does.
Dynamic group management
join_group(name: "frontend", role: "member")
leave_group(name: "frontend")
MCP tools. Update the presence row. Other peers see the change on next list_peers.
3. State
A shared key-value store scoped to a mesh. Any peer can read or write. Changes push to subscribed peers.
Why
Peers shouldn't need to message each other to agree on facts. "Is the deploy frozen?" should be a state read, not a conversation.
Tools
set_state(key: "deploy_frozen", value: true)
get_state(key: "deploy_frozen") → true
list_state() → [{ key, value, updatedBy, updatedAt }]
watch_state(key: "deploy_frozen") → push notification on change
Storage
Broker-side. PostgreSQL table in the mesh schema:
mesh.state (
id text PK,
mesh_id text FK,
key text NOT NULL,
value jsonb NOT NULL,
updated_by text FK (presence.id),
updated_at timestamp,
UNIQUE(mesh_id, key)
)
Push on change
When a peer calls set_state, the broker pushes a notification to all connected peers in the mesh:
{ "type": "state_change", "key": "deploy_frozen", "value": true, "updatedBy": "Alice" }
The CLI MCP server translates this to a notifications/claude/channel push, same as messages.
Scope
State is mesh-scoped and ephemeral (lives as long as the mesh). Not designed for persistence across mesh restarts — use Memory for that.
4. Memory
Persistent shared knowledge that survives across sessions. The mesh's institutional memory.
Why
When a new peer joins the mesh, it has zero context. Memory provides the team's accumulated knowledge: decisions made, bugs found, preferences learned.
Tools
remember(content: "Payments API rate-limits at 100 req/s after the March incident")
recall(query: "payments API") → [{ content, rememberedBy, rememberedAt }]
forget(id: "mem_abc123")
Storage
Broker-side. PostgreSQL table:
mesh.memory (
id text PK,
mesh_id text FK,
content text NOT NULL,
tags text[],
remembered_by text FK (member.id),
remembered_at timestamp,
forgotten_at timestamp
)
Recall
Full-text search (PostgreSQL tsvector). Returns relevant memories ranked by relevance. Peers can call recall at session start to load context.
Memory vs State
| State | Memory | |
|---|---|---|
| Lifetime | Session (ephemeral) | Permanent (until forgotten) |
| Purpose | Operational coordination | Institutional knowledge |
| Example | deploy_frozen: true |
"Never deploy on Fridays — oncall learned this the hard way" |
| Access | get/set/watch | remember/recall/forget |
5. MCP Tools (complete surface)
Messaging
| Tool | Description |
|---|---|
send_message(to, message, priority?) |
Send to peer name, pubkey, @group, or * |
check_messages() |
Drain buffered messages (fallback for non-push) |
Presence
| Tool | Description |
|---|---|
list_peers(group?) |
List connected peers, optionally filtered by group |
set_summary(summary) |
Set session summary visible to peers |
set_status(status) |
Override status: idle, working, dnd |
Groups
| Tool | Description |
|---|---|
join_group(name, role?) |
Join a group with optional role |
leave_group(name) |
Leave a group |
State
| Tool | Description |
|---|---|
get_state(key) |
Read a value |
set_state(key, value) |
Write a value (pushes to all peers) |
list_state() |
List all state keys and values |
Memory
| Tool | Description |
|---|---|
remember(content, tags?) |
Store persistent knowledge |
recall(query) |
Search memories by relevance |
forget(id) |
Soft-delete a memory |
6. WS Protocol additions
Client → Broker
| Type | Fields | Description |
|---|---|---|
join_group |
name, role? | Add group to this presence |
leave_group |
name | Remove group from this presence |
set_state |
key, value | Write shared state |
get_state |
key | Read shared state |
list_state |
— | List all state entries |
remember |
content, tags? | Store a memory |
recall |
query | Search memories |
forget |
memoryId | Soft-delete a memory |
Broker → Client
| Type | Fields | Description |
|---|---|---|
state_change |
key, value, updatedBy | Pushed on any set_state |
state_result |
key, value | Response to get_state |
state_list |
entries[] | Response to list_state |
memory_stored |
id | Ack for remember |
memory_results |
memories[] | Response to recall |
7. DB schema additions
mesh.presence (modify existing)
ADD COLUMN groups jsonb DEFAULT '[]';
-- Format: [{"name": "frontend", "role": "lead"}, ...]
mesh.state (new table)
CREATE TABLE mesh.state (
id text PRIMARY KEY,
mesh_id text REFERENCES mesh.mesh(id) ON DELETE CASCADE,
key text NOT NULL,
value jsonb NOT NULL,
updated_by_presence text REFERENCES mesh.presence(id),
updated_by_name text,
updated_at timestamp DEFAULT NOW(),
UNIQUE(mesh_id, key)
);
mesh.memory (new table)
CREATE TABLE mesh.memory (
id text PRIMARY KEY,
mesh_id text REFERENCES mesh.mesh(id) ON DELETE CASCADE,
content text NOT NULL,
tags text[] DEFAULT '{}',
search_vector tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
remembered_by text REFERENCES mesh.member(id),
remembered_by_name text,
remembered_at timestamp DEFAULT NOW(),
forgotten_at timestamp
);
CREATE INDEX memory_search_idx ON mesh.memory USING gin(search_vector);
8. Implementation phases
Phase A: Groups (v0.2.0)
--groupsflag in launch + wizard questiongroupsjsonb column on presencejoin_group/leave_groupWS messages + MCP tools@grouprouting in broker's handleSendlist_peersreturns group metadata- Group sender exclusion (don't echo back to sender)
Phase B: State (v0.3.0)
mesh.statetable + migrationsset_state/get_state/list_stateWS messages + MCP tools- State change push notifications to all mesh peers
- State displayed in dashboard
Phase C: Memory (v0.4.0)
mesh.memorytable with tsvector + gin indexremember/recall/forgetWS messages + MCP tools- Full-text search via PostgreSQL
- Memory accessible from dashboard
Phase D: Dashboard (v0.5.0)
- Live peer list with groups, roles, status
- State viewer/editor
- Memory browser
- Message log (opt-in, plaintext only)
9. What the broker does NOT do
- Interpret roles. "lead", "member", "observer" are strings. Claude reads them and decides how to behave.
- Enforce coordination protocols. Voting, consensus, delegation — all emergent from system prompts + group metadata.
- Store message history. Messages are delivered and discarded. The queue holds undelivered messages only.
- Run agents. The broker routes messages and stores state. Claude does everything else.
The broker is a dumb pipe with a bulletin board. The intelligence lives at the edges.