brainclaw 1.8.0 → 1.9.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/README.md +12 -11
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +138 -13
- package/dist/commands/add-step.js +1 -1
- package/dist/commands/bootstrap.js +2 -26
- package/dist/commands/check-security-mcp.js +50 -33
- package/dist/commands/check-security.js +86 -43
- package/dist/commands/claim.js +22 -21
- package/dist/commands/confirm.js +26 -0
- package/dist/commands/context-diff.js +1 -1
- package/dist/commands/dispatch-watch.js +142 -0
- package/dist/commands/doctor.js +113 -2
- package/dist/commands/estimation-report.js +115 -16
- package/dist/commands/harvest.js +285 -22
- package/dist/commands/init.js +123 -21
- package/dist/commands/loops-handlers.js +4 -0
- package/dist/commands/mcp-read-handlers.js +198 -29
- package/dist/commands/mcp.js +588 -92
- package/dist/commands/memory.js +21 -17
- package/dist/commands/migrate.js +81 -17
- package/dist/commands/prune.js +78 -4
- package/dist/commands/reflect.js +26 -20
- package/dist/commands/register-agent.js +57 -1
- package/dist/commands/repair.js +20 -0
- package/dist/commands/session-end.js +15 -6
- package/dist/commands/session-start.js +18 -1
- package/dist/commands/setup-security.js +39 -18
- package/dist/commands/setup.js +26 -27
- package/dist/commands/stale.js +16 -2
- package/dist/commands/uninstall.js +126 -34
- package/dist/commands/update-step.js +6 -0
- package/dist/commands/worktree.js +60 -0
- package/dist/core/actions.js +12 -3
- package/dist/core/agent-capability.js +11 -13
- package/dist/core/agent-files.js +844 -547
- package/dist/core/agent-integrations.js +0 -3
- package/dist/core/agent-inventory.js +67 -0
- package/dist/core/agent-registry.js +163 -29
- package/dist/core/agentrun-reconciler.js +33 -2
- package/dist/core/agentruns.js +7 -1
- package/dist/core/ai-agent-detection.js +31 -44
- package/dist/core/archival.js +15 -9
- package/dist/core/assignment-reconciler.js +56 -0
- package/dist/core/assignment-sweeper.js +127 -4
- package/dist/core/assignments.js +69 -11
- package/dist/core/bootstrap.js +233 -67
- package/dist/core/brainclaw-version.js +22 -0
- package/dist/core/candidates.js +21 -1
- package/dist/core/claims.js +313 -150
- package/dist/core/config.js +6 -1
- package/dist/core/context-diff.js +148 -20
- package/dist/core/context.js +129 -8
- package/dist/core/coordination.js +22 -3
- package/dist/core/dispatch-status.js +79 -5
- package/dist/core/dispatcher.js +64 -11
- package/dist/core/entity-operations.js +45 -24
- package/dist/core/entity-registry.js +31 -5
- package/dist/core/event-log.js +138 -21
- package/dist/core/events/checkpoint.js +258 -0
- package/dist/core/events/genesis.js +220 -0
- package/dist/core/events/journal.js +507 -0
- package/dist/core/events/materialize.js +126 -0
- package/dist/core/events/registry-post-image.js +110 -0
- package/dist/core/events/verify.js +109 -0
- package/dist/core/execution-adapters.js +23 -0
- package/dist/core/facade-schema.js +38 -0
- package/dist/core/gc-semantic.js +130 -5
- package/dist/core/handoff-snapshot.js +68 -0
- package/dist/core/ids.js +19 -8
- package/dist/core/instruction-templates.js +34 -115
- package/dist/core/io.js +39 -3
- package/dist/core/json-store.js +10 -1
- package/dist/core/lock.js +153 -28
- package/dist/core/loops/bootstrap-acquire.js +25 -1
- package/dist/core/loops/facade-schema.js +2 -0
- package/dist/core/loops/hooks/survey-signals-baseline.js +36 -0
- package/dist/core/loops/index.js +1 -0
- package/dist/core/loops/presets/bootstrap.js +7 -0
- package/dist/core/loops/store.js +17 -0
- package/dist/core/loops/verbs.js +24 -1
- package/dist/core/markdown.js +8 -76
- package/dist/core/mcp-command-resolution.js +245 -0
- package/dist/core/memory-compactor.js +5 -3
- package/dist/core/memory-lifecycle.js +282 -0
- package/dist/core/merge-risk.js +150 -0
- package/dist/core/messaging.js +8 -1
- package/dist/core/migration.js +11 -1
- package/dist/core/observer-mode.js +26 -0
- package/dist/core/operations/memory-mutation.js +90 -65
- package/dist/core/operations/plan.js +27 -1
- package/dist/core/protocol-skills.js +210 -0
- package/dist/core/reflection-safety.js +6 -7
- package/dist/core/reputation.js +84 -2
- package/dist/core/runtime-signals.js +71 -9
- package/dist/core/runtime.js +84 -1
- package/dist/core/schema.js +114 -0
- package/dist/core/security-detectors.js +125 -0
- package/dist/core/security-extract.js +189 -0
- package/dist/core/security-guard.js +107 -29
- package/dist/core/security-packages.js +121 -0
- package/dist/core/security-scoring.js +76 -9
- package/dist/core/security.js +34 -2
- package/dist/core/sequence.js +11 -2
- package/dist/core/setup-flow.js +141 -13
- package/dist/core/staleness.js +72 -1
- package/dist/core/state.js +250 -54
- package/dist/core/store-resolution.js +19 -5
- package/dist/core/worktree.js +72 -8
- package/dist/facts.js +8 -8
- package/dist/facts.json +7 -7
- package/docs/PROTOCOL.md +223 -0
- package/docs/cli.md +11 -10
- package/docs/concepts/coordinator-runbook.md +129 -0
- package/docs/concepts/event-log-store-critique-A.md +333 -0
- package/docs/concepts/event-log-store-critique-B.md +353 -0
- package/docs/concepts/event-log-store-phase0-measurements.md +58 -0
- package/docs/concepts/event-log-store-proposal-A.md +365 -0
- package/docs/concepts/event-log-store-proposal-B.md +404 -0
- package/docs/concepts/event-log-store.md +928 -0
- package/docs/concepts/identity-model-proposal.md +371 -0
- package/docs/concepts/memory.md +5 -4
- package/docs/concepts/observer-protocol.md +361 -0
- package/docs/concepts/parallel-merge-protocol.md +71 -0
- package/docs/concepts/plans-and-claims.md +43 -0
- package/docs/concepts/skills.md +78 -0
- package/docs/concepts/workspace-bootstrapping.md +61 -0
- package/docs/integrations/agents.md +4 -4
- package/docs/integrations/cline.md +10 -11
- package/docs/integrations/codex.md +2 -2
- package/docs/integrations/continue.md +5 -5
- package/docs/integrations/copilot.md +14 -12
- package/docs/integrations/openclaw.md +7 -6
- package/docs/integrations/overview.md +7 -7
- package/docs/integrations/roo.md +3 -3
- package/docs/integrations/windsurf.md +6 -6
- package/docs/mcp-schema-changelog.md +29 -2
- package/docs/quickstart.md +48 -47
- package/docs/security.md +174 -15
- package/docs/storage.md +4 -2
- package/package.json +8 -6
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Federated Identity Model — PROPOSAL
|
|
2
|
+
|
|
3
|
+
> Status: **PROPOSAL** for the architecture loop (Codex schema review + Juan
|
|
4
|
+
> product calls). Not a decision. Produced 2026-06-11 from a two-track
|
|
5
|
+
> identity analysis (local model + federation readiness); full reports held
|
|
6
|
+
> by the coordinator. Couples directly with `event-log-store.md` OPEN
|
|
7
|
+
> QUESTION **C4** (federation conflict primitive) — review them together.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
The federation ambition: several human users, each running one or more
|
|
12
|
+
agents, working on the same project from different machines. Today's
|
|
13
|
+
identity tuple `{agent_name, agent_id, host_id, user}` is locally minted,
|
|
14
|
+
unauthenticated, and name-keyed at every load-bearing point:
|
|
15
|
+
|
|
16
|
+
- Cloud inbox routed by **bare agent name** (`/api/v1/inbox/claude-code`)
|
|
17
|
+
— two humans' claude-codes are one identity to the bus.
|
|
18
|
+
- Event cursors, claim ownership comparisons, reputation, trust: keyed by
|
|
19
|
+
name or per-store registry doc; "Juan's claude-code" is **inexpressible**.
|
|
20
|
+
- `FederationMessage.from` is self-asserted under a single API key; routing
|
|
21
|
+
uses machine-local `project_path`.
|
|
22
|
+
- ed25519 keypairs are generated per agent and **never used** (private keys
|
|
23
|
+
currently under `~/.codex/brainclaw/keys`).
|
|
24
|
+
- 2,000+ store files carry the OS username; worktree paths are absolute.
|
|
25
|
+
- No write-authority model exists for same-project-multi-machine (the
|
|
26
|
+
signaling-vs-execution decision covered cross-*project* only). C4 cannot
|
|
27
|
+
be answered without one.
|
|
28
|
+
|
|
29
|
+
Local incidents (2026-06-10) showing the same root: VS Code extension
|
|
30
|
+
inherited agent env → impersonation + cursor consumption; directory
|
|
31
|
+
presence (`~/.config/opencode`) minted a **trusted** identity; three
|
|
32
|
+
simultaneous claude-code instances shared one agent_id (loop slot auth
|
|
33
|
+
distinguishes nothing); a copilot worker committed as the human.
|
|
34
|
+
|
|
35
|
+
## The model
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
principal prn_<id> a HUMAN (or service account), optionally under org_<id>
|
|
39
|
+
(org = the premium billing/management unit)
|
|
40
|
+
actor agt_<id> an AGENT INSTANCE = (principal, machine, agent software)
|
|
41
|
+
keeps today's agt_ ids; gains principal_id, origin_id,
|
|
42
|
+
public key
|
|
43
|
+
origin orig_<id> a STORE REPLICA on a machine — minted once at
|
|
44
|
+
init/materialize, stored next to project.identity.json.
|
|
45
|
+
NOT os.hostname() (hostnames collide; one machine can
|
|
46
|
+
hold several replicas). host_id stays as metadata.
|
|
47
|
+
display name "claude-code" demoted to a label. Never a key.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Write authority (answers C4's prerequisite)
|
|
51
|
+
|
|
52
|
+
- **Execution entities** (claims, sessions, runs, locks): origin-scoped,
|
|
53
|
+
**single-writer**. Other origins materialize them read-only, tagged with
|
|
54
|
+
their origin. Claims get eager push-on-write to the bus (piggybacked on
|
|
55
|
+
the existing claim mutation path — still no daemon) for advisory
|
|
56
|
+
cross-machine visibility; true atomic arbitration is deferred to the
|
|
57
|
+
cloud dispatcher (Durable Object, premium) and local-first never blocks
|
|
58
|
+
on it. **Eager-push messages MUST carry the same record identity as
|
|
59
|
+
journal slices** — `(origin_id, origin_epoch, seq, entity_rev)` plus the
|
|
60
|
+
full post-image. Then arrival order vs the periodic pull is irrelevant:
|
|
61
|
+
the receiver applies any copy through the same never-regress comparison
|
|
62
|
+
(apply iff incoming rev > materialized rev for that origin), and a push
|
|
63
|
+
that lands after the pull already delivered it is a no-op. A push lacking
|
|
64
|
+
this identity would reintroduce ordering bugs; it is not optional.
|
|
65
|
+
- **Memory entities** (decisions, traps, plans…): **highest-rev-wins with
|
|
66
|
+
origin tiebreak** — not wall-clock LWW; timestamps never order anything
|
|
67
|
+
(same rule as the event-log spec §2.2). On equal `entity_rev` from two
|
|
68
|
+
origins, the winner for the materialized view is deterministic
|
|
69
|
+
(lexicographic `origin_id`) and the loser is surfaced as a conflict
|
|
70
|
+
candidate, never silently dropped.
|
|
71
|
+
- **The scalar-rev hole, named and fixed**: a bare rev comparison cannot
|
|
72
|
+
distinguish *descends-from* from *concurrent-with*. Origin A edits
|
|
73
|
+
rev 5→6→7; origin B concurrently edits 5→6. A's 7 beats B's 6 and the
|
|
74
|
+
comparison looks like a clean fast-forward — B's edit is lost with **no
|
|
75
|
+
conflict surfaced**. Scalar `entity_rev` alone therefore cannot honor the
|
|
76
|
+
"conflicts surface as candidates" promise. The minimal fix (still no
|
|
77
|
+
vectors): exported memory-entity records carry **`base_rev`** — the rev
|
|
78
|
+
the write was based on. Receiver rule: incoming wins only if
|
|
79
|
+
`incoming.base_rev >= current.rev` (true fast-forward); otherwise the
|
|
80
|
+
write was concurrent → materialize the winner by the tiebreak above AND
|
|
81
|
+
emit a conflict candidate carrying both post-images. One extra integer
|
|
82
|
+
per exported record; local writes don't need it (single store, the lock
|
|
83
|
+
serializes). This is sufficient *because* payloads are full post-images
|
|
84
|
+
(event-log §2.1) — no merge, just detect-and-surface.
|
|
85
|
+
- **Restore-from-backup breaks `(origin_id, seq)` — origin epochs**: a
|
|
86
|
+
store restored from backup at seq 700 will re-issue seqs 701… with
|
|
87
|
+
different content under the same `origin_id`, while peers hold cursors
|
|
88
|
+
at, say, 1000 — the divergent range is silently ignored and the
|
|
89
|
+
addressing invariant (one `(origin_id, seq)` = one immutable record) is
|
|
90
|
+
violated. Fix: slice headers carry **`origin_epoch`** (integer, starts
|
|
91
|
+
at 0). Detection is cheap and reliable: the cloud keeps a per-origin
|
|
92
|
+
high-water mark; an origin reconnecting with `local next_seq <=
|
|
93
|
+
cloud watermark` has been restored → it bumps its epoch (recorded
|
|
94
|
+
locally as a `seq_repair`-class event) before pushing anything. Peers
|
|
95
|
+
treat a new epoch as a rebase: drop the cursor for that origin and
|
|
96
|
+
re-pull from the origin's latest checkpoint. Never-regress stays intact
|
|
97
|
+
*within* an epoch; epochs make the restore case explicit instead of
|
|
98
|
+
silent.
|
|
99
|
+
- **Consequence for C4**: with partitioned authority, **scalar
|
|
100
|
+
`entity_rev` + origin tag + `base_rev` on exported memory records**
|
|
101
|
+
suffices — no vector clocks. Federation transfers are
|
|
102
|
+
`(origin_id, origin_epoch, seq)`-headed segment slices.
|
|
103
|
+
|
|
104
|
+
### Authn — the minimal dumb-bus guarantee
|
|
105
|
+
|
|
106
|
+
Activate the dormant ed25519 keys. `brainclaw cloud register` (already
|
|
107
|
+
specced in the cli-cloud plan) binds *actor public key → principal* once,
|
|
108
|
+
under the principal's API token (one token per human). The cloud verifies
|
|
109
|
+
each message signature once and stamps `verified_principal`; receivers
|
|
110
|
+
trust the stamp and MUST reject unstamped messages on cloud transport. No
|
|
111
|
+
PKI web. Routing moves from `project_path` to `project_id` + channel.
|
|
112
|
+
(Local cross-project-link transport stays a separate, weaker trust domain:
|
|
113
|
+
same machine, same OS user — signatures optional there.)
|
|
114
|
+
|
|
115
|
+
#### FederationMessage v2 envelope & signature block (concrete)
|
|
116
|
+
|
|
117
|
+
```jsonc
|
|
118
|
+
{
|
|
119
|
+
"schema_version": 2,
|
|
120
|
+
"id": "msg_...",
|
|
121
|
+
"from": {
|
|
122
|
+
"principal_id": "prn_...",
|
|
123
|
+
"actor_id": "agt_...",
|
|
124
|
+
"origin_id": "orig_...",
|
|
125
|
+
"origin_epoch": 0,
|
|
126
|
+
"origin_msg_seq": 412, // per-origin monotonic counter (replay watermark)
|
|
127
|
+
"agent_name": "claude-code" // display only — never a key
|
|
128
|
+
},
|
|
129
|
+
"to": { "project_id": "proj_...", "channel": "signals", "actor_id": "agt_..."? },
|
|
130
|
+
"type": "signal|handoff|candidate|runtime_note|board_snapshot|claim_advisory|slice",
|
|
131
|
+
"payload_hash": "sha256:...", // hash of canonical payload bytes
|
|
132
|
+
"payload": { ... }, // inline iff <= 64 KB, else payload_ref (see slices)
|
|
133
|
+
"created_at": "...",
|
|
134
|
+
"causal_parent": "msg_..."?,
|
|
135
|
+
"idempotency_key": "...", // DEDUP ONLY — explicitly not a security control
|
|
136
|
+
"sig": {
|
|
137
|
+
"alg": "ed25519",
|
|
138
|
+
"key_fingerprint": "...", // selects among the actor's registered keys
|
|
139
|
+
"sig": "base64..."
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- **What is signed**: the ed25519 signature covers the **RFC 8785 (JCS)
|
|
145
|
+
canonical JSON** of the envelope with `sig` removed and `payload`
|
|
146
|
+
replaced by `payload_hash`. Today's `JSON.stringify` of a parsed object
|
|
147
|
+
is insertion-order-dependent and NOT a stable signing base across
|
|
148
|
+
writers/runtimes — canonicalization is required, not optional. Signing
|
|
149
|
+
`payload_hash` instead of inline payload means oversized payloads can
|
|
150
|
+
move out-of-band (blob transfer, below) without changing the signature
|
|
151
|
+
scheme.
|
|
152
|
+
- **Replay protection**: `idempotency_key` (content hash, today truncated
|
|
153
|
+
to 64 bits) is **dedup, not replay defense** — it only helps if the
|
|
154
|
+
receiver retains seen-keys forever, and it cannot distinguish a
|
|
155
|
+
legitimate redelivery from a malicious replay. The actual defense is the
|
|
156
|
+
**per-origin monotonic watermark**: every v2 message carries
|
|
157
|
+
`(origin_id, origin_epoch, origin_msg_seq)`; receivers keep a high-water
|
|
158
|
+
mark per origin and reject anything at-or-below it. This unifies signal
|
|
159
|
+
messages with slice cursor semantics — one mechanism, no retention
|
|
160
|
+
window to tune. `idempotency_key` survives for content-level dedup but
|
|
161
|
+
its computation must move to canonical JSON (same JCS bytes as signing).
|
|
162
|
+
- **Key rotation**: actor registration holds a **list** of keys with
|
|
163
|
+
`created_at` / `retired_at` validity windows, not a single key. Rotation
|
|
164
|
+
= registering a new key under the principal's API token (same act as
|
|
165
|
+
initial registration — no cross-signing chain needed because the token,
|
|
166
|
+
not the old key, is the root of the binding). The cloud verifies against
|
|
167
|
+
whichever registered key the `key_fingerprint` selects, checking the
|
|
168
|
+
validity window against `created_at`.
|
|
169
|
+
- **Revocation (the fired-teammate case)**: revocation is enforced at the
|
|
170
|
+
**single online verification point** — the org admin revokes the
|
|
171
|
+
principal (dashboard / `members` projection); the cloud immediately stops
|
|
172
|
+
stamping that principal's messages and rejects its API token. Because
|
|
173
|
+
receivers trust the stamp rather than verifying raw signatures
|
|
174
|
+
themselves, **revocation does not need to propagate to offline stores to
|
|
175
|
+
stop new writes** — an offline store cannot receive new federation
|
|
176
|
+
messages anyway. What offline stores DO hold: (a) previously
|
|
177
|
+
materialized data from the revoked principal — that is history, not
|
|
178
|
+
authority; it stays, attributed; (b) a stale `members` projection that
|
|
179
|
+
still lists the principal — bounded by re-pull on reconnect. The
|
|
180
|
+
residual product call: whether a store whose members projection exceeds
|
|
181
|
+
a max staleness age should degrade to read-only federation (deny new
|
|
182
|
+
grants by default) — see [JUAN] below.
|
|
183
|
+
|
|
184
|
+
### Authz
|
|
185
|
+
|
|
186
|
+
Trust moves from per-local-agent-doc to **per (principal, project)**
|
|
187
|
+
grants, distributed as a signed `members` projection (managed in the
|
|
188
|
+
Phase-2 dashboard, materialized locally like everything else). Local-only
|
|
189
|
+
operation is unchanged: principal defaults to an implicit `prn_local`.
|
|
190
|
+
|
|
191
|
+
### Slice transfer & event-log coupling
|
|
192
|
+
|
|
193
|
+
The event-log spec is written for ONE store-global seq space assigned
|
|
194
|
+
under the local lock. Foreign events cannot be appended into it (their
|
|
195
|
+
seqs are foreign); the spec's own §2.2 scope boundary anticipates exactly
|
|
196
|
+
this. The ingestion model, stated so the two documents stop hand-waving at
|
|
197
|
+
each other:
|
|
198
|
+
|
|
199
|
+
- **Export** is what the proposal already says: slices of the local
|
|
200
|
+
journal, header `{origin_id, origin_epoch, from_seq, to_seq,
|
|
201
|
+
slice_hash}` — zero local schema cost, origin appears only on export.
|
|
202
|
+
- **Import — per-origin sidecar journals, not seq renumbering.** Ingested
|
|
203
|
+
slices land in `events/foreign/<origin_id>/seg-*.jsonl`, preserving the
|
|
204
|
+
remote `(epoch, seq)` addressing (verifiable against `slice_hash`,
|
|
205
|
+
re-requestable). The receiver keeps **one cursor per foreign origin**
|
|
206
|
+
(`{origin_epoch, last_seq}`) — a new mechanism; the spec's `meta.json`
|
|
207
|
+
per-family watermarks don't cover it and must grow a `foreign_origins`
|
|
208
|
+
map. Renumbering foreign events into the local seq space was rejected:
|
|
209
|
+
it destroys the remote addressability that re-sync, audit, and
|
|
210
|
+
`slice_hash` verification depend on.
|
|
211
|
+
- **Materialization is a local journal event.** Applying a foreign batch
|
|
212
|
+
appends a local `federation_apply` event (local seq, payload = the
|
|
213
|
+
per-entity outcomes: applied / regressed-skip / conflict-candidate).
|
|
214
|
+
Local rebuild therefore replays to the same state WITHOUT consulting
|
|
215
|
+
foreign journals — checkpoints stay self-contained, and the spec's
|
|
216
|
+
rebuild/gc/verify story is untouched. Foreign sidecars are cache-class:
|
|
217
|
+
re-pullable, gc-able by their own retention, never part of local
|
|
218
|
+
checkpoint verification.
|
|
219
|
+
- **Remote gc floor**: the origin may have archived segments below a
|
|
220
|
+
requested cursor (spec §2.3 retention). The slice protocol therefore has
|
|
221
|
+
two response shapes, mirroring §2.5's `{gap: true}`: contiguous events,
|
|
222
|
+
OR a checkpoint-derived snapshot slice (full post-images at the origin's
|
|
223
|
+
latest checkpoint) from which the receiver re-bases its cursor.
|
|
224
|
+
Snapshot slices and the epoch-rebase path (above) are the same code.
|
|
225
|
+
- **Oversized payloads**: the phase-0 falsifier FIRED on handoffs (p50
|
|
226
|
+
~110 KB, 15-45× over the 64 KB record threshold). Whatever shape
|
|
227
|
+
`payload_ref` takes in event-log phase 1, federation inherits it:
|
|
228
|
+
slices reference blobs by sha256; receivers fetch
|
|
229
|
+
content-addressed blobs (`blobs/<sha256>`) out-of-band and verify the
|
|
230
|
+
hash. The envelope/signature design above already accommodates this
|
|
231
|
+
(signatures cover `payload_hash`, never inline bytes). Note the
|
|
232
|
+
composable alternative flagged in the measurements doc — a handoff
|
|
233
|
+
"diet" externalizing `snapshot.diff` — also shrinks the federation
|
|
234
|
+
privacy surface (the diff is the #1 secret-leak channel) and should be
|
|
235
|
+
preferred where possible.
|
|
236
|
+
- **Idempotency for slices**: the event-log spec says federation dedup
|
|
237
|
+
uses `(seq, writer)`. At the transfer layer this becomes
|
|
238
|
+
`(origin_id, origin_epoch, seq)` — the per-origin watermark — with
|
|
239
|
+
`slice_hash` as the integrity check. The content-hash `idempotency_key`
|
|
240
|
+
is for *signal* messages only; do not use it for slices.
|
|
241
|
+
|
|
242
|
+
### Privacy boundary
|
|
243
|
+
|
|
244
|
+
The federation export strips or rewrites — verified against
|
|
245
|
+
`src/core/schema.ts`, not just the obvious fields:
|
|
246
|
+
|
|
247
|
+
- `user` (OS username — Claim, CurrentSessionState) → dropped;
|
|
248
|
+
`principal_id` is the human attribution.
|
|
249
|
+
- **`author` / `author_id`** (present on every memory entity; in practice
|
|
250
|
+
the OS username or a human name, occasionally an email) → mapped to the
|
|
251
|
+
principal's display handle. The original strip list missed this — it is
|
|
252
|
+
the single most widespread PII field in the store (2,000+ files).
|
|
253
|
+
- **Reviewer/actor name fields**: `HandoffReview.requester/reviewer/
|
|
254
|
+
reviewed_by`, `Candidate.starred_by[]/resolved_by`,
|
|
255
|
+
`ActionRequiredResponse.responded_by`, `assignee`, `Sequence.owner` →
|
|
256
|
+
same principal-handle mapping.
|
|
257
|
+
- **`host_id`** — today raw `os.hostname()` (src/core/host.ts), and
|
|
258
|
+
hostnames routinely embed personal names ("juans-macbook"). Kept as
|
|
259
|
+
*metadata* locally, but on export it is hashed (or dropped; `origin_id`
|
|
260
|
+
already provides replica identity).
|
|
261
|
+
- Absolute paths: `worktree_path`, `related_paths` → repo-relative.
|
|
262
|
+
**Also**: `AgentRun.command` / `shell` (spawn command lines embed
|
|
263
|
+
absolute paths, usernames, and potentially tokens) → stripped on export,
|
|
264
|
+
not relativized — command lines are not reconstructable safely; and
|
|
265
|
+
`verify_cmd` (decisions/traps) → redaction-pattern pass.
|
|
266
|
+
- pid, session env internals (`CurrentSessionState.pid/user/
|
|
267
|
+
active_project.path`) → dropped.
|
|
268
|
+
- **Existing leak, fix independent of v2**: `FederationMessage.from.
|
|
269
|
+
project_path` / `to.project_path` are REQUIRED absolute paths in v1 and
|
|
270
|
+
are pushed to the cloud **today** whenever cloud_sync is enabled. v2
|
|
271
|
+
routing by `project_id` removes them; until then this is a live PII
|
|
272
|
+
exposure, not a future risk.
|
|
273
|
+
- **Narrative content (`text`, `narrative`, `snapshot.diff`,
|
|
274
|
+
`InboxMessage.payload`, `RuntimeEvent.metadata`) cannot be schema-
|
|
275
|
+
stripped** — free text can quote anything, including secrets and names.
|
|
276
|
+
This is policy + redaction territory only: the existing
|
|
277
|
+
`RedactionConfig` patterns, the explicit `visibility: 'shared'` opt-in
|
|
278
|
+
(already enforced for handoffs/candidates), and the J1 `doctor redact`
|
|
279
|
+
erasure path. The proposal claims no schema-level guarantee here, by
|
|
280
|
+
design.
|
|
281
|
+
|
|
282
|
+
Pairs with the J1 `doctor redact` decision.
|
|
283
|
+
|
|
284
|
+
## Identity acquisition (local prerequisite, pln#562)
|
|
285
|
+
|
|
286
|
+
Detection conflates three questions today; they split as follows:
|
|
287
|
+
|
|
288
|
+
| Question | Owner | Legitimate evidence |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| What is installed on this machine? | `agent-inventory` (single source) | disk presence — an **origin** attribute; doubles as the capability advertisement the cloud dispatcher needs |
|
|
291
|
+
| Who is calling? | `detectAiAgent` | process-scoped env markers ONLY — never directory presence; claimed identity sanity-checked *against* inventory |
|
|
292
|
+
| What trust? | explicit acts (human at setup, dispatcher at spawn), capped at contributor | never a read-path side effect |
|
|
293
|
+
|
|
294
|
+
## Migration (additive where verified; two steps are NOT)
|
|
295
|
+
|
|
296
|
+
Each claim re-checked against the actual zod schemas and call sites:
|
|
297
|
+
|
|
298
|
+
1. `AgentIdentityDocument` += optional `principal_id`, `origin_id` —
|
|
299
|
+
**verified additive** (schema.ts keeps `version: z.literal(1)`; new
|
|
300
|
+
fields zod-optional; old docs parse). Do NOT bump the version literal.
|
|
301
|
+
2. `origin_id` minted lazily at first federation use, stored next to
|
|
302
|
+
`project.identity.json`; the v2 event envelope stays agnostic (origin
|
|
303
|
+
added only to *exported* slices via segment headers — zero local
|
|
304
|
+
*export* cost). **Import is not zero-cost**: per-origin cursors +
|
|
305
|
+
foreign sidecars (see slice section) are new mechanisms, landing only
|
|
306
|
+
with federation itself.
|
|
307
|
+
3. Cursors and cloud inboxes re-keyed name → instance. **Two corrections**:
|
|
308
|
+
(a) local event cursors (`events/.cursors/<agent>.json`) are being
|
|
309
|
+
migrated to seq watermarks by event-log phase 1 anyway — do the re-key
|
|
310
|
+
*in that migration*, not as a separate rename done twice; (b) the cloud
|
|
311
|
+
inbox re-key (`/api/v1/inbox/<agent_name>` → instance-keyed) is a
|
|
312
|
+
**coordinated cloud API change, not a local rename** — an old client
|
|
313
|
+
pulling the name-keyed endpoint against a re-keyed cloud silently gets
|
|
314
|
+
an empty inbox (the pull error path returns `[]`). Needs an API
|
|
315
|
+
version window or dual-key serving.
|
|
316
|
+
4. Wire `isDuplicate` + a persisted `since` watermark for cloud pulls now
|
|
317
|
+
(pre-federation bugfix — **verified real**: session-start.ts pulls with
|
|
318
|
+
`{limit: 100}`, no `since`, no dedup; every session re-materializes the
|
|
319
|
+
same signals as new entities). Slightly more than "wire it":
|
|
320
|
+
`materializeFederationSignal` mints fresh local ids and **discards the
|
|
321
|
+
incoming `idempotency_key`**, so there is nothing on disk to dedup
|
|
322
|
+
against — the fix must (a) persist a seen-keys watermark file and
|
|
323
|
+
(b) stamp `provenance.kind: 'federation'` + `remote_id` on
|
|
324
|
+
materialized entities (the provenance schema already has the slot).
|
|
325
|
+
5. `FederationMessage` v2 — **the secretly-breaking step**.
|
|
326
|
+
`FederationMessageSchema` pins `schema_version: z.literal(1)` and
|
|
327
|
+
`deserializeMessage` hard-parses; a v1-only receiver given a v2 message
|
|
328
|
+
throws, and `pullSignals`' catch-skip **silently drops it forever**
|
|
329
|
+
(local-transport inboxes are written by the *sender* into the target
|
|
330
|
+
project). And v1's required `project_path` fields mean a v2 message
|
|
331
|
+
that omits them fails v1 parsing by absence too. Mitigation: ship the
|
|
332
|
+
v1|v2 **union reader first** (accept both, one release ahead), only
|
|
333
|
+
then switch writers to v2; cloud negotiates per-client version during
|
|
334
|
+
the window. `idempotency_key` survives as dedup-only, recomputed over
|
|
335
|
+
canonical JSON (see envelope section).
|
|
336
|
+
|
|
337
|
+
## Open questions for this review
|
|
338
|
+
|
|
339
|
+
Resolved by the 2026-06-10 schema review pass (claims now in the body
|
|
340
|
+
above): envelope/signature concretization (JCS canonical form, payload_hash
|
|
341
|
+
detachment, per-origin replay watermark, key rotation via token-rooted key
|
|
342
|
+
list); the scalar-rev concurrency hole (`base_rev` fix); restore-from-backup
|
|
343
|
+
(`origin_epoch`); eager-push ordering (envelope identity + never-regress);
|
|
344
|
+
slice ingestion vs the event-log seq space (per-origin sidecars +
|
|
345
|
+
`federation_apply`); remote-gc snapshot slices; the privacy strip-list gaps
|
|
346
|
+
(author/reviewer fields, raw-hostname host_id, AgentRun.command, the live
|
|
347
|
+
v1 project_path leak); the two non-additive migration steps (3b, 5).
|
|
348
|
+
|
|
349
|
+
Residual — genuinely needs a second model's eye:
|
|
350
|
+
|
|
351
|
+
- **[CODEX]** `members` projection wire format: exact signed structure
|
|
352
|
+
(who signs — the cloud as trust root, or the org-admin principal key?),
|
|
353
|
+
grant granularity (per-project role enum vs capability list), and the
|
|
354
|
+
max-staleness/deny-by-default semantics interaction with local-only
|
|
355
|
+
stores that NEVER reconnect. The revocation *enforcement point* is
|
|
356
|
+
settled (cloud stamp); the projection's own authenticity chain is not.
|
|
357
|
+
- **[CODEX]** `base_rev` adversarial check: can a malicious or buggy
|
|
358
|
+
origin *forge* fast-forwards (claim `base_rev = current.rev` to suppress
|
|
359
|
+
conflict surfacing)? Within-org actors are semi-trusted — decide whether
|
|
360
|
+
conflict suppression by a hostile insider is in scope, or accepted as
|
|
361
|
+
"history is signed and attributable, dispute via audit".
|
|
362
|
+
- **[CODEX]** `federation_apply` event schema vs event-log C1 review (it
|
|
363
|
+
joins the `checkpoint_ref`/`journal_note`/`seq_repair`/`backfill` set):
|
|
364
|
+
payload shape for per-entity outcomes, and whether a huge foreign batch
|
|
365
|
+
(10k entities) needs chunked apply events to respect the 256 KB record
|
|
366
|
+
cap and §2.9 chunked-lock rule.
|
|
367
|
+
- **[JUAN]** org model scope for v1 (implicit single-org? explicit org
|
|
368
|
+
onboarding?); one API token per human vs per machine; whether claim
|
|
369
|
+
eager-push is acceptable bus chatter on the free tier; members-projection
|
|
370
|
+
max-staleness → read-only-federation degradation (availability vs
|
|
371
|
+
security posture).
|
package/docs/concepts/memory.md
CHANGED
|
@@ -30,7 +30,7 @@ Shared project knowledge worth keeping.
|
|
|
30
30
|
|
|
31
31
|
Examples: constraints, decisions, traps, completed plans, handoffs.
|
|
32
32
|
|
|
33
|
-
Shared traps now have a lifecycle too: `active`, `resolved`, or `expired`. Active views such as generated context
|
|
33
|
+
Shared traps now have a lifecycle too: `active`, `resolved`, or `expired`. Active views such as generated context and status prioritize only active traps so old machine-setup issues stop polluting the current working set, while the canonical memory still keeps resolved traps for audit and search.
|
|
34
34
|
|
|
35
35
|
These live as individual JSON files under `.brainclaw/memory/` (constraints, decisions, traps, instructions) and `.brainclaw/coordination/` (plans, claims, handoffs). They are versioned in `.brainclaw/.git` and shared via Git.
|
|
36
36
|
|
|
@@ -59,9 +59,10 @@ brainclaw makes this context visible and versionable.
|
|
|
59
59
|
|
|
60
60
|
brainclaw keeps:
|
|
61
61
|
|
|
62
|
-
- canonical structured JSON as the source of truth (individual files under `.brainclaw/memory/` and `.brainclaw/coordination/`)
|
|
63
|
-
- a derived readable view (
|
|
64
|
-
-
|
|
62
|
+
- canonical structured JSON as the source of truth (individual files under `.brainclaw/memory/` and `.brainclaw/coordination/`)
|
|
63
|
+
- a legacy derived readable view (`.brainclaw/project.md`) regenerated best-effort from canonical state
|
|
64
|
+
- root `PROJECT.md` as the stable project vision when present
|
|
65
|
+
- native agent instruction files (`CLAUDE.md`, `.cursor/rules/brainclaw.md`, etc.) generated via `brainclaw export`
|
|
65
66
|
|
|
66
67
|
This balances machine reliability with human readability.
|
|
67
68
|
|