@ai-agent-forge/plugin-memory 0.85.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.
Files changed (148) hide show
  1. package/README.md +65 -0
  2. package/agent-forge.json +11 -0
  3. package/dist/capability.d.ts +182 -0
  4. package/dist/capability.d.ts.map +1 -0
  5. package/dist/capability.js +2565 -0
  6. package/dist/capability.js.map +1 -0
  7. package/dist/entry.d.ts +36 -0
  8. package/dist/entry.d.ts.map +1 -0
  9. package/dist/entry.js +154 -0
  10. package/dist/entry.js.map +1 -0
  11. package/dist/index.d.ts +49 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +49 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/memory/assistant-card.d.ts +31 -0
  16. package/dist/memory/assistant-card.d.ts.map +1 -0
  17. package/dist/memory/assistant-card.js +108 -0
  18. package/dist/memory/assistant-card.js.map +1 -0
  19. package/dist/memory/candidates.d.ts +65 -0
  20. package/dist/memory/candidates.d.ts.map +1 -0
  21. package/dist/memory/candidates.js +100 -0
  22. package/dist/memory/candidates.js.map +1 -0
  23. package/dist/memory/code-memory.d.ts +89 -0
  24. package/dist/memory/code-memory.d.ts.map +1 -0
  25. package/dist/memory/code-memory.js +104 -0
  26. package/dist/memory/code-memory.js.map +1 -0
  27. package/dist/memory/compaction-sequencer.d.ts +63 -0
  28. package/dist/memory/compaction-sequencer.d.ts.map +1 -0
  29. package/dist/memory/compaction-sequencer.js +129 -0
  30. package/dist/memory/compaction-sequencer.js.map +1 -0
  31. package/dist/memory/continuation.d.ts +44 -0
  32. package/dist/memory/continuation.d.ts.map +1 -0
  33. package/dist/memory/continuation.js +49 -0
  34. package/dist/memory/continuation.js.map +1 -0
  35. package/dist/memory/curation.d.ts +58 -0
  36. package/dist/memory/curation.d.ts.map +1 -0
  37. package/dist/memory/curation.js +68 -0
  38. package/dist/memory/curation.js.map +1 -0
  39. package/dist/memory/egress-policy.d.ts +50 -0
  40. package/dist/memory/egress-policy.d.ts.map +1 -0
  41. package/dist/memory/egress-policy.js +71 -0
  42. package/dist/memory/egress-policy.js.map +1 -0
  43. package/dist/memory/embedding-provider.d.ts +70 -0
  44. package/dist/memory/embedding-provider.d.ts.map +1 -0
  45. package/dist/memory/embedding-provider.js +164 -0
  46. package/dist/memory/embedding-provider.js.map +1 -0
  47. package/dist/memory/embedding-reranker.d.ts +56 -0
  48. package/dist/memory/embedding-reranker.d.ts.map +1 -0
  49. package/dist/memory/embedding-reranker.js +109 -0
  50. package/dist/memory/embedding-reranker.js.map +1 -0
  51. package/dist/memory/foundation.d.ts +168 -0
  52. package/dist/memory/foundation.d.ts.map +1 -0
  53. package/dist/memory/foundation.js +487 -0
  54. package/dist/memory/foundation.js.map +1 -0
  55. package/dist/memory/host-module-import.d.ts +25 -0
  56. package/dist/memory/host-module-import.d.ts.map +1 -0
  57. package/dist/memory/host-module-import.js +41 -0
  58. package/dist/memory/host-module-import.js.map +1 -0
  59. package/dist/memory/ledger.d.ts +58 -0
  60. package/dist/memory/ledger.d.ts.map +1 -0
  61. package/dist/memory/ledger.js +315 -0
  62. package/dist/memory/ledger.js.map +1 -0
  63. package/dist/memory/lifecycle.d.ts +124 -0
  64. package/dist/memory/lifecycle.d.ts.map +1 -0
  65. package/dist/memory/lifecycle.js +201 -0
  66. package/dist/memory/lifecycle.js.map +1 -0
  67. package/dist/memory/memory-network.d.ts +55 -0
  68. package/dist/memory/memory-network.d.ts.map +1 -0
  69. package/dist/memory/memory-network.js +70 -0
  70. package/dist/memory/memory-network.js.map +1 -0
  71. package/dist/memory/model-cache-hygiene.d.ts +18 -0
  72. package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
  73. package/dist/memory/model-cache-hygiene.js +38 -0
  74. package/dist/memory/model-cache-hygiene.js.map +1 -0
  75. package/dist/memory/preference-disambiguator.d.ts +43 -0
  76. package/dist/memory/preference-disambiguator.d.ts.map +1 -0
  77. package/dist/memory/preference-disambiguator.js +81 -0
  78. package/dist/memory/preference-disambiguator.js.map +1 -0
  79. package/dist/memory/preference-lifecycle.d.ts +66 -0
  80. package/dist/memory/preference-lifecycle.d.ts.map +1 -0
  81. package/dist/memory/preference-lifecycle.js +129 -0
  82. package/dist/memory/preference-lifecycle.js.map +1 -0
  83. package/dist/memory/preference-promotion.d.ts +87 -0
  84. package/dist/memory/preference-promotion.d.ts.map +1 -0
  85. package/dist/memory/preference-promotion.js +102 -0
  86. package/dist/memory/preference-promotion.js.map +1 -0
  87. package/dist/memory/preference-resolver.d.ts +44 -0
  88. package/dist/memory/preference-resolver.d.ts.map +1 -0
  89. package/dist/memory/preference-resolver.js +107 -0
  90. package/dist/memory/preference-resolver.js.map +1 -0
  91. package/dist/memory/purge-journal.d.ts +76 -0
  92. package/dist/memory/purge-journal.d.ts.map +1 -0
  93. package/dist/memory/purge-journal.js +130 -0
  94. package/dist/memory/purge-journal.js.map +1 -0
  95. package/dist/memory/purge.d.ts +90 -0
  96. package/dist/memory/purge.d.ts.map +1 -0
  97. package/dist/memory/purge.js +138 -0
  98. package/dist/memory/purge.js.map +1 -0
  99. package/dist/memory/recall-agent.d.ts +84 -0
  100. package/dist/memory/recall-agent.d.ts.map +1 -0
  101. package/dist/memory/recall-agent.js +199 -0
  102. package/dist/memory/recall-agent.js.map +1 -0
  103. package/dist/memory/recall-index.d.ts +87 -0
  104. package/dist/memory/recall-index.d.ts.map +1 -0
  105. package/dist/memory/recall-index.js +222 -0
  106. package/dist/memory/recall-index.js.map +1 -0
  107. package/dist/memory/recall-packet.d.ts +121 -0
  108. package/dist/memory/recall-packet.d.ts.map +1 -0
  109. package/dist/memory/recall-packet.js +156 -0
  110. package/dist/memory/recall-packet.js.map +1 -0
  111. package/dist/memory/scheduler-api.d.ts +99 -0
  112. package/dist/memory/scheduler-api.d.ts.map +1 -0
  113. package/dist/memory/scheduler-api.js +93 -0
  114. package/dist/memory/scheduler-api.js.map +1 -0
  115. package/dist/memory/scheduler.d.ts +55 -0
  116. package/dist/memory/scheduler.d.ts.map +1 -0
  117. package/dist/memory/scheduler.js +91 -0
  118. package/dist/memory/scheduler.js.map +1 -0
  119. package/dist/memory/store.d.ts +107 -0
  120. package/dist/memory/store.d.ts.map +1 -0
  121. package/dist/memory/store.js +208 -0
  122. package/dist/memory/store.js.map +1 -0
  123. package/dist/memory/suite-memory.d.ts +208 -0
  124. package/dist/memory/suite-memory.d.ts.map +1 -0
  125. package/dist/memory/suite-memory.js +288 -0
  126. package/dist/memory/suite-memory.js.map +1 -0
  127. package/dist/memory/transfer.d.ts +142 -0
  128. package/dist/memory/transfer.d.ts.map +1 -0
  129. package/dist/memory/transfer.js +210 -0
  130. package/dist/memory/transfer.js.map +1 -0
  131. package/dist/memory/vector-index.d.ts +39 -0
  132. package/dist/memory/vector-index.d.ts.map +1 -0
  133. package/dist/memory/vector-index.js +136 -0
  134. package/dist/memory/vector-index.js.map +1 -0
  135. package/dist/memory/write-budget.d.ts +33 -0
  136. package/dist/memory/write-budget.d.ts.map +1 -0
  137. package/dist/memory/write-budget.js +45 -0
  138. package/dist/memory/write-budget.js.map +1 -0
  139. package/dist/testing/memory-testkit.d.ts +149 -0
  140. package/dist/testing/memory-testkit.d.ts.map +1 -0
  141. package/dist/testing/memory-testkit.js +438 -0
  142. package/dist/testing/memory-testkit.js.map +1 -0
  143. package/dist/utils/sync-sleep.d.ts +2 -0
  144. package/dist/utils/sync-sleep.d.ts.map +1 -0
  145. package/dist/utils/sync-sleep.js +11 -0
  146. package/dist/utils/sync-sleep.js.map +1 -0
  147. package/package.json +56 -0
  148. package/plugin.json +10 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Zero-byte cache-artifact hygiene for transformers.js model downloads.
3
+ *
4
+ * A failed download attempt (unreachable mirror, aborted transfer) can leave a
5
+ * 0-byte file in the cache; transformers.js then treats the file as a cache
6
+ * hit on the NEXT attempt — including attempts against a different mirror —
7
+ * and never heals. Empty tokenizer/weight files are unambiguously failed
8
+ * downloads (real tokenizer.json is never empty), so the mirror-rotation loop
9
+ * purges zero-byte files between hosts. Non-empty-but-truncated files are
10
+ * deliberately NOT touched: those stay terminal per 设计 §3.2 (manual action
11
+ * list), because deleting user-provided manual imports on a guess would be
12
+ * worse than failing loudly.
13
+ *
14
+ * Best effort: purge failures propagate — if we cannot even delete an empty
15
+ * file, the subsequent download attempt should fail with that truth.
16
+ */
17
+ export declare function purgeZeroByteCacheArtifacts(modelDir: string): void;
18
+ //# sourceMappingURL=model-cache-hygiene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-cache-hygiene.d.ts","sourceRoot":"","sources":["../../src/memory/model-cache-hygiene.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAiBlE","sourcesContent":["import type { Dirent } from \"node:fs\";\nimport { readdirSync, rmSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Zero-byte cache-artifact hygiene for transformers.js model downloads.\n *\n * A failed download attempt (unreachable mirror, aborted transfer) can leave a\n * 0-byte file in the cache; transformers.js then treats the file as a cache\n * hit on the NEXT attempt — including attempts against a different mirror —\n * and never heals. Empty tokenizer/weight files are unambiguously failed\n * downloads (real tokenizer.json is never empty), so the mirror-rotation loop\n * purges zero-byte files between hosts. Non-empty-but-truncated files are\n * deliberately NOT touched: those stay terminal per 设计 §3.2 (manual action\n * list), because deleting user-provided manual imports on a guess would be\n * worse than failing loudly.\n *\n * Best effort: purge failures propagate — if we cannot even delete an empty\n * file, the subsequent download attempt should fail with that truth.\n */\nexport function purgeZeroByteCacheArtifacts(modelDir: string): void {\n\tlet entries: Dirent[];\n\ttry {\n\t\tentries = readdirSync(modelDir, { withFileTypes: true }) as Dirent[];\n\t} catch {\n\t\treturn; // No dir yet = nothing to purge.\n\t}\n\tfor (const entry of entries) {\n\t\tconst entryPath = join(modelDir, entry.name.toString());\n\t\tif (entry.isDirectory()) {\n\t\t\tpurgeZeroByteCacheArtifacts(entryPath);\n\t\t\tcontinue;\n\t\t}\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import { readdirSync, rmSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /**
4
+ * Zero-byte cache-artifact hygiene for transformers.js model downloads.
5
+ *
6
+ * A failed download attempt (unreachable mirror, aborted transfer) can leave a
7
+ * 0-byte file in the cache; transformers.js then treats the file as a cache
8
+ * hit on the NEXT attempt — including attempts against a different mirror —
9
+ * and never heals. Empty tokenizer/weight files are unambiguously failed
10
+ * downloads (real tokenizer.json is never empty), so the mirror-rotation loop
11
+ * purges zero-byte files between hosts. Non-empty-but-truncated files are
12
+ * deliberately NOT touched: those stay terminal per 设计 §3.2 (manual action
13
+ * list), because deleting user-provided manual imports on a guess would be
14
+ * worse than failing loudly.
15
+ *
16
+ * Best effort: purge failures propagate — if we cannot even delete an empty
17
+ * file, the subsequent download attempt should fail with that truth.
18
+ */
19
+ export function purgeZeroByteCacheArtifacts(modelDir) {
20
+ let entries;
21
+ try {
22
+ entries = readdirSync(modelDir, { withFileTypes: true });
23
+ }
24
+ catch {
25
+ return; // No dir yet = nothing to purge.
26
+ }
27
+ for (const entry of entries) {
28
+ const entryPath = join(modelDir, entry.name.toString());
29
+ if (entry.isDirectory()) {
30
+ purgeZeroByteCacheArtifacts(entryPath);
31
+ continue;
32
+ }
33
+ if (statSync(entryPath).size === 0) {
34
+ rmSync(entryPath, { force: true });
35
+ }
36
+ }
37
+ }
38
+ //# sourceMappingURL=model-cache-hygiene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-cache-hygiene.js","sourceRoot":"","sources":["../../src/memory/model-cache-hygiene.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,2BAA2B,CAAC,QAAgB,EAAQ;IACnE,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACJ,OAAO,GAAG,WAAW,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAa,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,CAAC,iCAAiC;IAC1C,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACzB,2BAA2B,CAAC,SAAS,CAAC,CAAC;YACvC,SAAS;QACV,CAAC;QACD,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpC,CAAC;IACF,CAAC;AAAA,CACD","sourcesContent":["import type { Dirent } from \"node:fs\";\nimport { readdirSync, rmSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Zero-byte cache-artifact hygiene for transformers.js model downloads.\n *\n * A failed download attempt (unreachable mirror, aborted transfer) can leave a\n * 0-byte file in the cache; transformers.js then treats the file as a cache\n * hit on the NEXT attempt — including attempts against a different mirror —\n * and never heals. Empty tokenizer/weight files are unambiguously failed\n * downloads (real tokenizer.json is never empty), so the mirror-rotation loop\n * purges zero-byte files between hosts. Non-empty-but-truncated files are\n * deliberately NOT touched: those stay terminal per 设计 §3.2 (manual action\n * list), because deleting user-provided manual imports on a guess would be\n * worse than failing loudly.\n *\n * Best effort: purge failures propagate — if we cannot even delete an empty\n * file, the subsequent download attempt should fail with that truth.\n */\nexport function purgeZeroByteCacheArtifacts(modelDir: string): void {\n\tlet entries: Dirent[];\n\ttry {\n\t\tentries = readdirSync(modelDir, { withFileTypes: true }) as Dirent[];\n\t} catch {\n\t\treturn; // No dir yet = nothing to purge.\n\t}\n\tfor (const entry of entries) {\n\t\tconst entryPath = join(modelDir, entry.name.toString());\n\t\tif (entry.isDirectory()) {\n\t\t\tpurgeZeroByteCacheArtifacts(entryPath);\n\t\t\tcontinue;\n\t\t}\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Complex semantic disambiguator (2.4d2) — deterministic, replayable
3
+ * disambiguation over competing preference candidates that a resolver marked
4
+ * conflicting (2.4c `conflict_unconfirmed`).
5
+ *
6
+ * Scope-specificity signal, most specific wins:
7
+ * task match > project match > scene match > scopeless.
8
+ * When the signal cannot separate the candidates (ties at the same
9
+ * specificity level), the outcome is `unresolvable` — every candidate fact
10
+ * (value, scope, evidence class, confidence) is included in the structured
11
+ * explanation so the boundary stays auditable and the caller can surface an
12
+ * explicit user choice. The same input always yields the same decision and
13
+ * explanation. Relation diffusion and Memory Networks are out of scope.
14
+ */
15
+ import type { JsonValue } from "@agent-forge/plugin-sdk";
16
+ import type { MemoryPreferenceEnvelopeV1 } from "./foundation.ts";
17
+ export interface DisambiguationCandidateV1 {
18
+ readonly memoryId: string;
19
+ readonly preferredValue: JsonValue;
20
+ readonly scope: MemoryPreferenceEnvelopeV1["scope"];
21
+ readonly evidenceClass: MemoryPreferenceEnvelopeV1["evidence"]["class"];
22
+ readonly applicabilityConfidence: number;
23
+ }
24
+ export interface DisambiguationContextV1 {
25
+ readonly scenes?: readonly string[];
26
+ readonly projects?: readonly string[];
27
+ readonly tasks?: readonly string[];
28
+ }
29
+ export type DisambiguationOutcomeV1 = {
30
+ readonly status: "resolved";
31
+ readonly winnerMemoryId: string;
32
+ readonly winnerValue: JsonValue;
33
+ readonly basis: "task" | "project" | "scene" | "scopeless";
34
+ readonly explanation: readonly string[];
35
+ } | {
36
+ readonly status: "unresolvable";
37
+ readonly explanation: readonly string[];
38
+ };
39
+ export interface PreferenceDisambiguatorV1 {
40
+ disambiguate(candidates: readonly DisambiguationCandidateV1[], context: DisambiguationContextV1): DisambiguationOutcomeV1;
41
+ }
42
+ export declare function createPreferenceDisambiguator(): PreferenceDisambiguatorV1;
43
+ //# sourceMappingURL=preference-disambiguator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preference-disambiguator.d.ts","sourceRoot":"","sources":["../../src/memory/preference-disambiguator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAElE,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACpD,QAAQ,CAAC,aAAa,EAAE,0BAA0B,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC;IACxE,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,MAAM,MAAM,uBAAuB,GAChC;IACA,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAAC;IAC3D,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,GACD;IACA,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,CAAC;AAEL,MAAM,WAAW,yBAAyB;IACzC,YAAY,CACX,UAAU,EAAE,SAAS,yBAAyB,EAAE,EAChD,OAAO,EAAE,uBAAuB,GAC9B,uBAAuB,CAAC;CAC3B;AAqCD,wBAAgB,6BAA6B,IAAI,yBAAyB,CA0DzE","sourcesContent":["/**\n * Complex semantic disambiguator (2.4d2) — deterministic, replayable\n * disambiguation over competing preference candidates that a resolver marked\n * conflicting (2.4c `conflict_unconfirmed`).\n *\n * Scope-specificity signal, most specific wins:\n * task match > project match > scene match > scopeless.\n * When the signal cannot separate the candidates (ties at the same\n * specificity level), the outcome is `unresolvable` — every candidate fact\n * (value, scope, evidence class, confidence) is included in the structured\n * explanation so the boundary stays auditable and the caller can surface an\n * explicit user choice. The same input always yields the same decision and\n * explanation. Relation diffusion and Memory Networks are out of scope.\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryPreferenceEnvelopeV1 } from \"./foundation.ts\";\n\nexport interface DisambiguationCandidateV1 {\n\treadonly memoryId: string;\n\treadonly preferredValue: JsonValue;\n\treadonly scope: MemoryPreferenceEnvelopeV1[\"scope\"];\n\treadonly evidenceClass: MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"];\n\treadonly applicabilityConfidence: number;\n}\n\nexport interface DisambiguationContextV1 {\n\treadonly scenes?: readonly string[];\n\treadonly projects?: readonly string[];\n\treadonly tasks?: readonly string[];\n}\n\nexport type DisambiguationOutcomeV1 =\n\t| {\n\t\t\treadonly status: \"resolved\";\n\t\t\treadonly winnerMemoryId: string;\n\t\t\treadonly winnerValue: JsonValue;\n\t\t\treadonly basis: \"task\" | \"project\" | \"scene\" | \"scopeless\";\n\t\t\treadonly explanation: readonly string[];\n\t }\n\t| {\n\t\t\treadonly status: \"unresolvable\";\n\t\t\treadonly explanation: readonly string[];\n\t };\n\nexport interface PreferenceDisambiguatorV1 {\n\tdisambiguate(\n\t\tcandidates: readonly DisambiguationCandidateV1[],\n\t\tcontext: DisambiguationContextV1,\n\t): DisambiguationOutcomeV1;\n}\n\nconst SPECIFICITY_ORDER = [\"task\", \"project\", \"scene\", \"scopeless\"] as const;\ntype SpecificityLevel = (typeof SPECIFICITY_ORDER)[number];\n\nfunction candidateSpecificity(\n\tcandidate: DisambiguationCandidateV1,\n\tcontext: DisambiguationContextV1,\n): SpecificityLevel | undefined {\n\tconst scope = candidate.scope;\n\tconst intersects = (scopeValues: readonly string[] | undefined, current: readonly string[] | undefined): boolean =>\n\t\tscopeValues !== undefined && current !== undefined && scopeValues.some((value) => current.includes(value));\n\tif (intersects(scope.tasks, context.tasks)) return \"task\";\n\tif (intersects(scope.projects, context.projects)) return \"project\";\n\tif (intersects(scope.scenes, context.scenes)) return \"scene\";\n\tif (\n\t\t(scope.tasks === undefined || scope.tasks.length === 0) &&\n\t\t(scope.projects === undefined || scope.projects.length === 0) &&\n\t\t(scope.scenes === undefined || scope.scenes.length === 0)\n\t) {\n\t\treturn \"scopeless\";\n\t}\n\t// The candidate's scope exists but does not match the current context.\n\treturn undefined;\n}\n\nfunction describeCandidate(candidate: DisambiguationCandidateV1): string {\n\tconst scope = candidate.scope;\n\tconst scopeText = JSON.stringify({\n\t\tlevel: scope.level,\n\t\ttasks: scope.tasks ?? [],\n\t\tprojects: scope.projects ?? [],\n\t\tscenes: scope.scenes ?? [],\n\t});\n\treturn `candidate ${candidate.memoryId} value=${JSON.stringify(candidate.preferredValue)} evidence=${candidate.evidenceClass} confidence=${candidate.applicabilityConfidence} scope=${scopeText}`;\n}\n\nexport function createPreferenceDisambiguator(): PreferenceDisambiguatorV1 {\n\treturn {\n\t\tdisambiguate(candidates, context) {\n\t\t\tif (candidates.length < 2) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"unresolvable\",\n\t\t\t\t\texplanation: [\"Disambiguation requires at least two competing candidates\"],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst explanations = candidates.map(describeCandidate);\n\t\t\tconst scored = candidates.map((candidate) => ({\n\t\t\t\tcandidate,\n\t\t\t\tlevel: candidateSpecificity(candidate, context),\n\t\t\t}));\n\t\t\tconst matched = scored.filter(\n\t\t\t\t(entry): entry is { candidate: DisambiguationCandidateV1; level: SpecificityLevel } =>\n\t\t\t\t\tentry.level !== undefined,\n\t\t\t);\n\t\t\tif (matched.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"unresolvable\",\n\t\t\t\t\texplanation: [\"No candidate scope matches the current context\", ...explanations],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst bestLevel = matched.reduce(\n\t\t\t\t(best, entry) =>\n\t\t\t\t\tSPECIFICITY_ORDER.indexOf(entry.level) < SPECIFICITY_ORDER.indexOf(best) ? entry.level : best,\n\t\t\t\t\"scopeless\" as SpecificityLevel,\n\t\t\t);\n\t\t\tconst finalists = matched.filter((entry) => entry.level === bestLevel);\n\t\t\tif (finalists.length !== 1) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"unresolvable\",\n\t\t\t\t\texplanation: [\n\t\t\t\t\t\t`${finalists.length} candidates tie at scope specificity \"${bestLevel}\"; explicit confirmation required`,\n\t\t\t\t\t\t...explanations,\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst winner = finalists[0]!.candidate;\n\t\t\tconst losers = matched.filter((entry) => entry.candidate !== winner);\n\t\t\tconst unmatched = scored.length - matched.length;\n\t\t\treturn {\n\t\t\t\tstatus: \"resolved\",\n\t\t\t\twinnerMemoryId: winner.memoryId,\n\t\t\t\twinnerValue: winner.preferredValue,\n\t\t\t\tbasis: bestLevel,\n\t\t\t\texplanation: [\n\t\t\t\t\t`Winner ${winner.memoryId} selected by scope specificity \"${bestLevel}\"`,\n\t\t\t\t\t...losers.map((entry) => `Loser ${entry.candidate.memoryId} at specificity \"${entry.level}\"`),\n\t\t\t\t\t...(unmatched > 0\n\t\t\t\t\t\t? [`${unmatched} candidate(s) excluded: scope does not match the current context`]\n\t\t\t\t\t\t: []),\n\t\t\t\t\t...explanations,\n\t\t\t\t],\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,81 @@
1
+ const SPECIFICITY_ORDER = ["task", "project", "scene", "scopeless"];
2
+ function candidateSpecificity(candidate, context) {
3
+ const scope = candidate.scope;
4
+ const intersects = (scopeValues, current) => scopeValues !== undefined && current !== undefined && scopeValues.some((value) => current.includes(value));
5
+ if (intersects(scope.tasks, context.tasks))
6
+ return "task";
7
+ if (intersects(scope.projects, context.projects))
8
+ return "project";
9
+ if (intersects(scope.scenes, context.scenes))
10
+ return "scene";
11
+ if ((scope.tasks === undefined || scope.tasks.length === 0) &&
12
+ (scope.projects === undefined || scope.projects.length === 0) &&
13
+ (scope.scenes === undefined || scope.scenes.length === 0)) {
14
+ return "scopeless";
15
+ }
16
+ // The candidate's scope exists but does not match the current context.
17
+ return undefined;
18
+ }
19
+ function describeCandidate(candidate) {
20
+ const scope = candidate.scope;
21
+ const scopeText = JSON.stringify({
22
+ level: scope.level,
23
+ tasks: scope.tasks ?? [],
24
+ projects: scope.projects ?? [],
25
+ scenes: scope.scenes ?? [],
26
+ });
27
+ return `candidate ${candidate.memoryId} value=${JSON.stringify(candidate.preferredValue)} evidence=${candidate.evidenceClass} confidence=${candidate.applicabilityConfidence} scope=${scopeText}`;
28
+ }
29
+ export function createPreferenceDisambiguator() {
30
+ return {
31
+ disambiguate(candidates, context) {
32
+ if (candidates.length < 2) {
33
+ return {
34
+ status: "unresolvable",
35
+ explanation: ["Disambiguation requires at least two competing candidates"],
36
+ };
37
+ }
38
+ const explanations = candidates.map(describeCandidate);
39
+ const scored = candidates.map((candidate) => ({
40
+ candidate,
41
+ level: candidateSpecificity(candidate, context),
42
+ }));
43
+ const matched = scored.filter((entry) => entry.level !== undefined);
44
+ if (matched.length === 0) {
45
+ return {
46
+ status: "unresolvable",
47
+ explanation: ["No candidate scope matches the current context", ...explanations],
48
+ };
49
+ }
50
+ const bestLevel = matched.reduce((best, entry) => SPECIFICITY_ORDER.indexOf(entry.level) < SPECIFICITY_ORDER.indexOf(best) ? entry.level : best, "scopeless");
51
+ const finalists = matched.filter((entry) => entry.level === bestLevel);
52
+ if (finalists.length !== 1) {
53
+ return {
54
+ status: "unresolvable",
55
+ explanation: [
56
+ `${finalists.length} candidates tie at scope specificity "${bestLevel}"; explicit confirmation required`,
57
+ ...explanations,
58
+ ],
59
+ };
60
+ }
61
+ const winner = finalists[0].candidate;
62
+ const losers = matched.filter((entry) => entry.candidate !== winner);
63
+ const unmatched = scored.length - matched.length;
64
+ return {
65
+ status: "resolved",
66
+ winnerMemoryId: winner.memoryId,
67
+ winnerValue: winner.preferredValue,
68
+ basis: bestLevel,
69
+ explanation: [
70
+ `Winner ${winner.memoryId} selected by scope specificity "${bestLevel}"`,
71
+ ...losers.map((entry) => `Loser ${entry.candidate.memoryId} at specificity "${entry.level}"`),
72
+ ...(unmatched > 0
73
+ ? [`${unmatched} candidate(s) excluded: scope does not match the current context`]
74
+ : []),
75
+ ...explanations,
76
+ ],
77
+ };
78
+ },
79
+ };
80
+ }
81
+ //# sourceMappingURL=preference-disambiguator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preference-disambiguator.js","sourceRoot":"","sources":["../../src/memory/preference-disambiguator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Complex semantic disambiguator (2.4d2) — deterministic, replayable\n * disambiguation over competing preference candidates that a resolver marked\n * conflicting (2.4c `conflict_unconfirmed`).\n *\n * Scope-specificity signal, most specific wins:\n * task match > project match > scene match > scopeless.\n * When the signal cannot separate the candidates (ties at the same\n * specificity level), the outcome is `unresolvable` — every candidate fact\n * (value, scope, evidence class, confidence) is included in the structured\n * explanation so the boundary stays auditable and the caller can surface an\n * explicit user choice. The same input always yields the same decision and\n * explanation. Relation diffusion and Memory Networks are out of scope.\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryPreferenceEnvelopeV1 } from \"./foundation.ts\";\n\nexport interface DisambiguationCandidateV1 {\n\treadonly memoryId: string;\n\treadonly preferredValue: JsonValue;\n\treadonly scope: MemoryPreferenceEnvelopeV1[\"scope\"];\n\treadonly evidenceClass: MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"];\n\treadonly applicabilityConfidence: number;\n}\n\nexport interface DisambiguationContextV1 {\n\treadonly scenes?: readonly string[];\n\treadonly projects?: readonly string[];\n\treadonly tasks?: readonly string[];\n}\n\nexport type DisambiguationOutcomeV1 =\n\t| {\n\t\t\treadonly status: \"resolved\";\n\t\t\treadonly winnerMemoryId: string;\n\t\t\treadonly winnerValue: JsonValue;\n\t\t\treadonly basis: \"task\" | \"project\" | \"scene\" | \"scopeless\";\n\t\t\treadonly explanation: readonly string[];\n\t }\n\t| {\n\t\t\treadonly status: \"unresolvable\";\n\t\t\treadonly explanation: readonly string[];\n\t };\n\nexport interface PreferenceDisambiguatorV1 {\n\tdisambiguate(\n\t\tcandidates: readonly DisambiguationCandidateV1[],\n\t\tcontext: DisambiguationContextV1,\n\t): DisambiguationOutcomeV1;\n}\n\nconst SPECIFICITY_ORDER = [\"task\", \"project\", \"scene\", \"scopeless\"] as const;\ntype SpecificityLevel = (typeof SPECIFICITY_ORDER)[number];\n\nfunction candidateSpecificity(\n\tcandidate: DisambiguationCandidateV1,\n\tcontext: DisambiguationContextV1,\n): SpecificityLevel | undefined {\n\tconst scope = candidate.scope;\n\tconst intersects = (scopeValues: readonly string[] | undefined, current: readonly string[] | undefined): boolean =>\n\t\tscopeValues !== undefined && current !== undefined && scopeValues.some((value) => current.includes(value));\n\tif (intersects(scope.tasks, context.tasks)) return \"task\";\n\tif (intersects(scope.projects, context.projects)) return \"project\";\n\tif (intersects(scope.scenes, context.scenes)) return \"scene\";\n\tif (\n\t\t(scope.tasks === undefined || scope.tasks.length === 0) &&\n\t\t(scope.projects === undefined || scope.projects.length === 0) &&\n\t\t(scope.scenes === undefined || scope.scenes.length === 0)\n\t) {\n\t\treturn \"scopeless\";\n\t}\n\t// The candidate's scope exists but does not match the current context.\n\treturn undefined;\n}\n\nfunction describeCandidate(candidate: DisambiguationCandidateV1): string {\n\tconst scope = candidate.scope;\n\tconst scopeText = JSON.stringify({\n\t\tlevel: scope.level,\n\t\ttasks: scope.tasks ?? [],\n\t\tprojects: scope.projects ?? [],\n\t\tscenes: scope.scenes ?? [],\n\t});\n\treturn `candidate ${candidate.memoryId} value=${JSON.stringify(candidate.preferredValue)} evidence=${candidate.evidenceClass} confidence=${candidate.applicabilityConfidence} scope=${scopeText}`;\n}\n\nexport function createPreferenceDisambiguator(): PreferenceDisambiguatorV1 {\n\treturn {\n\t\tdisambiguate(candidates, context) {\n\t\t\tif (candidates.length < 2) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"unresolvable\",\n\t\t\t\t\texplanation: [\"Disambiguation requires at least two competing candidates\"],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst explanations = candidates.map(describeCandidate);\n\t\t\tconst scored = candidates.map((candidate) => ({\n\t\t\t\tcandidate,\n\t\t\t\tlevel: candidateSpecificity(candidate, context),\n\t\t\t}));\n\t\t\tconst matched = scored.filter(\n\t\t\t\t(entry): entry is { candidate: DisambiguationCandidateV1; level: SpecificityLevel } =>\n\t\t\t\t\tentry.level !== undefined,\n\t\t\t);\n\t\t\tif (matched.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"unresolvable\",\n\t\t\t\t\texplanation: [\"No candidate scope matches the current context\", ...explanations],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst bestLevel = matched.reduce(\n\t\t\t\t(best, entry) =>\n\t\t\t\t\tSPECIFICITY_ORDER.indexOf(entry.level) < SPECIFICITY_ORDER.indexOf(best) ? entry.level : best,\n\t\t\t\t\"scopeless\" as SpecificityLevel,\n\t\t\t);\n\t\t\tconst finalists = matched.filter((entry) => entry.level === bestLevel);\n\t\t\tif (finalists.length !== 1) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"unresolvable\",\n\t\t\t\t\texplanation: [\n\t\t\t\t\t\t`${finalists.length} candidates tie at scope specificity \"${bestLevel}\"; explicit confirmation required`,\n\t\t\t\t\t\t...explanations,\n\t\t\t\t\t],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst winner = finalists[0]!.candidate;\n\t\t\tconst losers = matched.filter((entry) => entry.candidate !== winner);\n\t\t\tconst unmatched = scored.length - matched.length;\n\t\t\treturn {\n\t\t\t\tstatus: \"resolved\",\n\t\t\t\twinnerMemoryId: winner.memoryId,\n\t\t\t\twinnerValue: winner.preferredValue,\n\t\t\t\tbasis: bestLevel,\n\t\t\t\texplanation: [\n\t\t\t\t\t`Winner ${winner.memoryId} selected by scope specificity \"${bestLevel}\"`,\n\t\t\t\t\t...losers.map((entry) => `Loser ${entry.candidate.memoryId} at specificity \"${entry.level}\"`),\n\t\t\t\t\t...(unmatched > 0\n\t\t\t\t\t\t? [`${unmatched} candidate(s) excluded: scope does not match the current context`]\n\t\t\t\t\t\t: []),\n\t\t\t\t\t...explanations,\n\t\t\t\t],\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Complete preference lifecycle projection (2.4d3) — promotion (2.4d1),
3
+ * deterministic decay, reactivation, and archiving over an event sequence.
4
+ *
5
+ * The projection is a pure fold over a replayable event log: the same event
6
+ * sequence always yields the same state and transition audit, on any
7
+ * projection instance. Decay uses the 1C.6b half-life formula
8
+ * w × 2^(-elapsed/halfLife) anchored at the last usage/promotion; falling
9
+ * below the floor marks the preference `decayed` (kept in the ledger, hidden
10
+ * from recall per 1C store semantics). New evidence reactivates a decayed
11
+ * preference; archiving is terminal and ignores later events. Every
12
+ * transition records the policy version that governed it, so policy
13
+ * migrations are auditable across replays. Relation networks and advanced
14
+ * indexing remain out of scope (2.4d4).
15
+ */
16
+ import type { PreferenceEvidenceClassV1 } from "./preference-promotion.ts";
17
+ export interface PreferenceLifecyclePolicyV1 {
18
+ readonly policyVersion: string;
19
+ readonly halfLifeMs: number;
20
+ /** Confidence at or below which the preference becomes `decayed` (0..1). */
21
+ readonly decayFloor: number;
22
+ /** Confidence assigned on reactivation from `decayed`. */
23
+ readonly reactivationConfidence: number;
24
+ }
25
+ export type PreferenceLifecycleEventV1 = {
26
+ readonly kind: "observed";
27
+ readonly at: number;
28
+ readonly evidenceClass: PreferenceEvidenceClassV1;
29
+ } | {
30
+ readonly kind: "promoted";
31
+ readonly at: number;
32
+ readonly toConfidence: number;
33
+ readonly policyVersion: string;
34
+ } | {
35
+ readonly kind: "used";
36
+ readonly at: number;
37
+ } | {
38
+ readonly kind: "decay_tick";
39
+ readonly at: number;
40
+ } | {
41
+ readonly kind: "archive";
42
+ readonly at: number;
43
+ readonly reason: string;
44
+ };
45
+ export type PreferenceLifecycleStateV1 = "active" | "decayed" | "archived";
46
+ export interface PreferenceLifecycleTransitionV1 {
47
+ readonly kind: "observed" | "promoted" | "used" | "decay_tick" | "decayed" | "reactivated" | "archived" | "ignored";
48
+ readonly at: number;
49
+ readonly confidence: number;
50
+ readonly state: PreferenceLifecycleStateV1;
51
+ readonly policyVersion: string;
52
+ readonly detail?: string;
53
+ }
54
+ export interface PreferenceLifecycleProjectionV1 {
55
+ readonly state: PreferenceLifecycleStateV1;
56
+ readonly confidence: number;
57
+ readonly transitions: readonly PreferenceLifecycleTransitionV1[];
58
+ }
59
+ export interface PreferenceLifecycleProjectorV1 {
60
+ project(events: readonly PreferenceLifecycleEventV1[], initialConfidence?: number): PreferenceLifecycleProjectionV1;
61
+ readonly policyVersion: string;
62
+ }
63
+ export declare function createPreferenceLifecycleProjector(options: {
64
+ readonly policy: PreferenceLifecyclePolicyV1;
65
+ }): PreferenceLifecycleProjectorV1;
66
+ //# sourceMappingURL=preference-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preference-lifecycle.d.ts","sourceRoot":"","sources":["../../src/memory/preference-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAE3E,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,MAAM,0BAA0B,GACnC;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,yBAAyB,CAAA;CAAE,GACrG;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACjH;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,MAAM,MAAM,0BAA0B,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAE3E,MAAM,WAAW,+BAA+B;IAC/C,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,GAAG,aAAa,GAAG,UAAU,GAAG,SAAS,CAAC;IACpH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAC;IAC3C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,+BAA+B;IAC/C,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,SAAS,+BAA+B,EAAE,CAAC;CACjE;AAED,MAAM,WAAW,8BAA8B;IAC9C,OAAO,CAAC,MAAM,EAAE,SAAS,0BAA0B,EAAE,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,+BAA+B,CAAC;IACpH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,kCAAkC,CAAC,OAAO,EAAE;IAC3D,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAC;CAC7C,GAAG,8BAA8B,CA6HjC","sourcesContent":["/**\n * Complete preference lifecycle projection (2.4d3) — promotion (2.4d1),\n * deterministic decay, reactivation, and archiving over an event sequence.\n *\n * The projection is a pure fold over a replayable event log: the same event\n * sequence always yields the same state and transition audit, on any\n * projection instance. Decay uses the 1C.6b half-life formula\n * w × 2^(-elapsed/halfLife) anchored at the last usage/promotion; falling\n * below the floor marks the preference `decayed` (kept in the ledger, hidden\n * from recall per 1C store semantics). New evidence reactivates a decayed\n * preference; archiving is terminal and ignores later events. Every\n * transition records the policy version that governed it, so policy\n * migrations are auditable across replays. Relation networks and advanced\n * indexing remain out of scope (2.4d4).\n */\nimport type { PreferenceEvidenceClassV1 } from \"./preference-promotion.ts\";\n\nexport interface PreferenceLifecyclePolicyV1 {\n\treadonly policyVersion: string;\n\treadonly halfLifeMs: number;\n\t/** Confidence at or below which the preference becomes `decayed` (0..1). */\n\treadonly decayFloor: number;\n\t/** Confidence assigned on reactivation from `decayed`. */\n\treadonly reactivationConfidence: number;\n}\n\nexport type PreferenceLifecycleEventV1 =\n\t| { readonly kind: \"observed\"; readonly at: number; readonly evidenceClass: PreferenceEvidenceClassV1 }\n\t| { readonly kind: \"promoted\"; readonly at: number; readonly toConfidence: number; readonly policyVersion: string }\n\t| { readonly kind: \"used\"; readonly at: number }\n\t| { readonly kind: \"decay_tick\"; readonly at: number }\n\t| { readonly kind: \"archive\"; readonly at: number; readonly reason: string };\n\nexport type PreferenceLifecycleStateV1 = \"active\" | \"decayed\" | \"archived\";\n\nexport interface PreferenceLifecycleTransitionV1 {\n\treadonly kind: \"observed\" | \"promoted\" | \"used\" | \"decay_tick\" | \"decayed\" | \"reactivated\" | \"archived\" | \"ignored\";\n\treadonly at: number;\n\treadonly confidence: number;\n\treadonly state: PreferenceLifecycleStateV1;\n\treadonly policyVersion: string;\n\treadonly detail?: string;\n}\n\nexport interface PreferenceLifecycleProjectionV1 {\n\treadonly state: PreferenceLifecycleStateV1;\n\treadonly confidence: number;\n\treadonly transitions: readonly PreferenceLifecycleTransitionV1[];\n}\n\nexport interface PreferenceLifecycleProjectorV1 {\n\tproject(events: readonly PreferenceLifecycleEventV1[], initialConfidence?: number): PreferenceLifecycleProjectionV1;\n\treadonly policyVersion: string;\n}\n\nexport function createPreferenceLifecycleProjector(options: {\n\treadonly policy: PreferenceLifecyclePolicyV1;\n}): PreferenceLifecycleProjectorV1 {\n\tconst policy = options.policy;\n\tif (policy.halfLifeMs <= 0) throw new Error(\"Lifecycle halfLifeMs must be positive\");\n\tif (policy.decayFloor < 0 || policy.decayFloor >= 1) throw new Error(\"Lifecycle decayFloor must be within [0..1)\");\n\tif (policy.reactivationConfidence <= policy.decayFloor) {\n\t\tthrow new Error(\"Lifecycle reactivationConfidence must exceed decayFloor\");\n\t}\n\n\treturn {\n\t\tpolicyVersion: policy.policyVersion,\n\t\tproject(events, initialConfidence = 0.5) {\n\t\t\tlet state: PreferenceLifecycleStateV1 = \"active\";\n\t\t\tlet confidence = initialConfidence;\n\t\t\tlet anchor = events[0]?.at ?? 0;\n\t\t\tconst transitions: PreferenceLifecycleTransitionV1[] = [];\n\t\t\tconst record = (transition: PreferenceLifecycleTransitionV1): void => {\n\t\t\t\ttransitions.push(transition);\n\t\t\t};\n\n\t\t\tfor (const event of events) {\n\t\t\t\tif (state === \"archived\") {\n\t\t\t\t\trecord({\n\t\t\t\t\t\tkind: \"ignored\",\n\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\tdetail: \"Terminal archived state ignores later events\",\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tswitch (event.kind) {\n\t\t\t\t\tcase \"observed\": {\n\t\t\t\t\t\tif (state === \"decayed\") {\n\t\t\t\t\t\t\tstate = \"active\";\n\t\t\t\t\t\t\tconfidence = policy.reactivationConfidence;\n\t\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\t\tkind: \"reactivated\",\n\t\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\t\tdetail: `Reactivated by ${event.evidenceClass} evidence`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\t\tkind: \"observed\",\n\t\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\t\tdetail: event.evidenceClass,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tanchor = event.at;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"promoted\": {\n\t\t\t\t\t\tconfidence = Math.max(event.toConfidence, confidence);\n\t\t\t\t\t\tanchor = event.at;\n\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\tkind: \"promoted\",\n\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\tpolicyVersion: event.policyVersion,\n\t\t\t\t\t\t\tdetail: \"Confidence raised by promotion policy\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"used\": {\n\t\t\t\t\t\tanchor = event.at;\n\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\tkind: \"used\",\n\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\tdetail: \"Decay anchor refreshed\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"decay_tick\": {\n\t\t\t\t\t\tconst elapsed = event.at - anchor;\n\t\t\t\t\t\tconfidence = elapsed > 0 ? confidence * 2 ** (-elapsed / policy.halfLifeMs) : confidence;\n\t\t\t\t\t\tanchor = event.at;\n\t\t\t\t\t\tif (confidence <= policy.decayFloor) {\n\t\t\t\t\t\t\tstate = \"decayed\";\n\t\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\t\tkind: \"decayed\",\n\t\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\t\tdetail: `Confidence fell to the decay floor ${policy.decayFloor}`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\t\tkind: \"decay_tick\",\n\t\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\t\tdetail: `Decayed to ${confidence}`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"archive\": {\n\t\t\t\t\t\tstate = \"archived\";\n\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\tkind: \"archived\",\n\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\tdetail: event.reason,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { state, confidence, transitions: Object.freeze(transitions) };\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,129 @@
1
+ export function createPreferenceLifecycleProjector(options) {
2
+ const policy = options.policy;
3
+ if (policy.halfLifeMs <= 0)
4
+ throw new Error("Lifecycle halfLifeMs must be positive");
5
+ if (policy.decayFloor < 0 || policy.decayFloor >= 1)
6
+ throw new Error("Lifecycle decayFloor must be within [0..1)");
7
+ if (policy.reactivationConfidence <= policy.decayFloor) {
8
+ throw new Error("Lifecycle reactivationConfidence must exceed decayFloor");
9
+ }
10
+ return {
11
+ policyVersion: policy.policyVersion,
12
+ project(events, initialConfidence = 0.5) {
13
+ let state = "active";
14
+ let confidence = initialConfidence;
15
+ let anchor = events[0]?.at ?? 0;
16
+ const transitions = [];
17
+ const record = (transition) => {
18
+ transitions.push(transition);
19
+ };
20
+ for (const event of events) {
21
+ if (state === "archived") {
22
+ record({
23
+ kind: "ignored",
24
+ at: event.at,
25
+ confidence,
26
+ state,
27
+ policyVersion: policy.policyVersion,
28
+ detail: "Terminal archived state ignores later events",
29
+ });
30
+ continue;
31
+ }
32
+ switch (event.kind) {
33
+ case "observed": {
34
+ if (state === "decayed") {
35
+ state = "active";
36
+ confidence = policy.reactivationConfidence;
37
+ record({
38
+ kind: "reactivated",
39
+ at: event.at,
40
+ confidence,
41
+ state,
42
+ policyVersion: policy.policyVersion,
43
+ detail: `Reactivated by ${event.evidenceClass} evidence`,
44
+ });
45
+ }
46
+ else {
47
+ record({
48
+ kind: "observed",
49
+ at: event.at,
50
+ confidence,
51
+ state,
52
+ policyVersion: policy.policyVersion,
53
+ detail: event.evidenceClass,
54
+ });
55
+ }
56
+ anchor = event.at;
57
+ break;
58
+ }
59
+ case "promoted": {
60
+ confidence = Math.max(event.toConfidence, confidence);
61
+ anchor = event.at;
62
+ record({
63
+ kind: "promoted",
64
+ at: event.at,
65
+ confidence,
66
+ state,
67
+ policyVersion: event.policyVersion,
68
+ detail: "Confidence raised by promotion policy",
69
+ });
70
+ break;
71
+ }
72
+ case "used": {
73
+ anchor = event.at;
74
+ record({
75
+ kind: "used",
76
+ at: event.at,
77
+ confidence,
78
+ state,
79
+ policyVersion: policy.policyVersion,
80
+ detail: "Decay anchor refreshed",
81
+ });
82
+ break;
83
+ }
84
+ case "decay_tick": {
85
+ const elapsed = event.at - anchor;
86
+ confidence = elapsed > 0 ? confidence * 2 ** (-elapsed / policy.halfLifeMs) : confidence;
87
+ anchor = event.at;
88
+ if (confidence <= policy.decayFloor) {
89
+ state = "decayed";
90
+ record({
91
+ kind: "decayed",
92
+ at: event.at,
93
+ confidence,
94
+ state,
95
+ policyVersion: policy.policyVersion,
96
+ detail: `Confidence fell to the decay floor ${policy.decayFloor}`,
97
+ });
98
+ }
99
+ else {
100
+ record({
101
+ kind: "decay_tick",
102
+ at: event.at,
103
+ confidence,
104
+ state,
105
+ policyVersion: policy.policyVersion,
106
+ detail: `Decayed to ${confidence}`,
107
+ });
108
+ }
109
+ break;
110
+ }
111
+ case "archive": {
112
+ state = "archived";
113
+ record({
114
+ kind: "archived",
115
+ at: event.at,
116
+ confidence,
117
+ state,
118
+ policyVersion: policy.policyVersion,
119
+ detail: event.reason,
120
+ });
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ return { state, confidence, transitions: Object.freeze(transitions) };
126
+ },
127
+ };
128
+ }
129
+ //# sourceMappingURL=preference-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preference-lifecycle.js","sourceRoot":"","sources":["../../src/memory/preference-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Complete preference lifecycle projection (2.4d3) — promotion (2.4d1),\n * deterministic decay, reactivation, and archiving over an event sequence.\n *\n * The projection is a pure fold over a replayable event log: the same event\n * sequence always yields the same state and transition audit, on any\n * projection instance. Decay uses the 1C.6b half-life formula\n * w × 2^(-elapsed/halfLife) anchored at the last usage/promotion; falling\n * below the floor marks the preference `decayed` (kept in the ledger, hidden\n * from recall per 1C store semantics). New evidence reactivates a decayed\n * preference; archiving is terminal and ignores later events. Every\n * transition records the policy version that governed it, so policy\n * migrations are auditable across replays. Relation networks and advanced\n * indexing remain out of scope (2.4d4).\n */\nimport type { PreferenceEvidenceClassV1 } from \"./preference-promotion.ts\";\n\nexport interface PreferenceLifecyclePolicyV1 {\n\treadonly policyVersion: string;\n\treadonly halfLifeMs: number;\n\t/** Confidence at or below which the preference becomes `decayed` (0..1). */\n\treadonly decayFloor: number;\n\t/** Confidence assigned on reactivation from `decayed`. */\n\treadonly reactivationConfidence: number;\n}\n\nexport type PreferenceLifecycleEventV1 =\n\t| { readonly kind: \"observed\"; readonly at: number; readonly evidenceClass: PreferenceEvidenceClassV1 }\n\t| { readonly kind: \"promoted\"; readonly at: number; readonly toConfidence: number; readonly policyVersion: string }\n\t| { readonly kind: \"used\"; readonly at: number }\n\t| { readonly kind: \"decay_tick\"; readonly at: number }\n\t| { readonly kind: \"archive\"; readonly at: number; readonly reason: string };\n\nexport type PreferenceLifecycleStateV1 = \"active\" | \"decayed\" | \"archived\";\n\nexport interface PreferenceLifecycleTransitionV1 {\n\treadonly kind: \"observed\" | \"promoted\" | \"used\" | \"decay_tick\" | \"decayed\" | \"reactivated\" | \"archived\" | \"ignored\";\n\treadonly at: number;\n\treadonly confidence: number;\n\treadonly state: PreferenceLifecycleStateV1;\n\treadonly policyVersion: string;\n\treadonly detail?: string;\n}\n\nexport interface PreferenceLifecycleProjectionV1 {\n\treadonly state: PreferenceLifecycleStateV1;\n\treadonly confidence: number;\n\treadonly transitions: readonly PreferenceLifecycleTransitionV1[];\n}\n\nexport interface PreferenceLifecycleProjectorV1 {\n\tproject(events: readonly PreferenceLifecycleEventV1[], initialConfidence?: number): PreferenceLifecycleProjectionV1;\n\treadonly policyVersion: string;\n}\n\nexport function createPreferenceLifecycleProjector(options: {\n\treadonly policy: PreferenceLifecyclePolicyV1;\n}): PreferenceLifecycleProjectorV1 {\n\tconst policy = options.policy;\n\tif (policy.halfLifeMs <= 0) throw new Error(\"Lifecycle halfLifeMs must be positive\");\n\tif (policy.decayFloor < 0 || policy.decayFloor >= 1) throw new Error(\"Lifecycle decayFloor must be within [0..1)\");\n\tif (policy.reactivationConfidence <= policy.decayFloor) {\n\t\tthrow new Error(\"Lifecycle reactivationConfidence must exceed decayFloor\");\n\t}\n\n\treturn {\n\t\tpolicyVersion: policy.policyVersion,\n\t\tproject(events, initialConfidence = 0.5) {\n\t\t\tlet state: PreferenceLifecycleStateV1 = \"active\";\n\t\t\tlet confidence = initialConfidence;\n\t\t\tlet anchor = events[0]?.at ?? 0;\n\t\t\tconst transitions: PreferenceLifecycleTransitionV1[] = [];\n\t\t\tconst record = (transition: PreferenceLifecycleTransitionV1): void => {\n\t\t\t\ttransitions.push(transition);\n\t\t\t};\n\n\t\t\tfor (const event of events) {\n\t\t\t\tif (state === \"archived\") {\n\t\t\t\t\trecord({\n\t\t\t\t\t\tkind: \"ignored\",\n\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\tdetail: \"Terminal archived state ignores later events\",\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tswitch (event.kind) {\n\t\t\t\t\tcase \"observed\": {\n\t\t\t\t\t\tif (state === \"decayed\") {\n\t\t\t\t\t\t\tstate = \"active\";\n\t\t\t\t\t\t\tconfidence = policy.reactivationConfidence;\n\t\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\t\tkind: \"reactivated\",\n\t\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\t\tdetail: `Reactivated by ${event.evidenceClass} evidence`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\t\tkind: \"observed\",\n\t\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\t\tdetail: event.evidenceClass,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tanchor = event.at;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"promoted\": {\n\t\t\t\t\t\tconfidence = Math.max(event.toConfidence, confidence);\n\t\t\t\t\t\tanchor = event.at;\n\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\tkind: \"promoted\",\n\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\tpolicyVersion: event.policyVersion,\n\t\t\t\t\t\t\tdetail: \"Confidence raised by promotion policy\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"used\": {\n\t\t\t\t\t\tanchor = event.at;\n\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\tkind: \"used\",\n\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\tdetail: \"Decay anchor refreshed\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"decay_tick\": {\n\t\t\t\t\t\tconst elapsed = event.at - anchor;\n\t\t\t\t\t\tconfidence = elapsed > 0 ? confidence * 2 ** (-elapsed / policy.halfLifeMs) : confidence;\n\t\t\t\t\t\tanchor = event.at;\n\t\t\t\t\t\tif (confidence <= policy.decayFloor) {\n\t\t\t\t\t\t\tstate = \"decayed\";\n\t\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\t\tkind: \"decayed\",\n\t\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\t\tdetail: `Confidence fell to the decay floor ${policy.decayFloor}`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\t\tkind: \"decay_tick\",\n\t\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\t\tdetail: `Decayed to ${confidence}`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"archive\": {\n\t\t\t\t\t\tstate = \"archived\";\n\t\t\t\t\t\trecord({\n\t\t\t\t\t\t\tkind: \"archived\",\n\t\t\t\t\t\t\tat: event.at,\n\t\t\t\t\t\t\tconfidence,\n\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\t\t\tdetail: event.reason,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { state, confidence, transitions: Object.freeze(transitions) };\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Automatic preference promotion (2.4d1) — the explicit-confirmation-free
3
+ * promotion policy over preference observations.
4
+ *
5
+ * Promotion NEVER fakes a user confirmation (no `confirmedAt`, no
6
+ * `explicit` evidence class): it deterministically raises the winning
7
+ * preference's `applicabilityConfidence` once independent repeated-behavior
8
+ * or inferred observations reach the policy thresholds. Policies carry a
9
+ * version and an explicit `enabled` switch — a disabled policy returns
10
+ * `disabled` for every input and never promotes silently. The same
11
+ * observation sequence under the same policy version resolves identically
12
+ * on any engine instance, so decisions are migratable/replayable.
13
+ * Complex disambiguation (2.4d2), full lifecycle promotion/decay (2.4d3),
14
+ * and networks (2.4d4) are out of scope.
15
+ */
16
+ import type { JsonValue } from "@agent-forge/plugin-sdk";
17
+ import type { MemoryAtomV1, MemoryPreferenceEnvelopeV1 } from "./foundation.ts";
18
+ export interface PreferencePromotionPolicyV1 {
19
+ /** Master switch; disabled engines never promote. */
20
+ readonly enabled: boolean;
21
+ readonly policyVersion: string;
22
+ /** Independent repeated-behavior observations required for promotion. */
23
+ readonly repeatedBehaviorThreshold: number;
24
+ /** Independent inferred observations required for promotion. */
25
+ readonly inferredThreshold: number;
26
+ /** The confidence assigned by a promotion decision (0..1]. */
27
+ readonly promotedConfidence: number;
28
+ }
29
+ export type PreferenceEvidenceClassV1 = MemoryPreferenceEnvelopeV1["evidence"]["class"];
30
+ export interface PreferenceObservationV1 {
31
+ readonly evidenceClass: PreferenceEvidenceClassV1;
32
+ /** Distinct observation identity; duplicates within a sequence are ignored. */
33
+ readonly observationId: string;
34
+ readonly confidence?: number;
35
+ }
36
+ export type PreferencePromotionDecisionV1 = {
37
+ readonly decision: "promoted";
38
+ readonly policyVersion: string;
39
+ readonly fromConfidence: number;
40
+ readonly toConfidence: number;
41
+ readonly supportingObservations: number;
42
+ } | {
43
+ readonly decision: "not_promoted";
44
+ readonly policyVersion: string;
45
+ readonly supportingObservations: number;
46
+ readonly requiredObservations: number;
47
+ } | {
48
+ readonly decision: "disabled";
49
+ readonly policyVersion: string;
50
+ };
51
+ export interface PreferencePromotionEngineV1 {
52
+ /** Evaluates one preference candidate (same subject+key+value) deterministically. */
53
+ evaluate(input: {
54
+ readonly currentConfidence: number;
55
+ readonly observations: readonly PreferenceObservationV1[];
56
+ }): PreferencePromotionDecisionV1;
57
+ readonly policyVersion: string;
58
+ }
59
+ export declare function createPreferencePromotionEngine(options: {
60
+ readonly policy: PreferencePromotionPolicyV1;
61
+ }): PreferencePromotionEngineV1;
62
+ /** Serializes a policy for migration/replay ledgers. */
63
+ export declare function serializePromotionPolicy(policy: PreferencePromotionPolicyV1): JsonValue;
64
+ export interface PromotePreferenceToUserDefaultInputV1 {
65
+ /** The source preference atom; it is preserved untouched. */
66
+ readonly atom: MemoryAtomV1;
67
+ /** Who authorized the promotion (explicit confirmation provenance); required. */
68
+ readonly authorizedBy: string;
69
+ /** ISO-8601 confirmation timestamp stamped into the promoted envelope's `confirmedAt`. */
70
+ readonly confirmedAt: string;
71
+ }
72
+ /**
73
+ * Explicitly promotes a preference atom to the user-default scope (方案系统
74
+ * 设计 §6.1 跨方案规则). Produces a NEW canonical atom — the original is never
75
+ * rewritten — whose envelope carries `scope.level "user-default"` and
76
+ * `confirmedAt`, the canonical explicit-confirmation marker the suite read
77
+ * boundary ({@link memorySuiteVisibility} in foundation.ts) requires for
78
+ * cross-suite visibility. Scope anchors are intentionally dropped: a
79
+ * user-default preference applies without narrowing. The automatic
80
+ * applicabilityConfidence policy above stays orthogonal — it never touches
81
+ * user-default and never sets `confirmedAt`.
82
+ *
83
+ * Rejects non-preference atoms, atoms already at user-default, and missing
84
+ * `authorizedBy`/`confirmedAt`.
85
+ */
86
+ export declare function promotePreferenceToUserDefault(input: PromotePreferenceToUserDefaultInputV1): MemoryAtomV1;
87
+ //# sourceMappingURL=preference-promotion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preference-promotion.d.ts","sourceRoot":"","sources":["../../src/memory/preference-promotion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAGhF,MAAM,WAAW,2BAA2B;IAC3C,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;IAC3C,gEAAgE;IAChE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,MAAM,yBAAyB,GAAG,0BAA0B,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC;AAExF,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,aAAa,EAAE,yBAAyB,CAAC;IAClD,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,6BAA6B,GACtC;IACA,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACvC,GACD;IACA,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACrC,GACD;IAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE,MAAM,WAAW,2BAA2B;IAC3C,qFAAqF;IACrF,QAAQ,CAAC,KAAK,EAAE;QACf,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;QACnC,QAAQ,CAAC,YAAY,EAAE,SAAS,uBAAuB,EAAE,CAAC;KAC1D,GAAG,6BAA6B,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,+BAA+B,CAAC,OAAO,EAAE;IACxD,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAC;CAC7C,GAAG,2BAA2B,CAiD9B;AAED,wDAAwD;AACxD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,2BAA2B,GAAG,SAAS,CAQvF;AAMD,MAAM,WAAW,qCAAqC;IACrD,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0FAA0F;IAC1F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,qCAAqC,GAAG,YAAY,CA6BzG","sourcesContent":["/**\n * Automatic preference promotion (2.4d1) — the explicit-confirmation-free\n * promotion policy over preference observations.\n *\n * Promotion NEVER fakes a user confirmation (no `confirmedAt`, no\n * `explicit` evidence class): it deterministically raises the winning\n * preference's `applicabilityConfidence` once independent repeated-behavior\n * or inferred observations reach the policy thresholds. Policies carry a\n * version and an explicit `enabled` switch — a disabled policy returns\n * `disabled` for every input and never promotes silently. The same\n * observation sequence under the same policy version resolves identically\n * on any engine instance, so decisions are migratable/replayable.\n * Complex disambiguation (2.4d2), full lifecycle promotion/decay (2.4d3),\n * and networks (2.4d4) are out of scope.\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryAtomV1, MemoryPreferenceEnvelopeV1 } from \"./foundation.ts\";\nimport { validateMemoryAtomV1 } from \"./foundation.ts\";\n\nexport interface PreferencePromotionPolicyV1 {\n\t/** Master switch; disabled engines never promote. */\n\treadonly enabled: boolean;\n\treadonly policyVersion: string;\n\t/** Independent repeated-behavior observations required for promotion. */\n\treadonly repeatedBehaviorThreshold: number;\n\t/** Independent inferred observations required for promotion. */\n\treadonly inferredThreshold: number;\n\t/** The confidence assigned by a promotion decision (0..1]. */\n\treadonly promotedConfidence: number;\n}\n\nexport type PreferenceEvidenceClassV1 = MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"];\n\nexport interface PreferenceObservationV1 {\n\treadonly evidenceClass: PreferenceEvidenceClassV1;\n\t/** Distinct observation identity; duplicates within a sequence are ignored. */\n\treadonly observationId: string;\n\treadonly confidence?: number;\n}\n\nexport type PreferencePromotionDecisionV1 =\n\t| {\n\t\t\treadonly decision: \"promoted\";\n\t\t\treadonly policyVersion: string;\n\t\t\treadonly fromConfidence: number;\n\t\t\treadonly toConfidence: number;\n\t\t\treadonly supportingObservations: number;\n\t }\n\t| {\n\t\t\treadonly decision: \"not_promoted\";\n\t\t\treadonly policyVersion: string;\n\t\t\treadonly supportingObservations: number;\n\t\t\treadonly requiredObservations: number;\n\t }\n\t| { readonly decision: \"disabled\"; readonly policyVersion: string };\n\nexport interface PreferencePromotionEngineV1 {\n\t/** Evaluates one preference candidate (same subject+key+value) deterministically. */\n\tevaluate(input: {\n\t\treadonly currentConfidence: number;\n\t\treadonly observations: readonly PreferenceObservationV1[];\n\t}): PreferencePromotionDecisionV1;\n\treadonly policyVersion: string;\n}\n\nexport function createPreferencePromotionEngine(options: {\n\treadonly policy: PreferencePromotionPolicyV1;\n}): PreferencePromotionEngineV1 {\n\tconst policy = options.policy;\n\tif (policy.promotedConfidence <= 0 || policy.promotedConfidence > 1) {\n\t\tthrow new Error(\"Promotion promotedConfidence must be within (0..1]\");\n\t}\n\tif (policy.repeatedBehaviorThreshold < 1 || policy.inferredThreshold < 1) {\n\t\tthrow new Error(\"Promotion thresholds must be at least 1 observation\");\n\t}\n\n\treturn {\n\t\tpolicyVersion: policy.policyVersion,\n\t\tevaluate(input) {\n\t\t\tif (!policy.enabled) {\n\t\t\t\treturn { decision: \"disabled\", policyVersion: policy.policyVersion };\n\t\t\t}\n\t\t\t// Independent observations only: dedupe by observationId deterministically.\n\t\t\tconst seen = new Map<string, PreferenceObservationV1>();\n\t\t\tfor (const observation of input.observations) {\n\t\t\t\tif (observation.evidenceClass !== \"repeated_behavior\" && observation.evidenceClass !== \"inferred\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!seen.has(observation.observationId)) seen.set(observation.observationId, observation);\n\t\t\t}\n\t\t\tconst supporting = [...seen.values()].filter((observation) => (observation.confidence ?? 1) >= 0.5);\n\n\t\t\tconst repeatedCount = supporting.filter(\n\t\t\t\t(observation) => observation.evidenceClass === \"repeated_behavior\",\n\t\t\t).length;\n\t\t\tconst inferredCount = supporting.filter((observation) => observation.evidenceClass === \"inferred\").length;\n\t\t\tconst required = repeatedCount >= inferredCount ? policy.repeatedBehaviorThreshold : policy.inferredThreshold;\n\t\t\tconst eligibleCount = Math.max(repeatedCount, inferredCount);\n\n\t\t\tif (repeatedCount >= policy.repeatedBehaviorThreshold || inferredCount >= policy.inferredThreshold) {\n\t\t\t\treturn {\n\t\t\t\t\tdecision: \"promoted\",\n\t\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\t\tfromConfidence: input.currentConfidence,\n\t\t\t\t\ttoConfidence: Math.max(policy.promotedConfidence, input.currentConfidence),\n\t\t\t\t\tsupportingObservations: eligibleCount,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tdecision: \"not_promoted\",\n\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\tsupportingObservations: eligibleCount,\n\t\t\t\trequiredObservations: required,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/** Serializes a policy for migration/replay ledgers. */\nexport function serializePromotionPolicy(policy: PreferencePromotionPolicyV1): JsonValue {\n\treturn {\n\t\tenabled: policy.enabled,\n\t\tpolicyVersion: policy.policyVersion,\n\t\trepeatedBehaviorThreshold: policy.repeatedBehaviorThreshold,\n\t\tinferredThreshold: policy.inferredThreshold,\n\t\tpromotedConfidence: policy.promotedConfidence,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Explicit user-confirmed promotion to user-default (方案系统设计 §6.1, M5)\n// ---------------------------------------------------------------------------\n\nexport interface PromotePreferenceToUserDefaultInputV1 {\n\t/** The source preference atom; it is preserved untouched. */\n\treadonly atom: MemoryAtomV1;\n\t/** Who authorized the promotion (explicit confirmation provenance); required. */\n\treadonly authorizedBy: string;\n\t/** ISO-8601 confirmation timestamp stamped into the promoted envelope's `confirmedAt`. */\n\treadonly confirmedAt: string;\n}\n\n/**\n * Explicitly promotes a preference atom to the user-default scope (方案系统\n * 设计 §6.1 跨方案规则). Produces a NEW canonical atom — the original is never\n * rewritten — whose envelope carries `scope.level \"user-default\"` and\n * `confirmedAt`, the canonical explicit-confirmation marker the suite read\n * boundary ({@link memorySuiteVisibility} in foundation.ts) requires for\n * cross-suite visibility. Scope anchors are intentionally dropped: a\n * user-default preference applies without narrowing. The automatic\n * applicabilityConfidence policy above stays orthogonal — it never touches\n * user-default and never sets `confirmedAt`.\n *\n * Rejects non-preference atoms, atoms already at user-default, and missing\n * `authorizedBy`/`confirmedAt`.\n */\nexport function promotePreferenceToUserDefault(input: PromotePreferenceToUserDefaultInputV1): MemoryAtomV1 {\n\tif (typeof input.authorizedBy !== \"string\" || input.authorizedBy.trim().length === 0) {\n\t\tthrow new Error(\"User-default preference promotion requires a non-empty authorizedBy\");\n\t}\n\tif (typeof input.confirmedAt !== \"string\" || input.confirmedAt.trim().length === 0) {\n\t\tthrow new Error(\"User-default preference promotion requires a non-empty confirmedAt\");\n\t}\n\tif (Number.isNaN(Date.parse(input.confirmedAt))) {\n\t\tthrow new Error(\"User-default preference promotion confirmedAt must be an ISO-8601 timestamp\");\n\t}\n\tconst atom = input.atom;\n\tif (atom.memoryKind !== \"preference\" || atom.preference === undefined) {\n\t\tthrow new Error(\"User-default preference promotion requires a preference atom with a preference envelope\");\n\t}\n\tif (atom.preference.scope.level === \"user-default\") {\n\t\tthrow new Error(`Preference ${atom.memoryId} is already in the user-default scope`);\n\t}\n\tconst envelope: MemoryPreferenceEnvelopeV1 = {\n\t\t...atom.preference,\n\t\tscope: { level: \"user-default\" },\n\t\tconfirmedAt: input.confirmedAt,\n\t};\n\treturn validateMemoryAtomV1({\n\t\t...atom,\n\t\tmemoryId: `${atom.memoryId}-user-default`,\n\t\tobservationId: `${atom.observationId}-user-default`,\n\t\tpreference: envelope,\n\t\twriteReason: `promote-preference-to-user-default by ${input.authorizedBy}`,\n\t});\n}\n"]}