@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.
- package/CHANGELOG.md +71 -0
- package/README.md +1 -1
- package/dist/conflict/index.js +0 -1
- package/dist/conflict/pipeline.js +0 -1
- package/dist/conflict/pipeline.js.map +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js +0 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
- package/dist/conflict/types.d.ts +1 -1
- package/dist/conflict/types.js.map +1 -1
- package/dist/consolidator/index.js +2 -0
- package/dist/consolidator/index.js.map +1 -1
- package/dist/consolidator/lock.js +7 -1
- package/dist/consolidator/lock.js.map +1 -1
- package/dist/consolidator/phases/deep.js +19 -12
- package/dist/consolidator/phases/deep.js.map +1 -1
- package/dist/consolidator/phases/induce.d.ts.map +1 -1
- package/dist/consolidator/phases/induce.js +2 -3
- package/dist/consolidator/phases/induce.js.map +1 -1
- package/dist/consolidator/phases/reflect.js +35 -8
- package/dist/consolidator/phases/reflect.js.map +1 -1
- package/dist/consolidator/phases/standard.js +199 -28
- package/dist/consolidator/phases/standard.js.map +1 -1
- package/dist/consolidator/reconcile.js +16 -15
- package/dist/consolidator/reconcile.js.map +1 -1
- package/dist/consolidator/runtime.d.ts.map +1 -1
- package/dist/consolidator/runtime.js +129 -47
- package/dist/consolidator/runtime.js.map +1 -1
- package/dist/consolidator/scheduler.d.ts +17 -3
- package/dist/consolidator/scheduler.d.ts.map +1 -1
- package/dist/consolidator/scheduler.js.map +1 -1
- package/dist/consolidator/types.d.ts +30 -1
- package/dist/consolidator/types.d.ts.map +1 -1
- package/dist/consolidator/types.js +10 -0
- package/dist/consolidator/types.js.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.d.ts +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
- package/dist/context-engine/compaction/compactor.d.ts +1 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -1
- package/dist/context-engine/compaction/compactor.js +32 -8
- package/dist/context-engine/compaction/compactor.js.map +1 -1
- package/dist/context-engine/compaction/hooks/types.d.ts +1 -1
- package/dist/context-engine/compaction/index.d.ts +1 -0
- package/dist/context-engine/compaction/templates/marker.d.ts +33 -0
- package/dist/context-engine/compaction/templates/marker.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/marker.js +34 -0
- package/dist/context-engine/compaction/templates/marker.js.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.js +4 -3
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
- package/dist/context-engine/engine.d.ts +7 -176
- package/dist/context-engine/engine.d.ts.map +1 -1
- package/dist/context-engine/engine.js +0 -2
- package/dist/context-engine/engine.js.map +1 -1
- package/dist/context-engine/index.d.ts +8 -49
- package/dist/context-engine/io-types.d.ts +144 -0
- package/dist/context-engine/io-types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +1 -1
- package/dist/context-engine/locale-packs/types.js.map +1 -1
- package/dist/context-engine/privacy-filter.d.ts +1 -2
- package/dist/context-engine/privacy-filter.d.ts.map +1 -1
- package/dist/context-engine/privacy-filter.js +1 -2
- package/dist/context-engine/privacy-filter.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +2 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/facade.d.ts +43 -52
- package/dist/facade.d.ts.map +1 -1
- package/dist/facade.js +5 -3
- package/dist/facade.js.map +1 -1
- package/dist/graph/entity-resolver.js +1 -1
- package/dist/graph/entity-resolver.js.map +1 -1
- package/dist/index.d.ts +16 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/internal/envelope.js +57 -0
- package/dist/internal/envelope.js.map +1 -0
- package/dist/internal/injection-heuristics.js +0 -0
- package/dist/internal/injection-heuristics.js.map +1 -1
- package/dist/internal/llm-json.js +40 -0
- package/dist/internal/llm-json.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +23 -10
- package/dist/internal/storage-adapter.d.ts.map +1 -1
- package/dist/memory-interface.d.ts +131 -0
- package/dist/memory-interface.d.ts.map +1 -0
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/search/iterative.d.ts.map +1 -1
- package/dist/search/iterative.js +2 -3
- package/dist/search/iterative.js.map +1 -1
- package/dist/search/query-transform.d.ts.map +1 -1
- package/dist/search/query-transform.js +2 -3
- package/dist/search/query-transform.js.map +1 -1
- package/dist/tiers/episodic-memory.js +3 -3
- package/dist/tiers/episodic-memory.js.map +1 -1
- package/dist/tiers/index.d.ts +2 -2
- package/dist/tiers/insight-memory.js +1 -1
- package/dist/tiers/insight-memory.js.map +1 -1
- package/dist/tiers/procedural-memory.d.ts +1 -1
- package/dist/tiers/procedural-memory.js +2 -2
- package/dist/tiers/procedural-memory.js.map +1 -1
- package/dist/tiers/semantic-memory.d.ts +58 -4
- package/dist/tiers/semantic-memory.d.ts.map +1 -1
- package/dist/tiers/semantic-memory.js +103 -20
- package/dist/tiers/semantic-memory.js.map +1 -1
- package/dist/tools/block-tools.d.ts +19 -57
- package/dist/tools/block-tools.d.ts.map +1 -1
- package/dist/tools/block-tools.js.map +1 -1
- package/dist/tools/fact-tools.d.ts +53 -214
- package/dist/tools/fact-tools.d.ts.map +1 -1
- package/dist/tools/fact-tools.js +3 -2
- package/dist/tools/fact-tools.js.map +1 -1
- package/dist/tools/recall-tools.d.ts +34 -175
- package/dist/tools/recall-tools.d.ts.map +1 -1
- package/dist/tools/recall-tools.js +5 -4
- package/dist/tools/recall-tools.js.map +1 -1
- package/dist/tools/runbook-tools.d.ts +15 -58
- package/dist/tools/runbook-tools.d.ts.map +1 -1
- package/dist/tools/runbook-tools.js.map +1 -1
- package/dist/tools/types.d.ts +1 -1
- package/package.json +18 -17
- package/src/conflict/index.ts +29 -0
- package/src/conflict/locale-packs/en.ts +142 -0
- package/src/conflict/locale-packs/index.ts +17 -0
- package/src/conflict/locale-packs/types.ts +138 -0
- package/src/conflict/pipeline.ts +371 -0
- package/src/conflict/stages/helpers.ts +65 -0
- package/src/conflict/stages/stage1-exact-dedup.ts +40 -0
- package/src/conflict/stages/stage2-embedding-three-zone.ts +54 -0
- package/src/conflict/stages/stage3-heuristic-regex.ts +51 -0
- package/src/conflict/stages/stage4-subject-predicate.ts +46 -0
- package/src/conflict/stages/stage5-defer-to-deep.ts +34 -0
- package/src/conflict/types.ts +263 -0
- package/src/consolidator/budget.ts +312 -0
- package/src/consolidator/decay.ts +246 -0
- package/src/consolidator/dlq.ts +61 -0
- package/src/consolidator/errors.ts +87 -0
- package/src/consolidator/idempotency.ts +27 -0
- package/src/consolidator/index.ts +269 -0
- package/src/consolidator/lock.ts +141 -0
- package/src/consolidator/noise-filter.ts +235 -0
- package/src/consolidator/phases/deep.ts +308 -0
- package/src/consolidator/phases/induce.ts +423 -0
- package/src/consolidator/phases/learned-context.ts +233 -0
- package/src/consolidator/phases/light.ts +166 -0
- package/src/consolidator/phases/reflect.ts +504 -0
- package/src/consolidator/phases/standard.ts +1002 -0
- package/src/consolidator/reconcile.ts +273 -0
- package/src/consolidator/runtime.ts +1049 -0
- package/src/consolidator/scheduler.ts +182 -0
- package/src/consolidator/triggers.ts +134 -0
- package/src/consolidator/types.ts +622 -0
- package/src/context-engine/annotations.ts +161 -0
- package/src/context-engine/auto-recall.ts +92 -0
- package/src/context-engine/compaction/clear-tool-results.ts +232 -0
- package/src/context-engine/compaction/compactor.ts +403 -0
- package/src/context-engine/compaction/hooks/reanchor-persona-block.ts +55 -0
- package/src/context-engine/compaction/hooks/reanchor-pinned-facts.ts +66 -0
- package/src/context-engine/compaction/hooks/reanchor-project-rules.ts +57 -0
- package/src/context-engine/compaction/hooks/reanchor-recent-results.ts +116 -0
- package/src/context-engine/compaction/hooks/types.ts +68 -0
- package/src/context-engine/compaction/index.ts +60 -0
- package/src/context-engine/compaction/templates/marker.ts +33 -0
- package/src/context-engine/compaction/templates/summary-9-section.ts +223 -0
- package/src/context-engine/compaction/thresholds.ts +107 -0
- package/src/context-engine/compaction/types.ts +255 -0
- package/src/context-engine/engine.ts +970 -0
- package/src/context-engine/index.ts +118 -0
- package/src/context-engine/io-types.ts +162 -0
- package/src/context-engine/locale-packs/en.ts +107 -0
- package/src/context-engine/locale-packs/index.ts +19 -0
- package/src/context-engine/locale-packs/resolver.ts +197 -0
- package/src/context-engine/locale-packs/types.ts +177 -0
- package/src/context-engine/metadata.ts +114 -0
- package/src/context-engine/preambles/inbound-en.ts +19 -0
- package/src/context-engine/privacy-filter.ts +190 -0
- package/src/context-engine/templates/base-en.ts +19 -0
- package/src/context-engine/templates/composer.ts +91 -0
- package/src/context-engine/token-budget.ts +319 -0
- package/src/context-engine/token-counter.ts +157 -0
- package/src/errors/index.ts +273 -0
- package/src/facade.ts +750 -0
- package/src/graph/entity-resolver.ts +364 -0
- package/src/graph/index.ts +24 -0
- package/src/index.ts +259 -0
- package/src/internal/contextualize.ts +173 -0
- package/src/internal/embedder-binding.ts +53 -0
- package/src/internal/envelope.ts +70 -0
- package/src/internal/id.ts +23 -0
- package/src/internal/injection-heuristics.ts +0 -0
- package/src/internal/llm-json.ts +38 -0
- package/src/internal/spans.ts +35 -0
- package/src/internal/storage-adapter.ts +963 -0
- package/src/memory-interface.ts +158 -0
- package/src/migration/embedder-migration.ts +237 -0
- package/src/migration/index.ts +13 -0
- package/src/search/explain.ts +123 -0
- package/src/search/fit-weights.ts +109 -0
- package/src/search/graph-ppr.ts +38 -0
- package/src/search/index.ts +55 -0
- package/src/search/iterative.ts +658 -0
- package/src/search/query-transform.ts +285 -0
- package/src/search/rrf.ts +225 -0
- package/src/search/trust.ts +55 -0
- package/src/search/types.ts +50 -0
- package/src/tiers/episodic-memory.ts +463 -0
- package/src/tiers/index.ts +50 -0
- package/src/tiers/insight-memory.ts +208 -0
- package/src/tiers/procedural-memory.ts +482 -0
- package/src/tiers/semantic-memory.ts +1729 -0
- package/src/tiers/session-memory.ts +271 -0
- package/src/tiers/shared-memory.ts +62 -0
- package/src/tiers/working-memory.ts +368 -0
- package/src/tools/block-tools.ts +172 -0
- package/src/tools/fact-tools.ts +473 -0
- package/src/tools/index.ts +109 -0
- package/src/tools/recall-tools.ts +303 -0
- package/src/tools/runbook-tools.ts +107 -0
- package/src/tools/taint.ts +39 -0
- package/src/tools/types.ts +37 -0
- package/dist/conflict/locale-packs/index.js +0 -4
- package/dist/context-engine/compaction/index.js +0 -10
- package/dist/context-engine/index.d.ts.map +0 -1
- package/dist/context-engine/index.js +0 -25
- package/dist/context-engine/locale-packs/index.js +0 -4
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled English locale pack used by the multi-stage conflict
|
|
3
|
+
* resolution pipeline (DEC-117 / ADR-018 ext / RB-02). The patterns
|
|
4
|
+
* cover the common change signals personal-assistant scenarios surface
|
|
5
|
+
* on a daily basis: relocation, job change, preference flips,
|
|
6
|
+
* relationship transitions, and health updates.
|
|
7
|
+
*
|
|
8
|
+
* Operators supplying a different locale register their own pack via
|
|
9
|
+
* {@link defineLocalePack}. The framework ships English by default so
|
|
10
|
+
* the pipeline always has a working baseline.
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { defineLocalePack, type LocalePack } from './types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The default English `LocalePack`. Frozen - share the singleton.
|
|
19
|
+
*
|
|
20
|
+
* @stable
|
|
21
|
+
*/
|
|
22
|
+
export const enLocalePack: LocalePack = defineLocalePack({
|
|
23
|
+
id: 'en',
|
|
24
|
+
supersedeMarkers: [
|
|
25
|
+
// Location.
|
|
26
|
+
{ regex: /\bmoved (?:to|from)\b/i, kind: 'location', confidence: 0.9 },
|
|
27
|
+
{
|
|
28
|
+
regex: /\bnow (?:live|living|based|residing) (?:in|at)\b/i,
|
|
29
|
+
kind: 'location',
|
|
30
|
+
confidence: 0.9,
|
|
31
|
+
},
|
|
32
|
+
{ regex: /\brelocated\b/i, kind: 'location', confidence: 0.85 },
|
|
33
|
+
{ regex: /\bjust moved\b/i, kind: 'location', confidence: 0.85 },
|
|
34
|
+
{ regex: /\bI live in\b/i, kind: 'location', confidence: 0.7 },
|
|
35
|
+
|
|
36
|
+
// Job.
|
|
37
|
+
{ regex: /\b(?:got|started|landed)(?: a)?(?: new)? job\b/i, kind: 'job', confidence: 0.9 },
|
|
38
|
+
{ regex: /\bgot promoted\b/i, kind: 'job', confidence: 0.9 },
|
|
39
|
+
{ regex: /\bgot fired\b/i, kind: 'job', confidence: 0.9 },
|
|
40
|
+
{ regex: /\b(?:I )?quit (?:my )?job\b/i, kind: 'job', confidence: 0.9 },
|
|
41
|
+
{ regex: /\bnew employer\b/i, kind: 'job', confidence: 0.85 },
|
|
42
|
+
{ regex: /\bnew job\b/i, kind: 'job', confidence: 0.8 },
|
|
43
|
+
{ regex: /\bswitched (?:companies|jobs|employers)\b/i, kind: 'job', confidence: 0.9 },
|
|
44
|
+
{ regex: /\bnow work(?:s|ing)? (?:at|for)\b/i, kind: 'job', confidence: 0.85 },
|
|
45
|
+
|
|
46
|
+
// Preference.
|
|
47
|
+
{ regex: /\bswitched (?:from|to)\b/i, kind: 'preference', confidence: 0.85 },
|
|
48
|
+
{ regex: /\bchanged my (?:mind|preference|opinion)\b/i, kind: 'preference', confidence: 0.85 },
|
|
49
|
+
{
|
|
50
|
+
regex: /\bused to (?:like|love|prefer|drink|eat|use)\b/i,
|
|
51
|
+
kind: 'preference',
|
|
52
|
+
confidence: 0.85,
|
|
53
|
+
},
|
|
54
|
+
{ regex: /\bused to (?:live|work|study|play|travel)\b/i, kind: 'generic', confidence: 0.85 },
|
|
55
|
+
{ regex: /\bI prefer\b/i, kind: 'preference', confidence: 0.6 },
|
|
56
|
+
|
|
57
|
+
// Relationship.
|
|
58
|
+
{ regex: /\bgot (?:married|divorced|engaged)\b/i, kind: 'relationship', confidence: 0.9 },
|
|
59
|
+
{ regex: /\bbroke up\b/i, kind: 'relationship', confidence: 0.85 },
|
|
60
|
+
|
|
61
|
+
// Health.
|
|
62
|
+
{ regex: /\bdiagnosed (?:with|as)\b/i, kind: 'health', confidence: 0.85 },
|
|
63
|
+
{ regex: /\brecovered from\b/i, kind: 'health', confidence: 0.85 },
|
|
64
|
+
|
|
65
|
+
// Generic.
|
|
66
|
+
{ regex: /\bformerly\b/i, kind: 'generic', confidence: 0.7 },
|
|
67
|
+
{ regex: /\bpreviously\b/i, kind: 'generic', confidence: 0.7 },
|
|
68
|
+
{ regex: /\bused to be\b/i, kind: 'generic', confidence: 0.7 },
|
|
69
|
+
{ regex: /\bis now\b/i, kind: 'generic', confidence: 0.7 },
|
|
70
|
+
],
|
|
71
|
+
negationMarkers: [
|
|
72
|
+
{ regex: /\bno longer\b/i, kind: 'preference', confidence: 0.85 },
|
|
73
|
+
{ regex: /\bdoesn(?:'|\u2019)?t\b/i, confidence: 0.6 },
|
|
74
|
+
{
|
|
75
|
+
regex:
|
|
76
|
+
/\b(?:don(?:'|\u2019)?t|do not) (?:like|prefer|use|drink|eat|live|work|play|go|want)\b/i,
|
|
77
|
+
kind: 'preference',
|
|
78
|
+
confidence: 0.85,
|
|
79
|
+
},
|
|
80
|
+
{ regex: /\bnever\b/i, confidence: 0.6 },
|
|
81
|
+
{ regex: /\bnot anymore\b/i, kind: 'preference', confidence: 0.85 },
|
|
82
|
+
{ regex: /\bisn(?:'|\u2019)?t\b/i, confidence: 0.6 },
|
|
83
|
+
{
|
|
84
|
+
regex: /\bnot (?:a fan|interested|willing|going|planning|in|into|looking) /i,
|
|
85
|
+
kind: 'preference',
|
|
86
|
+
confidence: 0.7,
|
|
87
|
+
},
|
|
88
|
+
{ regex: /\b(?:is|are|am|was|were) not\b/i, confidence: 0.5 },
|
|
89
|
+
],
|
|
90
|
+
predicateNormalisers: [
|
|
91
|
+
'is',
|
|
92
|
+
'are',
|
|
93
|
+
'am',
|
|
94
|
+
'was',
|
|
95
|
+
'were',
|
|
96
|
+
'be',
|
|
97
|
+
'been',
|
|
98
|
+
'being',
|
|
99
|
+
'has',
|
|
100
|
+
'have',
|
|
101
|
+
'had',
|
|
102
|
+
'do',
|
|
103
|
+
'does',
|
|
104
|
+
'did',
|
|
105
|
+
'lives',
|
|
106
|
+
'live',
|
|
107
|
+
'lived',
|
|
108
|
+
'living',
|
|
109
|
+
'works',
|
|
110
|
+
'work',
|
|
111
|
+
'worked',
|
|
112
|
+
'working',
|
|
113
|
+
'plays',
|
|
114
|
+
'play',
|
|
115
|
+
'played',
|
|
116
|
+
'playing',
|
|
117
|
+
'likes',
|
|
118
|
+
'like',
|
|
119
|
+
'liked',
|
|
120
|
+
'loves',
|
|
121
|
+
'love',
|
|
122
|
+
'loved',
|
|
123
|
+
'prefers',
|
|
124
|
+
'prefer',
|
|
125
|
+
'preferred',
|
|
126
|
+
'drinks',
|
|
127
|
+
'drink',
|
|
128
|
+
'drank',
|
|
129
|
+
'drunk',
|
|
130
|
+
'eats',
|
|
131
|
+
'eat',
|
|
132
|
+
'ate',
|
|
133
|
+
'eaten',
|
|
134
|
+
'in',
|
|
135
|
+
'at',
|
|
136
|
+
'on',
|
|
137
|
+
'with',
|
|
138
|
+
'for',
|
|
139
|
+
'to',
|
|
140
|
+
],
|
|
141
|
+
subjectStopWords: ['a', 'an', 'the', 'my', 'your', 'his', 'her', 'their', 'our', 'this', 'that'],
|
|
142
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the locale-pack module. The bundled English pack
|
|
3
|
+
* (`enLocalePack`) is the default; additional locales are
|
|
4
|
+
* community-supplied via {@link defineLocalePack}.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { enLocalePack } from './en.js';
|
|
10
|
+
export {
|
|
11
|
+
defineLocalePack,
|
|
12
|
+
evaluateMarkers,
|
|
13
|
+
type LocaleMatch,
|
|
14
|
+
type LocalePack,
|
|
15
|
+
type LocalePatternEntry,
|
|
16
|
+
type LocaleSupersedeKind,
|
|
17
|
+
} from './types.js';
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the per-locale heuristic packs consumed by the
|
|
3
|
+
* Stage 3 (regex) and Stage 4 (subject/predicate) stages of the
|
|
4
|
+
* multi-stage conflict resolution pipeline (DEC-117 / ADR-018 ext).
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Categorisation of a supersede marker. Surfaced on the audit row so
|
|
11
|
+
* downstream tooling can group conflicts by lifecycle event
|
|
12
|
+
* (relocation / job / preference / relationship / health / generic).
|
|
13
|
+
*
|
|
14
|
+
* @stable
|
|
15
|
+
*/
|
|
16
|
+
export type LocaleSupersedeKind =
|
|
17
|
+
| 'location'
|
|
18
|
+
| 'job'
|
|
19
|
+
| 'preference'
|
|
20
|
+
| 'relationship'
|
|
21
|
+
| 'health'
|
|
22
|
+
| 'generic';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Single regex pattern bundled inside a `LocalePack`. The pipeline
|
|
26
|
+
* applies the regex to the fact body in case-insensitive mode unless
|
|
27
|
+
* the pattern was constructed with explicit flags.
|
|
28
|
+
*
|
|
29
|
+
* @stable
|
|
30
|
+
*/
|
|
31
|
+
export interface LocalePatternEntry {
|
|
32
|
+
readonly regex: RegExp;
|
|
33
|
+
readonly kind?: LocaleSupersedeKind;
|
|
34
|
+
/**
|
|
35
|
+
* Optional confidence ∈ `[0, 1]` surfaced through the audit row's
|
|
36
|
+
* `reason` field. Defaults to `0.8`.
|
|
37
|
+
*/
|
|
38
|
+
readonly confidence?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A `LocalePack` defines the regex sets Stage 3 and the predicate
|
|
43
|
+
* verb set Stage 4 use to evaluate a candidate fact pair against the
|
|
44
|
+
* existing conflicts.
|
|
45
|
+
*
|
|
46
|
+
* The bundled English pack (`enLocalePack`) covers the most common
|
|
47
|
+
* personal-assistant change signals (relocation / job change /
|
|
48
|
+
* preference flip / relationship / health). Additional locales are
|
|
49
|
+
* registered through {@link defineLocalePack}.
|
|
50
|
+
*
|
|
51
|
+
* @stable
|
|
52
|
+
*/
|
|
53
|
+
export interface LocalePack {
|
|
54
|
+
/** Stable lowercase identifier (`'en'`, `'fr'`, …). */
|
|
55
|
+
readonly id: string;
|
|
56
|
+
/** Patterns that explicitly mark the candidate as superseding the existing fact. */
|
|
57
|
+
readonly supersedeMarkers: ReadonlyArray<LocalePatternEntry>;
|
|
58
|
+
/** Patterns that negate the existing fact (treated as supersede in Stage 3). */
|
|
59
|
+
readonly negationMarkers: ReadonlyArray<LocalePatternEntry>;
|
|
60
|
+
/**
|
|
61
|
+
* Verbs (or verb particles) Stage 4 strips while normalising a
|
|
62
|
+
* predicate so e.g. `'lives in'` and `'living in'` collapse to the
|
|
63
|
+
* same key. Lowercase tokens.
|
|
64
|
+
*/
|
|
65
|
+
readonly predicateNormalisers: ReadonlyArray<string>;
|
|
66
|
+
/** Tokens dropped from the subject before comparison (`'a'`, `'the'`, …). */
|
|
67
|
+
readonly subjectStopWords: ReadonlyArray<string>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Outcome of a single locale-pack regex evaluation. Returned by
|
|
72
|
+
* `evaluateMarkers(...)` so the pipeline can propagate the matched
|
|
73
|
+
* marker into the audit row's `reason` field.
|
|
74
|
+
*
|
|
75
|
+
* @stable
|
|
76
|
+
*/
|
|
77
|
+
export interface LocaleMatch {
|
|
78
|
+
readonly matched: boolean;
|
|
79
|
+
readonly kind?: LocaleSupersedeKind;
|
|
80
|
+
readonly confidence?: number;
|
|
81
|
+
readonly excerpt?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Builder used by application code that wants to ship a custom pack:
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* const ru = defineLocalePack({
|
|
89
|
+
* id: 'ru',
|
|
90
|
+
* supersedeMarkers: [{ regex: /\bпереехал\b/iu, kind: 'location' }],
|
|
91
|
+
* negationMarkers: [{ regex: /\bне\b/iu }],
|
|
92
|
+
* predicateNormalisers: ['жив', 'жил', 'живёт'],
|
|
93
|
+
* subjectStopWords: ['я', 'ты'],
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* The builder freezes every input so the pack can be safely reused
|
|
98
|
+
* across multiple `Memory` instances without accidental mutation.
|
|
99
|
+
*
|
|
100
|
+
* @stable
|
|
101
|
+
*/
|
|
102
|
+
export function defineLocalePack(input: LocalePack): LocalePack {
|
|
103
|
+
return Object.freeze({
|
|
104
|
+
id: input.id,
|
|
105
|
+
supersedeMarkers: Object.freeze([...input.supersedeMarkers]),
|
|
106
|
+
negationMarkers: Object.freeze([...input.negationMarkers]),
|
|
107
|
+
predicateNormalisers: Object.freeze([...input.predicateNormalisers]),
|
|
108
|
+
subjectStopWords: Object.freeze([...input.subjectStopWords]),
|
|
109
|
+
}) as LocalePack;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Apply a list of patterns to the supplied text and return the
|
|
114
|
+
* highest-confidence match (first match wins on ties). Surfaced for
|
|
115
|
+
* unit tests; the pipeline calls this internally.
|
|
116
|
+
*
|
|
117
|
+
* @stable
|
|
118
|
+
*/
|
|
119
|
+
export function evaluateMarkers(
|
|
120
|
+
text: string,
|
|
121
|
+
patterns: ReadonlyArray<LocalePatternEntry>,
|
|
122
|
+
): LocaleMatch {
|
|
123
|
+
let best: LocaleMatch = { matched: false };
|
|
124
|
+
for (const pattern of patterns) {
|
|
125
|
+
const m = pattern.regex.exec(text);
|
|
126
|
+
if (m === null) continue;
|
|
127
|
+
const confidence = pattern.confidence ?? 0.8;
|
|
128
|
+
if (best.matched && (best.confidence ?? 0) >= confidence) continue;
|
|
129
|
+
const candidate: LocaleMatch = {
|
|
130
|
+
matched: true,
|
|
131
|
+
...(pattern.kind !== undefined ? { kind: pattern.kind } : {}),
|
|
132
|
+
confidence,
|
|
133
|
+
excerpt: m[0],
|
|
134
|
+
};
|
|
135
|
+
best = candidate;
|
|
136
|
+
}
|
|
137
|
+
return best;
|
|
138
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runConflictPipeline` - orchestrator for the multi-stage conflict
|
|
3
|
+
* resolution pipeline shipped in `@graphorin/memory` Phase 10b
|
|
4
|
+
* (DEC-117 / ADR-018 ext / RB-02).
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Fact, MemoryHit } from '@graphorin/core';
|
|
10
|
+
import { withMemorySpan } from '../internal/spans.js';
|
|
11
|
+
import { enLocalePack, type LocalePack } from './locale-packs/index.js';
|
|
12
|
+
import { stage1ExactDedup } from './stages/stage1-exact-dedup.js';
|
|
13
|
+
import { stage2EmbeddingThreeZone } from './stages/stage2-embedding-three-zone.js';
|
|
14
|
+
import { stage3HeuristicRegex } from './stages/stage3-heuristic-regex.js';
|
|
15
|
+
import { stage4SubjectPredicate } from './stages/stage4-subject-predicate.js';
|
|
16
|
+
import { stage5DeferToDeep } from './stages/stage5-defer-to-deep.js';
|
|
17
|
+
import {
|
|
18
|
+
type ConflictDecision,
|
|
19
|
+
type ConflictPipeline,
|
|
20
|
+
type ConflictPipelineDeps,
|
|
21
|
+
type ConflictPipelineOptions,
|
|
22
|
+
type ConflictStage,
|
|
23
|
+
type ConflictThresholds,
|
|
24
|
+
DEFAULT_CONFLICT_THRESHOLDS,
|
|
25
|
+
type StageContext,
|
|
26
|
+
type StageOutcome,
|
|
27
|
+
} from './types.js';
|
|
28
|
+
|
|
29
|
+
let bypassWarnedOnce = false;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Reset the one-shot bypass-warning flag. Test-only helper - production
|
|
33
|
+
* callers never invoke this.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function _resetBypassWarningForTesting(): void {
|
|
38
|
+
bypassWarnedOnce = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* One-shot helper that mirrors RB-02 §8.1's `runConflictPipeline({...})`
|
|
43
|
+
* spec - convenient for callers that do not need to pre-build + cache
|
|
44
|
+
* the pipeline. Production wiring should still go through
|
|
45
|
+
* {@link createConflictPipeline} (`SemanticMemory` re-uses the cached
|
|
46
|
+
* instance per `Memory`).
|
|
47
|
+
*
|
|
48
|
+
* @stable
|
|
49
|
+
*/
|
|
50
|
+
export async function runConflictPipeline(args: {
|
|
51
|
+
readonly candidate: Fact;
|
|
52
|
+
readonly deps: ConflictPipelineDeps;
|
|
53
|
+
readonly options?: ConflictPipelineOptions;
|
|
54
|
+
}): Promise<ConflictDecision> {
|
|
55
|
+
const pipeline = createConflictPipeline(args.options ?? {});
|
|
56
|
+
return pipeline.run(args.deps, args.candidate);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns a frozen pipeline handle wired with the supplied options.
|
|
61
|
+
*
|
|
62
|
+
* @stable
|
|
63
|
+
*/
|
|
64
|
+
export function createConflictPipeline(options: ConflictPipelineOptions = {}): ConflictPipeline {
|
|
65
|
+
const mode: 'on' | 'off' = options.mode ?? 'on';
|
|
66
|
+
const localePack: LocalePack = options.localePack ?? enLocalePack;
|
|
67
|
+
const thresholds: ConflictThresholds = Object.freeze({
|
|
68
|
+
hot: options.thresholds?.hot ?? DEFAULT_CONFLICT_THRESHOLDS.hot,
|
|
69
|
+
nearDup: options.thresholds?.nearDup ?? DEFAULT_CONFLICT_THRESHOLDS.nearDup,
|
|
70
|
+
cold: options.thresholds?.cold ?? DEFAULT_CONFLICT_THRESHOLDS.cold,
|
|
71
|
+
});
|
|
72
|
+
validateThresholds(thresholds);
|
|
73
|
+
const stage2TopK = options.stage2TopK ?? 5;
|
|
74
|
+
const now = options.now ?? ((): string => new Date().toISOString());
|
|
75
|
+
|
|
76
|
+
async function run(deps: ConflictPipelineDeps, candidate: Fact): Promise<ConflictDecision> {
|
|
77
|
+
if (mode === 'off') {
|
|
78
|
+
if (!bypassWarnedOnce) {
|
|
79
|
+
bypassWarnedOnce = true;
|
|
80
|
+
warnBypass();
|
|
81
|
+
}
|
|
82
|
+
return { kind: 'admit', stage: 'exact-dedup', reason: 'pipeline-mode-off' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
checkAbort(deps.signal);
|
|
86
|
+
const conflictStore = options.conflictStore ?? deps.store.conflicts;
|
|
87
|
+
|
|
88
|
+
return withMemorySpan(
|
|
89
|
+
deps.tracer,
|
|
90
|
+
'memory.conflict',
|
|
91
|
+
{
|
|
92
|
+
userId: candidate.userId,
|
|
93
|
+
...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),
|
|
94
|
+
...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
'memory.conflict.candidate_id': candidate.id,
|
|
98
|
+
'memory.conflict.locale_pack': localePack.id,
|
|
99
|
+
},
|
|
100
|
+
async (span) => {
|
|
101
|
+
const existing = await collectVectorCandidates(deps, candidate, stage2TopK);
|
|
102
|
+
const ctx: StageContext = {
|
|
103
|
+
candidate,
|
|
104
|
+
existing,
|
|
105
|
+
localePack,
|
|
106
|
+
thresholds,
|
|
107
|
+
};
|
|
108
|
+
const decision = await orchestrate(ctx);
|
|
109
|
+
await persistDecision(decision, candidate, conflictStore, now);
|
|
110
|
+
span.setAttributes({
|
|
111
|
+
'memory.conflict.stage': decision.stage,
|
|
112
|
+
'memory.conflict.decision': decision.kind,
|
|
113
|
+
'memory.conflict.candidate_count': existing.length,
|
|
114
|
+
...(decision.kind === 'dedup' && decision.similarity !== undefined
|
|
115
|
+
? { 'memory.conflict.similarity': decision.similarity }
|
|
116
|
+
: {}),
|
|
117
|
+
...(decision.kind === 'pending' && decision.similarity !== undefined
|
|
118
|
+
? { 'memory.conflict.similarity': decision.similarity }
|
|
119
|
+
: {}),
|
|
120
|
+
...(decision.kind !== 'admit'
|
|
121
|
+
? { 'memory.conflict.reason': decision.reason ?? 'unspecified' }
|
|
122
|
+
: decision.reason !== undefined
|
|
123
|
+
? { 'memory.conflict.reason': decision.reason }
|
|
124
|
+
: {}),
|
|
125
|
+
});
|
|
126
|
+
return decision;
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Visit the five stages in the order documented by RB-02 §8.1 and
|
|
133
|
+
* the conflict-resolution policy (DEC-117). Stage 1 short-circuits
|
|
134
|
+
* exact duplicates. Stage 2 always computes the zone but the
|
|
135
|
+
* orchestrator splits its decision in two halves: the HOT zone is
|
|
136
|
+
* the only zone that overrides Stage 3 / Stage 4 (semantic identity
|
|
137
|
+
* outranks any explicit user marker); every other zone runs Stages
|
|
138
|
+
* 3 and 4 first to give the heuristic markers a chance to supersede
|
|
139
|
+
* before falling back to Stage 2's default decision.
|
|
140
|
+
*
|
|
141
|
+
* Stage 3 is declared "orthogonal to Stage 2", which is why the
|
|
142
|
+
* heuristic stages run regardless of Stage 2's COLD / NEAR-DUP /
|
|
143
|
+
* CONFLICT-CHECK assignment as long as Stage 2 surfaced at least
|
|
144
|
+
* one candidate. The HOT-zone short-circuit preserves the dedup
|
|
145
|
+
* invariant for semantic identity.
|
|
146
|
+
*/
|
|
147
|
+
async function orchestrate(ctx: StageContext): Promise<ConflictDecision> {
|
|
148
|
+
const stage1 = await stage1ExactDedup.evaluate(ctx);
|
|
149
|
+
if (stage1.kind !== 'continue') {
|
|
150
|
+
return mapOutcomeToDecision('exact-dedup', stage1, ctx.candidate);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const stage2 = await stage2EmbeddingThreeZone.evaluate(ctx);
|
|
154
|
+
if (stage2.kind === 'dedup' && stage2.reason === 'embedding-hot-zone') {
|
|
155
|
+
return mapOutcomeToDecision('embedding-three-zone', stage2, ctx.candidate);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (ctx.existing.length > 0) {
|
|
159
|
+
const stage3 = await stage3HeuristicRegex.evaluate(ctx);
|
|
160
|
+
if (stage3.kind !== 'continue') {
|
|
161
|
+
return mapOutcomeToDecision('heuristic-regex', stage3, ctx.candidate);
|
|
162
|
+
}
|
|
163
|
+
const stage4 = await stage4SubjectPredicate.evaluate(ctx);
|
|
164
|
+
if (stage4.kind !== 'continue') {
|
|
165
|
+
return mapOutcomeToDecision('subject-predicate', stage4, ctx.candidate);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (stage2.kind === 'dedup' || stage2.kind === 'admit') {
|
|
170
|
+
return mapOutcomeToDecision('embedding-three-zone', stage2, ctx.candidate);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const stage5 = await stage5DeferToDeep.evaluate(ctx);
|
|
174
|
+
return mapOutcomeToDecision('defer-to-deep', stage5, ctx.candidate);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return Object.freeze({
|
|
178
|
+
mode,
|
|
179
|
+
localePack,
|
|
180
|
+
thresholds,
|
|
181
|
+
run,
|
|
182
|
+
}) satisfies ConflictPipeline;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function mapOutcomeToDecision(
|
|
186
|
+
stage: ConflictStage,
|
|
187
|
+
outcome: StageOutcome,
|
|
188
|
+
candidate: Fact,
|
|
189
|
+
): ConflictDecision {
|
|
190
|
+
switch (outcome.kind) {
|
|
191
|
+
case 'continue':
|
|
192
|
+
return { kind: 'admit', stage, reason: 'continue-fallthrough' };
|
|
193
|
+
case 'admit':
|
|
194
|
+
return {
|
|
195
|
+
kind: 'admit',
|
|
196
|
+
stage,
|
|
197
|
+
...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),
|
|
198
|
+
};
|
|
199
|
+
case 'dedup':
|
|
200
|
+
return {
|
|
201
|
+
kind: 'dedup',
|
|
202
|
+
stage,
|
|
203
|
+
existingId: outcome.existingId,
|
|
204
|
+
...(outcome.similarity !== undefined ? { similarity: outcome.similarity } : {}),
|
|
205
|
+
...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),
|
|
206
|
+
};
|
|
207
|
+
case 'supersede':
|
|
208
|
+
return {
|
|
209
|
+
kind: 'supersede',
|
|
210
|
+
stage,
|
|
211
|
+
existingId: outcome.existingId,
|
|
212
|
+
reason: outcome.reason,
|
|
213
|
+
};
|
|
214
|
+
case 'pending':
|
|
215
|
+
return {
|
|
216
|
+
kind: 'pending',
|
|
217
|
+
stage,
|
|
218
|
+
candidateId: candidate.id,
|
|
219
|
+
conflictingIds: outcome.conflictingIds,
|
|
220
|
+
...(outcome.similarity !== undefined ? { similarity: outcome.similarity } : {}),
|
|
221
|
+
...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function persistDecision(
|
|
227
|
+
decision: ConflictDecision,
|
|
228
|
+
candidate: Fact,
|
|
229
|
+
conflictStore: ConflictPipelineDeps['store']['conflicts'],
|
|
230
|
+
now: () => string,
|
|
231
|
+
): Promise<void> {
|
|
232
|
+
if (conflictStore === undefined) return;
|
|
233
|
+
const scope = {
|
|
234
|
+
userId: candidate.userId,
|
|
235
|
+
...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),
|
|
236
|
+
...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),
|
|
237
|
+
};
|
|
238
|
+
const detectionZone = decisionToZone(decision);
|
|
239
|
+
const auditInput: Parameters<NonNullable<typeof conflictStore>['recordDecision']>[0] = {
|
|
240
|
+
scope,
|
|
241
|
+
candidateId: candidate.id,
|
|
242
|
+
decision: decision.kind,
|
|
243
|
+
stage: decision.stage,
|
|
244
|
+
...(detectionZone !== undefined ? { detectionZone } : {}),
|
|
245
|
+
...(decision.kind === 'dedup' && decision.existingId !== undefined
|
|
246
|
+
? { existingId: decision.existingId }
|
|
247
|
+
: {}),
|
|
248
|
+
...(decision.kind === 'supersede' ? { existingId: decision.existingId } : {}),
|
|
249
|
+
...(decision.kind === 'dedup' && decision.similarity !== undefined
|
|
250
|
+
? { similarity: decision.similarity }
|
|
251
|
+
: {}),
|
|
252
|
+
...(decision.kind === 'pending' && decision.similarity !== undefined
|
|
253
|
+
? { similarity: decision.similarity }
|
|
254
|
+
: {}),
|
|
255
|
+
...(decision.kind !== 'admit' && decision.reason !== undefined
|
|
256
|
+
? { reason: decision.reason }
|
|
257
|
+
: {}),
|
|
258
|
+
...(decision.kind === 'admit' && decision.reason !== undefined
|
|
259
|
+
? { reason: decision.reason }
|
|
260
|
+
: {}),
|
|
261
|
+
};
|
|
262
|
+
await conflictStore.recordDecision(auditInput);
|
|
263
|
+
if (decision.kind === 'pending') {
|
|
264
|
+
await conflictStore.enqueuePending({
|
|
265
|
+
scope,
|
|
266
|
+
factId: candidate.id,
|
|
267
|
+
candidateText: candidate.text,
|
|
268
|
+
stage: decision.stage,
|
|
269
|
+
conflictingIds: decision.conflictingIds,
|
|
270
|
+
...(decision.reason !== undefined ? { reason: decision.reason } : {}),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
void now;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function decisionToZone(decision: ConflictDecision): string | undefined {
|
|
277
|
+
switch (decision.kind) {
|
|
278
|
+
case 'admit':
|
|
279
|
+
if (decision.reason === 'embedding-cold-zone') return 'cold';
|
|
280
|
+
return undefined;
|
|
281
|
+
case 'dedup':
|
|
282
|
+
if (decision.reason === 'embedding-hot-zone') return 'hot';
|
|
283
|
+
if (decision.reason === 'embedding-near-dup-zone') return 'near-dup';
|
|
284
|
+
if (decision.reason === 'exact-hash-match') return 'exact';
|
|
285
|
+
if (decision.reason === 'subject-predicate-match-same-object') return 'subject-predicate';
|
|
286
|
+
return undefined;
|
|
287
|
+
case 'supersede':
|
|
288
|
+
if (decision.reason.startsWith('regex-supersede-marker')) return 'heuristic';
|
|
289
|
+
if (decision.reason.startsWith('regex-negation')) return 'heuristic';
|
|
290
|
+
if (decision.reason.startsWith('subject-predicate-match')) return 'subject-predicate';
|
|
291
|
+
return undefined;
|
|
292
|
+
case 'pending':
|
|
293
|
+
return 'conflict-check';
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function collectVectorCandidates(
|
|
298
|
+
deps: ConflictPipelineDeps,
|
|
299
|
+
candidate: Fact,
|
|
300
|
+
topK: number,
|
|
301
|
+
): Promise<ReadonlyArray<MemoryHit<Fact>>> {
|
|
302
|
+
if (deps.embedder === null || deps.embedderId === null) return [];
|
|
303
|
+
const adapter = deps.store.semantic;
|
|
304
|
+
if (typeof adapter.searchVector !== 'function') return [];
|
|
305
|
+
checkAbort(deps.signal);
|
|
306
|
+
// PS-10: the candidate is compared against stored facts, which are embedded
|
|
307
|
+
// as `passage` - keep it in the same space for the asymmetric (E5) embedder.
|
|
308
|
+
const [vector] = await deps.embedder.embed([candidate.text], { taskType: 'passage' });
|
|
309
|
+
if (vector === undefined) return [];
|
|
310
|
+
checkAbort(deps.signal);
|
|
311
|
+
const scope = {
|
|
312
|
+
userId: candidate.userId,
|
|
313
|
+
...(candidate.sessionId !== undefined ? { sessionId: candidate.sessionId } : {}),
|
|
314
|
+
...(candidate.agentId !== undefined ? { agentId: candidate.agentId } : {}),
|
|
315
|
+
};
|
|
316
|
+
// Vector search may throw when the storage layer's vector extension
|
|
317
|
+
// is missing or mis-configured (e.g. `sqlite-vec` not loaded). Fall
|
|
318
|
+
// back to "no candidates" so the conflict pipeline degrades to the
|
|
319
|
+
// 10a straight-through write rather than blocking the agent loop.
|
|
320
|
+
// AbortError is re-raised so cancellation propagates up.
|
|
321
|
+
let hits: ReadonlyArray<MemoryHit<Fact>>;
|
|
322
|
+
try {
|
|
323
|
+
hits = await adapter.searchVector(scope, vector, deps.embedderId, topK);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
if (err instanceof Error && err.name === 'AbortError') throw err;
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
return [...hits].filter((hit) => hit.record.id !== candidate.id);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function validateThresholds(thresholds: ConflictThresholds): void {
|
|
332
|
+
if (
|
|
333
|
+
!(
|
|
334
|
+
thresholds.hot >= thresholds.nearDup &&
|
|
335
|
+
thresholds.nearDup >= thresholds.cold &&
|
|
336
|
+
thresholds.hot <= 1 &&
|
|
337
|
+
thresholds.cold >= 0
|
|
338
|
+
)
|
|
339
|
+
) {
|
|
340
|
+
throw new TypeError(
|
|
341
|
+
`[graphorin/memory] invalid conflict thresholds: expected 0 <= cold <= nearDup <= hot <= 1, got ${JSON.stringify(thresholds)}.`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Throw the canonical `AbortError` when the supplied signal is
|
|
348
|
+
* already aborted. Safe to call with `undefined`.
|
|
349
|
+
*
|
|
350
|
+
* @internal
|
|
351
|
+
*/
|
|
352
|
+
function checkAbort(signal: AbortSignal | undefined): void {
|
|
353
|
+
if (signal === undefined) return;
|
|
354
|
+
if (signal.aborted) {
|
|
355
|
+
if (typeof signal.throwIfAborted === 'function') {
|
|
356
|
+
signal.throwIfAborted();
|
|
357
|
+
}
|
|
358
|
+
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function warnBypass(): void {
|
|
363
|
+
// Use the structured logger once it lands (Phase 04 follow-up). For
|
|
364
|
+
// now, surface a one-shot console.warn so operators notice the
|
|
365
|
+
// regression risk per the Phase 10b spec ("WARN-once per process").
|
|
366
|
+
console.warn(
|
|
367
|
+
'[graphorin/memory] conflict pipeline disabled (mode: "off"). ' +
|
|
368
|
+
'Semantic writes fall back to a straight-through path - bi-temporal supersede / dedup ' +
|
|
369
|
+
'will not fire until you re-enable the pipeline.',
|
|
370
|
+
);
|
|
371
|
+
}
|