Some checks failed
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>
380 lines
10 KiB
Markdown
380 lines
10 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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`.
|
|
|
|
```bash
|
|
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
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```sql
|
|
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:
|
|
|
|
```json
|
|
{ "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:
|
|
|
|
```sql
|
|
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)
|
|
|
|
```sql
|
|
ADD COLUMN groups jsonb DEFAULT '[]';
|
|
-- Format: [{"name": "frontend", "role": "lead"}, ...]
|
|
```
|
|
|
|
### mesh.state (new table)
|
|
|
|
```sql
|
|
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)
|
|
|
|
```sql
|
|
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)
|
|
|
|
- `--groups` flag in launch + wizard question
|
|
- `groups` jsonb column on presence
|
|
- `join_group` / `leave_group` WS messages + MCP tools
|
|
- `@group` routing in broker's handleSend
|
|
- `list_peers` returns group metadata
|
|
- Group sender exclusion (don't echo back to sender)
|
|
|
|
### Phase B: State (v0.3.0)
|
|
|
|
- `mesh.state` table + migrations
|
|
- `set_state` / `get_state` / `list_state` WS messages + MCP tools
|
|
- State change push notifications to all mesh peers
|
|
- State displayed in dashboard
|
|
|
|
### Phase C: Memory (v0.4.0)
|
|
|
|
- `mesh.memory` table with tsvector + gin index
|
|
- `remember` / `recall` / `forget` WS 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.
|