brainclaw 1.9.0 → 1.9.1
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/README.md +585 -499
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/commands/harvest.js +1 -1
- package/dist/commands/hooks.js +73 -73
- package/dist/commands/init.js +1 -1
- package/dist/commands/install-hooks.js +78 -78
- package/dist/commands/mcp-read-handlers.js +57 -14
- package/dist/commands/mcp.js +79 -13
- package/dist/commands/switch.js +26 -5
- package/dist/commands/version.js +1 -1
- package/dist/core/agent-capability.js +19 -4
- package/dist/core/agent-files.js +119 -119
- package/dist/core/codev-prompts.js +38 -38
- package/dist/core/default-profiles/doctor.yaml +11 -11
- package/dist/core/default-profiles/janitor.yaml +11 -11
- package/dist/core/default-profiles/onboarder.yaml +11 -11
- package/dist/core/default-profiles/reviewer.yaml +13 -13
- package/dist/core/dispatcher.js +1 -1
- package/dist/core/entity-operations.js +29 -3
- package/dist/core/execution.js +1 -1
- package/dist/core/loops/verbs.js +0 -1
- package/dist/core/messaging.js +2 -2
- package/dist/core/protocol-skills.js +164 -164
- package/dist/core/runtime-signals.js +1 -1
- package/dist/core/search.js +19 -2
- package/dist/core/security-guard.js +207 -207
- package/dist/core/spawn-check.js +16 -2
- package/dist/core/staleness.js +1 -1
- package/dist/core/store-resolution.js +26 -7
- package/dist/core/worktree.js +18 -18
- package/dist/facts.js +3 -3
- package/dist/facts.json +2 -2
- package/docs/PROTOCOL.md +1 -1
- package/docs/adapters/openclaw.md +43 -43
- package/docs/architecture/project-refs.md +328 -328
- package/docs/cli.md +2093 -2093
- package/docs/concepts/coordination.md +52 -52
- package/docs/concepts/coordinator-runbook.md +129 -129
- package/docs/concepts/dispatch-lifecycle.md +245 -245
- package/docs/concepts/event-log-store.md +928 -928
- package/docs/concepts/ideation-loop.md +317 -317
- package/docs/concepts/loop-engine.md +520 -511
- package/docs/concepts/mcp-governance.md +268 -268
- package/docs/concepts/memory.md +84 -84
- package/docs/concepts/multi-agent-workflows.md +167 -167
- package/docs/concepts/observer-protocol.md +361 -361
- package/docs/concepts/plans-and-claims.md +217 -217
- package/docs/concepts/project-md-convention.md +35 -35
- package/docs/concepts/runtime-notes.md +38 -38
- package/docs/concepts/troubleshooting.md +254 -254
- package/docs/concepts/workspace-bootstrapping.md +142 -142
- package/docs/context-format-changelog.md +35 -35
- package/docs/context-format.md +48 -48
- package/docs/index.md +65 -65
- package/docs/integrations/agents.md +158 -158
- package/docs/integrations/claude-code.md +23 -23
- package/docs/integrations/cline.md +77 -77
- package/docs/integrations/continue.md +55 -55
- package/docs/integrations/copilot.md +68 -68
- package/docs/integrations/cursor.md +23 -23
- package/docs/integrations/kilocode.md +72 -72
- package/docs/integrations/mcp.md +377 -377
- package/docs/integrations/mistral-vibe.md +122 -122
- package/docs/integrations/openclaw.md +92 -92
- package/docs/integrations/opencode.md +84 -84
- package/docs/integrations/overview.md +115 -115
- package/docs/integrations/roo.md +71 -71
- package/docs/integrations/windsurf.md +77 -77
- package/docs/mcp-schema-changelog.md +360 -356
- package/docs/playbooks/integration/index.md +121 -121
- package/docs/playbooks/orchestration.md +37 -0
- package/docs/playbooks/productivity/index.md +99 -99
- package/docs/playbooks/team/index.md +117 -117
- package/docs/product/agent-first-model.md +184 -184
- package/docs/product/entity-model-audit.md +462 -462
- package/docs/product/positioning.md +86 -86
- package/docs/quickstart-existing-project.md +107 -107
- package/docs/quickstart.md +183 -183
- package/docs/release-maintenance.md +79 -79
- package/docs/reputation.md +52 -52
- package/docs/review.md +45 -45
- package/docs/security.md +212 -212
- package/docs/server-operations.md +118 -118
- package/docs/storage.md +106 -106
- package/package.json +80 -65
- package/docs/concepts/event-log-store-critique-A.md +0 -333
- package/docs/concepts/event-log-store-critique-B.md +0 -353
- package/docs/concepts/event-log-store-phase0-measurements.md +0 -58
- package/docs/concepts/event-log-store-proposal-A.md +0 -365
- package/docs/concepts/event-log-store-proposal-B.md +0 -404
- package/docs/concepts/identity-model-proposal.md +0 -371
|
@@ -1,361 +1,361 @@
|
|
|
1
|
-
# Observer Protocol — language-agnostic read-only surfaces
|
|
2
|
-
|
|
3
|
-
Status: spec (pln#560 step 1). Pivot deliverable: it serves the VS Code
|
|
4
|
-
extension, the JetBrains plugin, and any future surface identically. Companion
|
|
5
|
-
to `event-log-store.md` (the journal this protocol consumes) and the VS Code
|
|
6
|
-
vision §5 (the UX it powers).
|
|
7
|
-
|
|
8
|
-
## 1. The one rule
|
|
9
|
-
|
|
10
|
-
**An observability surface is a pure consumer of the event journal. It never
|
|
11
|
-
acquires a store lock, never writes inside `.brainclaw/`, never runs a polling
|
|
12
|
-
timer against the MCP server for display.** It tails the append-only journal,
|
|
13
|
-
projects board state in memory, and refreshes the affected section when a new
|
|
14
|
-
record arrives. The MCP server is reserved for *actions* (accept / release /
|
|
15
|
-
dispatch / transition) through a separate, lazily-created client.
|
|
16
|
-
|
|
17
|
-
Why this exists (2026-06-10 calibration): the prior extension "read" the board
|
|
18
|
-
by calling a path that mutated and git-committed the entire store under the
|
|
19
|
-
lock (`autoAcknowledge → persistState`, held >5s), ran the agent-run reconciler
|
|
20
|
-
twice per poll with locked writes, created ~120 locked "unverified" event files
|
|
21
|
-
per hour per run, and impersonated the parent shell's agent identity —
|
|
22
|
-
consuming that agent's cursor. A dashboard is not an agent. This protocol makes
|
|
23
|
-
that class of bug unrepresentable: a conforming observer *cannot* write.
|
|
24
|
-
|
|
25
|
-
## 2. What the observer reads
|
|
26
|
-
|
|
27
|
-
The journal defined in `event-log-store.md`:
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
.brainclaw/events/
|
|
31
|
-
meta.json # { next_seq, active_segment, entity_revs } — a cache
|
|
32
|
-
seg-<firstSeq>.jsonl # immutable once rolled; named by first seq it holds
|
|
33
|
-
seg-<firstSeq>.jsonl # active = lexicographically-last segment
|
|
34
|
-
checkpoints/ # NOT YET EMITTED by the writer (see §5) — directory
|
|
35
|
-
ckpt-<seq>.json # may be absent today; self-contained state manifest
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Segment naming is normative.** `firstSeq` is encoded as a **decimal,
|
|
39
|
-
zero-padded to 8 digits** (e.g. `seg-00018342.jsonl`) so directory-listing
|
|
40
|
-
lex-sort matches numeric seq order with no parsing. Implementations MUST emit
|
|
41
|
-
and accept exactly this format; any other padding (or none) breaks the
|
|
42
|
-
binary-search-by-filename in §5. The 8-digit field overflows at `seq > 1e8`;
|
|
43
|
-
at the historical write rate (~17k events to date) this is decades away, but
|
|
44
|
-
a journal that crosses the boundary requires a coordinated widen-and-pad
|
|
45
|
-
migration in writer + every observer (deferred until needed; flagged here so
|
|
46
|
-
no implementer assumes the pad width is incidental).
|
|
47
|
-
|
|
48
|
-
`meta.json` is advisory only (§4): the observer reads it to cheaply detect
|
|
49
|
-
"did anything change" but never trusts its `entity_revs` or `next_seq` for
|
|
50
|
-
correctness — those are derived from the journal tail itself.
|
|
51
|
-
|
|
52
|
-
Record envelope (v2), one JSON object per line:
|
|
53
|
-
|
|
54
|
-
```jsonc
|
|
55
|
-
{ "v": 2, "seq": 18342, "ts": "…", "writer": "w_…", "agent": "claude-code",
|
|
56
|
-
"action": "update", "item_type": "plan", "item_id": "pln_…",
|
|
57
|
-
"entity_rev": 7, "summary": "…", "payload": { /* full post-image */ } }
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Action → class mapping (the observer needs the class, not a hardcoded verb
|
|
61
|
-
list — it is `ACTION_CLASS_BY_ACTION` in `event-log.ts`, and MUST be mirrored
|
|
62
|
-
in every implementation or fetched from a shared manifest):
|
|
63
|
-
|
|
64
|
-
| Class | Effect on the projection |
|
|
65
|
-
|---|---|
|
|
66
|
-
| `entity-state` (`create`,`update`,`accept`,`reject`,`claim`,`release_claim`,`rollback`,`upgrade`,`backfill`) | upsert `payload` at `(item_type,item_id)` |
|
|
67
|
-
| `tombstone` (`delete`) | remove `(item_type,item_id)` |
|
|
68
|
-
| `journal-meta` (`checkpoint_ref`,`journal_note`,`seq_repair`,`federation_apply`) | ignore for state; `checkpoint_ref` is a bootstrap hint (§5) |
|
|
69
|
-
| `observability` (`session_start`,`session_end`,`assignment_offered`,`assignment_progress`,`run_progress`) | activity feed only — never a state upsert |
|
|
70
|
-
| `registry-lifecycle` (`assignment_*`,`run_*`) | upsert when `payload` present (phase 1.5+), else a status/activity signal |
|
|
71
|
-
|
|
72
|
-
The observer is **forward-compatible**: an unknown `action` whose class it
|
|
73
|
-
cannot resolve is applied as `entity-state` iff it carries a `payload` and an
|
|
74
|
-
`item_id`, else treated as an activity signal. Never crash on an unknown verb.
|
|
75
|
-
|
|
76
|
-
## 3. The cursor lives OUTSIDE the store
|
|
77
|
-
|
|
78
|
-
The observer's read position is a **seq watermark** persisted in *client*
|
|
79
|
-
storage, never in `.brainclaw/`:
|
|
80
|
-
|
|
81
|
-
- VS Code: `ExtensionContext.workspaceState`, key `bclaw.observer.cursor.<project_id>`.
|
|
82
|
-
- JetBrains: `PropertiesComponent` / project-scoped state, same key shape.
|
|
83
|
-
- Generic: any client-private kv keyed by `project_id`.
|
|
84
|
-
|
|
85
|
-
Shape: `{ seq: number, checkpoint_seq: number }`. `seq` = highest record seq
|
|
86
|
-
applied; `checkpoint_seq` = the checkpoint the in-memory projection was last
|
|
87
|
-
seeded from (for fast re-bootstrap). The store's own `.cursors/` directory is
|
|
88
|
-
the AGENTS' read position and is **off-limits** to observers — touching it is
|
|
89
|
-
the identity-leak bug this protocol forbids.
|
|
90
|
-
|
|
91
|
-
Rationale: a watermark survives segment rotation, compaction, and archival
|
|
92
|
-
(byte offsets do not). It is private to the surface, so N observers never
|
|
93
|
-
interfere with each other or with agents.
|
|
94
|
-
|
|
95
|
-
## 4. Change detection — a file watch, not a poll, not a lock
|
|
96
|
-
|
|
97
|
-
The observer watches the journal directory for growth and reacts:
|
|
98
|
-
|
|
99
|
-
1. Watch `.brainclaw/events/` (the active segment's size/mtime, and creation of
|
|
100
|
-
new `seg-*.jsonl`). VS Code: `FileSystemWatcher` on `events/seg-*.jsonl` +
|
|
101
|
-
`meta.json`. JetBrains: `VirtualFileListener` / NIO `WatchService`. Generic
|
|
102
|
-
fallback: stat the active segment on a *long* interval (≥10 s) — this is a
|
|
103
|
-
stat, not an MCP call, and acquires no lock.
|
|
104
|
-
2. On a growth signal, **tail forward** from `cursor.seq` (§5) and apply records
|
|
105
|
-
to the in-memory projection.
|
|
106
|
-
3. `meta.json` is advisory only; never trust it for correctness — the journal
|
|
107
|
-
tail is the truth (it may be a stale cache mid-write). Use it only to detect
|
|
108
|
-
"did anything change" cheaply.
|
|
109
|
-
|
|
110
|
-
There is no MCP server process for display. The watcher is OS-level; the read
|
|
111
|
-
is a file read. Under the 2026-06-10 load (3 workers + open surface) this yields
|
|
112
|
-
zero lock acquisitions by the surface — the validation gate (step 3).
|
|
113
|
-
|
|
114
|
-
## 5. Bootstrap and tail algorithm
|
|
115
|
-
|
|
116
|
-
**Status of checkpoint emission (2026-06-12, pln#543 step 4 landed):** the
|
|
117
|
-
writer does NOT yet produce `checkpoints/ckpt-*.json` files — checkpoint
|
|
118
|
-
emission ships with step 3/5 of pln#543. Until then, the empty-seed + full
|
|
119
|
-
tail path below is the **primary** cold-start path in production, not a
|
|
120
|
-
degenerate fallback. The checkpoint-first path is the spec the consumer must
|
|
121
|
-
implement for forward-compatibility; an observer that hard-requires a
|
|
122
|
-
checkpoint at activation is broken against today's store. The perf targets
|
|
123
|
-
in §10 assume checkpoint emission; until it ships, "activation → first
|
|
124
|
-
summary" is bounded by the full tail length instead (~10 MB of segments in
|
|
125
|
-
the typical case, sub-second in practice, but the budget no longer has
|
|
126
|
-
slack).
|
|
127
|
-
|
|
128
|
-
**Cold start (no cursor, or cursor below the oldest live segment):**
|
|
129
|
-
|
|
130
|
-
1. **If a verified checkpoint exists**, load the newest `ckpt-<S>.json` →
|
|
131
|
-
seed the in-memory projection (full post-image set at head `S`). Set
|
|
132
|
-
`cursor.checkpoint_seq = S`. (Today: this branch is dead until the
|
|
133
|
-
writer ships checkpoint emission.)
|
|
134
|
-
2. **Otherwise** (today's primary path), seed from the empty projection
|
|
135
|
-
with `cursor.checkpoint_seq = 0`.
|
|
136
|
-
3. Tail every record with `seq > checkpoint_seq` across segments in
|
|
137
|
-
(segment, file-line) order; apply by class (§2). With no checkpoint
|
|
138
|
-
this is a full replay from seq 1, bounded by retention (the
|
|
139
|
-
`events/archive/` floor — segments are park-don't-deleted past the
|
|
140
|
-
second-newest verified checkpoint; with no checkpoint, no segment is
|
|
141
|
-
ever eligible for archive, so "the journal" = "every segment ever
|
|
142
|
-
written" until checkpoint emission ships).
|
|
143
|
-
4. Set `cursor.seq` to the last applied seq. Render.
|
|
144
|
-
|
|
145
|
-
If the cursor's `seq` is **below the oldest non-archived segment's first
|
|
146
|
-
seq** (gap — segments archived past the watermark), discard the cursor and
|
|
147
|
-
cold-start: notifications degrade, state never does. Today no segment is
|
|
148
|
-
ever archived (gc requires a verified checkpoint floor, see §2.3 of the
|
|
149
|
-
store spec), so this branch is unreachable in production until checkpoint
|
|
150
|
-
emission ships — but the rule is normative regardless: an observer that
|
|
151
|
-
crashes on a gap is broken against any future store.
|
|
152
|
-
|
|
153
|
-
**Warm tail (cursor present, within live segments):**
|
|
154
|
-
|
|
155
|
-
1. Binary-search the segment whose name (`seg-<firstSeq>`) contains
|
|
156
|
-
`cursor.seq + 1` (filenames sort by first seq).
|
|
157
|
-
2. Stream forward from that point across segments; apply by class.
|
|
158
|
-
3. A **torn tail** (final line unparseable or missing trailing `\n`) is expected
|
|
159
|
-
crash residue mid-write by an agent — skip it; it reappears complete on the
|
|
160
|
-
next growth signal. Never block on it.
|
|
161
|
-
4. A mid-file unparseable line is logged and skipped (do not halt the tail).
|
|
162
|
-
5. Advance `cursor.seq` only over records actually applied.
|
|
163
|
-
|
|
164
|
-
Replay order is always (segment order, then file-line order) — never sorted by
|
|
165
|
-
seq (matches the store's own reducer; a dup `seq` from a lock-steal applies
|
|
166
|
-
later-line-wins, harmlessly, in a read-only projection).
|
|
167
|
-
|
|
168
|
-
## 6. Board projection — which records touch which section
|
|
169
|
-
|
|
170
|
-
The in-memory projection is `Map<item_type, Map<item_id, payload>>` plus a
|
|
171
|
-
bounded recent-activity ring (observability + registry signals, last N). The
|
|
172
|
-
board sections are derived; a record invalidates only the sections its
|
|
173
|
-
`item_type` feeds, and only those re-render (push-by-affected-section, §5.3):
|
|
174
|
-
|
|
175
|
-
| `item_type` | Invalidates sections |
|
|
176
|
-
|---|---|
|
|
177
|
-
| `plan` | IN_PROGRESS, SPRINTS, BACKLOG, ATTENTION (badge), SYSTEM (counts) |
|
|
178
|
-
| `claim` | IN_PROGRESS, AGENTS (roster freshness) |
|
|
179
|
-
| `assignment` | IN_PROGRESS, ATTENTION (blocked/failed), "Recently terminal" |
|
|
180
|
-
| `agent_run` | IN_PROGRESS (worker rows), AGENTS, "Recently terminal" |
|
|
181
|
-
| `candidate` | ATTENTION (human-review), CANDIDATES |
|
|
182
|
-
| `action` | ATTENTION (the dominant attention input) |
|
|
183
|
-
| `constraint`/`decision`/`trap` | SYSTEM (counts), TRAPS |
|
|
184
|
-
| `handoff` | ACTIVITY, SYSTEM (counts) |
|
|
185
|
-
| `sequence` | SPRINTS |
|
|
186
|
-
| `session`/`*_progress` (observability) | ACTIVITY feed only — never a section state change |
|
|
187
|
-
|
|
188
|
-
`attention_required` is computed by the observer from the projection (actions +
|
|
189
|
-
human candidates + blocked/failed assignments + failed runs + evidence-
|
|
190
|
-
contradicted terminals), matching what the server-side composite returns — the
|
|
191
|
-
surface must not under-count by reading "actions only" (the pln#559 fix, now in
|
|
192
|
-
the projection rule).
|
|
193
|
-
|
|
194
|
-
### 6.1 Dual-mode coverage gap (CLOSED by pln#568 phase 1.5)
|
|
195
|
-
|
|
196
|
-
> **Status (pln#568):** the writer-side gap below is **closed**. The
|
|
197
|
-
> registry / coordination families (claim, assignment, agent_run,
|
|
198
|
-
> action_required [journaled under item_type `state`], candidate, sequence,
|
|
199
|
-
> and SHARED runtime_note) now emit full entity-state **post-images** on their
|
|
200
|
-
> persist chokepoint (`src/core/events/registry-post-image.ts`), and the
|
|
201
|
-
> observer materializer projects them (`board-projection.ts` ARRAY_SLOT).
|
|
202
|
-
>
|
|
203
|
-
> **Cutover signal (O2, resolved):** an observer switches a registry family
|
|
204
|
-
> from the MCP `board_summary` seed to the journal only once the journal
|
|
205
|
-
> carries the `journal_note` kind **`registry_genesis`** marker — emitted by
|
|
206
|
-
> `runRegistryGenesisSupplement` (run via `brainclaw migrate --enable-journal`)
|
|
207
|
-
> after it backfills every pre-existing registry entity. The marker is the
|
|
208
|
-
> safety gate: without a complete backfill a partially-journaled store would
|
|
209
|
-
> undercount the attention badge (trp#559). `BoardObserver.registryAuthoritative()`
|
|
210
|
-
> tracks the marker (sticky, re-derived on cold start by replaying from the
|
|
211
|
-
> checkpoint floor); `mergeCounts(journal, seed, journalActive, registryAuthoritative)`
|
|
212
|
-
> takes claims/assignments/runs/actions from the journal when it is set, and
|
|
213
|
-
> from the seed otherwise. `agents`/`sessions` are never journaled → always seed.
|
|
214
|
-
> A store that has NOT run the supplement keeps the seed (no regression).
|
|
215
|
-
>
|
|
216
|
-
> The historical (pre-pln#568) description below is kept for context.
|
|
217
|
-
|
|
218
|
-
The journal classifies records into five classes (§2). In phase 1 / `dual`
|
|
219
|
-
mode — what runs today after pln#543 step 4 — **registry-lifecycle records
|
|
220
|
-
are payload-OPTIONAL** (event-log-store.md §2.1.1, J4); the dual-write path
|
|
221
|
-
in `src/core/event-log.ts:152` forwards `assignment_*` and `run_*` events to
|
|
222
|
-
the journal with `item_id` only, no `payload`. The §2 rule "upsert when
|
|
223
|
-
payload present, else a status/activity signal" means today's journal carries
|
|
224
|
-
**no post-images for `assignment` or `agent_run`**: the in-memory projection
|
|
225
|
-
has zero rows for those item_types, and the materializer
|
|
226
|
-
(`src/core/events/materialize.ts`) only enumerates the 5 memory families
|
|
227
|
-
(constraint/decision/trap/handoff/plan).
|
|
228
|
-
|
|
229
|
-
Consequence for the §6 mapping table: until phase 1.5 ships, the rows that
|
|
230
|
-
the table claims `assignment` / `agent_run` / `claim` populate (IN_PROGRESS
|
|
231
|
-
worker rows, ATTENTION blocked/failed, Recently terminal under IN_PROGRESS)
|
|
232
|
-
cannot be drawn from the journal alone. A conforming observer in dual mode
|
|
233
|
-
MUST:
|
|
234
|
-
|
|
235
|
-
- Seed those sections at activation from a **single observer-flagged
|
|
236
|
-
`bclaw_context(kind: "board_summary")`** call (no timer, no poll) — that
|
|
237
|
-
read is lock-free under the §8 observer contract (validated against
|
|
238
|
-
`getDispatchStatus` and `loadAssignment`/`loadAgentRun`, which are pure
|
|
239
|
-
projection reads — `mcp-read-handlers.ts:1916`, `json-store.ts:47`); and
|
|
240
|
-
- Mark those sections "live-view degraded" in the tooltip until phase 1.5,
|
|
241
|
-
so the operator can tell journal-driven sections (memory entities,
|
|
242
|
-
attention badges) from MCP-seeded sections (workers, lifecycle).
|
|
243
|
-
|
|
244
|
-
Memory-entity sections (plan / constraint / decision / trap / handoff /
|
|
245
|
-
sequence / handoff-derived ACTIVITY) ARE journal-driven today via the
|
|
246
|
-
per-entity diff in `persistState` (`src/core/state.ts:400`) — the protocol
|
|
247
|
-
delivers its full value for them.
|
|
248
|
-
|
|
249
|
-
### 6.2 Section ID glossary
|
|
250
|
-
|
|
251
|
-
The §6 table uses display names; the canonical IDs in
|
|
252
|
-
`vscode-extension/src/board-tree.ts:321` are `attention | in-progress |
|
|
253
|
-
sprints | backlog | system | agents | candidates | activity | plans | claims
|
|
254
|
-
| assignments | runs | actions | handoffs | sprint | traps | cross-project`.
|
|
255
|
-
"Recently terminal" is **not** a top-level section — it is a sub-node
|
|
256
|
-
rendered under `in-progress`. The board also surfaces `cross-project`
|
|
257
|
-
(federation incoming signals) and `linked_projects`, neither of which has a
|
|
258
|
-
single journal `item_type` today: cross-project signals arrive via the
|
|
259
|
-
handoff/candidate streams (already covered), `linked_projects` is derived
|
|
260
|
-
from project config and is intentionally NOT a journal concern.
|
|
261
|
-
|
|
262
|
-
The projection is **state**, not administrative belief: a worker row's health
|
|
263
|
-
comes from evidence in the records (commits/fs signals carried on
|
|
264
|
-
registry-lifecycle payloads when present), not from a bare status field that the
|
|
265
|
-
2026-06-10 log proved lies. Where richer evidence requires it, the surface MAY
|
|
266
|
-
call `bclaw_dispatch_status` through the actions client (§7) — that is a
|
|
267
|
-
read-only MCP call, used sparingly (per visible terminal row), not a poll.
|
|
268
|
-
|
|
269
|
-
## 7. Actions go through a separate, lazy MCP client
|
|
270
|
-
|
|
271
|
-
Mutations (accept candidate, release claim, dispatch, transition, complete step)
|
|
272
|
-
are the *only* reason an observer talks to the MCP server. Rules:
|
|
273
|
-
|
|
274
|
-
- One lazily-created MCP client per project, spun up on first action, idle-timed
|
|
275
|
-
out after inactivity. Never created just to display.
|
|
276
|
-
- Distinct from any agent session: the client identifies as an **observer
|
|
277
|
-
principal** (see §8), so its calls never adopt an agent's claim/cursor.
|
|
278
|
-
- After an action, the observer does NOT optimistically mutate its projection;
|
|
279
|
-
it waits for the resulting journal record(s) to arrive via the tail (§5) and
|
|
280
|
-
re-projects. Single source of truth, no split-brain. (A short-lived "pending"
|
|
281
|
-
affordance on the clicked item is a UI concern, not projection state.)
|
|
282
|
-
- `bclaw_dispatch_status` and other read-only facades are permitted through this
|
|
283
|
-
client for on-demand evidence enrichment, but are never on a timer.
|
|
284
|
-
|
|
285
|
-
## 8. Observer identity (no impersonation, no side effects)
|
|
286
|
-
|
|
287
|
-
The surface declares itself an observer so the server suppresses every write a
|
|
288
|
-
read would otherwise trigger:
|
|
289
|
-
|
|
290
|
-
- Transport signal: `BRAINCLAW_OBSERVER=1` in the action client's env, and/or
|
|
291
|
-
MCP `clientInfo.name = "brainclaw-observer/<surface>"`.
|
|
292
|
-
- Server contract (already implemented, pln#558): observer reads do not
|
|
293
|
-
`autoAcknowledge`, do not run agent-run reconciliation, do not advance
|
|
294
|
-
`readUnseenEvents` cursors, do not implicit-heartbeat or auto-register an
|
|
295
|
-
identity. This protocol is the client half of that contract: even the read-
|
|
296
|
-
only facade calls in §7 carry the observer flag.
|
|
297
|
-
- The observer never presents an agent name as the actor of anything. Actions
|
|
298
|
-
the human triggers are attributed to the human operator principal, not to a
|
|
299
|
-
spawned agent.
|
|
300
|
-
|
|
301
|
-
## 9. Failure modes and degradation
|
|
302
|
-
|
|
303
|
-
| Condition | Behavior |
|
|
304
|
-
|---|---|
|
|
305
|
-
| `events/` absent (journal off / not migrated) | Fall back to a single MCP `board_summary` read at activation (no timer); show a "journal off — limited live view" hint. The surface still works, just not push-driven. |
|
|
306
|
-
| Cursor gap (archived past watermark) | Cold-start from newest checkpoint (§5); silent — state is correct, only missed-activity history is lost. |
|
|
307
|
-
| Checkpoint missing/corrupt | Fall back to the previous checkpoint, replay more segments (the two-checkpoint floor guarantees one exists); if none, seed empty + full tail. |
|
|
308
|
-
| Torn / unparseable line | Skip, keep tailing (§5). |
|
|
309
|
-
| Active segment shrinks / meta regresses | Trust the journal tail, re-derive; never write a "repair" (that is an agent/doctor job). |
|
|
310
|
-
| Watch unavailable (network FS, sandbox) | Degrade to a long-interval stat of the active segment; still zero locks. |
|
|
311
|
-
|
|
312
|
-
## 10. Performance budget (vision §5.3, restated as observer obligations)
|
|
313
|
-
|
|
314
|
-
| Operation | Target | Hard limit | How the protocol meets it |
|
|
315
|
-
|---|---|---|---|
|
|
316
|
-
| Activation → first summary | 500 ms | 2 s | seed from newest checkpoint, no full replay |
|
|
317
|
-
| Summary refresh | 300 ms | 1 s | apply only the new tail records |
|
|
318
|
-
| Section expand (warm) | 50 ms | 200 ms | projection is in memory; expand reads the map |
|
|
319
|
-
| Section expand (cold) | 500 ms | 2 s | first projection build from checkpoint+tail |
|
|
320
|
-
| Action round-trip | 500 ms | 2 s | lazy MCP client; result observed via tail |
|
|
321
|
-
|
|
322
|
-
Out of budget → surface in tooltip + a "performance degraded" status-bar
|
|
323
|
-
indicator (never escalate by calling a heavier path — that is the contention-
|
|
324
|
-
breeds-contention bug this protocol exists to kill).
|
|
325
|
-
|
|
326
|
-
## 11. Language-agnostic conformance checklist
|
|
327
|
-
|
|
328
|
-
A surface in any language conforms iff:
|
|
329
|
-
|
|
330
|
-
1. It reads only files under `.brainclaw/events/` (+ checkpoints) and writes
|
|
331
|
-
nothing under `.brainclaw/`.
|
|
332
|
-
2. Its cursor is a seq watermark in client-private storage, keyed by
|
|
333
|
-
`project_id`, never in the store's `.cursors/`.
|
|
334
|
-
3. It seeds from the newest verified checkpoint and tails by (segment, line)
|
|
335
|
-
order, applying records by action *class*, tolerant of unknown verbs and torn
|
|
336
|
-
tails.
|
|
337
|
-
4. Change detection is an OS file watch (or long-interval stat) — never an MCP
|
|
338
|
-
poll, never a lock.
|
|
339
|
-
5. Mutations go through a separate lazy MCP client flagged as an observer
|
|
340
|
-
principal; the projection updates only from the resulting journal records.
|
|
341
|
-
6. `attention_required` and worker health are computed from journal evidence,
|
|
342
|
-
not from administrative status fields alone.
|
|
343
|
-
|
|
344
|
-
Reference implementation: the VS Code extension (pln#560 step 2). The JetBrains
|
|
345
|
-
plugin (next plan) implements this same checklist in Kotlin — its existence is
|
|
346
|
-
the cross-language validation that this protocol, not the TypeScript code, is
|
|
347
|
-
the contract.
|
|
348
|
-
|
|
349
|
-
## 12. OPEN QUESTIONS
|
|
350
|
-
|
|
351
|
-
Carried from the 2026-06-12 symmetric review (pln#560 step 1, this branch).
|
|
352
|
-
Each is something the spec text cannot close on its own; one or more must
|
|
353
|
-
be answered before the JetBrains plugin (Kotlin) ships.
|
|
354
|
-
|
|
355
|
-
| # | Sev | Question |
|
|
356
|
-
|---|---|---|
|
|
357
|
-
| O1 | MED | **Shared `ACTION_CLASS_BY_ACTION` manifest.** §2 says implementations MUST mirror the table "or fetch it from a shared manifest." Today only the TS version exists (`src/core/events/journal.ts:66`); a Kotlin implementer would re-type 42 entries by hand and silently drift on the 43rd. Should this ship as a generated JSON next to `event-log-store.md` (single source of truth, both runtimes load it) or as part of a versioned schema bundle? Recommend the generated JSON — the table is small and changes per spec revision, not per release. |
|
|
358
|
-
| O2 | RESOLVED (pln#568) | **Phase-1.5 cutover signal for §6.1.** Resolved with a `journal_note` kind **`registry_genesis`** marker emitted by `runRegistryGenesisSupplement` after it backfills every pre-existing registry entity (`brainclaw migrate --enable-journal`). Observers detect it (`BoardObserver.registryAuthoritative()`, sticky + re-derived on cold start) and switch the registry counts from the MCP seed to the journal via `mergeCounts(..., registryAuthoritative)`. Chosen over a `meta.json` version bump because meta is a rebuildable cache (§2.3) — the marker is a durable journal record, the source of truth, and survives a meta rebuild. Open follow-up: when checkpoints start emitting (today `checkpoint_seq=0`), the checkpoint must encode the capability so a cold start past the marker's segment still re-derives authority. |
|
|
359
|
-
| O3 | LOW | **`bclaw_dispatch_status` enrichment scope.** §6 + §7 allow it "per visible terminal row" but the wording is ambiguous between "terminal-state row" (Recently terminal) and "row currently visible in the terminal UI" (every IN_PROGRESS row). Settle: probably the first (only failed/silent_death rows want the evidence digest) — but the contract must say so, otherwise an implementor renders an O(workers) burst on every refresh. |
|
|
360
|
-
| O4 | LOW | **Segment pad-width upgrade path.** §2 pins 8-digit decimal padding; the writer is 8-digit too (`src/core/events/journal.ts:214 SEGMENT_PAD=8`). At ~17k events historical, the 1e8 ceiling is decades out — but a future widen would require coordinated writer + every observer roll-out. Carry the migration recipe (pad-width in `meta.json`?) here so a future maintainer doesn't have to rediscover it. |
|
|
361
|
-
| O5 | LOW | **File watch semantics on Windows network mounts.** §4 falls back to a "long-interval stat" when the watcher is unavailable. VS Code's `FileSystemWatcher` on a junction-linked worktree (the brainclaw dispatch substrate) may fire on the link target's mtime but not the source-of-truth segment writes from another process; verify against the dispatch worktree machinery (`pln#498` junctions) before declaring the watch path universal. |
|
|
1
|
+
# Observer Protocol — language-agnostic read-only surfaces
|
|
2
|
+
|
|
3
|
+
Status: spec (pln#560 step 1). Pivot deliverable: it serves the VS Code
|
|
4
|
+
extension, the JetBrains plugin, and any future surface identically. Companion
|
|
5
|
+
to `event-log-store.md` (the journal this protocol consumes) and the VS Code
|
|
6
|
+
vision §5 (the UX it powers).
|
|
7
|
+
|
|
8
|
+
## 1. The one rule
|
|
9
|
+
|
|
10
|
+
**An observability surface is a pure consumer of the event journal. It never
|
|
11
|
+
acquires a store lock, never writes inside `.brainclaw/`, never runs a polling
|
|
12
|
+
timer against the MCP server for display.** It tails the append-only journal,
|
|
13
|
+
projects board state in memory, and refreshes the affected section when a new
|
|
14
|
+
record arrives. The MCP server is reserved for *actions* (accept / release /
|
|
15
|
+
dispatch / transition) through a separate, lazily-created client.
|
|
16
|
+
|
|
17
|
+
Why this exists (2026-06-10 calibration): the prior extension "read" the board
|
|
18
|
+
by calling a path that mutated and git-committed the entire store under the
|
|
19
|
+
lock (`autoAcknowledge → persistState`, held >5s), ran the agent-run reconciler
|
|
20
|
+
twice per poll with locked writes, created ~120 locked "unverified" event files
|
|
21
|
+
per hour per run, and impersonated the parent shell's agent identity —
|
|
22
|
+
consuming that agent's cursor. A dashboard is not an agent. This protocol makes
|
|
23
|
+
that class of bug unrepresentable: a conforming observer *cannot* write.
|
|
24
|
+
|
|
25
|
+
## 2. What the observer reads
|
|
26
|
+
|
|
27
|
+
The journal defined in `event-log-store.md`:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
.brainclaw/events/
|
|
31
|
+
meta.json # { next_seq, active_segment, entity_revs } — a cache
|
|
32
|
+
seg-<firstSeq>.jsonl # immutable once rolled; named by first seq it holds
|
|
33
|
+
seg-<firstSeq>.jsonl # active = lexicographically-last segment
|
|
34
|
+
checkpoints/ # NOT YET EMITTED by the writer (see §5) — directory
|
|
35
|
+
ckpt-<seq>.json # may be absent today; self-contained state manifest
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Segment naming is normative.** `firstSeq` is encoded as a **decimal,
|
|
39
|
+
zero-padded to 8 digits** (e.g. `seg-00018342.jsonl`) so directory-listing
|
|
40
|
+
lex-sort matches numeric seq order with no parsing. Implementations MUST emit
|
|
41
|
+
and accept exactly this format; any other padding (or none) breaks the
|
|
42
|
+
binary-search-by-filename in §5. The 8-digit field overflows at `seq > 1e8`;
|
|
43
|
+
at the historical write rate (~17k events to date) this is decades away, but
|
|
44
|
+
a journal that crosses the boundary requires a coordinated widen-and-pad
|
|
45
|
+
migration in writer + every observer (deferred until needed; flagged here so
|
|
46
|
+
no implementer assumes the pad width is incidental).
|
|
47
|
+
|
|
48
|
+
`meta.json` is advisory only (§4): the observer reads it to cheaply detect
|
|
49
|
+
"did anything change" but never trusts its `entity_revs` or `next_seq` for
|
|
50
|
+
correctness — those are derived from the journal tail itself.
|
|
51
|
+
|
|
52
|
+
Record envelope (v2), one JSON object per line:
|
|
53
|
+
|
|
54
|
+
```jsonc
|
|
55
|
+
{ "v": 2, "seq": 18342, "ts": "…", "writer": "w_…", "agent": "claude-code",
|
|
56
|
+
"action": "update", "item_type": "plan", "item_id": "pln_…",
|
|
57
|
+
"entity_rev": 7, "summary": "…", "payload": { /* full post-image */ } }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Action → class mapping (the observer needs the class, not a hardcoded verb
|
|
61
|
+
list — it is `ACTION_CLASS_BY_ACTION` in `event-log.ts`, and MUST be mirrored
|
|
62
|
+
in every implementation or fetched from a shared manifest):
|
|
63
|
+
|
|
64
|
+
| Class | Effect on the projection |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `entity-state` (`create`,`update`,`accept`,`reject`,`claim`,`release_claim`,`rollback`,`upgrade`,`backfill`) | upsert `payload` at `(item_type,item_id)` |
|
|
67
|
+
| `tombstone` (`delete`) | remove `(item_type,item_id)` |
|
|
68
|
+
| `journal-meta` (`checkpoint_ref`,`journal_note`,`seq_repair`,`federation_apply`) | ignore for state; `checkpoint_ref` is a bootstrap hint (§5) |
|
|
69
|
+
| `observability` (`session_start`,`session_end`,`assignment_offered`,`assignment_progress`,`run_progress`) | activity feed only — never a state upsert |
|
|
70
|
+
| `registry-lifecycle` (`assignment_*`,`run_*`) | upsert when `payload` present (phase 1.5+), else a status/activity signal |
|
|
71
|
+
|
|
72
|
+
The observer is **forward-compatible**: an unknown `action` whose class it
|
|
73
|
+
cannot resolve is applied as `entity-state` iff it carries a `payload` and an
|
|
74
|
+
`item_id`, else treated as an activity signal. Never crash on an unknown verb.
|
|
75
|
+
|
|
76
|
+
## 3. The cursor lives OUTSIDE the store
|
|
77
|
+
|
|
78
|
+
The observer's read position is a **seq watermark** persisted in *client*
|
|
79
|
+
storage, never in `.brainclaw/`:
|
|
80
|
+
|
|
81
|
+
- VS Code: `ExtensionContext.workspaceState`, key `bclaw.observer.cursor.<project_id>`.
|
|
82
|
+
- JetBrains: `PropertiesComponent` / project-scoped state, same key shape.
|
|
83
|
+
- Generic: any client-private kv keyed by `project_id`.
|
|
84
|
+
|
|
85
|
+
Shape: `{ seq: number, checkpoint_seq: number }`. `seq` = highest record seq
|
|
86
|
+
applied; `checkpoint_seq` = the checkpoint the in-memory projection was last
|
|
87
|
+
seeded from (for fast re-bootstrap). The store's own `.cursors/` directory is
|
|
88
|
+
the AGENTS' read position and is **off-limits** to observers — touching it is
|
|
89
|
+
the identity-leak bug this protocol forbids.
|
|
90
|
+
|
|
91
|
+
Rationale: a watermark survives segment rotation, compaction, and archival
|
|
92
|
+
(byte offsets do not). It is private to the surface, so N observers never
|
|
93
|
+
interfere with each other or with agents.
|
|
94
|
+
|
|
95
|
+
## 4. Change detection — a file watch, not a poll, not a lock
|
|
96
|
+
|
|
97
|
+
The observer watches the journal directory for growth and reacts:
|
|
98
|
+
|
|
99
|
+
1. Watch `.brainclaw/events/` (the active segment's size/mtime, and creation of
|
|
100
|
+
new `seg-*.jsonl`). VS Code: `FileSystemWatcher` on `events/seg-*.jsonl` +
|
|
101
|
+
`meta.json`. JetBrains: `VirtualFileListener` / NIO `WatchService`. Generic
|
|
102
|
+
fallback: stat the active segment on a *long* interval (≥10 s) — this is a
|
|
103
|
+
stat, not an MCP call, and acquires no lock.
|
|
104
|
+
2. On a growth signal, **tail forward** from `cursor.seq` (§5) and apply records
|
|
105
|
+
to the in-memory projection.
|
|
106
|
+
3. `meta.json` is advisory only; never trust it for correctness — the journal
|
|
107
|
+
tail is the truth (it may be a stale cache mid-write). Use it only to detect
|
|
108
|
+
"did anything change" cheaply.
|
|
109
|
+
|
|
110
|
+
There is no MCP server process for display. The watcher is OS-level; the read
|
|
111
|
+
is a file read. Under the 2026-06-10 load (3 workers + open surface) this yields
|
|
112
|
+
zero lock acquisitions by the surface — the validation gate (step 3).
|
|
113
|
+
|
|
114
|
+
## 5. Bootstrap and tail algorithm
|
|
115
|
+
|
|
116
|
+
**Status of checkpoint emission (2026-06-12, pln#543 step 4 landed):** the
|
|
117
|
+
writer does NOT yet produce `checkpoints/ckpt-*.json` files — checkpoint
|
|
118
|
+
emission ships with step 3/5 of pln#543. Until then, the empty-seed + full
|
|
119
|
+
tail path below is the **primary** cold-start path in production, not a
|
|
120
|
+
degenerate fallback. The checkpoint-first path is the spec the consumer must
|
|
121
|
+
implement for forward-compatibility; an observer that hard-requires a
|
|
122
|
+
checkpoint at activation is broken against today's store. The perf targets
|
|
123
|
+
in §10 assume checkpoint emission; until it ships, "activation → first
|
|
124
|
+
summary" is bounded by the full tail length instead (~10 MB of segments in
|
|
125
|
+
the typical case, sub-second in practice, but the budget no longer has
|
|
126
|
+
slack).
|
|
127
|
+
|
|
128
|
+
**Cold start (no cursor, or cursor below the oldest live segment):**
|
|
129
|
+
|
|
130
|
+
1. **If a verified checkpoint exists**, load the newest `ckpt-<S>.json` →
|
|
131
|
+
seed the in-memory projection (full post-image set at head `S`). Set
|
|
132
|
+
`cursor.checkpoint_seq = S`. (Today: this branch is dead until the
|
|
133
|
+
writer ships checkpoint emission.)
|
|
134
|
+
2. **Otherwise** (today's primary path), seed from the empty projection
|
|
135
|
+
with `cursor.checkpoint_seq = 0`.
|
|
136
|
+
3. Tail every record with `seq > checkpoint_seq` across segments in
|
|
137
|
+
(segment, file-line) order; apply by class (§2). With no checkpoint
|
|
138
|
+
this is a full replay from seq 1, bounded by retention (the
|
|
139
|
+
`events/archive/` floor — segments are park-don't-deleted past the
|
|
140
|
+
second-newest verified checkpoint; with no checkpoint, no segment is
|
|
141
|
+
ever eligible for archive, so "the journal" = "every segment ever
|
|
142
|
+
written" until checkpoint emission ships).
|
|
143
|
+
4. Set `cursor.seq` to the last applied seq. Render.
|
|
144
|
+
|
|
145
|
+
If the cursor's `seq` is **below the oldest non-archived segment's first
|
|
146
|
+
seq** (gap — segments archived past the watermark), discard the cursor and
|
|
147
|
+
cold-start: notifications degrade, state never does. Today no segment is
|
|
148
|
+
ever archived (gc requires a verified checkpoint floor, see §2.3 of the
|
|
149
|
+
store spec), so this branch is unreachable in production until checkpoint
|
|
150
|
+
emission ships — but the rule is normative regardless: an observer that
|
|
151
|
+
crashes on a gap is broken against any future store.
|
|
152
|
+
|
|
153
|
+
**Warm tail (cursor present, within live segments):**
|
|
154
|
+
|
|
155
|
+
1. Binary-search the segment whose name (`seg-<firstSeq>`) contains
|
|
156
|
+
`cursor.seq + 1` (filenames sort by first seq).
|
|
157
|
+
2. Stream forward from that point across segments; apply by class.
|
|
158
|
+
3. A **torn tail** (final line unparseable or missing trailing `\n`) is expected
|
|
159
|
+
crash residue mid-write by an agent — skip it; it reappears complete on the
|
|
160
|
+
next growth signal. Never block on it.
|
|
161
|
+
4. A mid-file unparseable line is logged and skipped (do not halt the tail).
|
|
162
|
+
5. Advance `cursor.seq` only over records actually applied.
|
|
163
|
+
|
|
164
|
+
Replay order is always (segment order, then file-line order) — never sorted by
|
|
165
|
+
seq (matches the store's own reducer; a dup `seq` from a lock-steal applies
|
|
166
|
+
later-line-wins, harmlessly, in a read-only projection).
|
|
167
|
+
|
|
168
|
+
## 6. Board projection — which records touch which section
|
|
169
|
+
|
|
170
|
+
The in-memory projection is `Map<item_type, Map<item_id, payload>>` plus a
|
|
171
|
+
bounded recent-activity ring (observability + registry signals, last N). The
|
|
172
|
+
board sections are derived; a record invalidates only the sections its
|
|
173
|
+
`item_type` feeds, and only those re-render (push-by-affected-section, §5.3):
|
|
174
|
+
|
|
175
|
+
| `item_type` | Invalidates sections |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `plan` | IN_PROGRESS, SPRINTS, BACKLOG, ATTENTION (badge), SYSTEM (counts) |
|
|
178
|
+
| `claim` | IN_PROGRESS, AGENTS (roster freshness) |
|
|
179
|
+
| `assignment` | IN_PROGRESS, ATTENTION (blocked/failed), "Recently terminal" |
|
|
180
|
+
| `agent_run` | IN_PROGRESS (worker rows), AGENTS, "Recently terminal" |
|
|
181
|
+
| `candidate` | ATTENTION (human-review), CANDIDATES |
|
|
182
|
+
| `action` | ATTENTION (the dominant attention input) |
|
|
183
|
+
| `constraint`/`decision`/`trap` | SYSTEM (counts), TRAPS |
|
|
184
|
+
| `handoff` | ACTIVITY, SYSTEM (counts) |
|
|
185
|
+
| `sequence` | SPRINTS |
|
|
186
|
+
| `session`/`*_progress` (observability) | ACTIVITY feed only — never a section state change |
|
|
187
|
+
|
|
188
|
+
`attention_required` is computed by the observer from the projection (actions +
|
|
189
|
+
human candidates + blocked/failed assignments + failed runs + evidence-
|
|
190
|
+
contradicted terminals), matching what the server-side composite returns — the
|
|
191
|
+
surface must not under-count by reading "actions only" (the pln#559 fix, now in
|
|
192
|
+
the projection rule).
|
|
193
|
+
|
|
194
|
+
### 6.1 Dual-mode coverage gap (CLOSED by pln#568 phase 1.5)
|
|
195
|
+
|
|
196
|
+
> **Status (pln#568):** the writer-side gap below is **closed**. The
|
|
197
|
+
> registry / coordination families (claim, assignment, agent_run,
|
|
198
|
+
> action_required [journaled under item_type `state`], candidate, sequence,
|
|
199
|
+
> and SHARED runtime_note) now emit full entity-state **post-images** on their
|
|
200
|
+
> persist chokepoint (`src/core/events/registry-post-image.ts`), and the
|
|
201
|
+
> observer materializer projects them (`board-projection.ts` ARRAY_SLOT).
|
|
202
|
+
>
|
|
203
|
+
> **Cutover signal (O2, resolved):** an observer switches a registry family
|
|
204
|
+
> from the MCP `board_summary` seed to the journal only once the journal
|
|
205
|
+
> carries the `journal_note` kind **`registry_genesis`** marker — emitted by
|
|
206
|
+
> `runRegistryGenesisSupplement` (run via `brainclaw migrate --enable-journal`)
|
|
207
|
+
> after it backfills every pre-existing registry entity. The marker is the
|
|
208
|
+
> safety gate: without a complete backfill a partially-journaled store would
|
|
209
|
+
> undercount the attention badge (trp#559). `BoardObserver.registryAuthoritative()`
|
|
210
|
+
> tracks the marker (sticky, re-derived on cold start by replaying from the
|
|
211
|
+
> checkpoint floor); `mergeCounts(journal, seed, journalActive, registryAuthoritative)`
|
|
212
|
+
> takes claims/assignments/runs/actions from the journal when it is set, and
|
|
213
|
+
> from the seed otherwise. `agents`/`sessions` are never journaled → always seed.
|
|
214
|
+
> A store that has NOT run the supplement keeps the seed (no regression).
|
|
215
|
+
>
|
|
216
|
+
> The historical (pre-pln#568) description below is kept for context.
|
|
217
|
+
|
|
218
|
+
The journal classifies records into five classes (§2). In phase 1 / `dual`
|
|
219
|
+
mode — what runs today after pln#543 step 4 — **registry-lifecycle records
|
|
220
|
+
are payload-OPTIONAL** (event-log-store.md §2.1.1, J4); the dual-write path
|
|
221
|
+
in `src/core/event-log.ts:152` forwards `assignment_*` and `run_*` events to
|
|
222
|
+
the journal with `item_id` only, no `payload`. The §2 rule "upsert when
|
|
223
|
+
payload present, else a status/activity signal" means today's journal carries
|
|
224
|
+
**no post-images for `assignment` or `agent_run`**: the in-memory projection
|
|
225
|
+
has zero rows for those item_types, and the materializer
|
|
226
|
+
(`src/core/events/materialize.ts`) only enumerates the 5 memory families
|
|
227
|
+
(constraint/decision/trap/handoff/plan).
|
|
228
|
+
|
|
229
|
+
Consequence for the §6 mapping table: until phase 1.5 ships, the rows that
|
|
230
|
+
the table claims `assignment` / `agent_run` / `claim` populate (IN_PROGRESS
|
|
231
|
+
worker rows, ATTENTION blocked/failed, Recently terminal under IN_PROGRESS)
|
|
232
|
+
cannot be drawn from the journal alone. A conforming observer in dual mode
|
|
233
|
+
MUST:
|
|
234
|
+
|
|
235
|
+
- Seed those sections at activation from a **single observer-flagged
|
|
236
|
+
`bclaw_context(kind: "board_summary")`** call (no timer, no poll) — that
|
|
237
|
+
read is lock-free under the §8 observer contract (validated against
|
|
238
|
+
`getDispatchStatus` and `loadAssignment`/`loadAgentRun`, which are pure
|
|
239
|
+
projection reads — `mcp-read-handlers.ts:1916`, `json-store.ts:47`); and
|
|
240
|
+
- Mark those sections "live-view degraded" in the tooltip until phase 1.5,
|
|
241
|
+
so the operator can tell journal-driven sections (memory entities,
|
|
242
|
+
attention badges) from MCP-seeded sections (workers, lifecycle).
|
|
243
|
+
|
|
244
|
+
Memory-entity sections (plan / constraint / decision / trap / handoff /
|
|
245
|
+
sequence / handoff-derived ACTIVITY) ARE journal-driven today via the
|
|
246
|
+
per-entity diff in `persistState` (`src/core/state.ts:400`) — the protocol
|
|
247
|
+
delivers its full value for them.
|
|
248
|
+
|
|
249
|
+
### 6.2 Section ID glossary
|
|
250
|
+
|
|
251
|
+
The §6 table uses display names; the canonical IDs in
|
|
252
|
+
`vscode-extension/src/board-tree.ts:321` are `attention | in-progress |
|
|
253
|
+
sprints | backlog | system | agents | candidates | activity | plans | claims
|
|
254
|
+
| assignments | runs | actions | handoffs | sprint | traps | cross-project`.
|
|
255
|
+
"Recently terminal" is **not** a top-level section — it is a sub-node
|
|
256
|
+
rendered under `in-progress`. The board also surfaces `cross-project`
|
|
257
|
+
(federation incoming signals) and `linked_projects`, neither of which has a
|
|
258
|
+
single journal `item_type` today: cross-project signals arrive via the
|
|
259
|
+
handoff/candidate streams (already covered), `linked_projects` is derived
|
|
260
|
+
from project config and is intentionally NOT a journal concern.
|
|
261
|
+
|
|
262
|
+
The projection is **state**, not administrative belief: a worker row's health
|
|
263
|
+
comes from evidence in the records (commits/fs signals carried on
|
|
264
|
+
registry-lifecycle payloads when present), not from a bare status field that the
|
|
265
|
+
2026-06-10 log proved lies. Where richer evidence requires it, the surface MAY
|
|
266
|
+
call `bclaw_dispatch_status` through the actions client (§7) — that is a
|
|
267
|
+
read-only MCP call, used sparingly (per visible terminal row), not a poll.
|
|
268
|
+
|
|
269
|
+
## 7. Actions go through a separate, lazy MCP client
|
|
270
|
+
|
|
271
|
+
Mutations (accept candidate, release claim, dispatch, transition, complete step)
|
|
272
|
+
are the *only* reason an observer talks to the MCP server. Rules:
|
|
273
|
+
|
|
274
|
+
- One lazily-created MCP client per project, spun up on first action, idle-timed
|
|
275
|
+
out after inactivity. Never created just to display.
|
|
276
|
+
- Distinct from any agent session: the client identifies as an **observer
|
|
277
|
+
principal** (see §8), so its calls never adopt an agent's claim/cursor.
|
|
278
|
+
- After an action, the observer does NOT optimistically mutate its projection;
|
|
279
|
+
it waits for the resulting journal record(s) to arrive via the tail (§5) and
|
|
280
|
+
re-projects. Single source of truth, no split-brain. (A short-lived "pending"
|
|
281
|
+
affordance on the clicked item is a UI concern, not projection state.)
|
|
282
|
+
- `bclaw_dispatch_status` and other read-only facades are permitted through this
|
|
283
|
+
client for on-demand evidence enrichment, but are never on a timer.
|
|
284
|
+
|
|
285
|
+
## 8. Observer identity (no impersonation, no side effects)
|
|
286
|
+
|
|
287
|
+
The surface declares itself an observer so the server suppresses every write a
|
|
288
|
+
read would otherwise trigger:
|
|
289
|
+
|
|
290
|
+
- Transport signal: `BRAINCLAW_OBSERVER=1` in the action client's env, and/or
|
|
291
|
+
MCP `clientInfo.name = "brainclaw-observer/<surface>"`.
|
|
292
|
+
- Server contract (already implemented, pln#558): observer reads do not
|
|
293
|
+
`autoAcknowledge`, do not run agent-run reconciliation, do not advance
|
|
294
|
+
`readUnseenEvents` cursors, do not implicit-heartbeat or auto-register an
|
|
295
|
+
identity. This protocol is the client half of that contract: even the read-
|
|
296
|
+
only facade calls in §7 carry the observer flag.
|
|
297
|
+
- The observer never presents an agent name as the actor of anything. Actions
|
|
298
|
+
the human triggers are attributed to the human operator principal, not to a
|
|
299
|
+
spawned agent.
|
|
300
|
+
|
|
301
|
+
## 9. Failure modes and degradation
|
|
302
|
+
|
|
303
|
+
| Condition | Behavior |
|
|
304
|
+
|---|---|
|
|
305
|
+
| `events/` absent (journal off / not migrated) | Fall back to a single MCP `board_summary` read at activation (no timer); show a "journal off — limited live view" hint. The surface still works, just not push-driven. |
|
|
306
|
+
| Cursor gap (archived past watermark) | Cold-start from newest checkpoint (§5); silent — state is correct, only missed-activity history is lost. |
|
|
307
|
+
| Checkpoint missing/corrupt | Fall back to the previous checkpoint, replay more segments (the two-checkpoint floor guarantees one exists); if none, seed empty + full tail. |
|
|
308
|
+
| Torn / unparseable line | Skip, keep tailing (§5). |
|
|
309
|
+
| Active segment shrinks / meta regresses | Trust the journal tail, re-derive; never write a "repair" (that is an agent/doctor job). |
|
|
310
|
+
| Watch unavailable (network FS, sandbox) | Degrade to a long-interval stat of the active segment; still zero locks. |
|
|
311
|
+
|
|
312
|
+
## 10. Performance budget (vision §5.3, restated as observer obligations)
|
|
313
|
+
|
|
314
|
+
| Operation | Target | Hard limit | How the protocol meets it |
|
|
315
|
+
|---|---|---|---|
|
|
316
|
+
| Activation → first summary | 500 ms | 2 s | seed from newest checkpoint, no full replay |
|
|
317
|
+
| Summary refresh | 300 ms | 1 s | apply only the new tail records |
|
|
318
|
+
| Section expand (warm) | 50 ms | 200 ms | projection is in memory; expand reads the map |
|
|
319
|
+
| Section expand (cold) | 500 ms | 2 s | first projection build from checkpoint+tail |
|
|
320
|
+
| Action round-trip | 500 ms | 2 s | lazy MCP client; result observed via tail |
|
|
321
|
+
|
|
322
|
+
Out of budget → surface in tooltip + a "performance degraded" status-bar
|
|
323
|
+
indicator (never escalate by calling a heavier path — that is the contention-
|
|
324
|
+
breeds-contention bug this protocol exists to kill).
|
|
325
|
+
|
|
326
|
+
## 11. Language-agnostic conformance checklist
|
|
327
|
+
|
|
328
|
+
A surface in any language conforms iff:
|
|
329
|
+
|
|
330
|
+
1. It reads only files under `.brainclaw/events/` (+ checkpoints) and writes
|
|
331
|
+
nothing under `.brainclaw/`.
|
|
332
|
+
2. Its cursor is a seq watermark in client-private storage, keyed by
|
|
333
|
+
`project_id`, never in the store's `.cursors/`.
|
|
334
|
+
3. It seeds from the newest verified checkpoint and tails by (segment, line)
|
|
335
|
+
order, applying records by action *class*, tolerant of unknown verbs and torn
|
|
336
|
+
tails.
|
|
337
|
+
4. Change detection is an OS file watch (or long-interval stat) — never an MCP
|
|
338
|
+
poll, never a lock.
|
|
339
|
+
5. Mutations go through a separate lazy MCP client flagged as an observer
|
|
340
|
+
principal; the projection updates only from the resulting journal records.
|
|
341
|
+
6. `attention_required` and worker health are computed from journal evidence,
|
|
342
|
+
not from administrative status fields alone.
|
|
343
|
+
|
|
344
|
+
Reference implementation: the VS Code extension (pln#560 step 2). The JetBrains
|
|
345
|
+
plugin (next plan) implements this same checklist in Kotlin — its existence is
|
|
346
|
+
the cross-language validation that this protocol, not the TypeScript code, is
|
|
347
|
+
the contract.
|
|
348
|
+
|
|
349
|
+
## 12. OPEN QUESTIONS
|
|
350
|
+
|
|
351
|
+
Carried from the 2026-06-12 symmetric review (pln#560 step 1, this branch).
|
|
352
|
+
Each is something the spec text cannot close on its own; one or more must
|
|
353
|
+
be answered before the JetBrains plugin (Kotlin) ships.
|
|
354
|
+
|
|
355
|
+
| # | Sev | Question |
|
|
356
|
+
|---|---|---|
|
|
357
|
+
| O1 | MED | **Shared `ACTION_CLASS_BY_ACTION` manifest.** §2 says implementations MUST mirror the table "or fetch it from a shared manifest." Today only the TS version exists (`src/core/events/journal.ts:66`); a Kotlin implementer would re-type 42 entries by hand and silently drift on the 43rd. Should this ship as a generated JSON next to `event-log-store.md` (single source of truth, both runtimes load it) or as part of a versioned schema bundle? Recommend the generated JSON — the table is small and changes per spec revision, not per release. |
|
|
358
|
+
| O2 | RESOLVED (pln#568) | **Phase-1.5 cutover signal for §6.1.** Resolved with a `journal_note` kind **`registry_genesis`** marker emitted by `runRegistryGenesisSupplement` after it backfills every pre-existing registry entity (`brainclaw migrate --enable-journal`). Observers detect it (`BoardObserver.registryAuthoritative()`, sticky + re-derived on cold start) and switch the registry counts from the MCP seed to the journal via `mergeCounts(..., registryAuthoritative)`. Chosen over a `meta.json` version bump because meta is a rebuildable cache (§2.3) — the marker is a durable journal record, the source of truth, and survives a meta rebuild. Open follow-up: when checkpoints start emitting (today `checkpoint_seq=0`), the checkpoint must encode the capability so a cold start past the marker's segment still re-derives authority. |
|
|
359
|
+
| O3 | LOW | **`bclaw_dispatch_status` enrichment scope.** §6 + §7 allow it "per visible terminal row" but the wording is ambiguous between "terminal-state row" (Recently terminal) and "row currently visible in the terminal UI" (every IN_PROGRESS row). Settle: probably the first (only failed/silent_death rows want the evidence digest) — but the contract must say so, otherwise an implementor renders an O(workers) burst on every refresh. |
|
|
360
|
+
| O4 | LOW | **Segment pad-width upgrade path.** §2 pins 8-digit decimal padding; the writer is 8-digit too (`src/core/events/journal.ts:214 SEGMENT_PAD=8`). At ~17k events historical, the 1e8 ceiling is decades out — but a future widen would require coordinated writer + every observer roll-out. Carry the migration recipe (pad-width in `meta.json`?) here so a future maintainer doesn't have to rediscover it. |
|
|
361
|
+
| O5 | LOW | **File watch semantics on Windows network mounts.** §4 falls back to a "long-interval stat" when the watcher is unavailable. VS Code's `FileSystemWatcher` on a junction-linked worktree (the brainclaw dispatch substrate) may fire on the link target's mtime but not the source-of-truth segment writes from another process; verify against the dispatch worktree machinery (`pln#498` junctions) before declaring the watch path universal. |
|