wendkeep 0.66.1 → 0.66.4
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/CHANGELOG.md +36 -0
- package/README.en.md +18 -5
- package/README.md +18 -5
- package/docs/en/commands/memory.md +46 -3
- package/docs/pt-BR/commands/memory.md +46 -2
- package/hooks/vault-health.mjs +6 -0
- package/package.json +1 -1
- package/packages/cli/src/index.mjs +1 -0
- package/packages/vault/src/memory-store.mjs +77 -5
- package/src/memory.mjs +763 -12
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ All notable changes to **wendkeep** are documented here. Format based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project follows
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.66.4] — 2026-07-30
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`memory repair` fecha o acknowledgement apenas para a outbox que a própria execução
|
|
12
|
+
consumiu integralmente.** O receipt do projetor preserva idempotência e impede que attempts
|
|
13
|
+
históricos, parciais, causalmente divergentes ou concorrentes sejam reclassificados.
|
|
14
|
+
- **`memory recover-attempt <session>` recupera com segurança o estado já projetado pela
|
|
15
|
+
0.66.3.** O dry-run é padrão e `--apply` altera somente o registry/checkpoint sob prova física,
|
|
16
|
+
lock e CAS; A→B→A, ledger/sidecars divergentes, outbox pendente e links inseguros falham sem
|
|
17
|
+
escrita. O diagnóstico agora orienta esse comando quando o acknowledgement ficou pendente.
|
|
18
|
+
|
|
19
|
+
## [0.66.3] — 2026-07-30
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **O replay reavalia candidates transitórios contra a fonte moderna final.** Um Stop da mesma
|
|
24
|
+
sessão/activation/epoch e turno maior agora avança depois que a correção causal já presente se
|
|
25
|
+
torna ativa; turno menor fica superseded e divergências reais continuam para curadoria.
|
|
26
|
+
- **`memory repair` migra o checkpoint antigo somente com prova e CAS.** O repair compara a
|
|
27
|
+
semântica anterior e a atual, faz backup, atualiza attempt e espelho e registra auditoria sem
|
|
28
|
+
reordenar, reescrever ou acrescentar evento ao ledger. Prova incompleta continua bloqueada. A
|
|
29
|
+
0.66.2 não deve ser publicada no npm; publique e instale a 0.66.3.
|
|
30
|
+
|
|
31
|
+
## [0.66.2] — 2026-07-29
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **Promoção preserva valor e identidade causal do evento escolhido.** `memory promote` não
|
|
36
|
+
converte mais objetos/arrays JSON em texto e mantém sessão, activation/epoch, turno de origem e
|
|
37
|
+
sequência; o próximo `SessionStop` da mesma cadeia avança o handoff sem recriar candidate.
|
|
38
|
+
- **A recuperação 0.66.1 cobre inversão física/temporal sem ampliar autoridade.** Quando uma
|
|
39
|
+
promoção legada projetada fica fora do candidate por `observed_at`, `memory promote` só a inclui
|
|
40
|
+
em `supersedes` sob prova de ancestralidade e mesma linhagem; fonte moderna ou alheia falha antes
|
|
41
|
+
de anexar. O E2E percorre o dispatcher público e prova Stop posterior e duplicado idempotentes.
|
|
42
|
+
|
|
7
43
|
## [0.66.1] — 2026-07-29
|
|
8
44
|
|
|
9
45
|
### Fixed
|
package/README.en.md
CHANGED
|
@@ -256,7 +256,7 @@ Hot memory now separates human authorship, operational state, and evidence:
|
|
|
256
256
|
- **`CORE.md` is canonical.** It is the short, hand-curated nucleus for durable preferences, active patterns, and open issues; no projector may infer or overwrite it.
|
|
257
257
|
- **`SHARED_MEMORY.md` is generated operational state.** The `Stop` hook turns the session handoff into sanitized events; the projector deterministically reduces the ledger and publishes a verifiable revision, cursor, and hash. Facts are `verified` only with local evidence; unsupported reports remain `reported`, and disagreements become candidates for human judgment.
|
|
258
258
|
- **`MEMORY_EVENTS.jsonl` is the append-only authority.** `Stop` makes events durable in the outbox before acknowledging the attempt; the projector runs outside the registry lock and retries reuse the same IDs. Repeating an identical `event_id`/payload is a no-op; reusing the ID with different bytes is observable corruption.
|
|
259
|
-
- **`MEMORY_CANDIDATES.jsonl` is the curation queue.** Conflicts and legacy content are never silently promoted. `promote` and `reject` record the decision as a new event.
|
|
259
|
+
- **`MEMORY_CANDIDATES.jsonl` is the curation queue.** Conflicts and legacy content are never silently promoted. `promote` and `reject` record the decision as a new event; promotion preserves the selected event's JSON type, session, activation/epoch, and source turn.
|
|
260
260
|
|
|
261
261
|
Artifacts stay under `.brain/` only. Sanitization strips secrets, tokens, local paths, transcripts, and harness payloads both before persistence and before injection. Events carry a `project_id`, and one vault never accepts another project's events.
|
|
262
262
|
|
|
@@ -287,16 +287,29 @@ lost publication, or mismatched checkpoint blocks. See [migration](docs/en/comma
|
|
|
287
287
|
and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
|
|
288
288
|
|
|
289
289
|
If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
|
|
290
|
-
the corrupt ledger, retain valid lines, and re-project. Repair
|
|
291
|
-
|
|
290
|
+
the corrupt ledger, retain valid lines, and re-project. Repair remains structural: its only narrow
|
|
291
|
+
acknowledgement exception covers attempts entirely represented by the outbox consumed by that same
|
|
292
|
+
run; it does not scan or reclassify historical attempts. Valid pre-0.59 causal checkpoints and
|
|
293
|
+
exactly re-derived assert-only historical prefixes are
|
|
292
294
|
CAS-migrated on both the attempt and `memory_checkpoint` to the correct physical boundary with
|
|
293
295
|
backup/audit; divergent mirrors fail closed. A demonstrably superseded
|
|
294
296
|
ambiguity uses `memory reconcile <session> --by-session <successor>
|
|
295
297
|
--reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
|
|
296
298
|
without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
|
|
297
299
|
explicit, durable curation: `memory promote <id> --event <event-id>` selects one event from the
|
|
298
|
-
candidate, while `memory reject <id>` keeps the current value.
|
|
299
|
-
|
|
300
|
+
candidate, while `memory reject <id>` keeps the current value. The decision is idempotent, and a
|
|
301
|
+
new promotion accepts a later Stop from the same session/activation without recreating a conflict.
|
|
302
|
+
A promotion written by 0.66.1 remains historical: if the next Stop forms a new candidate, update
|
|
303
|
+
to 0.66.3 and run `memory repair`. During replay, a transient candidate is re-evaluated against the
|
|
304
|
+
final modern source: the same session/activation/epoch and a higher turn advances; a lower turn is
|
|
305
|
+
superseded. Repair migrates the checkpoint and mirror only when it proves the exact previous
|
|
306
|
+
replay, attempt identity, and absence of a real conflict; it creates a backup and audit without
|
|
307
|
+
appending or rewriting events. Ambiguity stays queued for explicit curation. Do not publish or
|
|
308
|
+
install 0.66.2; use 0.66.3 or later. Decisions survive repair/replay;
|
|
309
|
+
`blocked_by_core` cannot override CORE. Doctor only diagnoses. When status/doctor reports projected
|
|
310
|
+
acknowledgement pending on 0.66.4 or later, first run the targeted dry run
|
|
311
|
+
`memory recover-attempt <session> --vault <vault>`, then authorize `--apply`; it changes only
|
|
312
|
+
registry/checkpoint. See syntax, preconditions, and fail-closed behavior in
|
|
300
313
|
[memory and curation](docs/en/commands/memory.md).
|
|
301
314
|
|
|
302
315
|
Session notes use one live `## Agentes, tokens e custos` snapshot. Main-agent and subagent hooks recompose it atomically, with costs, token dimensions, reasoning tokens and effort per model/source. Every hook that rewrites a session note takes a per-file lock and writes through a temp file + rename, so the `SubagentStop` fan-out (one hook run per subagent) can never leave a note half-written; a note whose frontmatter reads back damaged is left untouched rather than patched.
|
package/README.md
CHANGED
|
@@ -256,7 +256,7 @@ Hot memory now separates human authorship, operational state, and evidence:
|
|
|
256
256
|
- **`CORE.md` is canonical.** It is the short, hand-curated nucleus for durable preferences, active patterns, and open issues; no projector may infer or overwrite it.
|
|
257
257
|
- **`SHARED_MEMORY.md` is generated operational state.** The `Stop` hook turns the session handoff into sanitized events; the projector deterministically reduces the ledger and publishes a verifiable revision, cursor, and hash. Facts are `verified` only with local evidence; unsupported reports remain `reported`, and disagreements become candidates for human judgment.
|
|
258
258
|
- **`MEMORY_EVENTS.jsonl` is the append-only authority.** `Stop` makes events durable in the outbox before acknowledging the attempt; the projector runs outside the registry lock and retries reuse the same IDs. Repeating an identical `event_id`/payload is a no-op; reusing the ID with different bytes is observable corruption.
|
|
259
|
-
- **`MEMORY_CANDIDATES.jsonl` is the curation queue.** Conflicts and legacy content are never silently promoted. `promote` and `reject` record the decision as a new event.
|
|
259
|
+
- **`MEMORY_CANDIDATES.jsonl` is the curation queue.** Conflicts and legacy content are never silently promoted. `promote` and `reject` record the decision as a new event; promotion preserves the selected event's JSON type, session, activation/epoch, and source turn.
|
|
260
260
|
|
|
261
261
|
Artifacts stay under `.brain/` only. Sanitization strips secrets, tokens, local paths, transcripts, and harness payloads both before persistence and before injection. Events carry a `project_id`, and one vault never accepts another project's events.
|
|
262
262
|
|
|
@@ -287,16 +287,29 @@ lost publication, or mismatched checkpoint blocks. See [migration](docs/en/comma
|
|
|
287
287
|
and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
|
|
288
288
|
|
|
289
289
|
If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
|
|
290
|
-
the corrupt ledger, retain valid lines, and re-project. Repair
|
|
291
|
-
|
|
290
|
+
the corrupt ledger, retain valid lines, and re-project. Repair remains structural: its only narrow
|
|
291
|
+
acknowledgement exception covers attempts entirely represented by the outbox consumed by that same
|
|
292
|
+
run; it does not scan or reclassify historical attempts. Valid pre-0.59 causal checkpoints and
|
|
293
|
+
exactly re-derived assert-only historical prefixes are
|
|
292
294
|
CAS-migrated on both the attempt and `memory_checkpoint` to the correct physical boundary with
|
|
293
295
|
backup/audit; divergent mirrors fail closed. A demonstrably superseded
|
|
294
296
|
ambiguity uses `memory reconcile <session> --by-session <successor>
|
|
295
297
|
--reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
|
|
296
298
|
without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
|
|
297
299
|
explicit, durable curation: `memory promote <id> --event <event-id>` selects one event from the
|
|
298
|
-
candidate, while `memory reject <id>` keeps the current value.
|
|
299
|
-
|
|
300
|
+
candidate, while `memory reject <id>` keeps the current value. The decision is idempotent, and a
|
|
301
|
+
new promotion accepts a later Stop from the same session/activation without recreating a conflict.
|
|
302
|
+
A promotion written by 0.66.1 remains historical: if the next Stop forms a new candidate, update
|
|
303
|
+
to 0.66.3 and run `memory repair`. During replay, a transient candidate is re-evaluated against the
|
|
304
|
+
final modern source: the same session/activation/epoch and a higher turn advances; a lower turn is
|
|
305
|
+
superseded. Repair migrates the checkpoint and mirror only when it proves the exact previous
|
|
306
|
+
replay, attempt identity, and absence of a real conflict; it creates a backup and audit without
|
|
307
|
+
appending or rewriting events. Ambiguity stays queued for explicit curation. Do not publish or
|
|
308
|
+
install 0.66.2; use 0.66.3 or later. Decisions survive repair/replay;
|
|
309
|
+
`blocked_by_core` cannot override CORE. Doctor only diagnoses. When status/doctor reports projected
|
|
310
|
+
acknowledgement pending on 0.66.4 or later, first run the targeted dry run
|
|
311
|
+
`memory recover-attempt <session> --vault <vault>`, then authorize `--apply`; it changes only
|
|
312
|
+
registry/checkpoint. See syntax, preconditions, and fail-closed behavior in
|
|
300
313
|
[memory and curation](docs/en/commands/memory.md).
|
|
301
314
|
|
|
302
315
|
Session notes use one live `## Agentes, tokens e custos` snapshot. Main-agent and subagent hooks recompose it atomically, with costs, token dimensions, reasoning tokens and effort per model/source. Every hook that rewrites a session note takes a per-file lock and writes through a temp file + rename, so the `SubagentStop` fan-out (one hook run per subagent) can never leave a note half-written; a note whose frontmatter reads back damaged is left untouched rather than patched.
|
|
@@ -25,6 +25,7 @@ Pass the vault explicitly in automation. Preserve backups and evidence before re
|
|
|
25
25
|
```bash
|
|
26
26
|
npx wendkeep memory status [--gate] --vault <vault>
|
|
27
27
|
npx wendkeep memory repair --vault <vault>
|
|
28
|
+
npx wendkeep memory recover-attempt <session> [--apply] --vault <vault>
|
|
28
29
|
npx wendkeep memory reconcile <ambiguous-session> --by-session <successor-session> --reason <reason> [--apply] --vault <vault>
|
|
29
30
|
npx wendkeep memory promote <candidate> [--event <event-id>] --vault <vault>
|
|
30
31
|
npx wendkeep memory reject <candidate> --vault <vault>
|
|
@@ -51,8 +52,23 @@ npx wendkeep validate-memory --vault <v2-vault>
|
|
|
51
52
|
historical prefix only when revision, cursor, hash, identity, turns, and the
|
|
52
53
|
`memory_checkpoint` mirror exactly reproduce the old semantics; the target is the current replay
|
|
53
54
|
of that prefix, without absorbing later events. Both paths CAS-check the attempt and mirror and
|
|
54
|
-
record backup/audit.
|
|
55
|
-
|
|
55
|
+
record backup/audit. The only narrow acknowledgement exception covers `enqueued`/`degraded`
|
|
56
|
+
attempts whose outbox was frozen and whose event IDs that same repair run consumed in full;
|
|
57
|
+
partial coverage does not change the attempt. Repair does not scan or reclassify historical
|
|
58
|
+
attempts and does not accept a tuple, operation, or mirror that cannot be fully re-derived.
|
|
59
|
+
- As of 0.66.4, `memory recover-attempt` targets one session and is a dry run by default. The
|
|
60
|
+
session must exist in the registry and its latest attempt must be `v2`, `applied`, and `enqueued`
|
|
61
|
+
or `degraded`, with non-empty, unique `event_ids`. Every event must be present in the ledger and
|
|
62
|
+
belong to the attempt's project/session/activation/epoch/turn; no later event from that session
|
|
63
|
+
or target event still in the outbox may exist, and SHARED/candidates must byte-for-byte reproduce
|
|
64
|
+
the full ledger projection. An already `projected` attempt is accepted only with a valid
|
|
65
|
+
checkpoint and returns `unchanged`.
|
|
66
|
+
- With `--apply`, `memory recover-attempt` changes only `SESSION_REGISTRY`: it marks
|
|
67
|
+
`last_memory_attempt`/`memory_status` as `projected` and stores the same checkpoint in the
|
|
68
|
+
attempt and `memory_checkpoint`. Ledger, CORE, SHARED, candidates, outbox, and notes remain
|
|
69
|
+
byte-identical. The command validates all authority again under `MEMORY.lock`, CAS-checks the
|
|
70
|
+
attempt, activation, epoch, turn, and checkpoint, and fails closed if any byte/context changes.
|
|
71
|
+
A busy lock is not reaped; retry after application returns `unchanged` without writing.
|
|
56
72
|
- `memory reconcile` is a dry run by default. `--apply` requires two named sessions plus a reason,
|
|
57
73
|
CAS-checks the exact attempt, backs up the registry, and limits mutation to the ambiguous attempt
|
|
58
74
|
and its successor. Replay is CORE-aware, checkpoints use the physical ledger cursor, and the
|
|
@@ -70,14 +86,27 @@ npx wendkeep validate-memory --vault <v2-vault>
|
|
|
70
86
|
`blocked_by_core` candidate can only be rejected: promotion first requires canonical CORE
|
|
71
87
|
curation. If the selected event still belongs to the matching latest `projected` attempt,
|
|
72
88
|
promotion also refreshes its checkpoint and mirror causally; JSON reports
|
|
73
|
-
`checkpointRefreshed`, and a newer concurrent attempt remains untouched.
|
|
89
|
+
`checkpointRefreshed`, and a newer concurrent attempt remains untouched. The decision keeps
|
|
90
|
+
the already validated JSON value without string coercion and copies the selected event's
|
|
91
|
+
`canonical_session_id`, activation/epoch, `source_turn_id`, and `turn_sequence`. A later Stop
|
|
92
|
+
from the same session/activation therefore advances the value instead of opening another candidate.
|
|
93
|
+
During replay, a transient candidate is re-evaluated against the final modern source. The same
|
|
94
|
+
session/activation/epoch and a higher turn applies the Stop; a lower turn is superseded. A
|
|
95
|
+
different, incomplete, or ambiguous identity keeps the candidate queued for curation. `memory
|
|
96
|
+
repair` compares the old and current replay and migrates checkpoint+mirror only with exact
|
|
97
|
+
identity, backup, audit, and CAS; it does not reorder, rewrite, or append a ledger event.
|
|
74
98
|
- `validate-memory <CORE.md>` checks the 25-line cap, required sections, and secrets.
|
|
75
99
|
- `validate-memory --vault` requires a complete v2 bundle and is not the legacy-vault gate.
|
|
100
|
+
- For `recover-attempt`, exit `0` means a valid dry run/apply, including `unchanged`; exit `1`
|
|
101
|
+
means a precondition, authority, CAS, topology, or lock check failed; exit `2` means a missing
|
|
102
|
+
session/`--vault`, unknown or duplicate option, extra argument, or invalid value.
|
|
76
103
|
|
|
77
104
|
## Examples
|
|
78
105
|
|
|
79
106
|
```bash
|
|
80
107
|
npx wendkeep memory status --gate --vault .MyApp-vault
|
|
108
|
+
npx wendkeep memory recover-attempt session-123 --vault .MyApp-vault
|
|
109
|
+
npx wendkeep memory recover-attempt session-123 --apply --vault .MyApp-vault
|
|
81
110
|
npx wendkeep memory reconcile old --by-session current --reason "delivery continued" --vault .MyApp-vault
|
|
82
111
|
npx wendkeep memory reconcile old --by-session current --reason "delivery continued" --apply --vault .MyApp-vault
|
|
83
112
|
npx wendkeep validate-memory .MyApp-vault/.brain/CORE.md
|
|
@@ -99,6 +128,14 @@ of a global projection that has already advanced with concurrent events.
|
|
|
99
128
|
repair merely to manufacture the first event.
|
|
100
129
|
- `degraded` with every event ID present in either the ledger or an intact outbox is recoverable;
|
|
101
130
|
let idempotent replay finish. An event ID absent from both locations means lost publication.
|
|
131
|
+
- Status/doctor reports `projected acknowledgement pending` and suggests
|
|
132
|
+
`memory recover-attempt <session>`: preserve the artifacts, inspect the dry-run JSON first, and
|
|
133
|
+
use `--apply` only when `eligible: true`. `dry-run` confirms eligibility; `applied` updates
|
|
134
|
+
registry/checkpoint; `unchanged` means the recovery was already applied idempotently.
|
|
135
|
+
- `recover-attempt` rejects a missing/divergent event, a target still in the outbox, stale
|
|
136
|
+
SHARED/candidates, a historical attempt, mismatched session/causal context, invalid checkpoint,
|
|
137
|
+
or busy lock. Do not bypass the gate by editing files: rerun `memory status --gate`, preserve
|
|
138
|
+
evidence, and resolve the divergent authority.
|
|
102
139
|
- An `ambiguous` attempt, an `applied` attempt without event IDs, a `projected` event found only in
|
|
103
140
|
the outbox, or a mismatched checkpoint is blocking: preserve the artifacts and investigate
|
|
104
141
|
before repair. If the ambiguity is demonstrably superseded by a successor session, inspect the
|
|
@@ -108,6 +145,12 @@ of a global projection that has already advanced with concurrent events.
|
|
|
108
145
|
- `promote` reports that `--event` is required: inspect the candidate `event_ids`, compare their
|
|
109
146
|
provenance/value, and name the winner explicitly. An ID outside the candidate fails without
|
|
110
147
|
mutating the ledger or projections.
|
|
148
|
+
- A promotion made by 0.66.1 followed by a transient candidate: update to 0.66.3, preserve a backup,
|
|
149
|
+
and run `memory repair`. Do not publish or install 0.66.2. Repair migrates the checkpoint only
|
|
150
|
+
when the old replay, attempt identity, and new event match exactly; a real conflict stays blocked
|
|
151
|
+
for a human `promote`/`reject` choice.
|
|
152
|
+
- `promote` says that the candidate no longer matches the causal projection: no event was appended.
|
|
153
|
+
Run `memory status`, inspect the current candidate again, and do not force a different lineage.
|
|
111
154
|
- Missing `event_cursor` or mismatched v2 hash: preserve the bundle and assess `memory repair`.
|
|
112
155
|
- `validate-memory --vault` fails on legacy: validate CORE only or migrate first.
|
|
113
156
|
|
|
@@ -25,6 +25,7 @@ Informe o vault explicitamente em automações. Preserve backups e evidências a
|
|
|
25
25
|
```bash
|
|
26
26
|
npx wendkeep memory status [--gate] --vault <cofre>
|
|
27
27
|
npx wendkeep memory repair --vault <cofre>
|
|
28
|
+
npx wendkeep memory recover-attempt <sessão> [--apply] --vault <cofre>
|
|
28
29
|
npx wendkeep memory reconcile <sessão-ambígua> --by-session <sessão-sucessora> --reason <motivo> [--apply] --vault <cofre>
|
|
29
30
|
npx wendkeep memory promote <candidate> [--event <event-id>] --vault <cofre>
|
|
30
31
|
npx wendkeep memory reject <candidate> --vault <cofre>
|
|
@@ -51,8 +52,24 @@ npx wendkeep validate-memory --vault <cofre-v2>
|
|
|
51
52
|
assert-only somente quando revision, cursor, hash, identidade, turns e o espelho
|
|
52
53
|
`memory_checkpoint` reproduzem exatamente a semântica antiga; o alvo é o replay atual daquele
|
|
53
54
|
prefixo, sem absorver eventos posteriores. Ambos os casos fazem CAS do attempt e do espelho e
|
|
54
|
-
registram backup/auditoria.
|
|
55
|
-
|
|
55
|
+
registram backup/auditoria. A única exceção estreita de acknowledgement cobre attempts
|
|
56
|
+
`enqueued`/`degraded` cuja outbox foi congelada e cujos event IDs a mesma execução do repair
|
|
57
|
+
consumiu integralmente; cobertura parcial não altera o attempt. O repair não varre nem
|
|
58
|
+
reclassifica attempts históricos e não aceita tuple, operação ou espelho que não seja
|
|
59
|
+
rederivado integralmente.
|
|
60
|
+
- Desde a 0.66.4, `memory recover-attempt` é dirigido a uma única sessão e faz dry-run por padrão.
|
|
61
|
+
A sessão deve existir no registry e possuir o último attempt `v2`, `applied`, em `enqueued` ou
|
|
62
|
+
`degraded`, com `event_ids` não vazios e únicos. Todos os eventos devem estar integralmente no
|
|
63
|
+
ledger, pertencer ao mesmo projeto/sessão/activation/epoch/turn do attempt, não pode haver evento
|
|
64
|
+
posterior da mesma sessão nem evento alvo restante na outbox, e SHARED/candidates devem
|
|
65
|
+
reproduzir byte a byte a projeção integral do ledger. Um attempt já `projected` só é aceito com
|
|
66
|
+
checkpoint válido e retorna `unchanged`.
|
|
67
|
+
- Com `--apply`, `memory recover-attempt` altera somente `SESSION_REGISTRY`: marca
|
|
68
|
+
`last_memory_attempt`/`memory_status` como `projected` e grava o checkpoint idêntico no attempt e
|
|
69
|
+
em `memory_checkpoint`. Ledger, CORE, SHARED, candidates, outbox e notas permanecem byte-intactos.
|
|
70
|
+
O comando valida novamente toda a autoridade sob `MEMORY.lock`, faz CAS do attempt, activation,
|
|
71
|
+
epoch, turno e checkpoint e falha fechado se qualquer byte/contexto mudar. Lock ocupado não é
|
|
72
|
+
colhido; retry após aplicação retorna `unchanged` sem escrita.
|
|
56
73
|
- `memory reconcile` é dry-run por padrão. `--apply` exige duas sessões nomeadas e motivo, faz CAS
|
|
57
74
|
do attempt exato, salva backup do registry e limita a mutação ao attempt ambíguo e à sucessora.
|
|
58
75
|
O replay é CORE-aware, usa cursor físico do ledger no checkpoint e não reescreve ledger, CORE ou
|
|
@@ -68,13 +85,26 @@ npx wendkeep validate-memory --vault <cofre-v2>
|
|
|
68
85
|
promover exige antes alterar CORE pela curadoria canônica. Se o evento escolhido ainda pertence
|
|
69
86
|
ao último attempt `projected` correspondente, a promoção também atualiza causalmente checkpoint
|
|
70
87
|
e espelho; o JSON retorna `checkpointRefreshed`, e um attempt concorrente mais novo não é tocado.
|
|
88
|
+
A decisão conserva, sem coerção para string, o valor JSON já validado e copia do evento escolhido
|
|
89
|
+
`canonical_session_id`, activation/epoch, `source_turn_id` e `turn_sequence`. Por isso, um Stop
|
|
90
|
+
posterior da mesma sessão/activation avança o valor em vez de abrir outro candidate. Durante o
|
|
91
|
+
replay, um candidate transitório é reavaliado contra a fonte moderna final. Mesma
|
|
92
|
+
sessão/activation/epoch e turno maior aplica o Stop; turno menor fica superseded. Identidade
|
|
93
|
+
divergente, incompleta ou ambígua mantém o candidate para curadoria. `memory repair` compara o
|
|
94
|
+
replay anterior e o atual e só migra checkpoint+espelho com identidade exata, backup, audit e
|
|
95
|
+
CAS; ele não reordena, reescreve nem acrescenta evento ao ledger.
|
|
71
96
|
- `validate-memory <CORE.md>` valida cap de 25 linhas, seções e segredos.
|
|
72
97
|
- `validate-memory --vault` exige bundle v2 completo; não é o gate correto para vault legado.
|
|
98
|
+
- Para `recover-attempt`, exit `0` indica dry-run/apply válido, inclusive `unchanged`; exit `1`
|
|
99
|
+
indica falha de pré-condição, autoridade, CAS, topologia ou lock; exit `2` indica
|
|
100
|
+
sessão/`--vault` ausente, opção desconhecida/duplicada, argumento extra ou valor inválido.
|
|
73
101
|
|
|
74
102
|
## Exemplos
|
|
75
103
|
|
|
76
104
|
```bash
|
|
77
105
|
npx wendkeep memory status --gate --vault .MeuApp-vault
|
|
106
|
+
npx wendkeep memory recover-attempt sessao-123 --vault .MeuApp-vault
|
|
107
|
+
npx wendkeep memory recover-attempt sessao-123 --apply --vault .MeuApp-vault
|
|
78
108
|
npx wendkeep memory reconcile antiga --by-session atual --reason "entrega continuada" --vault .MeuApp-vault
|
|
79
109
|
npx wendkeep memory reconcile antiga --by-session atual --reason "entrega continuada" --apply --vault .MeuApp-vault
|
|
80
110
|
npx wendkeep validate-memory .MeuApp-vault/.brain/CORE.md
|
|
@@ -96,6 +126,14 @@ prefixo válido de uma projeção global que já avançou com eventos concorrent
|
|
|
96
126
|
fabricar o primeiro evento.
|
|
97
127
|
- `degraded` com todos os event IDs presentes no ledger ou na outbox íntegra é recuperável; deixe o
|
|
98
128
|
replay idempotente concluir. Event ID ausente nos dois lugares indica publicação perdida.
|
|
129
|
+
- Status/doctor informa `acknowledgement projetado pendente` e sugere
|
|
130
|
+
`memory recover-attempt <sessão>`: preserve os artefatos, revise primeiro o JSON do dry-run e só
|
|
131
|
+
use `--apply` se `eligible: true`. `dry-run` confirma elegibilidade; `applied` atualiza
|
|
132
|
+
registry/checkpoint; `unchanged` indica que a recuperação já foi aplicada de forma idempotente.
|
|
133
|
+
- `recover-attempt` recusa evento ausente/divergente, outbox alvo ainda presente, SHARED/candidates
|
|
134
|
+
stale, attempt histórico, sessão/contexto causal divergente, checkpoint inválido ou lock ocupado.
|
|
135
|
+
Não tente contornar o gate com edição manual: rode novamente `memory status --gate`, preserve a
|
|
136
|
+
evidência e resolva a autoridade divergente.
|
|
99
137
|
- Attempt `ambiguous`, attempt `applied` sem event IDs, evento `projected` apenas na outbox ou
|
|
100
138
|
checkpoint divergente são bloqueantes: preserve os artefatos e investigue antes de repair. Se a
|
|
101
139
|
ambiguidade for comprovadamente substituída por uma sessão sucessora, revise o dry-run de
|
|
@@ -104,6 +142,12 @@ prefixo válido de uma projeção global que já avançou com eventos concorrent
|
|
|
104
142
|
- `promote` informa que `--event` é obrigatório: leia os `event_ids` do candidate, compare a
|
|
105
143
|
proveniência/valor e indique explicitamente o vencedor. ID que não pertence ao candidate falha
|
|
106
144
|
sem mutar ledger ou projeções.
|
|
145
|
+
- Promoção feita pela 0.66.1 seguida de candidate transitório: atualize para a 0.66.3, preserve um
|
|
146
|
+
backup e rode `memory repair`. Não publique nem instale a 0.66.2. O repair só migra o checkpoint
|
|
147
|
+
quando o replay anterior, a identidade do attempt e o evento novo coincidem exatamente; conflito
|
|
148
|
+
real continua bloqueado para escolha humana com `promote`/`reject`.
|
|
149
|
+
- `promote` informa que o candidate não corresponde mais à projeção causal: nenhum evento foi
|
|
150
|
+
anexado. Rode `memory status`, releia o candidate atual e não force uma linhagem diferente.
|
|
107
151
|
- `event_cursor` ausente ou hash divergente em v2: preserve o bundle e avalie `memory repair`.
|
|
108
152
|
- `validate-memory --vault` falha no legado: valide apenas CORE ou migre primeiro.
|
|
109
153
|
|
package/hooks/vault-health.mjs
CHANGED
|
@@ -338,6 +338,12 @@ function checkMemoryAttempts(registry, {
|
|
|
338
338
|
const missing = eventIds.filter((eventId) => !ledgerEventIds.has(eventId) && !outboxEventIds.has(eventId));
|
|
339
339
|
if (missing.length) {
|
|
340
340
|
failures.push(`Attempt v2 perdeu ${missing.length} evento(s): ausentes do ledger e da outbox. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
|
|
341
|
+
} else if (
|
|
342
|
+
state === 'enqueued'
|
|
343
|
+
&& eventIds.every((eventId) => ledgerEventIds.has(eventId))
|
|
344
|
+
&& eventIds.every((eventId) => !outboxEventIds.has(eventId))
|
|
345
|
+
) {
|
|
346
|
+
warnings.push(`Attempt de memória v2 possui acknowledgement projetado pendente. Recupere com: wendkeep memory recover-attempt ${sessionId}.`);
|
|
341
347
|
} else {
|
|
342
348
|
warnings.push(`Attempt de memória v2 ${state} permanece recuperável: ${eventIds.length} evento(s) durável(is) no ledger e/ou outbox.`);
|
|
343
349
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.66.
|
|
3
|
+
"version": "0.66.4",
|
|
4
4
|
"description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -100,6 +100,7 @@ Usage:
|
|
|
100
100
|
behind the closing block. Dry-run by default · --apply · --json.
|
|
101
101
|
wendkeep lesson add "t" "l" Record a project-local lesson (injected at SessionStart).
|
|
102
102
|
wendkeep memory <sub> Shared memory v2: status | migrate [--apply] | repair |
|
|
103
|
+
recover-attempt <session> [--apply] |
|
|
103
104
|
reconcile <session> --by-session <session> --reason <text> [--apply] |
|
|
104
105
|
promote <candidate> [--event <event-id>] | reject <candidate>. --vault P.
|
|
105
106
|
Reconcile is dry-run by default; the original attempt remains audited.
|
|
@@ -313,6 +313,8 @@ export function readMemoryLedger(vaultBase) {
|
|
|
313
313
|
if (eventIds.has(parsed.event_id)) {
|
|
314
314
|
if (eventPayloads.get(parsed.event_id) !== payload) {
|
|
315
315
|
errors.push(ledgerError(lineNumber, `event_id collision: ${parsed.event_id}`, partial));
|
|
316
|
+
} else {
|
|
317
|
+
errors.push(ledgerError(lineNumber, `duplicate event_id: ${parsed.event_id}`, partial));
|
|
316
318
|
}
|
|
317
319
|
return;
|
|
318
320
|
}
|
|
@@ -344,6 +346,22 @@ function sameCausalActivation(left, right) {
|
|
|
344
346
|
&& left.activation_id === right?.activation_id;
|
|
345
347
|
}
|
|
346
348
|
|
|
349
|
+
function hasCompleteCausalIdentity(event) {
|
|
350
|
+
return Boolean(event?.canonical_session_id)
|
|
351
|
+
&& Boolean(event?.activation_id)
|
|
352
|
+
&& Boolean(event?.source_turn_id)
|
|
353
|
+
&& Number.isInteger(event?.activation_epoch)
|
|
354
|
+
&& Number.isInteger(event?.turn_sequence);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function sameCompleteCausalLineage(left, right) {
|
|
358
|
+
return hasCompleteCausalIdentity(left)
|
|
359
|
+
&& hasCompleteCausalIdentity(right)
|
|
360
|
+
&& left.canonical_session_id === right.canonical_session_id
|
|
361
|
+
&& left.activation_id === right.activation_id
|
|
362
|
+
&& left.activation_epoch === right.activation_epoch;
|
|
363
|
+
}
|
|
364
|
+
|
|
347
365
|
function comparable(left, right) {
|
|
348
366
|
if (sameCausalActivation(left, right)) return true;
|
|
349
367
|
const leftSupersedes = left.supersedes_event_id || left.supersedes;
|
|
@@ -447,7 +465,9 @@ function isCausallyOlder(event, current) {
|
|
|
447
465
|
* Pure deterministic reducer. It pre-detects incomparable scalar siblings so replay order
|
|
448
466
|
* never turns one concurrent writer into an accidental winner.
|
|
449
467
|
*/
|
|
450
|
-
export function reduceMemoryEvents(inputEvents = [], {
|
|
468
|
+
export function reduceMemoryEvents(inputEvents = [], {
|
|
469
|
+
coreInvariants = new Map(), resolveDeferredAsserts = true,
|
|
470
|
+
} = {}) {
|
|
451
471
|
const protectedValues = coreInvariants instanceof Map
|
|
452
472
|
? coreInvariants
|
|
453
473
|
: new Map(Object.entries(coreInvariants || {}));
|
|
@@ -495,6 +515,8 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
|
|
|
495
515
|
const records = new Map();
|
|
496
516
|
const tombstones = new Map();
|
|
497
517
|
const candidates = [];
|
|
518
|
+
const pendingAssertConflicts = [];
|
|
519
|
+
const resolvedCandidateIds = new Set();
|
|
498
520
|
const emittedGroups = new Set();
|
|
499
521
|
const appliedEventIds = [];
|
|
500
522
|
const superseded = [];
|
|
@@ -543,7 +565,9 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
|
|
|
543
565
|
appliedEventIds.push(item.event_id);
|
|
544
566
|
continue;
|
|
545
567
|
}
|
|
546
|
-
|
|
568
|
+
const candidate = conflictCandidate(item.memory_key, [currentEventFromRecord(current), item]);
|
|
569
|
+
candidates.push(candidate);
|
|
570
|
+
pendingAssertConflicts.push({ candidate, event: item });
|
|
547
571
|
continue;
|
|
548
572
|
}
|
|
549
573
|
if (!current) {
|
|
@@ -616,6 +640,46 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
|
|
|
616
640
|
appliedEventIds.push(item.event_id);
|
|
617
641
|
}
|
|
618
642
|
|
|
643
|
+
if (resolveDeferredAsserts) {
|
|
644
|
+
// A physically late assert can sort before a corrective promotion because effective time
|
|
645
|
+
// precedes CLI decision time. Revisit only scalar assert conflicts left without an explicit
|
|
646
|
+
// decision, against the final complete causal source; the ledger and global ordering stay put.
|
|
647
|
+
const deferredAsserts = pendingAssertConflicts
|
|
648
|
+
.filter((pending) => !candidateDecisions.has(pending.candidate.candidate_id))
|
|
649
|
+
.sort((left, right) => String(left.event.memory_key).localeCompare(String(right.event.memory_key))
|
|
650
|
+
|| String(left.event.canonical_session_id || '').localeCompare(String(right.event.canonical_session_id || ''))
|
|
651
|
+
|| String(left.event.activation_id || '').localeCompare(String(right.event.activation_id || ''))
|
|
652
|
+
|| Number(left.event.activation_epoch ?? -1) - Number(right.event.activation_epoch ?? -1)
|
|
653
|
+
|| Number(left.event.turn_sequence ?? -1) - Number(right.event.turn_sequence ?? -1)
|
|
654
|
+
|| eventOrder(left.event, right.event));
|
|
655
|
+
let advanced = true;
|
|
656
|
+
while (advanced) {
|
|
657
|
+
advanced = false;
|
|
658
|
+
for (const pending of deferredAsserts) {
|
|
659
|
+
if (resolvedCandidateIds.has(pending.candidate.candidate_id)) continue;
|
|
660
|
+
const current = records.get(pending.event.memory_key);
|
|
661
|
+
const currentSource = current?.source;
|
|
662
|
+
if (!sameCompleteCausalLineage(pending.event, currentSource)) continue;
|
|
663
|
+
if (pending.event.turn_sequence > currentSource.turn_sequence) {
|
|
664
|
+
records.set(pending.event.memory_key, {
|
|
665
|
+
value: pending.event.value,
|
|
666
|
+
revision: current.revision + 1,
|
|
667
|
+
source: pending.event,
|
|
668
|
+
});
|
|
669
|
+
tombstones.delete(pending.event.memory_key);
|
|
670
|
+
superseded.push({ event_id: currentSource.event_id, by_event_id: pending.event.event_id });
|
|
671
|
+
revision += 1;
|
|
672
|
+
appliedEventIds.push(pending.event.event_id);
|
|
673
|
+
resolvedCandidateIds.add(pending.candidate.candidate_id);
|
|
674
|
+
advanced = true;
|
|
675
|
+
} else if (pending.event.turn_sequence < currentSource.turn_sequence) {
|
|
676
|
+
superseded.push({ event_id: pending.event.event_id, by_event_id: currentSource.event_id });
|
|
677
|
+
resolvedCandidateIds.add(pending.candidate.candidate_id);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
619
683
|
const stateEntries = [...records].map(([key, record]) => [key, record.value]);
|
|
620
684
|
const recordEntries = [...records].map(([key, record]) => [key, record]);
|
|
621
685
|
const tombstoneEntries = [...tombstones];
|
|
@@ -623,7 +687,8 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
|
|
|
623
687
|
const recordObject = sortedObject(recordEntries);
|
|
624
688
|
const tombstoneObject = sortedObject(tombstoneEntries);
|
|
625
689
|
const unresolvedCandidates = candidates
|
|
626
|
-
.filter((item) => !candidateDecisions.has(item.candidate_id)
|
|
690
|
+
.filter((item) => !candidateDecisions.has(item.candidate_id)
|
|
691
|
+
&& !resolvedCandidateIds.has(item.candidate_id));
|
|
627
692
|
unresolvedCandidates.sort((left, right) => left.candidate_id.localeCompare(right.candidate_id));
|
|
628
693
|
superseded.sort((left, right) => left.event_id.localeCompare(right.event_id));
|
|
629
694
|
const activeEvents = Object.entries(recordObject).map(([memoryKey, record]) => ({
|
|
@@ -655,8 +720,12 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
|
|
|
655
720
|
* `eventCursor` is the reducer's deterministic causal cursor; `ledgerCursor` is the
|
|
656
721
|
* physical prefix boundary used by durable checkpoints.
|
|
657
722
|
*/
|
|
658
|
-
export function deriveMemoryProjection(vaultBase, inputEvents = []
|
|
659
|
-
|
|
723
|
+
export function deriveMemoryProjection(vaultBase, inputEvents = [], {
|
|
724
|
+
resolveDeferredAsserts = true,
|
|
725
|
+
} = {}) {
|
|
726
|
+
const reduced = reduceMemoryEvents(inputEvents, {
|
|
727
|
+
coreInvariants: readCoreInvariants(vaultBase), resolveDeferredAsserts,
|
|
728
|
+
});
|
|
660
729
|
const ledgerCursor = inputEvents.at(-1)?.event_id || 'none';
|
|
661
730
|
const checkpoint = {
|
|
662
731
|
revision: reduced.revision,
|
|
@@ -844,15 +913,18 @@ function projectLocked(vaultBase, { faultAt } = {}) {
|
|
|
844
913
|
const projection = publishMemoryProjection(vaultBase, prepared);
|
|
845
914
|
injectFault(faultAt, 'after-projection');
|
|
846
915
|
|
|
916
|
+
const consumedEventIds = [];
|
|
847
917
|
for (const entry of outbox) {
|
|
848
918
|
unlinkVaultFile(vaultBase, entry.path, {
|
|
849
919
|
missingOk: false, label: 'evento consumido do outbox de memória',
|
|
850
920
|
});
|
|
921
|
+
consumedEventIds.push(entry.event.event_id);
|
|
851
922
|
}
|
|
852
923
|
return {
|
|
853
924
|
status: 'projected',
|
|
854
925
|
appended: newEvents.length,
|
|
855
926
|
consumed: outbox.length,
|
|
927
|
+
consumedEventIds,
|
|
856
928
|
pending: 0,
|
|
857
929
|
...projection,
|
|
858
930
|
};
|