@modusensus/dsh-mneme 0.9.5 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/docs/CONFIGURATION.md +21 -0
- package/docs/DAEMON.md +6 -2
- package/docs/DISTILL.md +90 -6
- package/docs/SLEEP.md +20 -6
- package/docs/STORAGE.md +1 -0
- package/lib/api-standalone.js +11 -3
- package/lib/api.js +76 -6
- package/lib/client.js +259 -11
- package/lib/config.js +33 -1
- package/lib/continuity.js +11 -3
- package/lib/dream/decisions.js +88 -27
- package/lib/dream/sleep.js +467 -81
- package/lib/dream.js +110 -13
- package/lib/fence.js +24 -0
- package/lib/graph/anchoring.js +7 -2
- package/lib/index.js +15 -6
- package/lib/inject.js +9 -4
- package/lib/lang.js +17 -12
- package/lib/pipeline-nudge-auto.js +3 -0
- package/lib/pipeline-nudge.js +41 -8
- package/lib/pipeline-runner.js +15 -0
- package/lib/pre-inject-gate.js +14 -0
- package/lib/profile-extract.js +4 -1
- package/lib/provenance-http.js +162 -37
- package/lib/service.js +99 -8
- package/lib/session-gate.js +91 -0
- package/lib/settings.js +13 -1
- package/lib/store.js +681 -18
- package/lib/summarize.js +152 -15
- package/lib/tools.js +26 -6
- package/package.json +4 -2
- package/src/api-standalone.js +11 -3
- package/src/api.js +76 -6
- package/src/config.js +33 -1
- package/src/continuity.js +11 -3
- package/src/dream/decisions.js +88 -27
- package/src/dream/sleep.js +467 -81
- package/src/dream.js +110 -13
- package/src/fence.js +24 -0
- package/src/graph/anchoring.js +7 -2
- package/src/index.js +15 -6
- package/src/inject.js +9 -4
- package/src/lang.js +17 -12
- package/src/pipeline-nudge-auto.js +3 -0
- package/src/pipeline-nudge.js +41 -8
- package/src/pipeline-runner.js +15 -0
- package/src/pre-inject-gate.js +14 -0
- package/src/profile-extract.js +4 -1
- package/src/provenance-http.js +162 -37
- package/src/service.js +99 -8
- package/src/session-gate.js +91 -0
- package/src/settings.js +13 -1
- package/src/store.js +681 -18
- package/src/summarize.js +152 -15
- package/src/tools.js +26 -6
- package/test/api.test.js +153 -4
- package/test/client.test.js +380 -0
- package/test/config.test.js +8 -0
- package/test/conflict-reviewed.test.js +79 -0
- package/test/continuity.test.js +29 -1
- package/test/dependency-overrides.test.js +78 -0
- package/test/differentiate-feedback.test.js +248 -0
- package/test/dream-decision-evidence.test.js +314 -0
- package/test/dream-fence.test.js +43 -0
- package/test/graph-boosted-relations.test.js +189 -0
- package/test/graph-multipath.test.js +269 -0
- package/test/inject-pools.test.js +2 -1
- package/test/mint-source.test.js +83 -0
- package/test/pipeline-nudge-auto.test.js +26 -2
- package/test/pipeline-nudge.test.js +87 -0
- package/test/pipeline-runner.test.js +26 -0
- package/test/prompt-stance.test.js +128 -0
- package/test/provenance-http.test.js +200 -17
- package/test/release-docs.test.js +7 -1
- package/test/security-doc.test.js +43 -4
- package/test/session-gate.test.js +264 -0
- package/test/settings.test.js +4 -0
- package/test/sleep-action-set.test.js +16 -11
- package/test/sleep-catchup.test.js +315 -0
- package/test/sleep-failure-backoff.test.js +156 -0
- package/test/sleep.test.js +293 -0
- package/test/standalone-api.test.js +50 -0
- package/test/store.test.js +220 -0
- package/test/summarize.test.js +172 -0
package/src/dream/decisions.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { STR, langOf } from "../lang.js";
|
|
2
|
+
import { contentHashOf } from "../content-hash.js";
|
|
2
3
|
|
|
3
4
|
// Issue #126:sleepActionSet="full" 时新增两个动作——supersede(演进:新版取代
|
|
4
|
-
// 旧版)与 differentiate
|
|
5
|
-
//
|
|
5
|
+
// 旧版)与 differentiate(互补:两条都留,各自保留原本的正文)。两者的校验与执行
|
|
6
|
+
// 都复用既有 merge/conflict 的机制(winner/loser、casGuard、transaction、receipt)。
|
|
7
|
+
// Issue #533 起 differentiate 不再改写正文,只落裁决账本——见 applyDifferentiate。
|
|
6
8
|
// 调用方(sleep)按档位派生「本档允许的动作集合」时需要读它,故导出。
|
|
7
9
|
export const ACTIONS = new Set(["keep", "merge", "archive", "conflict", "update", "create", "supersede", "differentiate"]);
|
|
8
10
|
|
|
@@ -179,9 +181,10 @@ export function validateDecisions(decisions, snapshot, options = {}) {
|
|
|
179
181
|
local.push(`${at}: ${d.action} needs distinct winner and loser`);
|
|
180
182
|
}
|
|
181
183
|
} else if (d.action === "differentiate") {
|
|
182
|
-
// Issue #126
|
|
183
|
-
//
|
|
184
|
-
//
|
|
184
|
+
// Issue #126:互补型——两条都留下。ids 必须 ≥2(随后走下方通用 id 校验:
|
|
185
|
+
// unknown / archived / summary / 重复 claim)。distinctions 仍然必填:#533 之后
|
|
186
|
+
// 它不再写进正文,但仍是「模型确实做过分类」的唯一凭据,落进裁决账本供审计
|
|
187
|
+
// (空数组意味着模型只是偷懒输出了一条动作名,不该被记成已裁决)。
|
|
185
188
|
if (!Array.isArray(d.ids) || d.ids.length < 2) {
|
|
186
189
|
local.push(`${at}: differentiate needs at least two ids`);
|
|
187
190
|
} else if (!Array.isArray(d.distinctions) || !d.distinctions.some((s) => typeof s === "string" && s.trim())) {
|
|
@@ -318,7 +321,22 @@ export function validateDecisions(decisions, snapshot, options = {}) {
|
|
|
318
321
|
// 单条非法 → 跳过该决策,不 claim id(其目标记忆留给其它合法决策/隐式
|
|
319
322
|
// keep),并记录到 skipped 供调用方日志/审计。信息性跳过绝不写入全局
|
|
320
323
|
// errors,否则会误触发下方的整单拒绝。
|
|
321
|
-
|
|
324
|
+
//
|
|
325
|
+
// #534:除四键骨架外补三个「模型到底给没给」的布尔,把 skipped 从
|
|
326
|
+
// 「error 字符串」变成可计数的形状。作者实测 19 轮 17 轮整轮无效,但
|
|
327
|
+
// skipped 里只有一句错误串,事后无法判断模型是漏字段(merge 缺
|
|
328
|
+
// title/content)、还是字段给了但值不合规(has_* 为 true 仍报错)。
|
|
329
|
+
// 只加布尔不加原文:原文进 llm_audit_logs.metadata 与 dream_runs.raw_output,
|
|
330
|
+
// 这里保持每条记录足够小(skipped 会按轮累积进一行 JSON)。
|
|
331
|
+
skipped.push({
|
|
332
|
+
index,
|
|
333
|
+
action: d?.action,
|
|
334
|
+
ids,
|
|
335
|
+
error: local.join("; "),
|
|
336
|
+
has_keepSource: d?.keepSource !== undefined && d?.keepSource !== null,
|
|
337
|
+
has_title: typeof d?.title === "string" && d.title.trim().length > 0,
|
|
338
|
+
has_content: typeof d?.content === "string" && d.content.trim().length > 0
|
|
339
|
+
});
|
|
322
340
|
} else {
|
|
323
341
|
errors.push(...local);
|
|
324
342
|
}
|
|
@@ -507,7 +525,7 @@ export function applyDecisions(decisions, service, logger = null, snapshot = nul
|
|
|
507
525
|
committed.push({ action: "keep", ids: d.ids });
|
|
508
526
|
continue;
|
|
509
527
|
}
|
|
510
|
-
const outcome = applyOne(d, service, snapshot, config);
|
|
528
|
+
const outcome = applyOne(d, service, snapshot, config, logger);
|
|
511
529
|
if (outcome === "skipped") continue;
|
|
512
530
|
applied += outcome.applied;
|
|
513
531
|
committed.push(outcome.committed);
|
|
@@ -524,14 +542,14 @@ export function applyDecisions(decisions, service, logger = null, snapshot = nul
|
|
|
524
542
|
return { applied, conflicts, failures, committed };
|
|
525
543
|
}
|
|
526
544
|
|
|
527
|
-
function applyOne(d, service, snapshot, config = {}) {
|
|
545
|
+
function applyOne(d, service, snapshot, config = {}, logger = null) {
|
|
528
546
|
switch (d.action) {
|
|
529
547
|
case "archive": return applyArchive(d, service, snapshot);
|
|
530
548
|
case "merge": return applyMerge(d, service, snapshot, config);
|
|
531
549
|
case "conflict": return applyConflict(d, service, snapshot, config);
|
|
532
550
|
case "supersede": return applySupersede(d, service, snapshot, config);
|
|
533
551
|
case "differentiate": return applyDifferentiate(d, service, snapshot, config);
|
|
534
|
-
case "create": return applyCreate(d, service, config);
|
|
552
|
+
case "create": return applyCreate(d, service, config, logger);
|
|
535
553
|
default: return applyUpdate(d, service, snapshot, config);
|
|
536
554
|
}
|
|
537
555
|
}
|
|
@@ -563,7 +581,7 @@ function pickBestKeeper(ids, preferred, service) {
|
|
|
563
581
|
* Evidence ids ride in the content so a pattern stays traceable to its source
|
|
564
582
|
* memories. saveWithDedupe dedupes identical mints (idempotent replay-safe).
|
|
565
583
|
*/
|
|
566
|
-
function applyCreate(d, service, config = {}) {
|
|
584
|
+
function applyCreate(d, service, config = {}, logger = null) {
|
|
567
585
|
const language = langOf(config);
|
|
568
586
|
const title = String(d.title ?? "").trim();
|
|
569
587
|
const content = String(d.content ?? "").trim();
|
|
@@ -588,6 +606,22 @@ function applyCreate(d, service, config = {}) {
|
|
|
588
606
|
});
|
|
589
607
|
const memory = created?.memory;
|
|
590
608
|
if (!memory) return "skipped"; // deduped/subsumed: nothing minted, clean no-op
|
|
609
|
+
// #512:这条记忆出生自哪一轮。只记 create——merge/conflict/update/supersede 对
|
|
610
|
+
// 既有行的改动已由 receipt_chain 按 (run_id, record_id) 记账,再造一份就是第二套账。
|
|
611
|
+
// 记账失败不许拖垮这次铸造:归因是审计,记忆已经落盘了。
|
|
612
|
+
try {
|
|
613
|
+
if (typeof service?.saveMemoryMint === "function" && config?.runId) {
|
|
614
|
+
service.saveMemoryMint({
|
|
615
|
+
memoryId: memory.id,
|
|
616
|
+
runId: config.runId,
|
|
617
|
+
runType: config.mintSource === "sleep" ? "sleep" : "dream",
|
|
618
|
+
triggerSource: config.mintSource === "sleep" ? "sleep" : "autoDream",
|
|
619
|
+
createdAt: new Date().toISOString()
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
} catch (mintError) {
|
|
623
|
+
logger?.warn?.(`dsh-mneme dream: failed to record memory mint: ${String(mintError?.message ?? mintError)}`);
|
|
624
|
+
}
|
|
591
625
|
return { applied: 1, committed: { action: "create", id: memory.id, type } };
|
|
592
626
|
}
|
|
593
627
|
|
|
@@ -716,35 +750,62 @@ function applySupersede(d, service, snapshot, config = {}) {
|
|
|
716
750
|
|
|
717
751
|
/**
|
|
718
752
|
* differentiate(Issue #126,sleepActionSet="full"):互补型重复——两条各自成立、
|
|
719
|
-
*
|
|
720
|
-
*
|
|
753
|
+
* 覆盖不同侧面,不该判输赢。双方都保留(不归档),**正文一个字节都不动**。
|
|
754
|
+
*
|
|
755
|
+
* 为什么不动正文(Issue #533):#126 的原设计是「双方各追加同一段差异注记,注记
|
|
756
|
+
* 进正文 → 进注入与 embedding → 下一轮就不会再被判成重复」。离线复现证伪了这个
|
|
757
|
+
* 假设——两侧共享同一段后缀会让两条向量**同向靠拢**,余弦一轮比一轮高(0.876 →
|
|
758
|
+
* 0.960 只用了 5 轮,把注记截到 0 字照样往上爬,因为推高相似度的是共享前缀本身而
|
|
759
|
+
* 不是注记长短),于是同一对下一轮又被判成重复、又追加一段,实测单条堆到 19 段。
|
|
760
|
+
* 现在的裁决落在 differentiated_pairs 账本(src/store.js)里:候选构造阶段读它,
|
|
761
|
+
* 已裁决且两侧正文都没改过就不再送模型,正文保持干净。
|
|
721
762
|
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
763
|
+
* 幂等三层,从便宜到贵:
|
|
764
|
+
* 1. 一侧已归档 → skipped(缺一条就没什么可区分的);
|
|
765
|
+
* 2. 存量兼容:两侧正文都还带着旧版注记块 → skipped(升级不清账);
|
|
766
|
+
* 3. 账本命中且两侧 content hash 与记账时一致 → skipped(真正的幂等重放)。
|
|
767
|
+
* 任一侧后来被改写 → hash 对不上 → 重新裁决,宁可再判一次也不静音真的演化。
|
|
724
768
|
*/
|
|
725
769
|
function applyDifferentiate(d, service, snapshot, config = {}) {
|
|
726
|
-
const language = langOf(config);
|
|
727
770
|
const ids = Array.isArray(d.ids) ? d.ids : [];
|
|
728
|
-
const notes = (Array.isArray(d.distinctions) ? d.distinctions : [])
|
|
729
|
-
.filter((s) => typeof s === "string" && s.trim())
|
|
730
|
-
.map((s) => s.trim());
|
|
731
|
-
const marker = STR.differentiatedMarker[language](notes);
|
|
732
771
|
const alive = ids.filter((id) => {
|
|
733
772
|
const m = service.getById(id);
|
|
734
773
|
return m && !m.archived;
|
|
735
774
|
});
|
|
736
775
|
if (alive.length < 2) return "skipped"; // 缺一条即已处理/不可用(幂等)
|
|
737
|
-
|
|
738
|
-
|
|
776
|
+
const hashes = new Map(alive.map((id) => [id, contentHashOf(service.getById(id))]));
|
|
777
|
+
const [a, b] = alive.slice(0, 2);
|
|
778
|
+
const record = typeof service.findDifferentiatedPair === "function"
|
|
779
|
+
? service.findDifferentiatedPair(a, b)
|
|
780
|
+
: undefined;
|
|
781
|
+
if (record) {
|
|
782
|
+
// 记账时 a/b 是按 id 排序存的,hash 也跟着换位,这里同样按排序取值比对。
|
|
783
|
+
const [ha, hb] = a <= b ? [hashes.get(a), hashes.get(b)] : [hashes.get(b), hashes.get(a)];
|
|
784
|
+
if (record.hash_a === ha && record.hash_b === hb) return "skipped";
|
|
785
|
+
} else {
|
|
786
|
+
// 存量库兼容:旧版本把注记写进了正文,那些行没有账本行。两侧都还带着注记块
|
|
787
|
+
// 才认定「已裁决过」——只有一侧有,说明另一侧后来被改写,应当重判。
|
|
788
|
+
const noted = alive.filter((id) => STR.differentiatedNoteRe.test(String(service.getById(id)?.content ?? "")));
|
|
789
|
+
if (noted.length >= 2) return "skipped";
|
|
739
790
|
}
|
|
791
|
+
// 账本缺席(人工确认路径的裸 service)就什么都不写:正文一字不动本来就是本动作
|
|
792
|
+
// 的全部副作用,没有账本就只剩「这条决策无处落地」,不是错误,也不是 applied。
|
|
793
|
+
if (typeof service.saveDifferentiatedPair !== "function") return "skipped";
|
|
740
794
|
service.transaction(() => {
|
|
741
795
|
casGuard(service, snapshot, ids);
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
796
|
+
const [ia, ib] = alive.slice(0, 2);
|
|
797
|
+
service.saveDifferentiatedPair({
|
|
798
|
+
run_id: config.runId,
|
|
799
|
+
memory_a: ia,
|
|
800
|
+
memory_b: ib,
|
|
801
|
+
// hash 取**执行时**的正文(不是送审时),与账本里下一次比对的口径同源。
|
|
802
|
+
hash_a: hashes.get(ia),
|
|
803
|
+
hash_b: hashes.get(ib),
|
|
804
|
+
// 模型给的差异点也落账:正文不再写注记,这些就是「当初为什么判互补」的唯一凭据。
|
|
805
|
+
distinctions: (Array.isArray(d.distinctions) ? d.distinctions : [])
|
|
806
|
+
.filter((x) => typeof x === "string" && x.trim())
|
|
807
|
+
.map((x) => x.trim())
|
|
808
|
+
});
|
|
748
809
|
});
|
|
749
810
|
return {
|
|
750
811
|
applied: 1,
|