claws-code 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/claws-auto.md +90 -0
- package/.claude/commands/claws-bin.md +28 -0
- package/.claude/commands/claws-cleanup.md +28 -0
- package/.claude/commands/claws-do.md +82 -0
- package/.claude/commands/claws-fix.md +40 -0
- package/.claude/commands/claws-goal.md +111 -0
- package/.claude/commands/claws-help.md +54 -0
- package/.claude/commands/claws-plan.md +103 -0
- package/.claude/commands/claws-report.md +29 -0
- package/.claude/commands/claws-status.md +37 -0
- package/.claude/commands/claws-update.md +32 -0
- package/.claude/commands/claws.md +64 -0
- package/.claude/rules/claws-default-behavior.md +76 -0
- package/.claude/settings.json +112 -0
- package/.claude/settings.local.json +19 -0
- package/.claude/skills/claws-auto-engine/SKILL.md +97 -0
- package/.claude/skills/claws-goal-tracker/SKILL.md +106 -0
- package/.claude/skills/claws-prompt-templates/SKILL.md +203 -0
- package/.claude/skills/claws-wave-lead/SKILL.md +126 -0
- package/.claude/skills/claws-wave-subworker/SKILL.md +60 -0
- package/CHANGELOG.md +1949 -0
- package/LICENSE +21 -0
- package/README.md +420 -0
- package/bin/cli.js +84 -0
- package/cli.js +223 -0
- package/docs/ARCHITECTURE.md +511 -0
- package/docs/event-protocol.md +588 -0
- package/docs/features.md +562 -0
- package/docs/guide.md +891 -0
- package/docs/index.html +716 -0
- package/docs/protocol.md +323 -0
- package/extension/.vscodeignore +15 -0
- package/extension/CHANGELOG.md +1906 -0
- package/extension/LICENSE +21 -0
- package/extension/README.md +137 -0
- package/extension/docs/features.md +424 -0
- package/extension/docs/protocol.md +197 -0
- package/extension/esbuild.mjs +25 -0
- package/extension/icon.png +0 -0
- package/extension/native/.metadata.json +10 -0
- package/extension/native/node-pty/LICENSE +69 -0
- package/extension/native/node-pty/README.md +165 -0
- package/extension/native/node-pty/lib/conpty_console_list_agent.js +16 -0
- package/extension/native/node-pty/lib/conpty_console_list_agent.js.map +1 -0
- package/extension/native/node-pty/lib/eventEmitter2.js +47 -0
- package/extension/native/node-pty/lib/eventEmitter2.js.map +1 -0
- package/extension/native/node-pty/lib/index.js +52 -0
- package/extension/native/node-pty/lib/index.js.map +1 -0
- package/extension/native/node-pty/lib/interfaces.js +7 -0
- package/extension/native/node-pty/lib/interfaces.js.map +1 -0
- package/extension/native/node-pty/lib/shared/conout.js +11 -0
- package/extension/native/node-pty/lib/shared/conout.js.map +1 -0
- package/extension/native/node-pty/lib/terminal.js +190 -0
- package/extension/native/node-pty/lib/terminal.js.map +1 -0
- package/extension/native/node-pty/lib/types.js +7 -0
- package/extension/native/node-pty/lib/types.js.map +1 -0
- package/extension/native/node-pty/lib/unixTerminal.js +346 -0
- package/extension/native/node-pty/lib/unixTerminal.js.map +1 -0
- package/extension/native/node-pty/lib/utils.js +39 -0
- package/extension/native/node-pty/lib/utils.js.map +1 -0
- package/extension/native/node-pty/lib/windowsConoutConnection.js +125 -0
- package/extension/native/node-pty/lib/windowsConoutConnection.js.map +1 -0
- package/extension/native/node-pty/lib/windowsPtyAgent.js +320 -0
- package/extension/native/node-pty/lib/windowsPtyAgent.js.map +1 -0
- package/extension/native/node-pty/lib/windowsTerminal.js +199 -0
- package/extension/native/node-pty/lib/windowsTerminal.js.map +1 -0
- package/extension/native/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/extension/native/node-pty/lib/worker/conoutSocketWorker.js.map +1 -0
- package/extension/native/node-pty/package.json +64 -0
- package/extension/native/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/extension/native/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
- package/extension/package-lock.json +605 -0
- package/extension/package.json +343 -0
- package/extension/scripts/bundle-native.mjs +104 -0
- package/extension/scripts/deploy-dev.mjs +60 -0
- package/extension/src/ansi-strip.ts +52 -0
- package/extension/src/backends/vscode/claws-pty.ts +483 -0
- package/extension/src/backends/vscode/status-bar.ts +99 -0
- package/extension/src/backends/vscode/vscode-backend.ts +282 -0
- package/extension/src/capture-store.ts +125 -0
- package/extension/src/event-log.ts +629 -0
- package/extension/src/event-schemas.ts +478 -0
- package/extension/src/extension.js +492 -0
- package/extension/src/extension.ts +873 -0
- package/extension/src/lifecycle-engine.ts +60 -0
- package/extension/src/lifecycle-rules.ts +171 -0
- package/extension/src/lifecycle-store.ts +506 -0
- package/extension/src/peer-registry.ts +176 -0
- package/extension/src/pipeline-registry.ts +82 -0
- package/extension/src/platform.ts +64 -0
- package/extension/src/protocol.ts +532 -0
- package/extension/src/server-config.ts +98 -0
- package/extension/src/server.ts +2210 -0
- package/extension/src/task-registry.ts +51 -0
- package/extension/src/terminal-backend.ts +211 -0
- package/extension/src/terminal-manager.ts +395 -0
- package/extension/src/topic-registry.ts +70 -0
- package/extension/src/topic-utils.ts +46 -0
- package/extension/src/transport.ts +45 -0
- package/extension/src/uninstall-cleanup.ts +232 -0
- package/extension/src/wave-registry.ts +314 -0
- package/extension/src/websocket-transport.ts +153 -0
- package/extension/tsconfig.json +23 -0
- package/lib/capabilities.js +145 -0
- package/lib/dry-run.js +43 -0
- package/lib/install.js +1018 -0
- package/lib/mcp-setup.js +92 -0
- package/lib/platform.js +240 -0
- package/lib/preflight.js +152 -0
- package/lib/shell-hook.js +343 -0
- package/lib/uninstall.js +162 -0
- package/lib/verify.js +166 -0
- package/mcp_server.js +3529 -0
- package/package.json +48 -0
- package/rules/claws-default-behavior.md +72 -0
- package/scripts/_helpers/atomic-file.mjs +137 -0
- package/scripts/_helpers/fix-repair.js +64 -0
- package/scripts/_helpers/json-safe.mjs +218 -0
- package/scripts/bump-version.sh +84 -0
- package/scripts/codegen/gen-docs.mjs +61 -0
- package/scripts/codegen/gen-json-schema.mjs +62 -0
- package/scripts/codegen/gen-mcp-tools.mjs +358 -0
- package/scripts/codegen/gen-types.mjs +172 -0
- package/scripts/codegen/index.mjs +42 -0
- package/scripts/dev-hooks/check-extension-dirs.js +77 -0
- package/scripts/dev-hooks/check-open-claws-terminals.js +70 -0
- package/scripts/dev-hooks/check-stale-main.js +55 -0
- package/scripts/dev-hooks/check-tag-pushed.js +51 -0
- package/scripts/dev-hooks/check-tag-vs-main.js +56 -0
- package/scripts/dev-vsix-install.sh +60 -0
- package/scripts/fix.sh +702 -0
- package/scripts/gen-client-types.mjs +81 -0
- package/scripts/git-hooks/pre-commit +31 -0
- package/scripts/hooks/lifecycle-state.js +61 -0
- package/scripts/hooks/package.json +4 -0
- package/scripts/hooks/post-tool-use-claws.js +292 -0
- package/scripts/hooks/pre-bash-no-verify-block.js +72 -0
- package/scripts/hooks/pre-tool-use-claws.js +206 -0
- package/scripts/hooks/session-start-claws.js +97 -0
- package/scripts/hooks/stop-claws.js +88 -0
- package/scripts/inject-claude-md.js +205 -0
- package/scripts/inject-dev-hooks.js +96 -0
- package/scripts/inject-global-claude-md.js +140 -0
- package/scripts/inject-settings-hooks.js +370 -0
- package/scripts/install.ps1 +146 -0
- package/scripts/install.sh +1729 -0
- package/scripts/monitor-arm-watch.js +155 -0
- package/scripts/rebuild-node-pty.sh +245 -0
- package/scripts/report.sh +232 -0
- package/scripts/shell-hook.fish +164 -0
- package/scripts/shell-hook.ps1 +33 -0
- package/scripts/shell-hook.sh +232 -0
- package/scripts/stream-events.js +399 -0
- package/scripts/terminal-wrapper.sh +36 -0
- package/scripts/test-enforcement.sh +132 -0
- package/scripts/test-install.sh +174 -0
- package/scripts/test-installer-parity.sh +135 -0
- package/scripts/test-template-enforcement.sh +76 -0
- package/scripts/uninstall.sh +143 -0
- package/scripts/update.sh +337 -0
- package/scripts/verify-release.sh +323 -0
- package/scripts/verify-wrapped.sh +194 -0
- package/templates/CLAUDE.global.md +135 -0
- package/templates/CLAUDE.project.md +37 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
# Claws Event Protocol — Convention Layer
|
|
2
|
+
|
|
3
|
+
This document defines the **observable behavior** every Claws-driven worker must
|
|
4
|
+
exhibit and every orchestrator can rely on. It is a convention, not a wire
|
|
5
|
+
protocol — the wire protocol is `claws/2` pub/sub (see `protocol.md` and
|
|
6
|
+
`extension/src/protocol.ts`). What lives here is the **shape and timing of
|
|
7
|
+
events** workers emit, the **command channel** orchestrators use to drive them,
|
|
8
|
+
and the **state machine** that bounds the dialogue.
|
|
9
|
+
|
|
10
|
+
The goal is real-time, no-polling orchestration. Events arrive at the
|
|
11
|
+
orchestrator within milliseconds of being published. The orchestrator decides
|
|
12
|
+
based on events, not on log scrapes. Workers signal their state explicitly
|
|
13
|
+
instead of being inferred.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Universal Event Envelope
|
|
18
|
+
|
|
19
|
+
Every payload published on any worker- or task-scoped topic SHOULD carry this
|
|
20
|
+
envelope. Consumers can rely on these fields existing.
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
{
|
|
24
|
+
"v": 1, // envelope schema version (integer)
|
|
25
|
+
"id": "<uuid-v4>", // unique message id — for dedup, reference
|
|
26
|
+
"correlation_id": "<uuid-v4>?", // groups related events (request/response, decision/outcome)
|
|
27
|
+
"parent_id": "<peer-id>?", // lineage — null for root orchestrator, peer-id of parent worker otherwise
|
|
28
|
+
"from_peer": "<peer-id>", // who published (server adds if absent)
|
|
29
|
+
"from_name": "<peer-name>", // human label (worker-X, orchestrator-main)
|
|
30
|
+
"terminal_id": "<id>?", // associated terminal id, if any
|
|
31
|
+
"ts_published": "2026-04-27T12:00:00.123Z", // worker clock at publish time
|
|
32
|
+
"ts_server": "2026-04-27T12:00:00.124Z", // server clock at fan-out time (server adds)
|
|
33
|
+
"schema": "<topic-schema-name>", // e.g. "worker-phase-v1"
|
|
34
|
+
"data": { /* schema-specific */ }
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Why the envelope:** dedup (`id`), threading (`correlation_id`), tree walks
|
|
39
|
+
(`parent_id`), clock skew detection (`ts_published` vs `ts_server`), and forward
|
|
40
|
+
compatibility (`v`, `schema`).
|
|
41
|
+
|
|
42
|
+
**Server adds** `ts_server`, `from_peer` (if missing), and refuses to fan out
|
|
43
|
+
events whose `from_peer` doesn't match the publishing connection's peer id.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. Topic Namespace
|
|
48
|
+
|
|
49
|
+
Topics are dot-separated, lowercase, hierarchical. Patterns support `*` (one
|
|
50
|
+
segment) and `**` (recursive, zero or more).
|
|
51
|
+
|
|
52
|
+
| Namespace | Owner | Purpose |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `worker.<peerId>.*` | worker `<peerId>` | facts the worker publishes about itself |
|
|
55
|
+
| `cmd.<peerId>.*` | orchestrator | commands targeted at one specific worker |
|
|
56
|
+
| `cmd.role.<role>` | orchestrator | broadcast to all workers of a role |
|
|
57
|
+
| `task.<taskId>.*` | orchestrator + assigned worker | task lifecycle (orthogonal to worker boot) |
|
|
58
|
+
| `system.*` | server | server-generated (peer.joined, peer.left, gate.fired) |
|
|
59
|
+
|
|
60
|
+
**Authorization (server-enforced — see §10):** workers can only publish on
|
|
61
|
+
`worker.<theirOwnPeerId>.*` and `task.<assignedTaskId>.*`. Only orchestrators
|
|
62
|
+
can publish on `cmd.*`. `system.*` is server-write-only.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 3. Worker-emitted Topics
|
|
67
|
+
|
|
68
|
+
Every worker MUST emit on these topics in the order shown. A worker that skips
|
|
69
|
+
`boot` is invisible to the orchestrator. A worker that skips `complete` looks
|
|
70
|
+
crashed.
|
|
71
|
+
|
|
72
|
+
### 3.1 `worker.<peerId>.boot`
|
|
73
|
+
|
|
74
|
+
**When:** Claude Code (or whatever process) is up and ready to accept the mission. Emit ONCE per worker lifetime, immediately after `claws_hello`.
|
|
75
|
+
|
|
76
|
+
```jsonc
|
|
77
|
+
{
|
|
78
|
+
"schema": "worker-boot-v1",
|
|
79
|
+
"data": {
|
|
80
|
+
"model": "claude-sonnet-4-6",
|
|
81
|
+
"role": "worker",
|
|
82
|
+
"parent_peer_id": "p_000001", // orchestrator's peer id
|
|
83
|
+
"mission_summary": "Audit src/server.ts for security issues",
|
|
84
|
+
"capabilities": ["mcp_claws", "sub_workers", "long_thinking"],
|
|
85
|
+
"cwd": "/Users/.../Desktop/Claws",
|
|
86
|
+
"terminal_id": "5"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3.2 `worker.<peerId>.phase`
|
|
92
|
+
|
|
93
|
+
**When:** every transition between the 8 lifecycle phases (or to a terminal
|
|
94
|
+
state). The state machine in §4 bounds legal transitions.
|
|
95
|
+
|
|
96
|
+
```jsonc
|
|
97
|
+
{
|
|
98
|
+
"schema": "worker-phase-v1",
|
|
99
|
+
"data": {
|
|
100
|
+
"phase": "DEPLOY", // current phase entered
|
|
101
|
+
"prev": "SPAWN", // phase exited
|
|
102
|
+
"transition_reason": "all-workers-spawned",
|
|
103
|
+
"phases_completed": ["PLAN","SPAWN","DEPLOY"],
|
|
104
|
+
"metadata": { /* phase-specific notes */ }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3.3 `worker.<peerId>.event`
|
|
110
|
+
|
|
111
|
+
**When:** any sentinel checkpoint within a phase. The `kind` field switches the
|
|
112
|
+
sub-schema.
|
|
113
|
+
|
|
114
|
+
```jsonc
|
|
115
|
+
{
|
|
116
|
+
"schema": "worker-event-v1",
|
|
117
|
+
"data": {
|
|
118
|
+
"kind": "BLOCKED|REQUEST|HARVEST|ERROR|DECISION|PROGRESS|LOG",
|
|
119
|
+
"severity": "info|warn|error|fatal",
|
|
120
|
+
"message": "human-readable headline",
|
|
121
|
+
"request_id": "<uuid>?", // present if this expects a response
|
|
122
|
+
/* + kind-specific fields, see §3.3.x */
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### 3.3.1 `kind: "BLOCKED"`
|
|
128
|
+
Worker has paused waiting for something external. Orchestrator should respond
|
|
129
|
+
on `cmd.<peerId>.unblock` with `correlation_id = data.request_id`.
|
|
130
|
+
```jsonc
|
|
131
|
+
{ "kind": "BLOCKED",
|
|
132
|
+
"severity": "warn",
|
|
133
|
+
"message": "needs approval to spawn 3 sub-workers",
|
|
134
|
+
"request_id": "req-abc",
|
|
135
|
+
"data": { "blocking_resource": "approval", "retryable": true } }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### 3.3.2 `kind: "REQUEST"`
|
|
139
|
+
Worker is asking the orchestrator to make a decision. Orchestrator responds on
|
|
140
|
+
`cmd.<peerId>.<action>` with `correlation_id = data.request_id`.
|
|
141
|
+
```jsonc
|
|
142
|
+
{ "kind": "REQUEST",
|
|
143
|
+
"severity": "info",
|
|
144
|
+
"message": "choose: full rewrite vs. patch?",
|
|
145
|
+
"request_id": "req-xyz",
|
|
146
|
+
"data": { "request_type": "decision",
|
|
147
|
+
"options": [{"id":"A","label":"full rewrite"},{"id":"B","label":"patch"}] } }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### 3.3.3 `kind: "HARVEST"`
|
|
151
|
+
Worker is delivering intermediate or final artifacts. Orchestrator may collect.
|
|
152
|
+
```jsonc
|
|
153
|
+
{ "kind": "HARVEST",
|
|
154
|
+
"severity": "info",
|
|
155
|
+
"message": "audit findings ready",
|
|
156
|
+
"data": { "artifacts": [{"path":"/tmp/audit.md","type":"markdown","size_bytes":4200}] } }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### 3.3.4 `kind: "ERROR"`
|
|
160
|
+
Something failed. Severity drives recovery: `warn` is informational, `error`
|
|
161
|
+
needs orchestrator attention, `fatal` means the worker is going down.
|
|
162
|
+
```jsonc
|
|
163
|
+
{ "kind": "ERROR",
|
|
164
|
+
"severity": "fatal",
|
|
165
|
+
"message": "ran out of token budget",
|
|
166
|
+
"data": { "error_class": "UsageLimitExceeded", "retryable": false,
|
|
167
|
+
"suggested_action": "respawn with smaller model" } }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### 3.3.5 `kind: "DECISION"`
|
|
171
|
+
Worker made a decision autonomously and is logging it (NOT requesting input).
|
|
172
|
+
For audit trails.
|
|
173
|
+
```jsonc
|
|
174
|
+
{ "kind": "DECISION",
|
|
175
|
+
"severity": "info",
|
|
176
|
+
"message": "chose option B (patch) — full rewrite would touch shared files",
|
|
177
|
+
"data": { "decision_id": "dec-1", "option_chosen": "B", "reasoning": "..." } }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### 3.3.6 `kind: "PROGRESS"`
|
|
181
|
+
Worker reports incremental progress within a long phase. Optional but useful.
|
|
182
|
+
```jsonc
|
|
183
|
+
{ "kind": "PROGRESS",
|
|
184
|
+
"severity": "info",
|
|
185
|
+
"message": "3 of 7 files reviewed",
|
|
186
|
+
"data": { "percent": 0.43, "current_step": 3, "total_steps": 7, "eta_ms": 120000 } }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### 3.3.7 `kind: "LOG"`
|
|
190
|
+
Catch-all for diagnostic narration. Orchestrator usually ignores; observers may
|
|
191
|
+
collect.
|
|
192
|
+
|
|
193
|
+
### 3.4 `worker.<peerId>.heartbeat`
|
|
194
|
+
|
|
195
|
+
**When:** every 10s while the worker is alive. Frequency may be tuned but MUST
|
|
196
|
+
be at least every 30s — workers silent for 30s+ are considered stale.
|
|
197
|
+
|
|
198
|
+
```jsonc
|
|
199
|
+
{
|
|
200
|
+
"schema": "worker-heartbeat-v1",
|
|
201
|
+
"data": {
|
|
202
|
+
"current_phase": "OBSERVE",
|
|
203
|
+
"time_in_phase_ms": 47000,
|
|
204
|
+
"tokens_used": 5421,
|
|
205
|
+
"cost_usd": 0.78,
|
|
206
|
+
"last_event_id": "<uuid of most recent event>",
|
|
207
|
+
"active_sub_workers": ["p_000004"]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
If the worker knows it's about to crash (e.g. SIGINT received), it SHOULD
|
|
213
|
+
publish a final heartbeat with `severity: "fatal"` and a parting message
|
|
214
|
+
before disconnecting.
|
|
215
|
+
|
|
216
|
+
### 3.5 `worker.<peerId>.complete`
|
|
217
|
+
|
|
218
|
+
**When:** worker has finished its mission successfully. Emit ONCE, immediately
|
|
219
|
+
before calling `claws_close` or disconnecting. Mutually exclusive with a
|
|
220
|
+
`fatal` ERROR.
|
|
221
|
+
|
|
222
|
+
```jsonc
|
|
223
|
+
{
|
|
224
|
+
"schema": "worker-complete-v1",
|
|
225
|
+
"data": {
|
|
226
|
+
"result": "ok",
|
|
227
|
+
"summary": "audited 7 files, found 2 issues — see /tmp/audit.md",
|
|
228
|
+
"artifacts": [{"path":"/tmp/audit.md","type":"markdown"}],
|
|
229
|
+
"phases_completed": ["PLAN","SPAWN","DEPLOY","OBSERVE","HARVEST","CLEANUP","REFLECT"],
|
|
230
|
+
"total_tokens": 18420,
|
|
231
|
+
"total_cost_usd": 2.41,
|
|
232
|
+
"duration_ms": 412000
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 4. Phase State Machine
|
|
240
|
+
|
|
241
|
+
Workers SHOULD transition through phases in this order. Illegal transitions
|
|
242
|
+
trigger a `system.gate.fired` event from the server (see §6) and may be
|
|
243
|
+
rejected outright.
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
┌──────────┐
|
|
247
|
+
│ PLAN │ ← only entry point; required
|
|
248
|
+
└────┬─────┘
|
|
249
|
+
▼
|
|
250
|
+
┌──────────┐
|
|
251
|
+
┌─►│ SPAWN │
|
|
252
|
+
│ └────┬─────┘
|
|
253
|
+
│ ▼
|
|
254
|
+
│ ┌──────────┐ ┌──────────┐
|
|
255
|
+
│ │ DEPLOY │◄──►│ RECOVER │
|
|
256
|
+
│ └────┬─────┘ └────┬─────┘
|
|
257
|
+
│ ▼ │
|
|
258
|
+
│ ┌──────────┐ │
|
|
259
|
+
└──┤ OBSERVE │◄────────┘
|
|
260
|
+
└────┬─────┘
|
|
261
|
+
▼
|
|
262
|
+
┌──────────┐
|
|
263
|
+
│ HARVEST │
|
|
264
|
+
└────┬─────┘
|
|
265
|
+
▼
|
|
266
|
+
┌──────────┐
|
|
267
|
+
│ CLEANUP │
|
|
268
|
+
└────┬─────┘
|
|
269
|
+
▼
|
|
270
|
+
┌──────────┐
|
|
271
|
+
│ REFLECT │ ← terminal
|
|
272
|
+
└──────────┘
|
|
273
|
+
|
|
274
|
+
+- FAILED (terminal) — reachable from any non-terminal state on fatal error
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Allowed transitions (other → other):
|
|
278
|
+
- `PLAN → SPAWN`
|
|
279
|
+
- `SPAWN → DEPLOY | RECOVER | FAILED`
|
|
280
|
+
- `DEPLOY → OBSERVE | RECOVER | FAILED`
|
|
281
|
+
- `OBSERVE → HARVEST | RECOVER | FAILED`
|
|
282
|
+
- `RECOVER → DEPLOY | OBSERVE | FAILED`
|
|
283
|
+
- `HARVEST → CLEANUP | FAILED`
|
|
284
|
+
- `CLEANUP → REFLECT | FAILED`
|
|
285
|
+
- `REFLECT → (terminal, no further transitions)`
|
|
286
|
+
- `FAILED → (terminal)`
|
|
287
|
+
|
|
288
|
+
`RECOVER` may be entered from DEPLOY, OBSERVE, or SPAWN and may exit back to
|
|
289
|
+
the same phase (re-deploy after a recovery). Workers should publish RECOVER
|
|
290
|
+
entry/exit pairs so observers can compute MTTR.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 5. Command Channel — Orchestrator → Worker
|
|
295
|
+
|
|
296
|
+
Workers do NOT subscribe to anything by default (they would need a long-poll
|
|
297
|
+
or a sidecar to receive pushes via MCP). Instead, the orchestrator delivers
|
|
298
|
+
commands via two routes:
|
|
299
|
+
|
|
300
|
+
### 5.1 Bracketed-paste injection (preferred default)
|
|
301
|
+
|
|
302
|
+
`mcp__claws__claws_broadcast(text="...", targetRole="worker", inject=true)`
|
|
303
|
+
or its single-target equivalent (broadcast filtered server-side by `peerId`).
|
|
304
|
+
|
|
305
|
+
The server writes the text directly into the worker's pty via bracketed paste.
|
|
306
|
+
The worker's Claude Code receives it as if the human typed a follow-up. This
|
|
307
|
+
needs no worker-side machinery.
|
|
308
|
+
|
|
309
|
+
When the orchestrator wants to drive a specific worker, the injected text
|
|
310
|
+
SHOULD start with a recognizable header so the worker knows it's a command
|
|
311
|
+
and not noise:
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
[CLAWS_CMD r=req-abc] approve_request: { "approved": true, "payload": {...} }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The worker's mission instructs it to look for `[CLAWS_CMD r=<id>]` lines and
|
|
318
|
+
correlate by `r`.
|
|
319
|
+
|
|
320
|
+
### 5.2 Worker-side sidecar (advanced)
|
|
321
|
+
|
|
322
|
+
For workers that need many commands or want to subscribe to broad patterns,
|
|
323
|
+
spawn a side process inside the worker terminal that runs
|
|
324
|
+
`scripts/stream-events.js` with `CLAWS_TOPIC=cmd.<myPeerId>.**` and a known
|
|
325
|
+
output file. The worker's Claude Code (which has access to Bash + Read) can
|
|
326
|
+
tail that file. Higher fidelity, more setup. Use when 5.1 isn't enough.
|
|
327
|
+
|
|
328
|
+
### 5.3 Standard command schemas
|
|
329
|
+
|
|
330
|
+
Topics under `cmd.<peerId>.*`:
|
|
331
|
+
|
|
332
|
+
| Topic | Purpose | Schema |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| `cmd.<peerId>.approve` | approve a `BLOCKED` or `REQUEST` event | `{correlation_id, payload}` |
|
|
335
|
+
| `cmd.<peerId>.reject` | reject with reason | `{correlation_id, reason}` |
|
|
336
|
+
| `cmd.<peerId>.abort` | terminate the worker now | `{reason}` |
|
|
337
|
+
| `cmd.<peerId>.pause` | pause until resumed | `{}` |
|
|
338
|
+
| `cmd.<peerId>.resume` | resume after pause | `{}` |
|
|
339
|
+
| `cmd.<peerId>.set_phase` | force phase transition (override) | `{phase, reason}` |
|
|
340
|
+
| `cmd.<peerId>.spawn` | tell worker to spawn a sub-worker | `{name, mission, model?}` |
|
|
341
|
+
| `cmd.<peerId>.inject_text` | raw text inject (no schema) | `{text, paste?}` |
|
|
342
|
+
|
|
343
|
+
Every command MUST carry `correlation_id` matching the `request_id` of the
|
|
344
|
+
event it answers (if any).
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## 6. Server-emitted Topics
|
|
349
|
+
|
|
350
|
+
The server publishes on `system.*` for events not tied to any one peer.
|
|
351
|
+
|
|
352
|
+
| Topic | When | Payload |
|
|
353
|
+
|---|---|---|
|
|
354
|
+
| `system.peer.joined` | a peer completes `hello` | `{peerId, role, peerName, ts}` |
|
|
355
|
+
| `system.peer.left` | a peer disconnects | `{peerId, role, reason: "clean"\|"crash"\|"timeout"}` |
|
|
356
|
+
| `system.peer.stale` | no heartbeat from a worker for >30s | `{peerId, last_seen, missed_heartbeats}` |
|
|
357
|
+
| `system.gate.fired` | a hook or server-side gate blocked an action | `{tool, reason, peerId}` |
|
|
358
|
+
| `system.budget.warning` | total cost across active workers exceeds threshold | `{current_usd, threshold_usd}` |
|
|
359
|
+
| `system.malformed.received` | server received an event that didn't validate | `{from, topic, error}` |
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
<!-- BEGIN GENERATED SCHEMAS -->
|
|
364
|
+
_This section is auto-generated by `npm run schemas` in `extension/`. Run to update._
|
|
365
|
+
|
|
366
|
+
| Topic Pattern | Schema Name | Key Required Fields |
|
|
367
|
+
|---|---|---|
|
|
368
|
+
| `worker.*.boot` | `worker-boot-v1` | model, role, mission_summary, cwd, terminal_id |
|
|
369
|
+
| `worker.*.phase` | `worker-phase-v1` | phase, prev, transition_reason, phases_completed |
|
|
370
|
+
| `worker.*.event` | `worker-event-v1` | kind, severity, message |
|
|
371
|
+
| `worker.*.heartbeat` | `worker-heartbeat-v1` | current_phase, time_in_phase_ms, tokens_used, cost_usd |
|
|
372
|
+
| `worker.*.complete` | `worker-complete-v1` | result, summary, artifacts, phases_completed |
|
|
373
|
+
| `cmd.*.approve` | `cmd-approve-v1` | correlation_id |
|
|
374
|
+
| `cmd.*.reject` | `cmd-reject-v1` | correlation_id, reason |
|
|
375
|
+
| `cmd.*.abort` | `cmd-abort-v1` | reason |
|
|
376
|
+
| `cmd.*.pause` | `cmd-pause-v1` | _(none)_ |
|
|
377
|
+
| `cmd.*.resume` | `cmd-resume-v1` | _(none)_ |
|
|
378
|
+
| `cmd.*.set_phase` | `cmd-set-phase-v1` | phase, reason |
|
|
379
|
+
| `cmd.*.spawn` | `cmd-spawn-v1` | name, mission |
|
|
380
|
+
| `cmd.*.inject_text` | `cmd-inject-text-v1` | text |
|
|
381
|
+
| `system.peer.joined` | `system-peer-joined-v1` | peerId, role, peerName, ts |
|
|
382
|
+
| `system.peer.left` | `system-peer-left-v1` | peerId, role, reason |
|
|
383
|
+
| `system.peer.stale` | `system-peer-stale-v1` | peerId, last_seen, missed_heartbeats |
|
|
384
|
+
| `system.gate.fired` | `system-gate-fired-v1` | tool, reason, peerId |
|
|
385
|
+
| `system.budget.warning` | `system-budget-warning-v1` | current_usd, threshold_usd |
|
|
386
|
+
| `system.malformed.received` | `system-malformed-received-v1` | from, topic, error |
|
|
387
|
+
<!-- END GENERATED SCHEMAS -->
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 7. Heartbeat Discipline
|
|
392
|
+
|
|
393
|
+
- Workers publish `worker.<peerId>.heartbeat` every 10s while in non-terminal
|
|
394
|
+
phases. Pause heartbeats during REFLECT (terminal) and after `complete`.
|
|
395
|
+
- The server tracks `last_seen` per peer (any frame counts, including
|
|
396
|
+
publishes). If `last_seen` is older than 30s, server emits
|
|
397
|
+
`system.peer.stale`.
|
|
398
|
+
- Orchestrator subscribed to `system.peer.stale` decides: nudge via
|
|
399
|
+
`cmd.<peerId>.inject_text`, abort via `cmd.<peerId>.abort`, or respawn.
|
|
400
|
+
- A worker that knows it's about to die SHOULD emit a `worker.<peerId>.event`
|
|
401
|
+
with `kind: "ERROR", severity: "fatal"` BEFORE disconnecting (the "last
|
|
402
|
+
will"). The server emits `system.peer.left` immediately on socket close.
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## 8. Lineage & Trees
|
|
407
|
+
|
|
408
|
+
- `parent_id` in the envelope encodes the parent peer.
|
|
409
|
+
- For workers spawned by the orchestrator: `parent_id` = orchestrator's peerId.
|
|
410
|
+
- For sub-workers spawned by another worker (worker-A spawns worker-B):
|
|
411
|
+
`parent_id` = worker-A's peerId.
|
|
412
|
+
- The orchestrator can walk the tree by querying `claws_peers` and joining on
|
|
413
|
+
`parent_id`.
|
|
414
|
+
|
|
415
|
+
A `correlation_id` MAY span the tree: when worker-A publishes a REQUEST and
|
|
416
|
+
worker-B is spawned to answer, both A's REQUEST and B's COMPLETE share the
|
|
417
|
+
same `correlation_id`. This lets observers thread cause and effect.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 9. Schema Versioning
|
|
422
|
+
|
|
423
|
+
- `v` in the envelope is the envelope-schema version (currently `1`).
|
|
424
|
+
- `schema` is the data-schema name with version suffix (currently `-v1`).
|
|
425
|
+
- Adding a new field to a `data` payload is non-breaking; consumers MUST
|
|
426
|
+
ignore unknown fields.
|
|
427
|
+
- Removing or renaming a field is breaking; bump to `-v2` and run both in
|
|
428
|
+
parallel for a deprecation window.
|
|
429
|
+
- Adding a new event `kind` is non-breaking if consumers default to ignoring
|
|
430
|
+
unknown kinds.
|
|
431
|
+
- Adding a new top-level topic is non-breaking.
|
|
432
|
+
- Removing a topic or changing its semantics is breaking; coordinate
|
|
433
|
+
carefully.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## 10. Authorization Rules (server-enforced)
|
|
438
|
+
|
|
439
|
+
In `server.ts`, the publish handler MUST reject:
|
|
440
|
+
|
|
441
|
+
- A peer publishing on `worker.<X>.*` where `X != ownPeerId` → reject with
|
|
442
|
+
`{ok: false, error: "publish forbidden — not your topic"}`.
|
|
443
|
+
- A non-orchestrator publishing on `cmd.*` → reject.
|
|
444
|
+
- Any peer publishing on `system.*` → reject (server-only).
|
|
445
|
+
- Subscribers may subscribe to anything (`**` is allowed).
|
|
446
|
+
|
|
447
|
+
These rules turn pub/sub into a trust boundary, not just a routing layer.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## 11. Persistence & Replay
|
|
452
|
+
|
|
453
|
+
- An optional sidecar with `CLAWS_TOPIC='**'` tees every event to
|
|
454
|
+
`.claws/events.jsonl` (one JSON object per line, newline-delimited).
|
|
455
|
+
- Tools can replay: `cat .claws/events.jsonl | jq 'select(.topic | startswith("worker."))'`.
|
|
456
|
+
- For multi-session replay, the file is appended (not truncated). Rotation
|
|
457
|
+
is the user's responsibility (e.g. `logrotate`).
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## 12. Worker Checklist (what a worker MUST do)
|
|
462
|
+
|
|
463
|
+
A compliant worker:
|
|
464
|
+
1. After `claws_hello`, immediately publish `worker.<peerId>.boot` with the
|
|
465
|
+
universal envelope.
|
|
466
|
+
2. Publish `worker.<peerId>.phase` on every phase transition.
|
|
467
|
+
3. Publish `worker.<peerId>.heartbeat` every 10s while non-terminal.
|
|
468
|
+
4. Publish `worker.<peerId>.event` at every sentinel checkpoint (BLOCKED,
|
|
469
|
+
REQUEST, HARVEST, ERROR, DECISION).
|
|
470
|
+
5. Watch its own pty input (or its sidecar tailed file) for
|
|
471
|
+
`[CLAWS_CMD r=<id>]` lines and correlate by `r`.
|
|
472
|
+
6. On terminal phase entry (REFLECT or FAILED): publish
|
|
473
|
+
`worker.<peerId>.complete` (or a final `ERROR` with `severity: fatal`)
|
|
474
|
+
THEN disconnect.
|
|
475
|
+
|
|
476
|
+
A worker that doesn't do (1)–(4) is invisible to the orchestrator and
|
|
477
|
+
defaults to the legacy polling model.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## 13. Orchestrator Checklist (what an orchestrator MUST do)
|
|
482
|
+
|
|
483
|
+
A compliant orchestrator:
|
|
484
|
+
1. Launch `scripts/stream-events.js` under Monitor with
|
|
485
|
+
`CLAWS_ROLE=orchestrator CLAWS_TOPIC='**'` (or a tighter pattern) at
|
|
486
|
+
session start. ONE persistent connection.
|
|
487
|
+
2. React to incoming events as Monitor notifications. NO polling.
|
|
488
|
+
3. Maintain its own model of the worker tree from `boot`, `phase`, and
|
|
489
|
+
`complete` events.
|
|
490
|
+
4. Respond to `BLOCKED` and `REQUEST` events within a sensible deadline by
|
|
491
|
+
publishing on `cmd.<peerId>.*` with matching `correlation_id`.
|
|
492
|
+
5. On `system.peer.stale` for a worker it spawned: nudge or respawn.
|
|
493
|
+
6. On `system.peer.left` with `reason: "crash"`: optionally respawn or escalate.
|
|
494
|
+
7. Before ending its own session: publish `cmd.role.worker` with `abort` and
|
|
495
|
+
wait for `complete` or `peer.left` from each worker.
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## 14. Examples — Full Dialogue
|
|
500
|
+
|
|
501
|
+
**Scenario:** orchestrator spawns worker-A to audit `src/server.ts`. A finds
|
|
502
|
+
a complex case and asks for guidance.
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
# orchestrator → server (via mcp__claws__claws_create + claws_send)
|
|
506
|
+
spawns wrapped terminal with Claude Code, mission: "audit src/server.ts"
|
|
507
|
+
|
|
508
|
+
# server → orchestrator's sidecar
|
|
509
|
+
{topic: "system.peer.joined", from_peer: "p7", role: "worker", peerName: "audit-A"}
|
|
510
|
+
|
|
511
|
+
# worker-A → all subscribers
|
|
512
|
+
{topic: "worker.p7.boot", data: {model:"sonnet-4-6", mission_summary:"audit src/server.ts", parent_peer_id:"p1"}}
|
|
513
|
+
|
|
514
|
+
# worker-A transitions
|
|
515
|
+
{topic: "worker.p7.phase", data: {phase:"DEPLOY", prev:"SPAWN"}}
|
|
516
|
+
{topic: "worker.p7.heartbeat", data: {current_phase:"DEPLOY", tokens_used:120}}
|
|
517
|
+
|
|
518
|
+
# worker-A finds something tricky
|
|
519
|
+
{topic: "worker.p7.event", data: {kind:"REQUEST", request_id:"r1",
|
|
520
|
+
message:"server.ts:380 — should I rewrite the handler or patch it?",
|
|
521
|
+
options: [{id:"A",label:"rewrite"},{id:"B",label:"patch"}]}}
|
|
522
|
+
|
|
523
|
+
# orchestrator (in chat with human) decides "B"
|
|
524
|
+
mcp__claws__claws_publish(topic:"cmd.p7.approve",
|
|
525
|
+
payload:{correlation_id:"r1", chosen:"B"})
|
|
526
|
+
|
|
527
|
+
# server → worker-A's pty (via inject route, see §5.1)
|
|
528
|
+
[CLAWS_CMD r=r1] approve_request: {"chosen":"B"}
|
|
529
|
+
|
|
530
|
+
# worker-A continues
|
|
531
|
+
{topic: "worker.p7.phase", data: {phase:"OBSERVE"}}
|
|
532
|
+
{topic: "worker.p7.event", data: {kind:"HARVEST",
|
|
533
|
+
message:"audit done", artifacts:[{path:"/tmp/audit.md"}]}}
|
|
534
|
+
{topic: "worker.p7.phase", data: {phase:"HARVEST"}}
|
|
535
|
+
{topic: "worker.p7.phase", data: {phase:"CLEANUP"}}
|
|
536
|
+
{topic: "worker.p7.phase", data: {phase:"REFLECT"}}
|
|
537
|
+
{topic: "worker.p7.complete", data: {result:"ok", summary:"...", artifacts:[...]}}
|
|
538
|
+
|
|
539
|
+
# worker-A disconnects
|
|
540
|
+
# server → orchestrator
|
|
541
|
+
{topic: "system.peer.left", from_peer:"p7", reason:"clean"}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
The whole exchange happens in real time. The orchestrator's chat sees each
|
|
545
|
+
event as a Monitor notification. No polling, no log scraping, no MISSION_COMPLETE
|
|
546
|
+
string match.
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## 15. What this Convention does NOT cover (yet)
|
|
551
|
+
|
|
552
|
+
- **Bandwidth/throttling**: a worker that publishes 1000 events/sec will
|
|
553
|
+
flood the orchestrator. Future work: rate-limit per topic per peer.
|
|
554
|
+
- **At-least-once delivery**: `claws/2` is in-process best-effort fan-out. If
|
|
555
|
+
the orchestrator's sidecar disconnects mid-event, the event is lost.
|
|
556
|
+
Persistence (§11) is the workaround.
|
|
557
|
+
- **Cross-machine**: `claws/2` is unix socket only today. WebSocket transport
|
|
558
|
+
is Phase 3 of the project.
|
|
559
|
+
- **Encryption / auth**: same machine, same user — no auth. Cross-machine
|
|
560
|
+
needs token auth (Phase 3).
|
|
561
|
+
- **Schema validation**: today the convention is enforced by humans reading
|
|
562
|
+
the spec. Future: a thin client wrapper that validates before publish, and
|
|
563
|
+
server-side rejection of malformed events.
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## 16. Quick Reference
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
WORKER MUST EMIT:
|
|
571
|
+
worker.<id>.boot once, after hello
|
|
572
|
+
worker.<id>.phase (on transition) every phase change
|
|
573
|
+
worker.<id>.heartbeat (every 10s) while non-terminal
|
|
574
|
+
worker.<id>.event (at checkpoints) BLOCKED | REQUEST | HARVEST | ERROR | DECISION | PROGRESS
|
|
575
|
+
worker.<id>.complete once, before disconnect
|
|
576
|
+
|
|
577
|
+
ORCHESTRATOR MUST SUBSCRIBE:
|
|
578
|
+
worker.** (everything from workers)
|
|
579
|
+
system.** (server-generated)
|
|
580
|
+
task.** (if using tasks)
|
|
581
|
+
|
|
582
|
+
ORCHESTRATOR MAY PUBLISH:
|
|
583
|
+
cmd.<peerId>.* (commands to specific worker)
|
|
584
|
+
cmd.role.* (broadcast to a role)
|
|
585
|
+
|
|
586
|
+
SENTINEL TOKEN IN INJECTED TEXT:
|
|
587
|
+
[CLAWS_CMD r=<id>] <command>: <json-payload>
|
|
588
|
+
```
|