dsh-prime-memory 0.14.1 → 0.16.1-beta.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/CHANGELOG.en.md +6 -0
- package/CHANGELOG.ja.md +6 -0
- package/CHANGELOG.ko.md +6 -0
- package/CHANGELOG.md +48 -0
- package/dist/client.js +932 -432
- package/dist/cognitive-hall.d.ts +18 -0
- package/dist/cognitive-hall.js +43 -0
- package/dist/config.d.ts +49 -1
- package/dist/config.js +30 -2
- package/dist/conflict-service.d.ts +1 -1
- package/dist/conflict-service.js +100 -16
- package/dist/contract.d.ts +144 -4
- package/dist/domain-gate.d.ts +49 -0
- package/dist/domain-gate.js +151 -0
- package/dist/hall-backfill.d.ts +33 -0
- package/dist/hall-backfill.js +95 -0
- package/dist/hall-migrate.d.ts +32 -0
- package/dist/hall-migrate.js +88 -0
- package/dist/hooks/recall.js +92 -5
- package/dist/hooks/slot-recall.d.ts +20 -0
- package/dist/hooks/slot-recall.js +44 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +37 -0
- package/dist/metadata-validators.d.ts +20 -0
- package/dist/metadata-validators.js +58 -0
- package/dist/pipeline/anchors.d.ts +1 -1
- package/dist/pipeline/anchors.js +1 -1
- package/dist/pipeline/l1.js +86 -9
- package/dist/pipeline/relabel.d.ts +64 -0
- package/dist/pipeline/relabel.js +201 -0
- package/dist/pipeline/ruminate.d.ts +14 -0
- package/dist/pipeline/ruminate.js +61 -5
- package/dist/projection/slots.d.ts +55 -0
- package/dist/projection/slots.js +111 -0
- package/dist/prompts/l1-dedup.d.ts +15 -2
- package/dist/prompts/l1-dedup.js +57 -3
- package/dist/prompts/l1-extraction.d.ts +1 -1
- package/dist/prompts/l1-extraction.js +9 -5
- package/dist/stats.d.ts +25 -2
- package/dist/stats.js +139 -8
- package/dist/store/conflicts.d.ts +134 -1
- package/dist/store/conflicts.js +114 -1
- package/dist/store/embedding-source.d.ts +31 -3
- package/dist/store/embedding-source.js +56 -10
- package/dist/store/embedding.d.ts +9 -0
- package/dist/store/embedding.js +53 -1
- package/dist/store/l1-snapshot.d.ts +21 -1
- package/dist/store/l1-snapshot.js +33 -2
- package/dist/store/l1.d.ts +72 -5
- package/dist/store/l1.js +81 -3
- package/dist/store/memory-backend-worker.d.ts +41 -0
- package/dist/store/memory-backend-worker.js +130 -0
- package/dist/store/memory-backend.d.ts +73 -0
- package/dist/store/memory-backend.js +30 -0
- package/dist/store/memory-worker.d.ts +1 -0
- package/dist/store/memory-worker.js +75 -0
- package/dist/store/session-modes.d.ts +19 -0
- package/dist/store/session-modes.js +69 -3
- package/dist/store/slots.d.ts +108 -0
- package/dist/store/slots.js +253 -0
- package/dist/store/sqlite.d.ts +128 -2
- package/dist/store/sqlite.js +345 -12
- package/dist/store/supersede.d.ts +8 -1
- package/dist/store/supersede.js +9 -1
- package/dist/tools/index.js +94 -7
- package/dist/tools/slots.d.ts +17 -0
- package/dist/tools/slots.js +236 -0
- package/dist/types.d.ts +26 -10
- package/dist/types.js +19 -9
- package/dist/util/yield.d.ts +10 -0
- package/dist/util/yield.js +10 -0
- package/dist/wing-backfill.d.ts +47 -0
- package/dist/wing-backfill.js +187 -0
- package/dist/wing-migrate.d.ts +32 -0
- package/dist/wing-migrate.js +88 -0
- package/dsh.plugin.json +1 -1
- package/package.json +3 -2
package/CHANGELOG.en.md
CHANGED
|
@@ -27,8 +27,14 @@ This file covers the **0.12.0** release notes and the current **unreleased** cha
|
|
|
27
27
|
- **Panel**: the records tab gained a collapsed "retired (recoverable)" section that only fetches when expanded, and delete confirmations now state that the action is reversible. **Physical cleanup deliberately has no panel entry** — irreversible actions stay on the RPC/model surface.
|
|
28
28
|
- **§C conflict freeze: same-batch contradictions can now be frozen (fixing "the only thing the model ever says is exactly what gets rejected").** Investigation showed that `validateConflictPair`'s third condition required the other side to be a known record from the candidate pool — and **ids of memories created in the same batch are not in it** (they were just generated and are not stored yet). So the most typical "the machine cannot decide" case, two fresh contradictory memories in one batch, was **necessarily rejected and fell back to `store`** even when the model correctly emitted `conflict`. Evidence: the model emits it 7/7 at the model level, yet that hop never reached the database (`conflict_pending` has had 0 rows since the DB was created, and `conflict` receipts are 0 across `store 381 / merge 204 / update 172 / skip 7`). Fix: `validateConflictPair` takes an optional `batchIds` (omitted = previous behaviour) while still requiring exactly one side to be the current record, which keeps pairing unique; plus a queue-full guard so that **a loser belonging to the current batch is never auto-resolved** (otherwise freshly extracted output would be retired immediately with nobody informed) — it is simply not parked and stored as usual.
|
|
29
29
|
|
|
30
|
+
- **§C conflict freeze now understands the "three time axes": a content contradiction that is ordered in time is no longer always handed to a human.** A memory already carried three non-substitutable time axes (record time `createdAt/updatedAt`, fact validity `validFrom/validTo`, persistence `persistence`), yet conflict detection only used record time — the detector could not see validity or persistence when deciding `conflict`, so "an old fact was superseded by a new one" was routinely parked for a human; and the verdict panel showed only the two texts, leaving people to decide blind. Both ends of the freeze now carry the axes:
|
|
31
|
+
- **Detection side**: with freeze **on**, the unified candidate pool passes each memory's `valid_from_ms` / `valid_to_ms` / `persistence` to the detector, and the `conflict` action clause gained a "three-axis first pass" — when content contradicts, compare validity and persistence first, and when one side has expired or is clearly later, prefer `update` / `merge` over `conflict`. **The three keys and the clause are both gated by `conflictFreeze`**: in the closed state the user prompt is **byte-identical** to the pre-change one (see Fixed below and [ADR-0012](./docs/adr/0012-conflict-3axis-advisory-time-axes.md)).
|
|
32
|
+
- **Verdict side**: `ConflictPairView` gained optional `winner_*` / `loser_*` axis fields (backwards compatible); the pending list and its rendering attach a "valid from / valid to, persistence" comparison to each pair, so a person can see at a glance which side is newer and which has expired.
|
|
33
|
+
- The machine **still does not auto-adjudicate**: the axes are advisory facts only, and the final verdict is still written by a human (or by the safety valve on timeout / full queue) — `ConflictResolution` values are unchanged.
|
|
34
|
+
|
|
30
35
|
### Fixed
|
|
31
36
|
|
|
37
|
+
- **Closed-state prompts silently gained the three axis fields (affects deployments with freeze off — the default).** The first version injected `valid_from_ms` / `valid_to_ms` / `persistence` into the candidate pool **unconditionally**, while that LLM call only read the switch for the system prompt — so with `conflictFreeze=false` (the deployment default) the model saw 3 extra keys per candidate, **explained by no clause at all**: extra tokens and a changed input. The keys are now gated by `conflictFreeze`, so the closed-state user prompt is **byte-identical** to the pre-change one, and the criterion was upgraded from "does not contain substring X" to a **sha1 golden anchor plus reverse verification** (breaking the gate turns that case red).
|
|
32
38
|
- **The panel reported "conflict freeze is off" while the switch was clearly on.** The `conflicts` / `conflict-resolve` endpoints read the **static deployment config** `cfg.conflictFreeze.enabled`, whereas the panel writes the **live runtime setting**. The deployment default is always `false`, so with the switch on and `true` persisted to `settings.yaml` the page still said it was off. It now resolves through `effectiveCfg(cfg, live)`, the same resolution the dedup pipeline uses: there is exactly **one** source of truth, and the read and write sides must see the same state — otherwise you get "the list says it is on while the verdict says it is off".
|
|
33
39
|
- **`dsh-memory/embedding-reindex` was declared but permanently 404.** The endpoint existed in the contract yet was missing from both the `MEMORY_ENDPOINTS` allow-list and the dispatcher `case` table, and `startReindex()` was **dead code** — which is why the settings "vector index" block only ever offered Cancel and never Start. Allow-list, `case`, and tests added.
|
|
34
40
|
- **`UiRecord.sourceMessageIds` was a dead field.** It read a column `l1_records` never had, so it always resolved to `[]` and the panel's source row **never rendered**. Replaced by `sourceAnchors`, which reads real data.
|
package/CHANGELOG.ja.md
CHANGED
|
@@ -29,8 +29,14 @@
|
|
|
29
29
|
- **パネル**:記録タブに「退場済み(復元可能)」区画を追加(既定は折り畳み、展開時のみ取得)。削除確認の文言は「復元可能」に変更。**物理クリーンアップは意図的にパネル入口を作りません**——不可逆操作は RPC / モデル側の出口だけに残します。
|
|
30
30
|
- **§C 矛盾凍結:同一バッチ内の矛盾も凍結できるようになりました(「モデルが唯一言うことだけが必ず拒否される」の修正)。** 調査の結果、`validateConflictPair` の第③条件は「相手が候補プール内の既知レコードであること」を要求していましたが、**同一バッチで生成された新記憶の id はそこに含まれません**(まだ保存されていないため)。つまり最も典型的な「機械には判定できない」ケース(同一バッチの矛盾した二つの新記憶)は、モデルが正しく `conflict` を出しても**必ず不成対と判定され `store` にフォールバック**していました。証拠:モデル層では 7/7 で出力されるのに、その一歩は一度も DB に到達していません(`conflict_pending` は DB 作成以来 0 行、`l1_receipts` の `conflict` は `store 381 / merge 204 / update 172 / skip 7` の中で 0 件)。修正:`validateConflictPair` に任意の `batchIds` を追加(省略 = 従来動作)。ただし「必ず一方が本条の記録」は維持し、ペアが一意になるようにします。さらにキュー満杯ガードを追加し、**敗方が当バッチの新記憶である場合は自動了結しない**(抽出直後の産出が誰にも知られず即退場するのを避ける)ようにし、単に停放置せず通常どおり格納します。
|
|
31
31
|
|
|
32
|
+
- **§C 矛盾凍結が「三軸時間」を理解できるようになりました——内容が矛盾していても時間的な前後があるものを、一律に人手へ回さなくなります。** 記憶レコードには元々、互いに代替できない三つの時間軸(記録時刻 `createdAt/updatedAt`、事実の有効期間 `validFrom/validTo`、持続性 `persistence`)がありましたが、矛盾検出は記録時刻しか使っていませんでした。検出器は `conflict` を判定する際に有効期間と持続性を見られないため、「古い事実が新しい事実に置き換わった」ケースが人手裁决の対象として停放置されるのが常態化し、裁决パネルも双方の本文しか表示しないため人は盲判を強いられていました。今回、凍結の両端に三軸を接続しました:
|
|
33
|
+
- **検出側**:凍結**開啟時**、統一候補プールが各記憶の `valid_from_ms` / `valid_to_ms` / `persistence` を検出器へ渡し、`conflict` 動作条項に「三軸の補助判定」を追加——内容が矛盾する場合はまず有効期間と持続性を比較し、一方が期限切れ、または明らかに遅い場合は `update` / `merge` を優先します。**三つのキーと条項はいずれも `conflictFreeze` でゲートされます**:關閉態の user prompt は変更前と**バイト単位で同一**です(下記「修正」および [ADR-0012](./docs/adr/0012-conflict-3axis-advisory-time-axes.md) 参照)。
|
|
34
|
+
- **裁决側**:`ConflictPairView` に任意の `winner_*` / `loser_*` 三軸フィールドを追加(後方互換)。待裁决リストとその描画は各ペアに「有効期間の起/止、持続性」の比較を付し、どちらが新しいか・どちらが期限切れかを一目で分かるようにします。
|
|
35
|
+
- 機械は**依然として自動裁决しません**:三軸は補助事実にすぎず、最終結論は従来どおり人(または安全弁のタイムアウト/満杯による自動了結)が書きます——`ConflictResolution` の値は変わりません。
|
|
36
|
+
|
|
32
37
|
### 修正
|
|
33
38
|
|
|
39
|
+
- **關閉態の prompt に三軸フィールドが黙って混入していました(凍結 OFF=デフォルトのデプロイに影響)。** 初版は `valid_from_ms` / `valid_to_ms` / `persistence` を候補プールへ**無条件**に注入していましたが、この LLM 呼び出しは system prompt にしかスイッチを読んでいませんでした。そのため `conflictFreeze=false`(デプロイ既定)ではモデルが候補ごとに 3 つのキーを余分に見ており、しかも**それを説明する条項が一切ありません**でした——トークンが増え、入力が変わっていました。三つのキーを `conflictFreeze` でゲートし、關閉態の user prompt は変更前と**バイト単位で同一**にしました。判据も「特定の部分文字列を含まない」から **sha1 golden アンカー + 反証検証**(ゲートを壊すと当該ケースが赤くなる)へ格上げしています。
|
|
34
40
|
- **スイッチは明らかに ON なのに、パネルは「矛盾凍結は未開啟」と表示していました。** `conflicts` / `conflict-resolve` 端点は**デプロイ時の静的設定** `cfg.conflictFreeze.enabled` を読んでいたのに対し、パネルは**実行時設定**(live)に書き込んでいました。デプロイ既定値は常に `false` のため、スイッチが ON で `settings.yaml` に `true` が保存されていても未開啟と表示されます。`effectiveCfg(cfg, live)` 経由に変更し、重複排除パイプラインと同じ解決に統一しました——真実の源は**一つ**であり、読み手と書き手が同じ状態を見る必要があります。
|
|
35
41
|
- **`dsh-memory/embedding-reindex` は宣言されているのに恒久的に 404。** 契約には存在するものの `MEMORY_ENDPOINTS` 許可リストにもディスパッチャの `case` にも無く、`startReindex()` は**死んだコード**でした(設定の「ベクトル索引」ブロックに「開始」が無く「キャンセル」だけだった理由)。許可リスト + `case` + テストを追加。
|
|
36
42
|
- **`UiRecord.sourceMessageIds` は死んだフィールドでした。** `l1_records` に**存在しない列**を読んでいたため常に `[]` になり、パネルの来源行は**一度も描画されていません**でした。実データを読む `sourceAnchors` に置き換えました。
|
package/CHANGELOG.ko.md
CHANGED
|
@@ -29,8 +29,14 @@
|
|
|
29
29
|
- **패널**: 기록 탭에 "퇴장됨(복구 가능)" 구획을 추가했습니다(기본 접힘, 펼칠 때만 조회). 삭제 확인 문구는 "복구 가능"으로 변경했습니다. **물리 정리는 의도적으로 패널 진입점을 만들지 않습니다** — 되돌릴 수 없는 동작은 RPC / 모델 표면에만 남깁니다.
|
|
30
30
|
- **§C 모순 동결: 동일 배치 내 모순도 이제 동결할 수 있습니다("모델이 유일하게 하는 말이 정확히 거부되는" 문제 수정).** 조사 결과 `validateConflictPair`의 세 번째 조건은 "상대방이 후보 풀의 기존 기록일 것"을 요구했지만, **동일 배치에서 생성된 새 기억의 id는 거기에 없습니다**(아직 저장되지 않았기 때문). 즉 가장 전형적인 "기계가 판단할 수 없는" 상황(한 배치의 모순된 두 새 기억)은 모델이 `conflict`를 올바르게 내도 **반드시 페어가 아니라고 판정되어 `store`로 폴백**했습니다. 증거: 모델 층에서는 7/7로 출력되지만 그 단계는 DB에 한 번도 도달하지 않았습니다(`conflict_pending`은 DB 생성 이래 0행, `l1_receipts`의 `conflict`는 `store 381 / merge 204 / update 172 / skip 7` 중 0건). 수정: `validateConflictPair`에 선택적 `batchIds` 추가(생략 = 기존 동작). 단 "정확히 한쪽이 본 기록"은 유지해 페어가 유일하도록 합니다. 또한 큐 만원 가드를 추가해 **패배자가 이번 배치의 새 기억이면 자동 종결하지 않습니다**(방금 추출된 산출물이 아무에게도 알려지지 않고 즉시 퇴장하는 것을 방지) — 단순히 대기시키지 않고 정상 저장합니다.
|
|
31
31
|
|
|
32
|
+
- **§C 모순 동결이 이제 "삼축 시간"을 이해합니다 — 내용은 모순이지만 시간상 선후가 있는 경우를 더 이상 일괄적으로 사람에게 넘기지 않습니다.** 기억 레코드에는 원래 서로 대체할 수 없는 세 개의 시간축(기록 시각 `createdAt/updatedAt`, 사실 유효 기간 `validFrom/validTo`, 지속성 `persistence`)이 있었지만, 모순 탐지는 기록 시각만 사용했습니다. 탐지기는 `conflict`를 판정할 때 유효 기간과 지속성을 볼 수 없어 "오래된 사실이 새 사실로 대체된" 경우를 사람이 판정할 대상으로 대기시키는 일이 흔했고, 판정 패널도 양쪽 본문만 보여주어 사람이 눈감고 판정해야 했습니다. 이번에 동결의 양 끝에 삼축을 연결했습니다:
|
|
33
|
+
- **탐지 측**: 동결 **개방 시** 통합 후보 풀이 각 기억의 `valid_from_ms` / `valid_to_ms` / `persistence`를 탐지기에 전달하고, `conflict` 동작 조항에 "삼축 보조 판정"을 추가했습니다 — 내용이 모순되면 먼저 유효 기간과 지속성을 비교하고, 한쪽이 만료되었거나 명백히 더 늦으면 `update` / `merge`를 우선합니다. **세 키와 조항 모두 `conflictFreeze`로 게이트됩니다**: 폐쇄 상태의 user prompt는 변경 전과 **바이트 단위로 동일**합니다(아래 "수정" 및 [ADR-0012](./docs/adr/0012-conflict-3axis-advisory-time-axes.md) 참조).
|
|
34
|
+
- **판정 측**: `ConflictPairView`에 선택적 `winner_*` / `loser_*` 삼축 필드를 추가했습니다(하위 호환). 대기 목록과 그 렌더링은 각 쌍에 "유효 기간 시작/종료, 지속성" 비교를 덧붙여 어느 쪽이 더 새로운지, 어느 쪽이 만료되었는지 한눈에 보이게 합니다.
|
|
35
|
+
- 기계는 **여전히 자동 판정하지 않습니다**: 삼축은 보조 사실일 뿐이며 최종 결론은 여전히 사람(또는 안전 밸브의 타임아웃/큐 만원 자동 종결)이 씁니다 — `ConflictResolution` 값은 변하지 않습니다.
|
|
36
|
+
|
|
32
37
|
### 수정
|
|
33
38
|
|
|
39
|
+
- **폐쇄 상태 prompt에 삼축 필드가 조용히 추가되어 있었습니다(동결 OFF = 기본 배포에 영향).** 첫 버전은 `valid_from_ms` / `valid_to_ms` / `persistence`를 후보 풀에 **무조건** 주입했지만, 이 LLM 호출은 system prompt에만 스위치를 읽었습니다. 그래서 `conflictFreeze=false`(배포 기본값)에서는 모델이 후보마다 3개의 키를 더 보았고, **이를 설명하는 조항이 전혀 없었습니다** — 토큰이 늘고 입력이 바뀌었습니다. 세 키를 `conflictFreeze`로 게이트하여 폐쇄 상태의 user prompt가 변경 전과 **바이트 단위로 동일**해졌으며, 판정 기준도 "특정 부분 문자열을 포함하지 않음"에서 **sha1 golden 앵커 + 반증 검증**(게이트를 깨면 해당 케이스가 빨간색)으로 상향했습니다.
|
|
34
40
|
- **스위치는 분명히 켜져 있는데 패널은 "모순 동결 미개방"이라고 표시했습니다.** `conflicts` / `conflict-resolve` 엔드포인트는 **배포 시점의 정적 설정** `cfg.conflictFreeze.enabled`를 읽었지만, 패널은 **런타임 설정**(live)에 기록했습니다. 배포 기본값은 항상 `false`이므로, 스위치가 켜지고 `settings.yaml`에 `true`가 저장되어도 미개방으로 표시됩니다. `effectiveCfg(cfg, live)` 경로로 변경해 중복 제거 파이프라인과 동일한 해석으로 통일했습니다 — 진실의 원천은 **하나**이며, 읽는 쪽과 쓰는 쪽이 같은 상태를 봐야 합니다.
|
|
35
41
|
- **`dsh-memory/embedding-reindex`는 선언되었지만 영구히 404였습니다.** 계약에는 있으나 `MEMORY_ENDPOINTS` 허용 목록과 디스패처 `case` 양쪽에 없었고, `startReindex()`는 **죽은 코드**였습니다(설정의 "벡터 인덱스" 블록에 "시작"이 없고 "취소"만 있던 이유). 허용 목록 + `case` + 테스트를 추가했습니다.
|
|
36
42
|
- **`UiRecord.sourceMessageIds`는 죽은 필드였습니다.** `l1_records`에 **존재하지 않는 컬럼**을 읽어 항상 `[]`가 되었고, 패널의 출처 행은 **한 번도 렌더링되지 않았습니다**. 실제 데이터를 읽는 `sourceAnchors`로 교체했습니다.
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
### 新增
|
|
12
12
|
|
|
13
|
+
- **激活槽位(Active Slot)——把「每次都该生效的约定」从语义召回里拿出来,变成可跨会话持久的常驻上下文。** 起因是一次真实失效:网络访问总则(上传走官方、下载走镜像)已写进记忆,下一轮对话里却**没有被召回**,于是旧的错误习惯继续生效。语义召回是概率性的,而这类规则需要的恰恰是确定性——所以给它们一条机械通道。
|
|
14
|
+
- **存储**:`<dataDir>/slots.json`,独立于 `state.json`(槽位是高频小改,不能把 checkpoint 的原子写拖着变频),复用 `util/io.ts` 的 `atomicWriteJson`;内存态只原地改,`list()/open()/alwaysOn()` 一律返回副本(对齐 `StateStore.reset()` 的活引用教训)。
|
|
15
|
+
- **工具面三件**:`memory_slot_write` / `memory_slot_list` / `memory_slot_close`。写入与关闭受既有的高权限门控 `live.memoryMutate`(默认关)——状态变更强风控;读取受会话档位门控,与 `memory_search` 同语义。新 registrar 独立成文件,`tools/index.ts`(1156 行)零侵入。
|
|
16
|
+
- **常驻注入**:`hooks/slot-recall.ts` 独立注册 `agent/pre-step`(waterfall prepend),与既有 `recall.ts` 可组合(两段注入顺序有测试钉住)。`pinned && open` 槽位按 priority 降序、按字节预算截断;超预算的以 `… 另有 N 个` 明示并给出找回路径——**截断不静默**。`validUntil` 由注入前的一次机械清算转 `expired`(纯时间戳比较,不引入任何 LLM),否则「有效期」只是装饰。
|
|
17
|
+
- **服务端投影 `memorySlots`**:经 `ctx.inject(['sessionProjections'])` 注册——宿主没有该服务时**静默不注册**,而不是让整行 profile 加载失败。`apply` 闭包 `SlotStore` 并以 `revision()` 判脏:**只在 `tool/result`(settled、非 error)且 rev 变化时重建**(`tool/call` 提交在 `execute()` 变更 store 之前,按 call 折会读到 stale),无关事件返回**同引用**;`view` 用 `WeakMap` 保引用稳定,且**不含 body**(判定与生成正交,正文按需再取)。本轮不含任何 client 代码:展示留给下一轮 brief。
|
|
18
|
+
- **schema 零新依赖**:`stateSchema` / `viewSchema` 自实现 `parse`(注册表运行时只调这一个方法),合法态**原样返回同一引用**、非法态抛错;不引 zod(`package.json` 与锁文件不在本轮改动白名单内)。
|
|
19
|
+
- 上限:≤8 槽(可配)/ 常驻 ≤2048 字节 / body ≤512 / title ≤60。
|
|
13
20
|
- **来源锚点(R7)——记忆现在能追回会话里的**真实位置**。** 在此之前溯源链是断的:L1 带着 `source_message_ids`,但那些是 **L0 消息 id**(`msg_<epoch_ms>_<hex>`),而 L0 表没有 `turn`/`step` 列;且该 id 列表**根本没写进检索库**(写入侧只取 `metadata`,字段被静默丢弃)。结果是**任何一条记忆都无法定位到原文**。
|
|
14
21
|
- `l0_conversations` 补 `turn`/`step` 两列(幂等 `ALTER TABLE`;旧行保持 NULL = 无锚点,**绝不猜测回填**),并新增 `(session_id, turn)` 索引。
|
|
15
22
|
- 捕获侧新增 `step/start` fold:`user/message` 在内核负载里**不带** `step`,靠同轮 `step/start` 推出;`assistant/message` 用事件自带的 `{turn, step}`。**首个 `step/start` 之前的消息 step 留空**——缺坐标时不编坐标,这是红线。
|
|
@@ -31,12 +38,53 @@
|
|
|
31
38
|
- **面板**:记录页新增「已退场(可恢复)」区(默认折叠、展开时才拉取,不让它拖慢正常浏览);删除确认文案改为明确"可恢复"。**物理清理刻意不做面板入口**——不可逆动作只留 RPC / 模型出口。
|
|
32
39
|
- **§C 矛盾冻结:同批次矛盾现在也能冻结(修掉"模型唯一会说的话恰好被拒收")。** 取证发现 `validateConflictPair` 的第③条硬性要求"另一方必须是候选池里的已知记录",而**同批次新记忆的 id 不在其中**(它们是本轮刚生成的、尚未入库)。于是"本轮两条新记忆互相矛盾"这种最典型的"机器判不了"情形,模型即便正确 emit 了 `conflict`,也**必然被判不成对而回落 `store`**。证据:模型层 7/7 会 emit,但那一跳从未落库(`conflict_pending` 建库以来 0 行、`l1_receipts` 里 `conflict` 凭证 0 条,而 `store 381 / merge 204 / update 172 / skip 7`)。修法:`validateConflictPair` 新增可选 `batchIds`(缺省 = 旧行为),仍要求"恰有一方是本条记忆"以保证配对唯一;并补队列满护栏——**败方属本轮新记忆时不做自动了结**,否则刚抽取的产出会立刻退场且没有任何人被告知,改为不停放、照常入库。
|
|
33
40
|
|
|
41
|
+
- **§C 矛盾冻结现在看得懂「三轴时间」——内容矛盾但时间上有先后的,不再一律塞给人工。** 记忆记录本就有三条互不替代的时间轴(记录时刻 `createdAt/updatedAt`、事实有效期 `validFrom/validTo`、持续性 `persistence`),但矛盾检测只用了记录时刻:检测器判 `conflict` 时看不到有效期与持续性,往往把「旧事实被新事实取代」误判成需要人裁决的对;裁决面板也只显示双方正文,人看不到有效期对比只能盲判。本轮把三轴接进冻结的两端:
|
|
42
|
+
- **检测侧**:统一候选池**在冻结开启时**向检测器透传每条记忆的 `valid_from_ms` / `valid_to_ms` / `persistence`;`conflict` 动作条款新增「三轴辅助判定」——内容矛盾时先比有效期/持续性,一方已过期或明显更晚的,引导走 `update`/`merge` 而非 `conflict`。**三键与条款一律受 `conflictFreeze` 门控**:关闭态 user prompt 与升级前**逐字节相同**(见下方「修复」与 [ADR-0012](./docs/adr/0012-conflict-3axis-advisory-time-axes.md))。
|
|
43
|
+
- **裁决侧**:`ConflictPairView` 新增可选的 `winner_*` / `loser_*` 三轴字段(向后兼容);待裁决列表与渲染为每条对附上「有效期起/止、持续性」对比,帮人一眼看出谁更新、谁已过期。
|
|
44
|
+
- 机器**仍不自动裁决**:三轴只是辅助事实,最终结论仍由人工(或安全阀超时/满队列自动了结)写——`ConflictResolution` 取值不变。
|
|
45
|
+
|
|
46
|
+
- **§C 三类冲突 + claim 分组 + 丢弃留痕(Phase 3-4)。** 冲突不再只有一种"硬矛盾"——LLM 现在能判定 `hard`(事实互斥)、`conditional`(前提不同才矛盾)、`supersession`(新旧取代)三类。面板按三类分段显示,每段有独立标题与说明;`defer` 按钮支持"看过但暂不裁决"(重置超时、累计复看次数)。`claim_key` 列允许标记同一主题的多对冲突,面板据此分组。被丢弃的不合法冲突决策可通过 `memory_conflicts_rejected` 工具与 `dsh-memory/conflicts-rejected` 端点查询。
|
|
47
|
+
- `conflict_pending` 新增 `conflict_type` / `claim_key` 两列(幂等 `ALTER TABLE` 迁移)。
|
|
48
|
+
- 额度计数只计 `hard`:`pendingHardTotal` 按类型过滤;`conditional` / `supersession` 不占额度。
|
|
49
|
+
- 投影哈希冻结:7 字段列投影 `projectConflictsForHash` 不含新列,存量快照校验不变。
|
|
50
|
+
- 面板:`ConflictsTab` 三类分段 + defer 按钮 + 三轴文案 + 复看次数 + claim 键显示。
|
|
51
|
+
|
|
34
52
|
### 修复
|
|
35
53
|
|
|
54
|
+
- **关闭态 prompt 曾悄悄多出三轴字段(默认关闭的部署受影响)。** 首版把 `valid_from_ms` / `valid_to_ms` / `persistence` **无条件**注入候选池,而这条 LLM 调用只对 system prompt 读了开关 ⇒ `conflictFreeze=false`(部署默认)时,模型每条候选多看 3 个**没有任何条款解释**的键:既费 token,又改变了输入。现已把三键纳入 `conflictFreeze` 门控,关闭态 user prompt 与升级前**逐字节相同**;判据同时从「不含某子串」升级为 **sha1 golden 锚 + 反向验证**(改坏门控该用例必红)。
|
|
36
55
|
- **面板说"矛盾冻结未开启",而开关明明是开的。** `conflicts` / `conflict-resolve` 端点读的是**部署静态配置** `cfg.conflictFreeze.enabled`,而面板写入的是**运行时设置**(live)。部署默认恒 `false`,于是开关已开、`settings.yaml` 已落 `true`,本页仍报未开启。改走 `effectiveCfg(cfg, live)`,与去重管线同一套解析——开关只有**一个**事实源,读端与写端必须看同一份状态,否则会出现"列表说开着、裁决说没开"的自相矛盾。
|
|
37
56
|
- **`dsh-memory/embedding-reindex` 声明了却恒返 404。** 端点写在契约里,但既缺席 `MEMORY_ENDPOINTS` 白名单、也没有分发 `case`;同时 `startReindex()` 是**死代码**,设置页「向量索引」区块因此只有"取消"没有"开始"。补白名单 + `case` + 用例。
|
|
38
57
|
- **`UiRecord.sourceMessageIds` 是死字段。** 它读的是 `l1_records` **从不存在的列**,永远回退 `[]`,于是记录面板的来源行**从未渲染过**。已替换为读真实数据的 `sourceAnchors`。
|
|
39
58
|
|
|
59
|
+
## [0.16.1] — 2026-09-24
|
|
60
|
+
|
|
61
|
+
### 修复
|
|
62
|
+
|
|
63
|
+
- **面板「删除记忆」确认后看似毫无反应——退场态没有透传给 UI。** 软删(retire)在服务端一直正常(`valid_to` 闭合 + 撤出检索面 + 可恢复),但按设计活动列表**保留**退场记录(`l1-retire` 测试钉死「面板浏览路径不隐藏退场记录」),而 `UiRecord` 契约里根本没有"是否已退场"字段、活动列表的记录行也没有任何视觉差异,"已退场"区又默认折叠——用户确认删除后看到的是**一条一模一样的记录**,自然以为"删除没生效"。
|
|
64
|
+
- 契约补 `UiRecord.retired` / `retiredReason`:`hitToUiRecord` 从 `valid_to` + 取代标记推导,活动列表与检索命中统一透传。
|
|
65
|
+
- 活动列表对退场记录渲染**「已退场 · 原因」徽标 + 整卡置灰**,行内按钮从「✕ 删除」换成「恢复」(走 `records-restore`,成功后自动刷新)——删除的瞬间界面立刻可见变化,且二次点删的困惑(幂等 no-op)不复存在。
|
|
66
|
+
- 新增 `tests/ui-retired-flag.test.ts` 钉死映射行为(活跃 / 人工退场 / 取代退场 / 无 `validTo` 形态各一)。
|
|
67
|
+
|
|
68
|
+
## [0.16.0] — 2026-09-24
|
|
69
|
+
|
|
70
|
+
### 修复
|
|
71
|
+
|
|
72
|
+
- **重标定/一键回填的 Wing 打标整段是废的**:提示词要求模型返回 `{"id":…,"wing":…}`,解析却读 `item.hall` → 永远取不到 → 整批静默丢弃**(实测 `wingLabeled=0 / tagged=0 / llmSkipped=60`,而日志里 LLM 明明成功)。这是 hall→Wing 改名的**第 3 次同类误伤**(前两次 `cfg.hall`、session-modes 的 `hall`)——提示词里的 JSON 字段名属于 **wire 协议**,不该跟着 UI 文案改名。现解析读 `wing` 并加枚举校验;**丢弃必须留日志**(id 配对失败 / 非法值各一条 warn),删掉永不触发的死 `catch`。
|
|
73
|
+
- **`embedding-state-get` 从 2.5–3.1s 降到毫秒级**。它每次现场跑 6 次 COUNT,其中两次是 `l1_records LEFT JOIN l1_vec … IS NULL`——`l1_vec` 是 **vec0 虚拟表(1024 维)**,普通谓词下退化成逐行 probe。改为「总数 − 已嵌入 − skip」相减(实测 L1 55ms→0ms、L0 371ms→3ms),并加**分级 TTL 缓存**(忙时 1s / 空闲 30s + 重建/切换/补齐收尾显式失效)。
|
|
74
|
+
- **重标定机械段不再一次性全量加载** `l1.all()`。改游标分页 + 只取 `id/type/metadata` 三列(`getAllL1Lite`),每批 200 条后让位;写回走 `patchL1Metadata`(**只改 metadata,绝不动正文**,有测试钉死)。
|
|
75
|
+
|
|
76
|
+
### 新增
|
|
77
|
+
|
|
78
|
+
- **Room 层:标签类自生长分类**。MemPalace 五层里 Room 位于 Wing/认知 hall 之下,由 `metadata.tags` **动态派生**(`json_each` 聚合,零 schema、零注册表)——新 tag 落库即成为新 Room。新增 `rooms-get` 端点与记录面板的 Room 分类区块(点击按该 tag 筛选,`list-records` 新增 `tag` 过滤通道)。实测本机已有 **78 个 Room**(聚合 2ms)。
|
|
79
|
+
- **共享校验模块 `src/metadata-validators.ts`**:`isWingId` / `isCognitiveHall` / `isTag` / `normTags` 集中一处。此前 Wing 侧**完全没有校验**(任何非空字符串都能写进 `metadata.hall`),而认知 hall 侧有 `isCognitiveHall()` 严格校验——两处枚举不对称是数据完整性缺口。
|
|
80
|
+
- **后台处理 worker 隔离(B 窄切)**:抽出 `MemoryBackend` 边界,后台批处理(反刍/回填/重标定)的 SQLite 访问搬进 `worker_threads`,不再占住宿主主事件循环;起不来自动退回进程内并留 warn(**隔离失败绝不让后台处理失效**)。热路径(召回/捕获)一行未动——它们每轮调 DB,线程化会付 IPC 税。
|
|
81
|
+
- **批次进度可按段区分**:机械巡检 / 补 Wing / 提炼标签各带独立 label 与批次进度(此前 `sub` 只在 LLM 段有值,机械段 1439 条写回期间面板全空白)。
|
|
82
|
+
|
|
83
|
+
### 变更
|
|
84
|
+
|
|
85
|
+
- 重标定的 metadata 写回从 `l1.upsert` 改为 `patchMetadata`。**只改 metadata 原本就不该重算嵌入**——此前每条记录写回都在跑一次 embedding,单次重标定最多浪费 900 次嵌入调用。
|
|
86
|
+
|
|
87
|
+
|
|
40
88
|
## [0.12.0] — 2026-09-17
|
|
41
89
|
|
|
42
90
|
### 新增
|