@graphorin/memory 0.6.0 → 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 +83 -0
  2. package/README.md +24 -7
  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 +17 -15
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +5 -7
  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 +6 -0
  89. package/dist/package.js.map +1 -0
  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,65 @@
1
+ /**
2
+ * Internal helpers shared across the five pipeline stages.
3
+ *
4
+ * @packageDocumentation
5
+ * @internal
6
+ */
7
+
8
+ import { md5 } from '@graphorin/core';
9
+ import type { LocalePack } from '../locale-packs/index.js';
10
+
11
+ /**
12
+ * Lowercase + collapse whitespace + trim. Used by Stage 1 (exact
13
+ * dedup) to compute the canonical fact body that feeds the MD5
14
+ * comparison.
15
+ */
16
+ export function canonicaliseFactBody(text: string): string {
17
+ return text.replace(/\s+/g, ' ').trim().toLowerCase();
18
+ }
19
+
20
+ /**
21
+ * Stable hash for a canonical fact body. We re-use the framework's
22
+ * MD5 helper to stay consistent with the storage layer's existing
23
+ * dedup hash column (see `facts.hash`).
24
+ */
25
+ export function fastFactHash(text: string): string {
26
+ return md5(text);
27
+ }
28
+
29
+ /** Strip leading articles + trim a candidate subject token. */
30
+ export function normaliseSubject(input: string, pack: LocalePack): string {
31
+ const tokens = canonicaliseFactBody(input).split(' ');
32
+ const stop = new Set(pack.subjectStopWords.map((s) => s.toLowerCase()));
33
+ return tokens.filter((token) => token.length > 0 && !stop.has(token)).join(' ');
34
+ }
35
+
36
+ /**
37
+ * Naive subject / predicate / object split. The locale pack supplies
38
+ * the verb tokens used as predicate normalisers - the first matching
39
+ * verb in the sentence becomes the predicate and splits the rest
40
+ * into subject (left side) and object (right side). The split is
41
+ * intentionally simple: anything more clever lives behind the entity-
42
+ * linking layer (post-MVP).
43
+ */
44
+ export function splitSubjectPredicateObject(
45
+ text: string,
46
+ pack: LocalePack,
47
+ ): { readonly subject: string; readonly predicate: string; readonly object: string } | null {
48
+ const canonical = canonicaliseFactBody(text);
49
+ if (canonical.length === 0) return null;
50
+ const tokens = canonical.split(' ');
51
+ const verbs = new Set(pack.predicateNormalisers.map((v) => v.toLowerCase()));
52
+ for (let i = 0; i < tokens.length; i++) {
53
+ const token = tokens[i];
54
+ if (token === undefined) continue;
55
+ const stripped = token.replace(/[.,!?;:]/g, '');
56
+ if (verbs.has(stripped)) {
57
+ const subject = normaliseSubject(tokens.slice(0, i).join(' '), pack);
58
+ const predicate = stripped;
59
+ const object = canonicaliseFactBody(tokens.slice(i + 1).join(' '));
60
+ if (subject.length === 0 || object.length === 0) continue;
61
+ return { subject, predicate, object };
62
+ }
63
+ }
64
+ return null;
65
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Stage 1 - Exact dedup. Computes an MD5 hash on the canonical fact
3
+ * body (lowercase + trimmed + collapsed whitespace) and short-circuits
4
+ * to `dedup` when an existing fact in the supplied vector hit list
5
+ * shares the same canonical form.
6
+ *
7
+ * Stage 1 only inspects `existing` when Stage 2 already populated it
8
+ * for the orchestrator (re-using the embedding round-trip). When the
9
+ * orchestrator calls Stage 1 with an empty list the stage falls
10
+ * through to `'continue'` - Stage 2 will gather candidates and Stage 1
11
+ * is folded into Stage 2's HOT zone via the same canonical-form
12
+ * comparison.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+
17
+ import type { PipelineStage } from '../types.js';
18
+ import { canonicaliseFactBody, fastFactHash } from './helpers.js';
19
+
20
+ export const stage1ExactDedup: PipelineStage = {
21
+ id: 'exact-dedup',
22
+ async evaluate(ctx) {
23
+ const candidateHash = fastFactHash(canonicaliseFactBody(ctx.candidate.text));
24
+ if (ctx.existing.length === 0) {
25
+ return { kind: 'continue' };
26
+ }
27
+ for (const hit of ctx.existing) {
28
+ const existingHash = fastFactHash(canonicaliseFactBody(hit.record.text));
29
+ if (existingHash === candidateHash) {
30
+ return {
31
+ kind: 'dedup',
32
+ existingId: hit.record.id,
33
+ similarity: 1,
34
+ reason: 'exact-hash-match',
35
+ };
36
+ }
37
+ }
38
+ return { kind: 'continue' };
39
+ },
40
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Stage 2 - Embedding three-zone classifier (RB-02 §8.1).
3
+ *
4
+ * Inspects the candidate similarity against the top-K nearest
5
+ * neighbours surfaced by the semantic vector store and routes the
6
+ * decision into one of four zones:
7
+ *
8
+ * - HOT (`sim ≥ thresholds.hot`): silent dedup - the existing fact is
9
+ * semantically identical, no audit-worthy conflict.
10
+ * - NEAR-DUP (`thresholds.nearDup ≤ sim < thresholds.hot`): same
11
+ * fact phrased differently - dedup with `near-dup` zone label so
12
+ * operators can spot the case in `fact_conflicts`.
13
+ * - CONFLICT-CHECK (`thresholds.cold < sim < thresholds.nearDup`):
14
+ * candidate sounds related; defer decision to Stages 3 → 4 → 5.
15
+ * - COLD (`sim ≤ thresholds.cold`): no conflict - admit the candidate.
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+
20
+ import type { PipelineStage, StageOutcome } from '../types.js';
21
+
22
+ export const stage2EmbeddingThreeZone: PipelineStage = {
23
+ id: 'embedding-three-zone',
24
+ async evaluate(ctx): Promise<StageOutcome> {
25
+ if (ctx.existing.length === 0) {
26
+ return { kind: 'admit', reason: 'no-existing-candidates' };
27
+ }
28
+ const top = ctx.existing[0];
29
+ if (top === undefined) {
30
+ return { kind: 'admit', reason: 'no-existing-candidates' };
31
+ }
32
+ const similarity = top.score;
33
+ if (similarity >= ctx.thresholds.hot) {
34
+ return {
35
+ kind: 'dedup',
36
+ existingId: top.record.id,
37
+ similarity,
38
+ reason: 'embedding-hot-zone',
39
+ };
40
+ }
41
+ if (similarity >= ctx.thresholds.nearDup) {
42
+ return {
43
+ kind: 'dedup',
44
+ existingId: top.record.id,
45
+ similarity,
46
+ reason: 'embedding-near-dup-zone',
47
+ };
48
+ }
49
+ if (similarity > ctx.thresholds.cold) {
50
+ return { kind: 'continue' };
51
+ }
52
+ return { kind: 'admit', reason: 'embedding-cold-zone' };
53
+ },
54
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Stage 3 - Heuristic regex (per-locale, pluggable).
3
+ *
4
+ * Inspects the candidate fact body for explicit supersede or negation
5
+ * markers from the active locale pack (English by default). When a
6
+ * marker fires the stage emits a `supersede` outcome against the
7
+ * highest-scoring CONFLICT-CHECK candidate from Stage 2.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+
12
+ import { evaluateMarkers } from '../locale-packs/index.js';
13
+ import type { PipelineStage, StageOutcome } from '../types.js';
14
+
15
+ export const stage3HeuristicRegex: PipelineStage = {
16
+ id: 'heuristic-regex',
17
+ async evaluate(ctx): Promise<StageOutcome> {
18
+ const top = ctx.existing[0];
19
+ if (top === undefined) {
20
+ return { kind: 'continue' };
21
+ }
22
+ const candidateText = ctx.candidate.text;
23
+ const supersede = evaluateMarkers(candidateText, ctx.localePack.supersedeMarkers);
24
+ if (supersede.matched) {
25
+ return {
26
+ kind: 'supersede',
27
+ existingId: top.record.id,
28
+ reason: formatReason('regex-supersede-marker', supersede),
29
+ };
30
+ }
31
+ const negation = evaluateMarkers(candidateText, ctx.localePack.negationMarkers);
32
+ if (negation.matched) {
33
+ return {
34
+ kind: 'supersede',
35
+ existingId: top.record.id,
36
+ reason: formatReason('regex-negation', negation),
37
+ };
38
+ }
39
+ return { kind: 'continue' };
40
+ },
41
+ };
42
+
43
+ function formatReason(
44
+ base: string,
45
+ match: { kind?: string | undefined; excerpt?: string | undefined },
46
+ ): string {
47
+ const parts = [base];
48
+ if (match.kind !== undefined) parts.push(`kind=${match.kind}`);
49
+ if (match.excerpt !== undefined) parts.push(`marker=${match.excerpt}`);
50
+ return parts.join('; ');
51
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Stage 4 - Subject / predicate match.
3
+ *
4
+ * Naively splits each fact body into `(subject, predicate, object)`
5
+ * triples using the locale pack's predicate normalisers. When the
6
+ * candidate and an existing CONFLICT-CHECK fact share the same
7
+ * subject + predicate but differ on the object, Stage 4 marks the
8
+ * existing fact as superseded.
9
+ *
10
+ * The split is intentionally simple: anything more sophisticated
11
+ * (full entity linking) lives in the post-MVP knowledge-graph layer.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
16
+ import type { PipelineStage, StageOutcome } from '../types.js';
17
+ import { splitSubjectPredicateObject } from './helpers.js';
18
+
19
+ export const stage4SubjectPredicate: PipelineStage = {
20
+ id: 'subject-predicate',
21
+ async evaluate(ctx): Promise<StageOutcome> {
22
+ const candidateTriple = splitSubjectPredicateObject(ctx.candidate.text, ctx.localePack);
23
+ if (candidateTriple === null) {
24
+ return { kind: 'continue' };
25
+ }
26
+ for (const hit of ctx.existing) {
27
+ const existingTriple = splitSubjectPredicateObject(hit.record.text, ctx.localePack);
28
+ if (existingTriple === null) continue;
29
+ if (candidateTriple.subject !== existingTriple.subject) continue;
30
+ if (candidateTriple.predicate !== existingTriple.predicate) continue;
31
+ if (candidateTriple.object === existingTriple.object) {
32
+ return {
33
+ kind: 'dedup',
34
+ existingId: hit.record.id,
35
+ reason: 'subject-predicate-match-same-object',
36
+ };
37
+ }
38
+ return {
39
+ kind: 'supersede',
40
+ existingId: hit.record.id,
41
+ reason: `subject-predicate-match; subject=${candidateTriple.subject}; predicate=${candidateTriple.predicate}`,
42
+ };
43
+ }
44
+ return { kind: 'continue' };
45
+ },
46
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Stage 5 - Defer to deep LLM judge.
3
+ *
4
+ * Reached only when Stages 1-4 left the decision unresolved. The
5
+ * stage records every CONFLICT-CHECK candidate id surfaced by Stage 2
6
+ * so the orchestrator can hand the queue write to the storage layer
7
+ * (the actual `conflict_check_pending` insert is the orchestrator's
8
+ * responsibility - Stage 5 only computes the payload).
9
+ *
10
+ * The candidate fact itself is admitted with a `pending` flag so it
11
+ * remains searchable; the deep phase (Phase 10c) drains the queue and
12
+ * applies the resolution later.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+
17
+ import type { PipelineStage, StageOutcome } from '../types.js';
18
+
19
+ export const stage5DeferToDeep: PipelineStage = {
20
+ id: 'defer-to-deep',
21
+ async evaluate(ctx): Promise<StageOutcome> {
22
+ if (ctx.existing.length === 0) {
23
+ return { kind: 'admit', reason: 'stage5-no-candidates' };
24
+ }
25
+ const conflictingIds = ctx.existing.map((hit) => hit.record.id);
26
+ const top = ctx.existing[0];
27
+ return {
28
+ kind: 'pending',
29
+ conflictingIds,
30
+ ...(top !== undefined ? { similarity: top.score } : {}),
31
+ reason: 'awaiting-deep-llm-judge',
32
+ };
33
+ },
34
+ };
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Public types for the multi-stage conflict resolution pipeline shipped
3
+ * in `@graphorin/memory` Phase 10b (DEC-117 / ADR-018 ext / RB-02).
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+
8
+ import type { EmbedderProvider, Fact, MemoryHit, Tracer } from '@graphorin/core';
9
+ import type { ConflictMemoryStoreExt, MemoryStoreAdapter } from '../internal/storage-adapter.js';
10
+ import type { LocalePack } from './locale-packs/index.js';
11
+
12
+ /**
13
+ * Stable lowercase identifier of the pipeline stage. Mirrored
14
+ * byte-for-byte by `@graphorin/store-sqlite`'s `ConflictPipelineStage`
15
+ * so `fact_conflicts` / `conflict_check_pending` rows can be grouped
16
+ * deterministically.
17
+ *
18
+ * @stable
19
+ */
20
+ export type ConflictStage =
21
+ | 'exact-dedup'
22
+ | 'embedding-three-zone'
23
+ | 'heuristic-regex'
24
+ | 'subject-predicate'
25
+ | 'defer-to-deep';
26
+
27
+ /**
28
+ * Configurable similarity thresholds for Stage 2 (embedding three-
29
+ * zone). Defaults pinned at `0.95 / 0.85 / 0.4` per RB-02 §8 - the
30
+ * production values for the default `Xenova/multilingual-e5-base`
31
+ * embedder (DEC-130).
32
+ *
33
+ * @stable
34
+ */
35
+ export interface ConflictThresholds {
36
+ readonly hot: number;
37
+ readonly nearDup: number;
38
+ readonly cold: number;
39
+ }
40
+
41
+ /**
42
+ * Hard-coded defaults for the three-zone thresholds. Imported by the
43
+ * pipeline + the test suite so call sites stay aligned.
44
+ *
45
+ * @stable
46
+ */
47
+ export const DEFAULT_CONFLICT_THRESHOLDS: ConflictThresholds = Object.freeze({
48
+ hot: 0.95,
49
+ nearDup: 0.85,
50
+ cold: 0.4,
51
+ });
52
+
53
+ /**
54
+ * Final pipeline outcome - discriminated union returned by
55
+ * {@link runConflictPipeline}. Mirrors RB-02 §8.1 / DEC-117 - every
56
+ * variant carries the originating `stage` so audit + replay tooling
57
+ * can pattern-match without inspecting the message.
58
+ *
59
+ * @stable
60
+ */
61
+ export type ConflictDecision =
62
+ | {
63
+ readonly kind: 'admit';
64
+ readonly stage: ConflictStage;
65
+ readonly reason?: string;
66
+ }
67
+ | {
68
+ readonly kind: 'dedup';
69
+ readonly stage: ConflictStage;
70
+ readonly existingId: string;
71
+ readonly similarity?: number;
72
+ readonly reason?: string;
73
+ }
74
+ | {
75
+ readonly kind: 'supersede';
76
+ readonly stage: ConflictStage;
77
+ readonly existingId: string;
78
+ readonly reason: string;
79
+ }
80
+ | {
81
+ readonly kind: 'pending';
82
+ readonly stage: ConflictStage;
83
+ /** The candidate id that was admitted as `pending`. */
84
+ readonly candidateId: string;
85
+ /** Top-K conflicting existing fact ids from Stage 2's vector search. */
86
+ readonly conflictingIds: ReadonlyArray<string>;
87
+ readonly similarity?: number;
88
+ readonly reason?: string;
89
+ };
90
+
91
+ /**
92
+ * Pipeline configuration accepted by `createMemory({ conflictPipeline:
93
+ * ... })` and surfaced through {@link createConflictPipeline}.
94
+ *
95
+ * The `mode` field is the master switch:
96
+ *
97
+ * - `'on'` (default) - the multi-stage pipeline runs on every
98
+ * `SemanticMemory.remember(...)` call.
99
+ * - `'off'` - bypass the pipeline and fall back to 10a's straight-
100
+ * through write. Emits a one-shot WARN (per process) so operators
101
+ * notice the regression risk.
102
+ *
103
+ * @stable
104
+ */
105
+ export interface ConflictPipelineOptions {
106
+ readonly mode?: 'on' | 'off';
107
+ readonly thresholds?: Partial<ConflictThresholds>;
108
+ readonly localePack?: LocalePack;
109
+ /** Per-list candidate count fed into Stage 2. Default `5` (RB-02 §8.1). */
110
+ readonly stage2TopK?: number;
111
+ /** Override the audit / pending sink. Defaults to `store.conflicts` when present. */
112
+ readonly conflictStore?: ConflictMemoryStoreExt;
113
+ /** Inject a deterministic clock. Defaults to `() => new Date().toISOString()`. */
114
+ readonly now?: () => string;
115
+ }
116
+
117
+ /**
118
+ * Inputs the orchestrator hands every stage. The `existing` array is
119
+ * populated during Stage 2 (vector search top-K); Stage 1 receives an
120
+ * empty array because the dedup hash is computed off the candidate
121
+ * alone.
122
+ *
123
+ * @stable
124
+ */
125
+ export interface StageContext {
126
+ readonly candidate: Fact;
127
+ readonly existing: ReadonlyArray<MemoryHit<Fact>>;
128
+ readonly localePack: LocalePack;
129
+ readonly thresholds: ConflictThresholds;
130
+ }
131
+
132
+ /**
133
+ * Per-stage outcome surfaced to the orchestrator. `'admit'` means the
134
+ * stage decided not to short-circuit - the pipeline continues to the
135
+ * next stage. Every other variant terminates the pipeline.
136
+ *
137
+ * @stable
138
+ */
139
+ export type StageOutcome =
140
+ | { readonly kind: 'continue' }
141
+ | { readonly kind: 'admit'; readonly reason?: string }
142
+ | {
143
+ readonly kind: 'dedup';
144
+ readonly existingId: string;
145
+ readonly similarity?: number;
146
+ readonly reason?: string;
147
+ }
148
+ | {
149
+ readonly kind: 'supersede';
150
+ readonly existingId: string;
151
+ readonly reason: string;
152
+ }
153
+ | {
154
+ readonly kind: 'pending';
155
+ readonly conflictingIds: ReadonlyArray<string>;
156
+ readonly similarity?: number;
157
+ readonly reason?: string;
158
+ };
159
+
160
+ /**
161
+ * Stage interface every step implements. The orchestrator visits
162
+ * stages in declaration order and stops at the first non-`'continue'`
163
+ * outcome.
164
+ *
165
+ * @stable
166
+ */
167
+ export interface PipelineStage {
168
+ readonly id: ConflictStage;
169
+ evaluate(ctx: StageContext): Promise<StageOutcome>;
170
+ }
171
+
172
+ /**
173
+ * Pre-built pipeline returned by {@link createConflictPipeline}. The
174
+ * `run` method is what `SemanticMemory.remember(...)` calls to make
175
+ * the conflict decision.
176
+ *
177
+ * @stable
178
+ */
179
+ export interface ConflictPipeline {
180
+ readonly mode: 'on' | 'off';
181
+ readonly localePack: LocalePack;
182
+ readonly thresholds: ConflictThresholds;
183
+ run(deps: ConflictPipelineDeps, candidate: Fact): Promise<ConflictDecision>;
184
+ }
185
+
186
+ /**
187
+ * Per-call dependency bag handed to the pipeline by `SemanticMemory`.
188
+ *
189
+ * @stable
190
+ */
191
+ export interface ConflictPipelineDeps {
192
+ readonly store: MemoryStoreAdapter;
193
+ readonly tracer: Tracer;
194
+ readonly embedder: EmbedderProvider | null;
195
+ readonly embedderId: string | null;
196
+ /** Optional cancellation signal forwarded to embedder + searchVector. */
197
+ readonly signal?: AbortSignal;
198
+ }
199
+
200
+ /**
201
+ * The four actions the neighbour-aware reconcile loop (P0-3) may choose
202
+ * for a candidate fact once the LLM has the most-similar existing
203
+ * memories in view. The de-facto-standard memory write loop
204
+ * (Mem0 / LangMem / Letta), with Graphorin's twist: `update` and
205
+ * `conflict` route through a **bi-temporal supersede** (close the old
206
+ * interval, insert the new) rather than a destructive delete.
207
+ *
208
+ * @stable
209
+ */
210
+ export type ReconcileAction = 'add' | 'update' | 'noop' | 'conflict';
211
+
212
+ /**
213
+ * Outcome of `reconcileCandidate`. `add` is independent / unsure
214
+ * (a fresh fact); `noop` is a duplicate that adds nothing; `update` is
215
+ * a newer version of `targetId`; `conflict` contradicts `targetId` and
216
+ * closes it. The three neighbour-referencing variants carry the
217
+ * `targetId` of the existing fact they resolve against.
218
+ *
219
+ * @stable
220
+ */
221
+ export type ReconcileDecision =
222
+ | { readonly action: 'add'; readonly reason?: string }
223
+ | { readonly action: 'update'; readonly targetId: string; readonly reason: string }
224
+ | { readonly action: 'noop'; readonly targetId: string; readonly reason?: string }
225
+ | { readonly action: 'conflict'; readonly targetId: string; readonly reason: string };
226
+
227
+ /**
228
+ * Map a {@link ReconcileDecision} onto the existing {@link ConflictDecision}
229
+ * so reconcile outcomes land in `fact_conflicts` through the same audit
230
+ * path as the inline pipeline (no new stage / schema): `add` → `admit`,
231
+ * `noop` → `dedup`, `update` / `conflict` → `supersede`. All reconcile
232
+ * decisions are stamped with the `defer-to-deep` stage - the reconcile
233
+ * loop is the consolidator's replacement for the deferred deep-judge
234
+ * step it supersedes.
235
+ *
236
+ * @stable
237
+ */
238
+ export function reconcileToConflictDecision(decision: ReconcileDecision): ConflictDecision {
239
+ const stage: ConflictStage = 'defer-to-deep';
240
+ switch (decision.action) {
241
+ case 'add':
242
+ return {
243
+ kind: 'admit',
244
+ stage,
245
+ ...(decision.reason !== undefined ? { reason: decision.reason } : {}),
246
+ };
247
+ case 'noop':
248
+ return {
249
+ kind: 'dedup',
250
+ stage,
251
+ existingId: decision.targetId,
252
+ ...(decision.reason !== undefined ? { reason: decision.reason } : {}),
253
+ };
254
+ case 'update':
255
+ case 'conflict':
256
+ return {
257
+ kind: 'supersede',
258
+ stage,
259
+ existingId: decision.targetId,
260
+ reason: decision.reason,
261
+ };
262
+ }
263
+ }