Skip to content

Commit 80eca04

Browse files
committed
docs: third-person voice across specs, runbooks, analysis
Strip first-person team-channel language from internal-history documents so they read as technical reference material. README and formal artifacts (whitepaper, IETF drafts, research papers) untouched.
1 parent 843e134 commit 80eca04

5 files changed

Lines changed: 41 additions & 41 deletions

ANALYSIS-per-prompt-injection.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
**Goal:** every time an openclaw LLM run starts a new turn, the pilot directive is in front of the model. No exceptions, no "fell out of context after compaction", no "only loaded if the user opens HEARTBEAT.md".
44

5-
**Status:** the daemon currently writes content to `HEARTBEAT.md` + `CLAW.md`, but neither is on the per-prompt path. We have to move to a real per-prompt surface.
5+
**Status:** the daemon currently writes content to `HEARTBEAT.md` + `CLAW.md`, but neither is on the per-prompt path. The daemon must move to a real per-prompt surface.
66

77
## The actual openclaw prompt-build pipeline
88

@@ -25,7 +25,7 @@ composeSystemPromptWithHookContext(
2525
fed to the model as the system message
2626
```
2727

28-
Critical detail (verified at `attempt.ts:1203-1216`): **all four returned fields are joined across multiple plugins via `joinPresentTextSegments` — plugins compose, they don't overwrite.** That means if the user has their own plugins that contribute to the system context, ours can add to it cleanly.
28+
Critical detail (verified at `attempt.ts:1203-1216`): **all four returned fields are joined across multiple plugins via `joinPresentTextSegments` — plugins compose, they don't overwrite.** That means if the user has their own plugins that contribute to the system context, the pilot plugin can add to it cleanly.
2929

3030
The hook runs **every single turn**. There is no "first message only" gating at this layer. Compaction-survival, fresh-resume, mid-session — irrelevant. If the plugin is loaded, the hook fires.
3131

@@ -50,7 +50,7 @@ const hook: PluginHookRegistration<"before_prompt_build"> = {
5050
- Composes with user plugins (`joinPresentTextSegments` does the right thing)
5151
- Doesn't touch the user's `openclaw.json` — clean operational footprint
5252
- Failure-isolated: `runBeforePromptBuild` wraps in `try/catch` and warns if a hook throws, so a bad pilot version can never break the user's openclaw
53-
- Already part of openclaw's first-class plugin API; survives openclaw upgrades unless they break the hook contract (which would break many plugins, not just ours)
53+
- Already part of openclaw's first-class plugin API; survives openclaw upgrades unless they break the hook contract (which would break many plugins, not just the pilot one)
5454

5555
**What needs to ship:**
5656
- A small openclaw plugin package — `@pilotprotocol/openclaw-plugin` on npm — registering the hook
@@ -75,13 +75,13 @@ const hook: PluginHookRegistration<"before_prompt_build"> = {
7575
**Why it's second choice:**
7676
- Conflicts with user-set `systemPrompt`; needs the `_pilotManaged` sentinel + prepend-with-divider merge logic from the previous spec
7777
- Edits a config file the user owns — risk of user reverting or the merge logic having bugs
78-
- Only fires the `systemPrompt` field, not the richer `prependSystemContext` (which lets us coexist with user plugins)
78+
- Only fires the `systemPrompt` field, not the richer `prependSystemContext` (which permits coexistence with user plugins)
7979

80-
**When to use:** as the path for users who haven't installed our plugin yet, or whose openclaw version is too old to support `before_prompt_build` hooks.
80+
**When to use:** as the path for users who haven't installed the pilot plugin yet, or whose openclaw version is too old to support `before_prompt_build` hooks.
8181

8282
### (3) `agents.<id>.systemPrompt` per-agent override ★ SCOPE-SPECIFIC
8383

84-
If a user has multiple openclaw agents and we want per-agent control, the schema (`src/config/zod-schema.providers-core.ts`) supports `systemPrompt` at the agent level. Same mechanism, narrower scope. Useful for a future "pilot-only" agent.
84+
For per-agent control when a user has multiple openclaw agents, the schema (`src/config/zod-schema.providers-core.ts`) supports `systemPrompt` at the agent level. Same mechanism, narrower scope. Useful for a future "pilot-only" agent.
8585

8686
## Why HEARTBEAT.md doesn't cut it
8787

@@ -99,7 +99,7 @@ The current state — `HEARTBEAT.md` carries the full directive — only reaches
9999
| **hermes** | Not installed locally; manifest writes to `~/.hermes/SOUL.md` (loaded as `selfHeartbeat`-equivalent) | n/a — would need user install | Hook surface unverified — if/when a user installs hermes, investigate before committing |
100100
| **openhands** | Not installed locally; manifest uses `selfHeartbeat: true` flag — implies openhands has its own pulling-in mechanism via `microagents/` | n/a — needs install | Pull-based; per-prompt likely handled by openhands itself once microagent exists |
101101

102-
The pattern across the four "Claude-like" tools (Claude Code / openclaw / picoclaw / hermes) is convergent: each exposes a *pre-LLM-call hook* that takes content and prepends it to the system or user context. The pilot fix is the same shape every time — register a hook, return our text — only the wire format differs per tool.
102+
The pattern across the four "Claude-like" tools (Claude Code / openclaw / picoclaw / hermes) is convergent: each exposes a *pre-LLM-call hook* that takes content and prepends it to the system or user context. The pilot fix is the same shape every time — register a hook, return the pilot text — only the wire format differs per tool.
103103

104104
## Why "a while ago this was the case" — the regression
105105

@@ -109,7 +109,7 @@ The user has mentioned this was working previously. Likely cause: an earlier ope
109109

110110
2. **Openclaw's `systemPromptWhen` default may have changed** at some point from `"always"` to `"first"`. If users had pilot text in `systemPrompt` and openclaw used to send it every turn, the directive used to land. Today's default = `"first"` means it only lands on turn 1.
111111

112-
Either way, the daemon's current behavior (write to HEARTBEAT.md + leave systemPromptWhen alone) is *insufficient* on current openclaw. We need to actively manage one of the per-prompt surfaces.
112+
Either way, the daemon's current behavior (write to HEARTBEAT.md + leave systemPromptWhen alone) is *insufficient* on current openclaw. The daemon must actively manage one of the per-prompt surfaces.
113113

114114
## Recommended path forward (openclaw-first)
115115

@@ -138,12 +138,12 @@ Phase 1 is the unblock. Phase 2 is the durable answer. Phase 3 generalizes.
138138

139139
## Open questions to resolve before implementation
140140

141-
1. **`heartbeats/openclaw.md` reuse**: should we trim the existing 104-line heartbeat for system-prompt-on-every-turn, or build a separate, tighter template? My recommendation: separate `heartbeats/openclaw-system-prompt.md` capped at ~600 tokens, since this rides on every turn's context budget.
141+
1. **`heartbeats/openclaw.md` reuse**: should the existing 104-line heartbeat be trimmed for system-prompt-on-every-turn, or should a separate, tighter template be built? Recommendation: separate `heartbeats/openclaw-system-prompt.md` capped at ~600 tokens, since this rides on every turn's context budget.
142142
2. **Daemon→openclaw bridge for plugin install**: does the daemon need access to `openclaw plugins install ...`, or does it write directly to `~/.openclaw/plugins/installs.json`? The latter is fragile (file format owned by openclaw); the former requires shelling out. Likely answer: shell out to `openclaw plugins install @pilotprotocol/openclaw-plugin` and treat openclaw-binary-not-on-PATH as a soft-fail.
143-
3. **Versioning**: when openclaw bumps its plugin contract (the `hostContractVersion` we saw at `2026.5.7` in `installs.json`), do we need to re-publish our plugin? Yes — but the plugin can declare a compat range.
143+
3. **Versioning**: when openclaw bumps its plugin contract (the `hostContractVersion` observed at `2026.5.7` in `installs.json`), does the pilot plugin need to be re-published? Yes — but the plugin can declare a compat range.
144144
4. **Disable for power users**: same opt-out machinery as the config mutation spec — env var + sentinel + CLI command.
145145

146-
## What I'll commit on this branch if you say go
146+
## Implementation plan
147147

148148
- `docs/ANALYSIS-per-prompt-injection.md` (this file)
149149
- Updates to `docs/SPEC-skillinject-openclaw-per-prompt.md` to point at this analysis for the "why plugin vs config" decision

INVESTIGATION-cloudflare-workers.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Run a regular daemon on conventional infra (VM, k8s pod, Fly.io, etc.) with full
6464

6565
**Pros:**
6666
- Worker becomes a thin client; nothing about its identity is special-cased on the wire (it inherits the bridge's identity, or has its own which the bridge proxies for).
67-
- We can ship in days, not months — the bridge is just an HTTP wrapper around `driver.Driver`.
67+
- Shippable in days, not months — the bridge is just an HTTP wrapper around `driver.Driver`.
6868
- TLS termination at the bridge; auth via a per-Worker bearer token.
6969

7070
**Cons:**
@@ -92,7 +92,7 @@ Build a pure-TypeScript implementation of the Pilot wire protocol (handshake/PIL
9292
- Long-term answer: the relay can later be replaced by direct UDP if Workers ever gains it.
9393

9494
**Cons:**
95-
- Substantial new TS implementation — the Go protocol code is ~10k LOC, the subset we'd port is ~2–3k LOC (envelope + handshake + replay-window + key-exchange + ECDH/AEAD via WebCrypto).
95+
- Substantial new TS implementation — the Go protocol code is ~10k LOC, the subset to port is ~2–3k LOC (envelope + handshake + replay-window + key-exchange + ECDH/AEAD via WebCrypto).
9696
- Have to keep the TS implementation in sync with future Go changes (the existing FFI approach has the Go code as single source of truth).
9797
- WebSocket → UDP mapping has its own edge cases (frame ordering, MTU, reconnects, NAT-keepalive equivalent).
9898
- Worker CPU budget (typical 50–100 ms per request, or up to 5 min on "unbound") needs careful pacing for handshakes + retransmits.
@@ -133,9 +133,9 @@ The TypeScript side owns sockets, timers, retx loops, and feeds bytes into the W
133133
|---|---|---|
134134
| Worker bundle, gzipped | 3 MB free / 10 MB paid | A standard Go WASM is ~10 MB raw, ~3 MB gzipped — **right at the free-tier ceiling, fits paid** |
135135
| Worker bundle, uncompressed | 64 MB | trivially fits |
136-
| WASI support | "experimental, only some syscalls" | irrelevant — we'd use the host-import bridge, not WASI |
136+
| WASI support | "experimental, only some syscalls" | irrelevant — the host-import bridge is used, not WASI |
137137
| Go→WASM target | `GOOS=js GOARCH=wasm` (browser-shim) or `GOOS=wasip1 GOARCH=wasm` | use `wasm_exec.js`-style shim with custom host imports |
138-
| TinyGo size | ~200–500 KB for our subset | better fit, *but* TinyGo's incomplete support for goroutines / channels / reflect would force us to refactor away those constructs in the pilot packages we compile |
138+
| TinyGo size | ~200–500 KB for the relevant subset | better fit, *but* TinyGo's incomplete support for goroutines / channels / reflect would force a refactor away from those constructs in the compiled pilot packages |
139139

140140
**What CAN be compiled to WASM:**
141141
- `internal/crypto` (Ed25519 sign/verify, X25519 derive)
@@ -160,8 +160,8 @@ The TypeScript side owns sockets, timers, retx loops, and feeds bytes into the W
160160
**Cons:**
161161
- Two languages for one library — debugging spans both.
162162
- Goroutine→TS event-loop bridging is non-trivial. The WASM module must be designed so all its "callbacks" surface as deterministic return values rather than spawning goroutines.
163-
- Standard Go's WASM runtime is ~3 MB gzipped — right at the free-tier ceiling. TinyGo would solve that but cuts off goroutines, which the asymmetric-recovery code relies on (background retx). We'd need to refactor those into "step functions" the TS driver calls each tick.
164-
- Need a custom `wasm_exec.js` shim that imports Workers' `connect()` / `crypto.subtle` / `setTimeout` into the wasm host. Cloudflare's WASI is experimental and lacks sockets, so we can't lean on it.
163+
- Standard Go's WASM runtime is ~3 MB gzipped — right at the free-tier ceiling. TinyGo would solve that but cuts off goroutines, which the asymmetric-recovery code relies on (background retx). Those would need to be refactored into "step functions" the TS driver calls each tick.
164+
- Need a custom `wasm_exec.js` shim that imports Workers' `connect()` / `crypto.subtle` / `setTimeout` into the wasm host. Cloudflare's WASI is experimental and lacks sockets, so it cannot be relied on.
165165

166166
**Effort estimate:** ~4–6 weeks. Faster than Option B (no protocol re-implementation), slower than Option A (still need the wasm host shim + a redesigned step-function-friendly protocol surface in the Go code).
167167

RUNBOOK-pilot-ca.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
**The Pilot CA is NOT yet in production use.** The compat-mode WSS endpoint at `beacon.pilotprotocol.network` currently uses a **Let's Encrypt** certificate, terminated by nginx on the rendezvous host. The daemon's `-tls-trust` default is `system`, which verifies against the OS trust store.
1010

11-
This runbook describes the future-hardening path: when we mint the production Pilot root, embed it in client binaries, and flip `-tls-trust=pinned` as the default, attacks that compromise a public CA can no longer MITM compat daemons. Until that release ships, the Pilot CA tooling below is rehearsal-only.
11+
This runbook describes the future-hardening path: once the production Pilot root is minted, embedded in client binaries, and `-tls-trust=pinned` becomes the default, attacks that compromise a public CA can no longer MITM compat daemons. Until that release ships, the Pilot CA tooling below is rehearsal-only.
1212

1313
## The trust model in one paragraph
1414

@@ -94,7 +94,7 @@ Leaf certs expire after 90 days. Re-issue at day 75 so there's a 15-day overlap
9494

9595
Daemons don't notice — TLS sessions in flight stay valid; new connections use the new cert.
9696

97-
Automation candidate: drive this from a periodic cron on the custody host, since the root key has to be online briefly. Out of scope for v1 — manual ops is fine while we have one or two beacons.
97+
Automation candidate: drive this from a periodic cron on the custody host, since the root key has to be online briefly. Out of scope for v1 — manual ops is fine while only one or two beacons exist.
9898

9999
## Phase 5 — Root rotation (every 5-10 years, or after compromise)
100100

@@ -127,5 +127,5 @@ For a compromise-driven rotation, compress the window aggressively — flip to t
127127
## What the tool deliberately does NOT do
128128

129129
- No automatic OCSP / CRL stapling. Compat mode is a small, controlled PKI — short-lived leaves + fast embed rotation are simpler than CRL distribution.
130-
- No ACME. ACME requires DNS or HTTP control of the validated domain, which exposes a public DNS/HTTP attack surface we don't otherwise need. Manual `pilot-ca issue-beacon` is the right granularity until we have hundreds of beacons.
130+
- No ACME. ACME requires DNS or HTTP control of the validated domain, which exposes a public DNS/HTTP attack surface the deployment otherwise does not need. Manual `pilot-ca issue-beacon` is the right granularity until the fleet reaches hundreds of beacons.
131131
- No HSM integration in the binary. The operator's custody is whatever they choose (Yubikey via OpenSSL engine, GPG smartcard, air-gapped Linux). `pilot-ca init-root` writes a software key; the operator is responsible for moving it.

SPEC-compat-mode.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ The beacon is the universal hub. UDP peers don't know whether a remote is on UDP
5858

5959
### Pilot root CA
6060

61-
- We mint our own root CA keypair (offline, e.g. on a Yubikey).
61+
- A dedicated root CA keypair is minted offline (e.g. on a Yubikey).
6262
- The root CA's PEM-encoded certificate is **embedded in `cmd/daemon` via `//go:embed`** so every daemon binary ships with the trust anchor pre-pinned.
6363
- The CA signs leaf certs for each beacon hostname (`beacon-us.pilotprotocol.network`, `beacon-eu.…`, etc.).
6464
- Leaf certs rotate via standard `tls.Config.GetCertificate`. Root rotation is a multi-release event handled by shipping the new root in a daemon update alongside the old one (overlap window).
@@ -108,7 +108,7 @@ The beacon is the universal hub. UDP peers don't know whether a remote is on UDP
108108

109109
### Transport interface
110110

111-
Today `pkg/daemon/udpio.Socket` owns the UDP FD and exposes `Send(frame []byte, dst *net.UDPAddr) error` and `Recv() (frame []byte, src *net.UDPAddr, err error)`. We extract that contract into a `daemonio.Transport` interface:
111+
Today `pkg/daemon/udpio.Socket` owns the UDP FD and exposes `Send(frame []byte, dst *net.UDPAddr) error` and `Recv() (frame []byte, src *net.UDPAddr, err error)`. That contract is extracted into a `daemonio.Transport` interface:
112112

113113
```go
114114
type Transport interface {
@@ -232,7 +232,7 @@ On the daemon:
232232

233233
Public WSS endpoint can be hit by anyone. Mitigations:
234234
- Per-source-IP rate-limit on upgrade attempts (above).
235-
- nginx in front lets us deploy fail2ban / rate-limit modules at the edge.
235+
- nginx in front allows deploying fail2ban / rate-limit modules at the edge.
236236
- Auth challenge requires the attacker to have a valid Pilot identity already registered — bots can't open holding-pattern WSS connections cheaply.
237237

238238
## Rollout — what shipped
@@ -295,11 +295,11 @@ All phases collapsed into a single deploy on 2026-05-18. Production at `pilot-re
295295
4. **WS subprotocol negotiation.** **Shipped:** `Sec-WebSocket-Protocol: pilot.v1` set on both sides.
296296
5. **Multi-beacon WSS connections.** **Deferred** — single conn per daemon for v1; multi-beacon redundancy is a future improvement.
297297

298-
## What we're NOT building
298+
## Out of scope
299299

300-
- Centralized HTTPS REST gateway. That'd be a separate service (Phase 8+) that proxies HTTPS REST → Pilot WSS. Easy to add later once compat mode is solid; doesn't belong in v1 of compat mode itself.
300+
- Centralized HTTPS REST gateway. That would be a separate service (Phase 8+) proxying HTTPS REST → Pilot WSS. Easy to add later once compat mode is solid; does not belong in v1 of compat mode itself.
301301
- HTTP/3 / QUIC. The whole point of compat mode is to use TCP/443 which firewalls don't block. QUIC is UDP and would defeat the purpose.
302-
- WebRTC. We considered it — the data-channel ICE machinery would give us NAT traversal for free — but WebRTC requires a signaling server *and* still uses UDP for the data plane. Doesn't help our problem.
302+
- WebRTC. Considered — the data-channel ICE machinery would provide NAT traversal for free — but WebRTC requires a signaling server *and* still uses UDP for the data plane. Does not address the underlying constraint.
303303

304304
## Companion docs to update
305305

0 commit comments

Comments
 (0)