@graphorin/memory 0.6.1 → 0.7.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 (227) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +1 -1
  3. package/dist/conflict/index.js +0 -1
  4. package/dist/conflict/pipeline.js +0 -1
  5. package/dist/conflict/pipeline.js.map +1 -1
  6. package/dist/conflict/stages/stage3-heuristic-regex.js +0 -1
  7. package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
  8. package/dist/conflict/types.d.ts +1 -1
  9. package/dist/conflict/types.js.map +1 -1
  10. package/dist/consolidator/index.js +2 -0
  11. package/dist/consolidator/index.js.map +1 -1
  12. package/dist/consolidator/lock.js +7 -1
  13. package/dist/consolidator/lock.js.map +1 -1
  14. package/dist/consolidator/phases/deep.js +19 -12
  15. package/dist/consolidator/phases/deep.js.map +1 -1
  16. package/dist/consolidator/phases/induce.d.ts.map +1 -1
  17. package/dist/consolidator/phases/induce.js +2 -3
  18. package/dist/consolidator/phases/induce.js.map +1 -1
  19. package/dist/consolidator/phases/reflect.js +35 -8
  20. package/dist/consolidator/phases/reflect.js.map +1 -1
  21. package/dist/consolidator/phases/standard.js +199 -28
  22. package/dist/consolidator/phases/standard.js.map +1 -1
  23. package/dist/consolidator/reconcile.js +16 -15
  24. package/dist/consolidator/reconcile.js.map +1 -1
  25. package/dist/consolidator/runtime.d.ts.map +1 -1
  26. package/dist/consolidator/runtime.js +129 -47
  27. package/dist/consolidator/runtime.js.map +1 -1
  28. package/dist/consolidator/scheduler.d.ts +17 -3
  29. package/dist/consolidator/scheduler.d.ts.map +1 -1
  30. package/dist/consolidator/scheduler.js.map +1 -1
  31. package/dist/consolidator/types.d.ts +30 -1
  32. package/dist/consolidator/types.d.ts.map +1 -1
  33. package/dist/consolidator/types.js +10 -0
  34. package/dist/consolidator/types.js.map +1 -1
  35. package/dist/context-engine/compaction/clear-tool-results.d.ts +1 -1
  36. package/dist/context-engine/compaction/clear-tool-results.js +1 -1
  37. package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
  38. package/dist/context-engine/compaction/compactor.d.ts +1 -0
  39. package/dist/context-engine/compaction/compactor.d.ts.map +1 -1
  40. package/dist/context-engine/compaction/compactor.js +32 -8
  41. package/dist/context-engine/compaction/compactor.js.map +1 -1
  42. package/dist/context-engine/compaction/hooks/types.d.ts +1 -1
  43. package/dist/context-engine/compaction/index.d.ts +1 -0
  44. package/dist/context-engine/compaction/templates/marker.d.ts +33 -0
  45. package/dist/context-engine/compaction/templates/marker.d.ts.map +1 -0
  46. package/dist/context-engine/compaction/templates/marker.js +34 -0
  47. package/dist/context-engine/compaction/templates/marker.js.map +1 -0
  48. package/dist/context-engine/compaction/templates/summary-9-section.d.ts +1 -1
  49. package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
  50. package/dist/context-engine/compaction/templates/summary-9-section.js +4 -3
  51. package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
  52. package/dist/context-engine/engine.d.ts +7 -176
  53. package/dist/context-engine/engine.d.ts.map +1 -1
  54. package/dist/context-engine/engine.js +0 -2
  55. package/dist/context-engine/engine.js.map +1 -1
  56. package/dist/context-engine/index.d.ts +8 -49
  57. package/dist/context-engine/io-types.d.ts +144 -0
  58. package/dist/context-engine/io-types.d.ts.map +1 -0
  59. package/dist/context-engine/locale-packs/types.d.ts +1 -1
  60. package/dist/context-engine/locale-packs/types.js.map +1 -1
  61. package/dist/context-engine/privacy-filter.d.ts +1 -2
  62. package/dist/context-engine/privacy-filter.d.ts.map +1 -1
  63. package/dist/context-engine/privacy-filter.js +1 -2
  64. package/dist/context-engine/privacy-filter.js.map +1 -1
  65. package/dist/errors/index.d.ts +2 -2
  66. package/dist/errors/index.js +2 -2
  67. package/dist/errors/index.js.map +1 -1
  68. package/dist/facade.d.ts +43 -52
  69. package/dist/facade.d.ts.map +1 -1
  70. package/dist/facade.js +5 -3
  71. package/dist/facade.js.map +1 -1
  72. package/dist/graph/entity-resolver.js +1 -1
  73. package/dist/graph/entity-resolver.js.map +1 -1
  74. package/dist/index.d.ts +16 -13
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +2 -5
  77. package/dist/index.js.map +1 -1
  78. package/dist/internal/envelope.js +57 -0
  79. package/dist/internal/envelope.js.map +1 -0
  80. package/dist/internal/injection-heuristics.js +0 -0
  81. package/dist/internal/injection-heuristics.js.map +1 -1
  82. package/dist/internal/llm-json.js +40 -0
  83. package/dist/internal/llm-json.js.map +1 -0
  84. package/dist/internal/storage-adapter.d.ts +23 -10
  85. package/dist/internal/storage-adapter.d.ts.map +1 -1
  86. package/dist/memory-interface.d.ts +131 -0
  87. package/dist/memory-interface.d.ts.map +1 -0
  88. package/dist/package.js +1 -1
  89. package/dist/package.js.map +1 -1
  90. package/dist/search/iterative.d.ts.map +1 -1
  91. package/dist/search/iterative.js +2 -3
  92. package/dist/search/iterative.js.map +1 -1
  93. package/dist/search/query-transform.d.ts.map +1 -1
  94. package/dist/search/query-transform.js +2 -3
  95. package/dist/search/query-transform.js.map +1 -1
  96. package/dist/tiers/episodic-memory.js +3 -3
  97. package/dist/tiers/episodic-memory.js.map +1 -1
  98. package/dist/tiers/index.d.ts +2 -2
  99. package/dist/tiers/insight-memory.js +1 -1
  100. package/dist/tiers/insight-memory.js.map +1 -1
  101. package/dist/tiers/procedural-memory.d.ts +1 -1
  102. package/dist/tiers/procedural-memory.js +2 -2
  103. package/dist/tiers/procedural-memory.js.map +1 -1
  104. package/dist/tiers/semantic-memory.d.ts +58 -4
  105. package/dist/tiers/semantic-memory.d.ts.map +1 -1
  106. package/dist/tiers/semantic-memory.js +103 -20
  107. package/dist/tiers/semantic-memory.js.map +1 -1
  108. package/dist/tools/block-tools.d.ts +19 -57
  109. package/dist/tools/block-tools.d.ts.map +1 -1
  110. package/dist/tools/block-tools.js.map +1 -1
  111. package/dist/tools/fact-tools.d.ts +53 -214
  112. package/dist/tools/fact-tools.d.ts.map +1 -1
  113. package/dist/tools/fact-tools.js +3 -2
  114. package/dist/tools/fact-tools.js.map +1 -1
  115. package/dist/tools/recall-tools.d.ts +34 -175
  116. package/dist/tools/recall-tools.d.ts.map +1 -1
  117. package/dist/tools/recall-tools.js +5 -4
  118. package/dist/tools/recall-tools.js.map +1 -1
  119. package/dist/tools/runbook-tools.d.ts +15 -58
  120. package/dist/tools/runbook-tools.d.ts.map +1 -1
  121. package/dist/tools/runbook-tools.js.map +1 -1
  122. package/dist/tools/types.d.ts +1 -1
  123. package/package.json +18 -17
  124. package/src/conflict/index.ts +29 -0
  125. package/src/conflict/locale-packs/en.ts +142 -0
  126. package/src/conflict/locale-packs/index.ts +17 -0
  127. package/src/conflict/locale-packs/types.ts +138 -0
  128. package/src/conflict/pipeline.ts +371 -0
  129. package/src/conflict/stages/helpers.ts +65 -0
  130. package/src/conflict/stages/stage1-exact-dedup.ts +40 -0
  131. package/src/conflict/stages/stage2-embedding-three-zone.ts +54 -0
  132. package/src/conflict/stages/stage3-heuristic-regex.ts +51 -0
  133. package/src/conflict/stages/stage4-subject-predicate.ts +46 -0
  134. package/src/conflict/stages/stage5-defer-to-deep.ts +34 -0
  135. package/src/conflict/types.ts +263 -0
  136. package/src/consolidator/budget.ts +312 -0
  137. package/src/consolidator/decay.ts +246 -0
  138. package/src/consolidator/dlq.ts +61 -0
  139. package/src/consolidator/errors.ts +87 -0
  140. package/src/consolidator/idempotency.ts +27 -0
  141. package/src/consolidator/index.ts +269 -0
  142. package/src/consolidator/lock.ts +141 -0
  143. package/src/consolidator/noise-filter.ts +235 -0
  144. package/src/consolidator/phases/deep.ts +308 -0
  145. package/src/consolidator/phases/induce.ts +423 -0
  146. package/src/consolidator/phases/learned-context.ts +233 -0
  147. package/src/consolidator/phases/light.ts +166 -0
  148. package/src/consolidator/phases/reflect.ts +504 -0
  149. package/src/consolidator/phases/standard.ts +1002 -0
  150. package/src/consolidator/reconcile.ts +273 -0
  151. package/src/consolidator/runtime.ts +1049 -0
  152. package/src/consolidator/scheduler.ts +182 -0
  153. package/src/consolidator/triggers.ts +134 -0
  154. package/src/consolidator/types.ts +622 -0
  155. package/src/context-engine/annotations.ts +161 -0
  156. package/src/context-engine/auto-recall.ts +92 -0
  157. package/src/context-engine/compaction/clear-tool-results.ts +232 -0
  158. package/src/context-engine/compaction/compactor.ts +403 -0
  159. package/src/context-engine/compaction/hooks/reanchor-persona-block.ts +55 -0
  160. package/src/context-engine/compaction/hooks/reanchor-pinned-facts.ts +66 -0
  161. package/src/context-engine/compaction/hooks/reanchor-project-rules.ts +57 -0
  162. package/src/context-engine/compaction/hooks/reanchor-recent-results.ts +116 -0
  163. package/src/context-engine/compaction/hooks/types.ts +68 -0
  164. package/src/context-engine/compaction/index.ts +60 -0
  165. package/src/context-engine/compaction/templates/marker.ts +33 -0
  166. package/src/context-engine/compaction/templates/summary-9-section.ts +223 -0
  167. package/src/context-engine/compaction/thresholds.ts +107 -0
  168. package/src/context-engine/compaction/types.ts +255 -0
  169. package/src/context-engine/engine.ts +970 -0
  170. package/src/context-engine/index.ts +118 -0
  171. package/src/context-engine/io-types.ts +162 -0
  172. package/src/context-engine/locale-packs/en.ts +107 -0
  173. package/src/context-engine/locale-packs/index.ts +19 -0
  174. package/src/context-engine/locale-packs/resolver.ts +197 -0
  175. package/src/context-engine/locale-packs/types.ts +177 -0
  176. package/src/context-engine/metadata.ts +114 -0
  177. package/src/context-engine/preambles/inbound-en.ts +19 -0
  178. package/src/context-engine/privacy-filter.ts +190 -0
  179. package/src/context-engine/templates/base-en.ts +19 -0
  180. package/src/context-engine/templates/composer.ts +91 -0
  181. package/src/context-engine/token-budget.ts +319 -0
  182. package/src/context-engine/token-counter.ts +157 -0
  183. package/src/errors/index.ts +273 -0
  184. package/src/facade.ts +750 -0
  185. package/src/graph/entity-resolver.ts +364 -0
  186. package/src/graph/index.ts +24 -0
  187. package/src/index.ts +259 -0
  188. package/src/internal/contextualize.ts +173 -0
  189. package/src/internal/embedder-binding.ts +53 -0
  190. package/src/internal/envelope.ts +70 -0
  191. package/src/internal/id.ts +23 -0
  192. package/src/internal/injection-heuristics.ts +0 -0
  193. package/src/internal/llm-json.ts +38 -0
  194. package/src/internal/spans.ts +35 -0
  195. package/src/internal/storage-adapter.ts +963 -0
  196. package/src/memory-interface.ts +158 -0
  197. package/src/migration/embedder-migration.ts +237 -0
  198. package/src/migration/index.ts +13 -0
  199. package/src/search/explain.ts +123 -0
  200. package/src/search/fit-weights.ts +109 -0
  201. package/src/search/graph-ppr.ts +38 -0
  202. package/src/search/index.ts +55 -0
  203. package/src/search/iterative.ts +658 -0
  204. package/src/search/query-transform.ts +285 -0
  205. package/src/search/rrf.ts +225 -0
  206. package/src/search/trust.ts +55 -0
  207. package/src/search/types.ts +50 -0
  208. package/src/tiers/episodic-memory.ts +463 -0
  209. package/src/tiers/index.ts +50 -0
  210. package/src/tiers/insight-memory.ts +208 -0
  211. package/src/tiers/procedural-memory.ts +482 -0
  212. package/src/tiers/semantic-memory.ts +1729 -0
  213. package/src/tiers/session-memory.ts +271 -0
  214. package/src/tiers/shared-memory.ts +62 -0
  215. package/src/tiers/working-memory.ts +368 -0
  216. package/src/tools/block-tools.ts +172 -0
  217. package/src/tools/fact-tools.ts +473 -0
  218. package/src/tools/index.ts +109 -0
  219. package/src/tools/recall-tools.ts +303 -0
  220. package/src/tools/runbook-tools.ts +107 -0
  221. package/src/tools/taint.ts +39 -0
  222. package/src/tools/types.ts +37 -0
  223. package/dist/conflict/locale-packs/index.js +0 -4
  224. package/dist/context-engine/compaction/index.js +0 -10
  225. package/dist/context-engine/index.d.ts.map +0 -1
  226. package/dist/context-engine/index.js +0 -25
  227. package/dist/context-engine/locale-packs/index.js +0 -4
@@ -0,0 +1,172 @@
1
+ import type { Tool } from '@graphorin/core';
2
+ import { tool } from '@graphorin/tools';
3
+ import { z } from 'zod';
4
+ import type { MemoryToolDeps } from './types.js';
5
+
6
+ const blockAppendInputSchema = z.object({
7
+ label: z.string().min(1).max(128),
8
+ content: z.string().min(1),
9
+ });
10
+ const blockAppendOutputSchema = z.object({
11
+ label: z.string(),
12
+ length: z.number(),
13
+ });
14
+
15
+ /**
16
+ * W-013: explicit interfaces instead of `z.infer<typeof schema>` - the
17
+ * inferred aliases baked concrete v3 zod object generics into
18
+ * the published d.ts, which do not typecheck under a zod@4 consumer.
19
+ * Interface<->schema equality is pinned by type tests.
20
+ */
21
+ export interface BlockAppendInput {
22
+ label: string;
23
+ content: string;
24
+ }
25
+ export interface BlockAppendOutput {
26
+ label: string;
27
+ length: number;
28
+ }
29
+
30
+ const blockReplaceInputSchema = z.object({
31
+ label: z.string().min(1).max(128),
32
+ oldUnique: z.string().min(1),
33
+ newText: z.string(),
34
+ });
35
+ const blockReplaceOutputSchema = z.object({
36
+ label: z.string(),
37
+ length: z.number(),
38
+ });
39
+
40
+ export interface BlockReplaceInput {
41
+ label: string;
42
+ oldUnique: string;
43
+ newText: string;
44
+ }
45
+ export interface BlockReplaceOutput {
46
+ label: string;
47
+ length: number;
48
+ }
49
+
50
+ const blockRethinkInputSchema = z.object({
51
+ label: z.string().min(1).max(128),
52
+ newValue: z.string(),
53
+ });
54
+ const blockRethinkOutputSchema = z.object({
55
+ label: z.string(),
56
+ length: z.number(),
57
+ });
58
+
59
+ export interface BlockRethinkInput {
60
+ label: string;
61
+ newValue: string;
62
+ }
63
+ export interface BlockRethinkOutput {
64
+ label: string;
65
+ length: number;
66
+ }
67
+
68
+ // W-013 parity gate (compile-time only, erased from the build and the
69
+ // d.ts): each explicit interface must stay MUTUALLY assignable with its
70
+ // schema's inference - a drifted transcription fails `tsc` right here.
71
+ type W013Equals<A, B> = A extends B ? (B extends A ? true : false) : false;
72
+ type W013Assert<T extends true> = T;
73
+ type _W013Check1 = W013Assert<W013Equals<BlockAppendInput, z.infer<typeof blockAppendInputSchema>>>;
74
+ type _W013Check2 = W013Assert<
75
+ W013Equals<BlockAppendOutput, z.infer<typeof blockAppendOutputSchema>>
76
+ >;
77
+ type _W013Check3 = W013Assert<
78
+ W013Equals<BlockReplaceInput, z.infer<typeof blockReplaceInputSchema>>
79
+ >;
80
+ type _W013Check4 = W013Assert<
81
+ W013Equals<BlockReplaceOutput, z.infer<typeof blockReplaceOutputSchema>>
82
+ >;
83
+ type _W013Check5 = W013Assert<
84
+ W013Equals<BlockRethinkInput, z.infer<typeof blockRethinkInputSchema>>
85
+ >;
86
+ type _W013Check6 = W013Assert<
87
+ W013Equals<BlockRethinkOutput, z.infer<typeof blockRethinkOutputSchema>>
88
+ >;
89
+
90
+ /**
91
+ * `block_append` - append text (with a newline separator) to a working
92
+ * memory block.
93
+ *
94
+ * @stable
95
+ */
96
+ export function createBlockAppendTool(
97
+ deps: MemoryToolDeps,
98
+ ): Tool<BlockAppendInput, BlockAppendOutput> {
99
+ return tool<BlockAppendInput, BlockAppendOutput>({
100
+ name: 'block_append',
101
+ description:
102
+ 'Append text to a working memory block. The block is identified by its label (registered via memory.working.define(...)). Use this when accumulating notes, observations, or reasoning steps that should persist across turns.',
103
+ inputSchema: blockAppendInputSchema,
104
+ outputSchema: blockAppendOutputSchema,
105
+ sideEffectClass: 'side-effecting',
106
+ sandboxPolicy: 'none',
107
+ sensitivity: 'internal',
108
+ memoryGuardTier: 'memory-aware',
109
+ tags: ['memory', 'working'],
110
+ async execute(input, ctx) {
111
+ const scope = await deps.resolveScope(ctx);
112
+ const block = await deps.working.append(scope, input.label, input.content);
113
+ return { label: block.label, length: block.value.length };
114
+ },
115
+ });
116
+ }
117
+
118
+ /**
119
+ * `block_replace` - replace a unique substring inside a working
120
+ * memory block. Throws when the substring is missing or appears more
121
+ * than once.
122
+ *
123
+ * @stable
124
+ */
125
+ export function createBlockReplaceTool(
126
+ deps: MemoryToolDeps,
127
+ ): Tool<BlockReplaceInput, BlockReplaceOutput> {
128
+ return tool<BlockReplaceInput, BlockReplaceOutput>({
129
+ name: 'block_replace',
130
+ description:
131
+ 'Replace a UNIQUE substring inside a working memory block. The substring must appear exactly once; the call fails fast if missing or non-unique. Use block_rethink for full rewrites.',
132
+ inputSchema: blockReplaceInputSchema,
133
+ outputSchema: blockReplaceOutputSchema,
134
+ sideEffectClass: 'side-effecting',
135
+ sandboxPolicy: 'none',
136
+ sensitivity: 'internal',
137
+ memoryGuardTier: 'memory-aware',
138
+ tags: ['memory', 'working'],
139
+ async execute(input, ctx) {
140
+ const scope = await deps.resolveScope(ctx);
141
+ const block = await deps.working.replace(scope, input.label, input.oldUnique, input.newText);
142
+ return { label: block.label, length: block.value.length };
143
+ },
144
+ });
145
+ }
146
+
147
+ /**
148
+ * `block_rethink` - rewrite a working memory block from scratch.
149
+ *
150
+ * @stable
151
+ */
152
+ export function createBlockRethinkTool(
153
+ deps: MemoryToolDeps,
154
+ ): Tool<BlockRethinkInput, BlockRethinkOutput> {
155
+ return tool<BlockRethinkInput, BlockRethinkOutput>({
156
+ name: 'block_rethink',
157
+ description:
158
+ "Replace a working memory block's value entirely. Use this when restructuring or summarising the block's contents. For surgical edits, prefer block_replace.",
159
+ inputSchema: blockRethinkInputSchema,
160
+ outputSchema: blockRethinkOutputSchema,
161
+ sideEffectClass: 'side-effecting',
162
+ sandboxPolicy: 'none',
163
+ sensitivity: 'internal',
164
+ memoryGuardTier: 'memory-aware',
165
+ tags: ['memory', 'working'],
166
+ async execute(input, ctx) {
167
+ const scope = await deps.resolveScope(ctx);
168
+ const block = await deps.working.rethink(scope, input.label, () => input.newValue);
169
+ return { label: block.label, length: block.value.length };
170
+ },
171
+ });
172
+ }
@@ -0,0 +1,473 @@
1
+ import type { Tool } from '@graphorin/core';
2
+ import { toolReturn } from '@graphorin/core';
3
+ import { tool } from '@graphorin/tools';
4
+ import { z } from 'zod';
5
+ import { recallTaint } from './taint.js';
6
+ import type { MemoryToolDeps } from './types.js';
7
+
8
+ const sensitivityEnum = z.enum(['public', 'internal', 'secret']);
9
+ const provenanceEnum = z.enum([
10
+ 'user',
11
+ 'tool',
12
+ 'extraction',
13
+ 'reflection',
14
+ 'induction',
15
+ 'imported',
16
+ ]);
17
+
18
+ const factRememberInputSchema = z.object({
19
+ text: z.string().min(1).max(8192),
20
+ tags: z.array(z.string().min(1).max(64)).max(32).optional(),
21
+ confidence: z.number().min(0).max(1).optional(),
22
+ sensitivity: sensitivityEnum.optional(),
23
+ validFrom: z.string().datetime().optional(),
24
+ validTo: z.string().datetime().optional(),
25
+ });
26
+ const factRememberOutputSchema = z.object({
27
+ factId: z.string(),
28
+ /**
29
+ * MRET-3: `true` when the write landed in quarantine (hidden from
30
+ * default recall until validated). A synthesized consolidator write or
31
+ * a candidate that tripped the injection heuristics quarantines.
32
+ */
33
+ quarantined: z.boolean(),
34
+ /** Why it was quarantined, when it was. */
35
+ quarantineReason: z.enum(['injection', 'synthesized']).optional(),
36
+ });
37
+
38
+ /**
39
+ * W-013: explicit interfaces instead of `z.infer<typeof schema>` - the
40
+ * inferred aliases baked concrete v3 zod object generics into
41
+ * the published d.ts, which do not typecheck under a zod@4 consumer.
42
+ * Interface<->schema equality is pinned by type tests. Optionals carry
43
+ * `| undefined` to match zod's `.optional()` inference exactly.
44
+ */
45
+ export interface FactRememberInput {
46
+ text: string;
47
+ tags?: string[] | undefined;
48
+ confidence?: number | undefined;
49
+ sensitivity?: 'public' | 'internal' | 'secret' | undefined;
50
+ validFrom?: string | undefined;
51
+ validTo?: string | undefined;
52
+ }
53
+ export interface FactRememberOutput {
54
+ factId: string;
55
+ quarantined: boolean;
56
+ quarantineReason?: 'injection' | 'synthesized' | undefined;
57
+ }
58
+
59
+ const factSearchInputSchema = z.object({
60
+ query: z.string().min(1).max(1024),
61
+ topK: z.number().int().positive().max(50).optional(),
62
+ tags: z.array(z.string().min(1)).max(16).optional(),
63
+ asOf: z.string().datetime().optional(),
64
+ });
65
+ const factSearchOutputSchema = z.object({
66
+ hits: z.array(
67
+ z.object({
68
+ factId: z.string(),
69
+ text: z.string(),
70
+ score: z.number(),
71
+ sensitivity: sensitivityEnum,
72
+ provenance: provenanceEnum.optional(),
73
+ /**
74
+ * Set when the fact's validity interval was closed (superseded /
75
+ * expired). Only present on `asOf` reads - default reads exclude
76
+ * such facts entirely (memory-retrieval-01).
77
+ */
78
+ validTo: z.string().optional(),
79
+ /** Id of the fact that superseded this one, when any. */
80
+ supersededBy: z.string().optional(),
81
+ }),
82
+ ),
83
+ });
84
+
85
+ export interface FactSearchInput {
86
+ query: string;
87
+ topK?: number | undefined;
88
+ tags?: string[] | undefined;
89
+ asOf?: string | undefined;
90
+ }
91
+ export interface FactSearchHit {
92
+ factId: string;
93
+ text: string;
94
+ score: number;
95
+ sensitivity: 'public' | 'internal' | 'secret';
96
+ provenance?: 'user' | 'tool' | 'extraction' | 'reflection' | 'induction' | 'imported' | undefined;
97
+ validTo?: string | undefined;
98
+ supersededBy?: string | undefined;
99
+ }
100
+ export interface FactSearchOutput {
101
+ hits: FactSearchHit[];
102
+ }
103
+
104
+ const factSupersedeInputSchema = z.object({
105
+ oldId: z.string().min(1),
106
+ newText: z.string().min(1).max(8192),
107
+ reason: z.string().max(512).optional(),
108
+ });
109
+ const factSupersedeOutputSchema = z.object({
110
+ oldId: z.string(),
111
+ newId: z.string(),
112
+ });
113
+
114
+ export interface FactSupersedeInput {
115
+ oldId: string;
116
+ newText: string;
117
+ reason?: string | undefined;
118
+ }
119
+ export interface FactSupersedeOutput {
120
+ oldId: string;
121
+ newId: string;
122
+ }
123
+
124
+ const factForgetInputSchema = z.object({
125
+ factId: z.string().min(1),
126
+ reason: z.string().max(512).optional(),
127
+ });
128
+ const factForgetOutputSchema = z.object({
129
+ factId: z.string(),
130
+ forgotten: z.boolean(),
131
+ });
132
+
133
+ export interface FactForgetInput {
134
+ factId: string;
135
+ reason?: string | undefined;
136
+ }
137
+ export interface FactForgetOutput {
138
+ factId: string;
139
+ forgotten: boolean;
140
+ }
141
+
142
+ const factHistoryInputSchema = z.object({
143
+ factId: z.string().min(1),
144
+ });
145
+ const factHistoryOutputSchema = z.object({
146
+ chain: z.array(
147
+ z.object({
148
+ factId: z.string(),
149
+ text: z.string(),
150
+ validFrom: z.string().optional(),
151
+ validTo: z.string().optional(),
152
+ supersedes: z.string().optional(),
153
+ supersededBy: z.string().optional(),
154
+ sensitivity: sensitivityEnum,
155
+ }),
156
+ ),
157
+ });
158
+
159
+ export interface FactHistoryInput {
160
+ factId: string;
161
+ }
162
+ export interface FactHistoryEntry {
163
+ factId: string;
164
+ text: string;
165
+ validFrom?: string | undefined;
166
+ validTo?: string | undefined;
167
+ supersedes?: string | undefined;
168
+ supersededBy?: string | undefined;
169
+ sensitivity: 'public' | 'internal' | 'secret';
170
+ }
171
+ export interface FactHistoryOutput {
172
+ chain: FactHistoryEntry[];
173
+ }
174
+
175
+ const factValidateInputSchema = z.object({
176
+ factId: z.string().min(1),
177
+ reason: z.string().max(512).optional(),
178
+ });
179
+ const factValidateOutputSchema = z.object({
180
+ factId: z.string(),
181
+ validated: z.boolean(),
182
+ });
183
+
184
+ export interface FactValidateInput {
185
+ factId: string;
186
+ reason?: string | undefined;
187
+ }
188
+ export interface FactValidateOutput {
189
+ factId: string;
190
+ validated: boolean;
191
+ }
192
+
193
+ // W-013 parity gate (compile-time only, erased from the build and the
194
+ // d.ts): each explicit interface must stay MUTUALLY assignable with its
195
+ // schema's inference - a drifted transcription fails `tsc` right here.
196
+ type W013Equals<A, B> = A extends B ? (B extends A ? true : false) : false;
197
+ type W013Assert<T extends true> = T;
198
+ type _W013Check1 = W013Assert<
199
+ W013Equals<FactRememberInput, z.infer<typeof factRememberInputSchema>>
200
+ >;
201
+ type _W013Check2 = W013Assert<
202
+ W013Equals<FactRememberOutput, z.infer<typeof factRememberOutputSchema>>
203
+ >;
204
+ type _W013Check3 = W013Assert<W013Equals<FactSearchInput, z.infer<typeof factSearchInputSchema>>>;
205
+ type _W013Check4 = W013Assert<W013Equals<FactSearchOutput, z.infer<typeof factSearchOutputSchema>>>;
206
+ type _W013Check5 = W013Assert<
207
+ W013Equals<FactSupersedeInput, z.infer<typeof factSupersedeInputSchema>>
208
+ >;
209
+ type _W013Check6 = W013Assert<
210
+ W013Equals<FactSupersedeOutput, z.infer<typeof factSupersedeOutputSchema>>
211
+ >;
212
+ type _W013Check7 = W013Assert<W013Equals<FactForgetInput, z.infer<typeof factForgetInputSchema>>>;
213
+ type _W013Check8 = W013Assert<W013Equals<FactForgetOutput, z.infer<typeof factForgetOutputSchema>>>;
214
+ type _W013Check9 = W013Assert<W013Equals<FactHistoryInput, z.infer<typeof factHistoryInputSchema>>>;
215
+ type _W013Check10 = W013Assert<
216
+ W013Equals<FactHistoryOutput, z.infer<typeof factHistoryOutputSchema>>
217
+ >;
218
+ type _W013Check11 = W013Assert<
219
+ W013Equals<FactValidateInput, z.infer<typeof factValidateInputSchema>>
220
+ >;
221
+ type _W013Check12 = W013Assert<
222
+ W013Equals<FactValidateOutput, z.infer<typeof factValidateOutputSchema>>
223
+ >;
224
+
225
+ /**
226
+ * `fact_remember` - persist a single semantic fact. The minimum-viable
227
+ * pipeline writes the fact straight through with MD5 deduplication;
228
+ * Phase 10b extends the body with the multi-stage conflict resolution.
229
+ *
230
+ * @stable
231
+ */
232
+ export function createFactRememberTool(
233
+ deps: MemoryToolDeps,
234
+ ): Tool<FactRememberInput, FactRememberOutput> {
235
+ return tool<FactRememberInput, FactRememberOutput>({
236
+ name: 'fact_remember',
237
+ description:
238
+ 'Persist an atomic factual statement about the user, the world, or the current session. Prefer one fact per call; the system applies dedup + conflict resolution downstream. Pass `validFrom` / `validTo` for bi-temporal facts and `sensitivity` to control redaction.',
239
+ inputSchema: factRememberInputSchema,
240
+ outputSchema: factRememberOutputSchema,
241
+ sideEffectClass: 'side-effecting',
242
+ sandboxPolicy: 'none',
243
+ sensitivity: 'internal',
244
+ memoryGuardTier: 'memory-aware',
245
+ tags: ['memory', 'semantic'],
246
+ async execute(input, ctx) {
247
+ const scope = await deps.resolveScope(ctx);
248
+ const outcome = await deps.semantic.rememberWithDecision(scope, {
249
+ text: input.text,
250
+ ...(input.tags !== undefined ? { tags: input.tags } : {}),
251
+ ...(input.confidence !== undefined ? { confidence: input.confidence } : {}),
252
+ ...(input.sensitivity !== undefined ? { sensitivity: input.sensitivity } : {}),
253
+ ...(input.validFrom !== undefined ? { validFrom: input.validFrom } : {}),
254
+ ...(input.validTo !== undefined ? { validTo: input.validTo } : {}),
255
+ });
256
+ // MRET-3: tell the caller when the write was quarantined so a
257
+ // poisoned fact cannot masquerade as a normally-stored one.
258
+ return {
259
+ factId: outcome.fact.id,
260
+ quarantined: outcome.fact.status === 'quarantined',
261
+ ...(outcome.quarantineReason !== undefined
262
+ ? { quarantineReason: outcome.quarantineReason }
263
+ : {}),
264
+ };
265
+ },
266
+ });
267
+ }
268
+
269
+ /**
270
+ * `fact_search` - hybrid (vector + FTS5) search over the user's
271
+ * semantic memory. Results merged through the configured reranker.
272
+ *
273
+ * @stable
274
+ */
275
+ export function createFactSearchTool(
276
+ deps: MemoryToolDeps,
277
+ ): Tool<FactSearchInput, FactSearchOutput> {
278
+ return tool<FactSearchInput, FactSearchOutput>({
279
+ name: 'fact_search',
280
+ description:
281
+ "Search the user's long-term factual memory by natural-language query. Returns up to `topK` matched facts with their score and sensitivity. Use this BEFORE asking the user a question they may have answered earlier. Pass `asOf` (ISO-8601) to read memory as it was at a past instant - point-in-time / 'what was true on date X' - instead of the current state.",
282
+ inputSchema: factSearchInputSchema,
283
+ outputSchema: factSearchOutputSchema,
284
+ sideEffectClass: 'read-only',
285
+ sandboxPolicy: 'none',
286
+ sensitivity: 'internal',
287
+ memoryGuardTier: 'pure',
288
+ tags: ['memory', 'semantic'],
289
+ async execute(input, ctx) {
290
+ const scope = await deps.resolveScope(ctx);
291
+ const hits = await deps.semantic.search(scope, input.query, {
292
+ ...(input.topK !== undefined ? { topK: input.topK } : {}),
293
+ // MRET-4: forward the tags filter - it was accepted by the
294
+ // schema and silently dropped here.
295
+ ...(input.tags !== undefined && input.tags.length > 0 ? { tags: input.tags } : {}),
296
+ ...(input.asOf !== undefined ? { asOf: input.asOf } : {}),
297
+ signal: ctx.signal,
298
+ });
299
+ const output = {
300
+ hits: hits.map((hit) => ({
301
+ factId: hit.record.id,
302
+ text: hit.record.text,
303
+ score: hit.score,
304
+ sensitivity: hit.record.sensitivity,
305
+ ...(hit.record.provenance !== undefined ? { provenance: hit.record.provenance } : {}),
306
+ // memory-retrieval-01: surface validity metadata so the model
307
+ // can tell a historical (asOf) hit from a current one.
308
+ ...(hit.record.validTo !== undefined ? { validTo: hit.record.validTo } : {}),
309
+ ...(hit.record.supersededBy !== undefined
310
+ ? { supersededBy: hit.record.supersededBy }
311
+ : {}),
312
+ })),
313
+ };
314
+ // C6: recalled quarantined / foreign-provenance content re-arms the
315
+ // taint ledger - the cross-session poisoning leg.
316
+ const taint = recallTaint(hits.map((h) => h.record));
317
+ return taint === undefined ? output : toolReturn({ output, taint });
318
+ },
319
+ });
320
+ }
321
+
322
+ /**
323
+ * `fact_supersede` - soft-supersede an old fact by storing a new one
324
+ * that replaces it. The old fact is kept for replay but no longer
325
+ * surfaced by default reads (they evaluate validity at NOW); it stays
326
+ * reachable via `asOf` / inspector paths.
327
+ *
328
+ * @stable
329
+ */
330
+ export function createFactSupersedeTool(
331
+ deps: MemoryToolDeps,
332
+ ): Tool<FactSupersedeInput, FactSupersedeOutput> {
333
+ return tool<FactSupersedeInput, FactSupersedeOutput>({
334
+ name: 'fact_supersede',
335
+ description:
336
+ "Mark an old fact superseded by a new one. Use this when the user's situation changed (moved cities, updated a goal, switched a preference) - the old fact is preserved for replay but no longer surfaced as current.",
337
+ inputSchema: factSupersedeInputSchema,
338
+ outputSchema: factSupersedeOutputSchema,
339
+ sideEffectClass: 'side-effecting',
340
+ sandboxPolicy: 'none',
341
+ sensitivity: 'internal',
342
+ memoryGuardTier: 'memory-aware',
343
+ tags: ['memory', 'semantic'],
344
+ async execute(input, ctx) {
345
+ const scope = await deps.resolveScope(ctx);
346
+ const result = await deps.semantic.supersede(
347
+ scope,
348
+ input.oldId,
349
+ { text: input.newText },
350
+ input.reason,
351
+ );
352
+ return { oldId: result.old, newId: result.new.id };
353
+ },
354
+ });
355
+ }
356
+
357
+ /**
358
+ * `fact_forget` - soft-delete a fact (kept for replay; never hard-
359
+ * deleted at this layer).
360
+ *
361
+ * @stable
362
+ */
363
+ export function createFactForgetTool(
364
+ deps: MemoryToolDeps,
365
+ ): Tool<FactForgetInput, FactForgetOutput> {
366
+ return tool<FactForgetInput, FactForgetOutput>({
367
+ name: 'fact_forget',
368
+ description:
369
+ 'Soft-delete a fact. The record is preserved for replay but no longer surfaced by fact_search. Use only for explicit user-driven removal (privacy / user request).',
370
+ inputSchema: factForgetInputSchema,
371
+ outputSchema: factForgetOutputSchema,
372
+ sideEffectClass: 'side-effecting',
373
+ sandboxPolicy: 'none',
374
+ sensitivity: 'internal',
375
+ memoryGuardTier: 'memory-aware',
376
+ tags: ['memory', 'semantic'],
377
+ async execute(input, ctx) {
378
+ const scope = await deps.resolveScope(ctx);
379
+ await deps.semantic.forget(scope, input.factId, input.reason);
380
+ return { factId: input.factId, forgotten: true };
381
+ },
382
+ });
383
+ }
384
+
385
+ /**
386
+ * `fact_history` - trace how a fact changed over time. Returns the
387
+ * full bi-temporal supersede chain the given fact belongs to, oldest →
388
+ * newest, including superseded entries, so the agent can answer "what
389
+ * did the user say before" / "how did this change". Read-only. P0-2.
390
+ *
391
+ * @stable
392
+ */
393
+ export function createFactHistoryTool(
394
+ deps: MemoryToolDeps,
395
+ ): Tool<FactHistoryInput, FactHistoryOutput> {
396
+ return tool<FactHistoryInput, FactHistoryOutput>({
397
+ name: 'fact_history',
398
+ description:
399
+ "Trace how a fact changed over time. Given a factId, returns its full bi-temporal supersede chain (oldest → newest), including superseded entries, with each entry's validFrom/validTo. Use this to answer 'how did this change' or 'what was the previous value'.",
400
+ inputSchema: factHistoryInputSchema,
401
+ outputSchema: factHistoryOutputSchema,
402
+ sideEffectClass: 'read-only',
403
+ sandboxPolicy: 'none',
404
+ sensitivity: 'internal',
405
+ memoryGuardTier: 'pure',
406
+ tags: ['memory', 'semantic', 'temporal'],
407
+ async execute(input, ctx) {
408
+ const scope = await deps.resolveScope(ctx);
409
+ const chain = await deps.semantic.history(scope, input.factId);
410
+ return {
411
+ chain: chain.map((f) => ({
412
+ factId: f.id,
413
+ text: f.text,
414
+ ...(f.validFrom !== undefined ? { validFrom: f.validFrom } : {}),
415
+ ...(f.validTo !== undefined ? { validTo: f.validTo } : {}),
416
+ ...(f.supersedes !== undefined ? { supersedes: f.supersedes } : {}),
417
+ ...(f.supersededBy !== undefined ? { supersededBy: f.supersededBy } : {}),
418
+ sensitivity: f.sensitivity,
419
+ })),
420
+ };
421
+ },
422
+ });
423
+ }
424
+
425
+ /**
426
+ * `fact_validate` - promote a quarantined fact to active (P1-4). The
427
+ * validation path that admits a synthesized (consolidator / reflection)
428
+ * memory into action-driving recall once it has been reviewed; the
429
+ * promotion is audited in `memory_history`.
430
+ *
431
+ * MRET-3 / MST-1 - two gates close the one-turn memory-poisoning chain
432
+ * (`fact_remember(poison)` → `fact_validate(id)` → active recall):
433
+ *
434
+ * 1. `needsApproval: true` - the run suspends for a human decision
435
+ * before this tool ever executes, so the agent cannot silently
436
+ * promote any quarantined fact.
437
+ * 2. The underlying `SemanticMemory.validate(...)` re-checks the fact's
438
+ * text against the injection heuristics and **refuses** (no `force`
439
+ * is passed here) - an injection-flagged memory cannot be promoted by
440
+ * the agent at all. Only an operator, via the programmatic API with
441
+ * `{ force: true }`, can override after review.
442
+ *
443
+ * Synthesized-but-clean consolidator writes promote normally once
444
+ * approved.
445
+ *
446
+ * @stable
447
+ */
448
+ export function createFactValidateTool(
449
+ deps: MemoryToolDeps,
450
+ ): Tool<FactValidateInput, FactValidateOutput> {
451
+ return tool<FactValidateInput, FactValidateOutput>({
452
+ name: 'fact_validate',
453
+ description:
454
+ 'Promote a quarantined fact to active so it becomes eligible for normal recall. Quarantined facts are memories the system synthesized or flagged as risky; validating one is a deliberate, human-approved admission and never a routine step. This action requires approval, and facts flagged as prompt-injection cannot be promoted here - they are an operator-only decision.',
455
+ inputSchema: factValidateInputSchema,
456
+ outputSchema: factValidateOutputSchema,
457
+ // MRET-3: the only real gate on a model-callable tool - suspend the
458
+ // run for a human decision before promoting anything out of quarantine.
459
+ needsApproval: true,
460
+ sideEffectClass: 'side-effecting',
461
+ sandboxPolicy: 'none',
462
+ sensitivity: 'internal',
463
+ memoryGuardTier: 'memory-aware',
464
+ tags: ['memory', 'semantic', 'safety'],
465
+ async execute(input, ctx) {
466
+ const scope = await deps.resolveScope(ctx);
467
+ // No `force`: the agent path can never promote an injection-flagged
468
+ // fact (validate throws QuarantinePromotionRefusedError for those).
469
+ await deps.semantic.validate(scope, input.factId, input.reason);
470
+ return { factId: input.factId, validated: true };
471
+ },
472
+ });
473
+ }