@graphorin/memory 0.5.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 +15 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/dist/conflict/index.d.ts +3 -0
- package/dist/conflict/index.js +7 -0
- package/dist/conflict/locale-packs/en.js +262 -0
- package/dist/conflict/locale-packs/en.js.map +1 -0
- package/dist/conflict/locale-packs/index.js +4 -0
- package/dist/conflict/locale-packs/types.d.ts +103 -0
- package/dist/conflict/locale-packs/types.d.ts.map +1 -0
- package/dist/conflict/locale-packs/types.js +55 -0
- package/dist/conflict/locale-packs/types.js.map +1 -0
- package/dist/conflict/pipeline.d.ts +35 -0
- package/dist/conflict/pipeline.d.ts.map +1 -0
- package/dist/conflict/pipeline.js +257 -0
- package/dist/conflict/pipeline.js.map +1 -0
- package/dist/conflict/stages/helpers.js +66 -0
- package/dist/conflict/stages/helpers.js.map +1 -0
- package/dist/conflict/stages/stage1-exact-dedup.js +21 -0
- package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js +37 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js +45 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -0
- package/dist/conflict/stages/stage4-subject-predicate.js +31 -0
- package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js +22 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -0
- package/dist/conflict/types.d.ts +222 -0
- package/dist/conflict/types.d.ts.map +1 -0
- package/dist/conflict/types.js +50 -0
- package/dist/conflict/types.js.map +1 -0
- package/dist/consolidator/budget.d.ts +1 -0
- package/dist/consolidator/budget.js +214 -0
- package/dist/consolidator/budget.js.map +1 -0
- package/dist/consolidator/decay.d.ts +35 -0
- package/dist/consolidator/decay.d.ts.map +1 -0
- package/dist/consolidator/decay.js +91 -0
- package/dist/consolidator/decay.js.map +1 -0
- package/dist/consolidator/dlq.js +58 -0
- package/dist/consolidator/dlq.js.map +1 -0
- package/dist/consolidator/errors.d.ts +54 -0
- package/dist/consolidator/errors.d.ts.map +1 -0
- package/dist/consolidator/errors.js +67 -0
- package/dist/consolidator/errors.js.map +1 -0
- package/dist/consolidator/idempotency.d.ts +1 -0
- package/dist/consolidator/idempotency.js +22 -0
- package/dist/consolidator/idempotency.js.map +1 -0
- package/dist/consolidator/index.d.ts +34 -0
- package/dist/consolidator/index.d.ts.map +1 -0
- package/dist/consolidator/index.js +163 -0
- package/dist/consolidator/index.js.map +1 -0
- package/dist/consolidator/lock.d.ts +2 -0
- package/dist/consolidator/lock.js +89 -0
- package/dist/consolidator/lock.js.map +1 -0
- package/dist/consolidator/noise-filter.d.ts +2 -0
- package/dist/consolidator/noise-filter.js +173 -0
- package/dist/consolidator/noise-filter.js.map +1 -0
- package/dist/consolidator/phases/deep.d.ts +4 -0
- package/dist/consolidator/phases/deep.js +198 -0
- package/dist/consolidator/phases/deep.js.map +1 -0
- package/dist/consolidator/phases/induce.d.ts +154 -0
- package/dist/consolidator/phases/induce.d.ts.map +1 -0
- package/dist/consolidator/phases/induce.js +253 -0
- package/dist/consolidator/phases/induce.js.map +1 -0
- package/dist/consolidator/phases/light.d.ts +4 -0
- package/dist/consolidator/phases/light.js +99 -0
- package/dist/consolidator/phases/light.js.map +1 -0
- package/dist/consolidator/phases/reflect.js +287 -0
- package/dist/consolidator/phases/reflect.js.map +1 -0
- package/dist/consolidator/phases/standard.d.ts +8 -0
- package/dist/consolidator/phases/standard.js +499 -0
- package/dist/consolidator/phases/standard.js.map +1 -0
- package/dist/consolidator/reconcile.js +192 -0
- package/dist/consolidator/reconcile.js.map +1 -0
- package/dist/consolidator/runtime.d.ts +62 -0
- package/dist/consolidator/runtime.d.ts.map +1 -0
- package/dist/consolidator/runtime.js +580 -0
- package/dist/consolidator/runtime.js.map +1 -0
- package/dist/consolidator/scheduler.d.ts +111 -0
- package/dist/consolidator/scheduler.d.ts.map +1 -0
- package/dist/consolidator/scheduler.js +71 -0
- package/dist/consolidator/scheduler.js.map +1 -0
- package/dist/consolidator/triggers.d.ts +40 -0
- package/dist/consolidator/triggers.d.ts.map +1 -0
- package/dist/consolidator/triggers.js +94 -0
- package/dist/consolidator/triggers.js.map +1 -0
- package/dist/consolidator/types.d.ts +397 -0
- package/dist/consolidator/types.d.ts.map +1 -0
- package/dist/consolidator/types.js +117 -0
- package/dist/consolidator/types.js.map +1 -0
- package/dist/context-engine/annotations.d.ts +118 -0
- package/dist/context-engine/annotations.d.ts.map +1 -0
- package/dist/context-engine/annotations.js +102 -0
- package/dist/context-engine/annotations.js.map +1 -0
- package/dist/context-engine/auto-recall.d.ts +52 -0
- package/dist/context-engine/auto-recall.d.ts.map +1 -0
- package/dist/context-engine/auto-recall.js +41 -0
- package/dist/context-engine/auto-recall.js.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts +55 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.js +97 -0
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -0
- package/dist/context-engine/compaction/compactor.d.ts +52 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -0
- package/dist/context-engine/compaction/compactor.js +196 -0
- package/dist/context-engine/compaction/compactor.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +27 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts +18 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +47 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +32 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -0
- package/dist/context-engine/compaction/hooks/types.d.ts +42 -0
- package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -0
- package/dist/context-engine/compaction/index.d.ts +9 -0
- package/dist/context-engine/compaction/index.js +9 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -0
- package/dist/context-engine/compaction/thresholds.d.ts +68 -0
- package/dist/context-engine/compaction/thresholds.d.ts.map +1 -0
- package/dist/context-engine/compaction/thresholds.js +63 -0
- package/dist/context-engine/compaction/thresholds.js.map +1 -0
- package/dist/context-engine/compaction/types.d.ts +202 -0
- package/dist/context-engine/compaction/types.d.ts.map +1 -0
- package/dist/context-engine/engine.d.ts +258 -0
- package/dist/context-engine/engine.d.ts.map +1 -0
- package/dist/context-engine/engine.js +424 -0
- package/dist/context-engine/engine.js.map +1 -0
- package/dist/context-engine/index.d.ts +68 -0
- package/dist/context-engine/index.d.ts.map +1 -0
- package/dist/context-engine/index.js +24 -0
- package/dist/context-engine/locale-packs/en.d.ts +14 -0
- package/dist/context-engine/locale-packs/en.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/en.js +87 -0
- package/dist/context-engine/locale-packs/en.js.map +1 -0
- package/dist/context-engine/locale-packs/index.js +4 -0
- package/dist/context-engine/locale-packs/resolver.d.ts +41 -0
- package/dist/context-engine/locale-packs/resolver.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/resolver.js +119 -0
- package/dist/context-engine/locale-packs/resolver.js.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +119 -0
- package/dist/context-engine/locale-packs/types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.js +24 -0
- package/dist/context-engine/locale-packs/types.js.map +1 -0
- package/dist/context-engine/metadata.d.ts +40 -0
- package/dist/context-engine/metadata.d.ts.map +1 -0
- package/dist/context-engine/metadata.js +66 -0
- package/dist/context-engine/metadata.js.map +1 -0
- package/dist/context-engine/preambles/inbound-en.d.ts +20 -0
- package/dist/context-engine/preambles/inbound-en.d.ts.map +1 -0
- package/dist/context-engine/preambles/inbound-en.js +23 -0
- package/dist/context-engine/preambles/inbound-en.js.map +1 -0
- package/dist/context-engine/privacy-filter.d.ts +108 -0
- package/dist/context-engine/privacy-filter.d.ts.map +1 -0
- package/dist/context-engine/privacy-filter.js +113 -0
- package/dist/context-engine/privacy-filter.js.map +1 -0
- package/dist/context-engine/templates/base-en.d.ts +19 -0
- package/dist/context-engine/templates/base-en.d.ts.map +1 -0
- package/dist/context-engine/templates/base-en.js +22 -0
- package/dist/context-engine/templates/base-en.js.map +1 -0
- package/dist/context-engine/templates/composer.d.ts +55 -0
- package/dist/context-engine/templates/composer.d.ts.map +1 -0
- package/dist/context-engine/templates/composer.js +51 -0
- package/dist/context-engine/templates/composer.js.map +1 -0
- package/dist/context-engine/token-budget.d.ts +99 -0
- package/dist/context-engine/token-budget.d.ts.map +1 -0
- package/dist/context-engine/token-budget.js +211 -0
- package/dist/context-engine/token-budget.js.map +1 -0
- package/dist/context-engine/token-counter.d.ts +54 -0
- package/dist/context-engine/token-counter.d.ts.map +1 -0
- package/dist/context-engine/token-counter.js +81 -0
- package/dist/context-engine/token-counter.js.map +1 -0
- package/dist/errors/index.d.ts +171 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +206 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/facade.d.ts +278 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +265 -0
- package/dist/facade.js.map +1 -0
- package/dist/graph/entity-resolver.d.ts +135 -0
- package/dist/graph/entity-resolver.d.ts.map +1 -0
- package/dist/graph/entity-resolver.js +202 -0
- package/dist/graph/entity-resolver.js.map +1 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/contextualize.d.ts +14 -0
- package/dist/internal/contextualize.d.ts.map +1 -0
- package/dist/internal/contextualize.js +110 -0
- package/dist/internal/contextualize.js.map +1 -0
- package/dist/internal/embedder-binding.js +50 -0
- package/dist/internal/embedder-binding.js.map +1 -0
- package/dist/internal/id.js +24 -0
- package/dist/internal/id.js.map +1 -0
- package/dist/internal/injection-heuristics.js +64 -0
- package/dist/internal/injection-heuristics.js.map +1 -0
- package/dist/internal/spans.js +35 -0
- package/dist/internal/spans.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +742 -0
- package/dist/internal/storage-adapter.d.ts.map +1 -0
- package/dist/migration/embedder-migration.d.ts +124 -0
- package/dist/migration/embedder-migration.d.ts.map +1 -0
- package/dist/migration/embedder-migration.js +109 -0
- package/dist/migration/embedder-migration.js.map +1 -0
- package/dist/migration/index.d.ts +2 -0
- package/dist/migration/index.js +3 -0
- package/dist/search/explain.d.ts +74 -0
- package/dist/search/explain.d.ts.map +1 -0
- package/dist/search/explain.js +50 -0
- package/dist/search/explain.js.map +1 -0
- package/dist/search/index.d.ts +6 -0
- package/dist/search/index.js +6 -0
- package/dist/search/iterative.d.ts +223 -0
- package/dist/search/iterative.d.ts.map +1 -0
- package/dist/search/iterative.js +330 -0
- package/dist/search/iterative.js.map +1 -0
- package/dist/search/query-transform.d.ts +112 -0
- package/dist/search/query-transform.d.ts.map +1 -0
- package/dist/search/query-transform.js +184 -0
- package/dist/search/query-transform.js.map +1 -0
- package/dist/search/rrf.d.ts +95 -0
- package/dist/search/rrf.d.ts.map +1 -0
- package/dist/search/rrf.js +166 -0
- package/dist/search/rrf.js.map +1 -0
- package/dist/search/types.d.ts +50 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.d.ts +142 -0
- package/dist/tiers/episodic-memory.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.js +254 -0
- package/dist/tiers/episodic-memory.js.map +1 -0
- package/dist/tiers/index.d.ts +8 -0
- package/dist/tiers/index.js +9 -0
- package/dist/tiers/insight-memory.d.ts +71 -0
- package/dist/tiers/insight-memory.d.ts.map +1 -0
- package/dist/tiers/insight-memory.js +133 -0
- package/dist/tiers/insight-memory.js.map +1 -0
- package/dist/tiers/procedural-memory.d.ts +147 -0
- package/dist/tiers/procedural-memory.d.ts.map +1 -0
- package/dist/tiers/procedural-memory.js +267 -0
- package/dist/tiers/procedural-memory.js.map +1 -0
- package/dist/tiers/semantic-memory.d.ts +461 -0
- package/dist/tiers/semantic-memory.d.ts.map +1 -0
- package/dist/tiers/semantic-memory.js +631 -0
- package/dist/tiers/semantic-memory.js.map +1 -0
- package/dist/tiers/session-memory.d.ts +117 -0
- package/dist/tiers/session-memory.d.ts.map +1 -0
- package/dist/tiers/session-memory.js +163 -0
- package/dist/tiers/session-memory.js.map +1 -0
- package/dist/tiers/shared-memory.d.ts +29 -0
- package/dist/tiers/shared-memory.d.ts.map +1 -0
- package/dist/tiers/shared-memory.js +58 -0
- package/dist/tiers/shared-memory.js.map +1 -0
- package/dist/tiers/working-memory.d.ts +129 -0
- package/dist/tiers/working-memory.d.ts.map +1 -0
- package/dist/tiers/working-memory.js +229 -0
- package/dist/tiers/working-memory.js.map +1 -0
- package/dist/tools/block-tools.d.ts +98 -0
- package/dist/tools/block-tools.d.ts.map +1 -0
- package/dist/tools/block-tools.js +114 -0
- package/dist/tools/block-tools.js.map +1 -0
- package/dist/tools/fact-tools.d.ts +294 -0
- package/dist/tools/fact-tools.d.ts.map +1 -0
- package/dist/tools/fact-tools.js +293 -0
- package/dist/tools/fact-tools.js.map +1 -0
- package/dist/tools/index.d.ts +35 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/recall-tools.d.ts +209 -0
- package/dist/tools/recall-tools.d.ts.map +1 -0
- package/dist/tools/recall-tools.js +170 -0
- package/dist/tools/recall-tools.js.map +1 -0
- package/dist/tools/types.d.ts +40 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
//#region src/consolidator/noise-filter.ts
|
|
2
|
+
const MIN_LENGTH_DEFAULT = 10;
|
|
3
|
+
const MIN_LENGTH_MINIMAL = 4;
|
|
4
|
+
const MAX_LENGTH = 1e4;
|
|
5
|
+
const STOP_WORD_RATIO = .8;
|
|
6
|
+
const STOP_WORDS = new Set([
|
|
7
|
+
"a",
|
|
8
|
+
"an",
|
|
9
|
+
"and",
|
|
10
|
+
"are",
|
|
11
|
+
"as",
|
|
12
|
+
"at",
|
|
13
|
+
"be",
|
|
14
|
+
"but",
|
|
15
|
+
"by",
|
|
16
|
+
"can",
|
|
17
|
+
"do",
|
|
18
|
+
"does",
|
|
19
|
+
"for",
|
|
20
|
+
"from",
|
|
21
|
+
"had",
|
|
22
|
+
"has",
|
|
23
|
+
"have",
|
|
24
|
+
"he",
|
|
25
|
+
"her",
|
|
26
|
+
"his",
|
|
27
|
+
"i",
|
|
28
|
+
"if",
|
|
29
|
+
"in",
|
|
30
|
+
"is",
|
|
31
|
+
"it",
|
|
32
|
+
"its",
|
|
33
|
+
"me",
|
|
34
|
+
"my",
|
|
35
|
+
"no",
|
|
36
|
+
"not",
|
|
37
|
+
"of",
|
|
38
|
+
"on",
|
|
39
|
+
"or",
|
|
40
|
+
"our",
|
|
41
|
+
"she",
|
|
42
|
+
"so",
|
|
43
|
+
"that",
|
|
44
|
+
"the",
|
|
45
|
+
"their",
|
|
46
|
+
"them",
|
|
47
|
+
"they",
|
|
48
|
+
"this",
|
|
49
|
+
"to",
|
|
50
|
+
"too",
|
|
51
|
+
"us",
|
|
52
|
+
"was",
|
|
53
|
+
"we",
|
|
54
|
+
"were",
|
|
55
|
+
"what",
|
|
56
|
+
"when",
|
|
57
|
+
"where",
|
|
58
|
+
"which",
|
|
59
|
+
"who",
|
|
60
|
+
"will",
|
|
61
|
+
"with",
|
|
62
|
+
"would",
|
|
63
|
+
"yes",
|
|
64
|
+
"you",
|
|
65
|
+
"your"
|
|
66
|
+
]);
|
|
67
|
+
/**
|
|
68
|
+
* Run the configured filter chain against the supplied messages.
|
|
69
|
+
* Filters short-circuit at the first hit so the cheapest checks
|
|
70
|
+
* always run first.
|
|
71
|
+
*
|
|
72
|
+
* @stable
|
|
73
|
+
*/
|
|
74
|
+
function applyNoiseFilters(messages, presets = ["default"]) {
|
|
75
|
+
if (presets.length === 0 || presets.every((p) => p === "none")) return {
|
|
76
|
+
kept: messages,
|
|
77
|
+
droppedCount: 0,
|
|
78
|
+
droppedByReason: Object.freeze({})
|
|
79
|
+
};
|
|
80
|
+
const minimal = presets.includes("minimal") && !presets.includes("default");
|
|
81
|
+
const minLength = minimal ? MIN_LENGTH_MINIMAL : MIN_LENGTH_DEFAULT;
|
|
82
|
+
const droppedByReason = {};
|
|
83
|
+
const kept = [];
|
|
84
|
+
const recentTexts = [];
|
|
85
|
+
for (const record of messages) {
|
|
86
|
+
const reason = classify(record.message, recentTexts, minLength, minimal);
|
|
87
|
+
if (reason === null) {
|
|
88
|
+
kept.push(record);
|
|
89
|
+
const text = renderText(record.message);
|
|
90
|
+
if (text.length > 0) {
|
|
91
|
+
recentTexts.push(text);
|
|
92
|
+
if (recentTexts.length > 5) recentTexts.shift();
|
|
93
|
+
}
|
|
94
|
+
} else droppedByReason[reason] = (droppedByReason[reason] ?? 0) + 1;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
kept,
|
|
98
|
+
droppedCount: messages.length - kept.length,
|
|
99
|
+
droppedByReason: Object.freeze({ ...droppedByReason })
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function classify(message, recentTexts, minLength, minimal) {
|
|
103
|
+
if (message.role === "system") return "role-system";
|
|
104
|
+
if (message.role === "tool" && minimal) return null;
|
|
105
|
+
const trimmed = renderText(message).trim();
|
|
106
|
+
if (trimmed.length === 0) return "empty";
|
|
107
|
+
if (trimmed.length < minLength) return "too-short";
|
|
108
|
+
if (trimmed.length > MAX_LENGTH) return "too-long";
|
|
109
|
+
if (looksLikeCodeOrJson(trimmed)) return "code-block";
|
|
110
|
+
if (!minimal && stopWordRatio(trimmed) > STOP_WORD_RATIO) return "stop-word-ratio";
|
|
111
|
+
if (!minimal && isNearDuplicate(trimmed, recentTexts)) return "near-duplicate";
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Render a message as a single text blob for the heuristic filters.
|
|
116
|
+
*
|
|
117
|
+
* @internal
|
|
118
|
+
*/
|
|
119
|
+
function renderText(message) {
|
|
120
|
+
if (message.role === "system") return message.content;
|
|
121
|
+
const c = message.content;
|
|
122
|
+
if (typeof c === "string") return c;
|
|
123
|
+
let out = "";
|
|
124
|
+
for (const part of c) if (part.type === "text" || part.type === "reasoning") out += `${part.text} `;
|
|
125
|
+
return out.trim();
|
|
126
|
+
}
|
|
127
|
+
function looksLikeCodeOrJson(text) {
|
|
128
|
+
if (text.startsWith("```")) return true;
|
|
129
|
+
const trimmed = text.trim();
|
|
130
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) try {
|
|
131
|
+
JSON.parse(trimmed);
|
|
132
|
+
return true;
|
|
133
|
+
} catch {}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
function stopWordRatio(text) {
|
|
137
|
+
const tokens = text.toLowerCase().split(/[^a-z0-9]+/u).filter((t) => t.length > 0);
|
|
138
|
+
if (tokens.length === 0) return 0;
|
|
139
|
+
let stop = 0;
|
|
140
|
+
for (const t of tokens) if (STOP_WORDS.has(t)) stop += 1;
|
|
141
|
+
return stop / tokens.length;
|
|
142
|
+
}
|
|
143
|
+
function isNearDuplicate(text, recent) {
|
|
144
|
+
const normalised = text.replace(/\s+/gu, " ").toLowerCase().trim();
|
|
145
|
+
for (const prior of recent) {
|
|
146
|
+
const priorNorm = prior.replace(/\s+/gu, " ").toLowerCase().trim();
|
|
147
|
+
if (priorNorm.length === 0) continue;
|
|
148
|
+
if (priorNorm === normalised) return true;
|
|
149
|
+
if (trigramSimilarity(priorNorm, normalised) >= .9) return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
function trigramSimilarity(a, b) {
|
|
154
|
+
const setA = trigramSet(a);
|
|
155
|
+
const setB = trigramSet(b);
|
|
156
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
157
|
+
let inter = 0;
|
|
158
|
+
for (const t of setA) if (setB.has(t)) inter += 1;
|
|
159
|
+
return inter / Math.max(setA.size, setB.size);
|
|
160
|
+
}
|
|
161
|
+
function trigramSet(text) {
|
|
162
|
+
const out = /* @__PURE__ */ new Set();
|
|
163
|
+
if (text.length < 3) {
|
|
164
|
+
out.add(text);
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
for (let i = 0; i < text.length - 2; i++) out.add(text.slice(i, i + 3));
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
export { applyNoiseFilters, renderText };
|
|
173
|
+
//# sourceMappingURL=noise-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noise-filter.js","names":["droppedByReason: Record<string, number>","kept: SessionMessageRecord[]","recentTexts: string[]"],"sources":["../../src/consolidator/noise-filter.ts"],"sourcesContent":["/**\n * Built-in noise filter pipeline. Runs before LLM extraction in the\n * standard phase + every light-phase invocation. The defaults are\n * heuristic-only (no LLM); adapters may extend the pipeline through\n * `noiseFilters: [...customFilters]` once the public surface is wired\n * (post-MVP — DEC-134 carve-out).\n *\n * @packageDocumentation\n */\n\nimport type { Message } from '@graphorin/core';\nimport type { SessionMessageRecord } from '../internal/storage-adapter.js';\n\n/**\n * Bundle level surfaced by {@link applyNoiseFilters}. The runtime\n * uses these to log + report `noise_filtered_count` per phase.\n *\n * @stable\n */\nexport type NoiseFilterPreset = 'default' | 'minimal' | 'none';\n\nconst MIN_LENGTH_DEFAULT = 10;\nconst MIN_LENGTH_MINIMAL = 4;\nconst MAX_LENGTH = 10_000;\nconst STOP_WORD_RATIO = 0.8;\nconst STOP_WORDS = new Set([\n 'a',\n 'an',\n 'and',\n 'are',\n 'as',\n 'at',\n 'be',\n 'but',\n 'by',\n 'can',\n 'do',\n 'does',\n 'for',\n 'from',\n 'had',\n 'has',\n 'have',\n 'he',\n 'her',\n 'his',\n 'i',\n 'if',\n 'in',\n 'is',\n 'it',\n 'its',\n 'me',\n 'my',\n 'no',\n 'not',\n 'of',\n 'on',\n 'or',\n 'our',\n 'she',\n 'so',\n 'that',\n 'the',\n 'their',\n 'them',\n 'they',\n 'this',\n 'to',\n 'too',\n 'us',\n 'was',\n 'we',\n 'were',\n 'what',\n 'when',\n 'where',\n 'which',\n 'who',\n 'will',\n 'with',\n 'would',\n 'yes',\n 'you',\n 'your',\n]);\n\n/**\n * Result returned by {@link applyNoiseFilters}.\n *\n * @stable\n */\nexport interface NoiseFilterResult {\n readonly kept: ReadonlyArray<SessionMessageRecord>;\n readonly droppedCount: number;\n readonly droppedByReason: Readonly<Record<string, number>>;\n}\n\n/**\n * Run the configured filter chain against the supplied messages.\n * Filters short-circuit at the first hit so the cheapest checks\n * always run first.\n *\n * @stable\n */\nexport function applyNoiseFilters(\n messages: ReadonlyArray<SessionMessageRecord>,\n presets: ReadonlyArray<NoiseFilterPreset> = ['default'],\n): NoiseFilterResult {\n if (presets.length === 0 || presets.every((p) => p === 'none')) {\n return { kept: messages, droppedCount: 0, droppedByReason: Object.freeze({}) };\n }\n const minimal = presets.includes('minimal') && !presets.includes('default');\n const minLength = minimal ? MIN_LENGTH_MINIMAL : MIN_LENGTH_DEFAULT;\n const droppedByReason: Record<string, number> = {};\n const kept: SessionMessageRecord[] = [];\n const recentTexts: string[] = [];\n\n for (const record of messages) {\n const reason = classify(record.message, recentTexts, minLength, minimal);\n if (reason === null) {\n kept.push(record);\n const text = renderText(record.message);\n if (text.length > 0) {\n recentTexts.push(text);\n if (recentTexts.length > 5) recentTexts.shift();\n }\n } else {\n droppedByReason[reason] = (droppedByReason[reason] ?? 0) + 1;\n }\n }\n return {\n kept,\n droppedCount: messages.length - kept.length,\n droppedByReason: Object.freeze({ ...droppedByReason }),\n };\n}\n\nfunction classify(\n message: Message,\n recentTexts: ReadonlyArray<string>,\n minLength: number,\n minimal: boolean,\n): string | null {\n if (message.role === 'system') return 'role-system';\n if (message.role === 'tool' && minimal) return null;\n const text = renderText(message);\n const trimmed = text.trim();\n if (trimmed.length === 0) return 'empty';\n if (trimmed.length < minLength) return 'too-short';\n if (trimmed.length > MAX_LENGTH) return 'too-long';\n if (looksLikeCodeOrJson(trimmed)) return 'code-block';\n if (!minimal && stopWordRatio(trimmed) > STOP_WORD_RATIO) return 'stop-word-ratio';\n if (!minimal && isNearDuplicate(trimmed, recentTexts)) return 'near-duplicate';\n return null;\n}\n\n/**\n * Render a message as a single text blob for the heuristic filters.\n *\n * @internal\n */\nexport function renderText(message: Message): string {\n if (message.role === 'system') return message.content;\n const c = message.content;\n if (typeof c === 'string') return c;\n let out = '';\n for (const part of c) {\n if (part.type === 'text' || part.type === 'reasoning') {\n out += `${part.text} `;\n }\n }\n return out.trim();\n}\n\nfunction looksLikeCodeOrJson(text: string): boolean {\n if (text.startsWith('```')) return true;\n const trimmed = text.trim();\n if (\n (trimmed.startsWith('{') && trimmed.endsWith('}')) ||\n (trimmed.startsWith('[') && trimmed.endsWith(']'))\n ) {\n try {\n JSON.parse(trimmed);\n return true;\n } catch {\n // Not actually JSON — fall through.\n }\n }\n return false;\n}\n\nfunction stopWordRatio(text: string): number {\n const tokens = text\n .toLowerCase()\n .split(/[^a-z0-9]+/u)\n .filter((t) => t.length > 0);\n if (tokens.length === 0) return 0;\n let stop = 0;\n for (const t of tokens) if (STOP_WORDS.has(t)) stop += 1;\n return stop / tokens.length;\n}\n\nfunction isNearDuplicate(text: string, recent: ReadonlyArray<string>): boolean {\n const normalised = text.replace(/\\s+/gu, ' ').toLowerCase().trim();\n for (const prior of recent) {\n const priorNorm = prior.replace(/\\s+/gu, ' ').toLowerCase().trim();\n if (priorNorm.length === 0) continue;\n if (priorNorm === normalised) return true;\n const similarity = trigramSimilarity(priorNorm, normalised);\n if (similarity >= 0.9) return true;\n }\n return false;\n}\n\nfunction trigramSimilarity(a: string, b: string): number {\n const setA = trigramSet(a);\n const setB = trigramSet(b);\n if (setA.size === 0 || setB.size === 0) return 0;\n let inter = 0;\n for (const t of setA) if (setB.has(t)) inter += 1;\n return inter / Math.max(setA.size, setB.size);\n}\n\nfunction trigramSet(text: string): Set<string> {\n const out = new Set<string>();\n if (text.length < 3) {\n out.add(text);\n return out;\n }\n for (let i = 0; i < text.length - 2; i++) {\n out.add(text.slice(i, i + 3));\n }\n return out;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { withMemorySpan } from "../../internal/spans.js";
|
|
2
|
+
|
|
3
|
+
//#region src/consolidator/phases/deep.ts
|
|
4
|
+
const JUDGE_PROMPT = [
|
|
5
|
+
"You are a conflict-resolution judge for a long-running personal-assistant memory.",
|
|
6
|
+
"Decide whether a candidate fact contradicts, dedups, or is unrelated to an existing fact.",
|
|
7
|
+
"Return strictly JSON: { \"decision\": \"supersede\" | \"dedup\" | \"admit\", \"reason\": string }.",
|
|
8
|
+
"Use \"supersede\" only when the candidate clearly replaces the older fact.",
|
|
9
|
+
"Use \"dedup\" when both convey the same information.",
|
|
10
|
+
"Use \"admit\" when the facts are independent or you are unsure."
|
|
11
|
+
].join(" ");
|
|
12
|
+
/** Run the deep phase. */
|
|
13
|
+
async function runDeepPhase(deps) {
|
|
14
|
+
return withMemorySpan(deps.tracer, "memory.consolidate.deep", deps.scope, {
|
|
15
|
+
"consolidator.phase": "deep",
|
|
16
|
+
"consolidator.tier": deps.tier,
|
|
17
|
+
"consolidator.deep_model": deps.deepModel ?? deps.provider.modelId
|
|
18
|
+
}, async (span) => {
|
|
19
|
+
const startedAt = (typeof deps.now === "function" ? deps.now : Date.now)();
|
|
20
|
+
const conflicts = deps.store.conflicts;
|
|
21
|
+
if (conflicts === void 0) {
|
|
22
|
+
span.setAttributes({
|
|
23
|
+
"consolidator.deep.skipped": "no-conflict-store",
|
|
24
|
+
"consolidator.duration_ms": Math.max(0, (typeof deps.now === "function" ? deps.now : Date.now)() - startedAt),
|
|
25
|
+
"consolidator.facts_extracted": 0,
|
|
26
|
+
"consolidator.budget_used_usd": 0
|
|
27
|
+
});
|
|
28
|
+
return makeOutcome("completed", { conflictsResolved: 0 });
|
|
29
|
+
}
|
|
30
|
+
const semantic = deps.store.semantic;
|
|
31
|
+
const pending = await conflicts.listPending(deps.scope, deps.maxConflictsPerRun);
|
|
32
|
+
span.setAttributes({ "consolidator.deep.pending_count": pending.length });
|
|
33
|
+
if (pending.length === 0) {
|
|
34
|
+
span.setAttributes({
|
|
35
|
+
"consolidator.duration_ms": Math.max(0, (typeof deps.now === "function" ? deps.now : Date.now)() - startedAt),
|
|
36
|
+
"consolidator.facts_extracted": 0,
|
|
37
|
+
"consolidator.budget_used_usd": 0
|
|
38
|
+
});
|
|
39
|
+
return makeOutcome("completed", { conflictsResolved: 0 });
|
|
40
|
+
}
|
|
41
|
+
let resolved = 0;
|
|
42
|
+
let factsUpdated = 0;
|
|
43
|
+
let totalTokens = 0;
|
|
44
|
+
let totalCost = 0;
|
|
45
|
+
const recordJudgeFailure = async (r) => {
|
|
46
|
+
if (r.attemptedAt !== null) await conflicts.markResolved(r.id, "judge-unparseable");
|
|
47
|
+
else if (typeof conflicts.markAttempted === "function") await conflicts.markAttempted(r.id);
|
|
48
|
+
};
|
|
49
|
+
for (const row of pending) {
|
|
50
|
+
if (deps.budget.snapshot().paused) break;
|
|
51
|
+
const candidateText = row.candidateText;
|
|
52
|
+
const conflictingId = row.conflictingIds[0] ?? null;
|
|
53
|
+
let existingText = null;
|
|
54
|
+
if (conflictingId !== null && typeof semantic.get === "function") existingText = (await semantic.get(conflictingId))?.text ?? null;
|
|
55
|
+
const request = buildJudgeRequest(deps, candidateText, existingText);
|
|
56
|
+
let response;
|
|
57
|
+
try {
|
|
58
|
+
response = await deps.provider.generate(request);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
span.recordException(err);
|
|
61
|
+
await recordJudgeFailure(row);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const usage = response.usage;
|
|
65
|
+
const tokens = (usage.promptTokens ?? 0) + (usage.completionTokens ?? 0) + (usage.reasoningTokens ?? 0);
|
|
66
|
+
const cost = deps.priceUsage?.({
|
|
67
|
+
promptTokens: usage.promptTokens,
|
|
68
|
+
completionTokens: usage.completionTokens
|
|
69
|
+
}) ?? 0;
|
|
70
|
+
totalTokens += tokens;
|
|
71
|
+
totalCost += cost;
|
|
72
|
+
deps.budget.record({
|
|
73
|
+
phase: "deep",
|
|
74
|
+
tokens,
|
|
75
|
+
costUsd: cost
|
|
76
|
+
});
|
|
77
|
+
const judge = parseJudge(response.text);
|
|
78
|
+
if (judge === null) {
|
|
79
|
+
await recordJudgeFailure(row);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (judge.decision === "supersede" && conflictingId !== null) {
|
|
83
|
+
if (typeof semantic.get === "function") {
|
|
84
|
+
const candidateFact = await semantic.get(row.factId);
|
|
85
|
+
if (candidateFact !== null) {
|
|
86
|
+
await semantic.supersede(conflictingId, candidateFact, judge.reason);
|
|
87
|
+
factsUpdated += 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
await conflicts.markResolved(row.id, "supersede");
|
|
91
|
+
} else if (judge.decision === "dedup" && conflictingId !== null) {
|
|
92
|
+
if (typeof semantic.purge === "function") await semantic.purge(row.factId, judge.reason);
|
|
93
|
+
else if (typeof semantic.forget === "function") await semantic.forget(row.factId, judge.reason);
|
|
94
|
+
factsUpdated += 1;
|
|
95
|
+
await conflicts.markResolved(row.id, "dedup");
|
|
96
|
+
} else await conflicts.markResolved(row.id, "admit");
|
|
97
|
+
resolved += 1;
|
|
98
|
+
}
|
|
99
|
+
const snapshot = deps.budget.snapshot();
|
|
100
|
+
span.setAttributes({
|
|
101
|
+
"consolidator.duration_ms": Math.max(0, (typeof deps.now === "function" ? deps.now : Date.now)() - startedAt),
|
|
102
|
+
"consolidator.facts_extracted": factsUpdated,
|
|
103
|
+
"consolidator.budget_used_usd": totalCost,
|
|
104
|
+
"consolidator.deep.resolved": resolved,
|
|
105
|
+
"consolidator.deep.facts_updated": factsUpdated,
|
|
106
|
+
"consolidator.deep.tokens.total": totalTokens,
|
|
107
|
+
"consolidator.deep.cost.estimate.usd": totalCost,
|
|
108
|
+
"consolidator.budget.remaining.tokens": snapshot.tokensRemaining,
|
|
109
|
+
"consolidator.budget.remaining.usd": snapshot.costRemaining
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
phase: "deep",
|
|
113
|
+
status: "completed",
|
|
114
|
+
factsCreated: 0,
|
|
115
|
+
factsUpdated,
|
|
116
|
+
conflictsResolved: resolved,
|
|
117
|
+
episodesFormed: 0,
|
|
118
|
+
insightsCreated: 0,
|
|
119
|
+
noiseFilteredCount: 0,
|
|
120
|
+
emptyExtractions: 0,
|
|
121
|
+
llmTokensUsed: totalTokens,
|
|
122
|
+
llmCostUsd: totalCost,
|
|
123
|
+
errorMessage: null
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function buildJudgeRequest(deps, candidateText, existingText) {
|
|
128
|
+
return {
|
|
129
|
+
messages: [{
|
|
130
|
+
role: "user",
|
|
131
|
+
content: [`Candidate fact: ${candidateText}`, existingText !== null ? `Existing fact: ${existingText}` : "Existing fact: (unknown)"].join("\n")
|
|
132
|
+
}],
|
|
133
|
+
systemMessage: JUDGE_PROMPT,
|
|
134
|
+
temperature: 0,
|
|
135
|
+
maxTokens: 256,
|
|
136
|
+
metadata: {
|
|
137
|
+
userId: deps.scope.userId,
|
|
138
|
+
...deps.scope.sessionId !== void 0 ? { sessionId: deps.scope.sessionId } : {},
|
|
139
|
+
...deps.scope.agentId !== void 0 ? { agentId: deps.scope.agentId } : {}
|
|
140
|
+
},
|
|
141
|
+
outputType: { kind: "structured" }
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function parseJudge(text) {
|
|
145
|
+
if (text === void 0 || text.length === 0) return null;
|
|
146
|
+
const candidate = stripFence(text).trim();
|
|
147
|
+
if (candidate.length === 0) return null;
|
|
148
|
+
let parsed;
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(candidate);
|
|
151
|
+
} catch {
|
|
152
|
+
const slice = sliceJsonObject(candidate);
|
|
153
|
+
if (slice === null) return null;
|
|
154
|
+
try {
|
|
155
|
+
parsed = JSON.parse(slice);
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (parsed === null || typeof parsed !== "object") return null;
|
|
161
|
+
const obj = parsed;
|
|
162
|
+
const decision = obj.decision;
|
|
163
|
+
if (decision !== "supersede" && decision !== "dedup" && decision !== "admit") return null;
|
|
164
|
+
return {
|
|
165
|
+
decision,
|
|
166
|
+
reason: typeof obj.reason === "string" ? obj.reason : "unspecified"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function stripFence(text) {
|
|
170
|
+
return /^```[^\n]*\n([\s\S]*?)\n```/u.exec(text.trim())?.[1] ?? text;
|
|
171
|
+
}
|
|
172
|
+
function sliceJsonObject(text) {
|
|
173
|
+
const start = text.indexOf("{");
|
|
174
|
+
const end = text.lastIndexOf("}");
|
|
175
|
+
if (start < 0 || end < start) return null;
|
|
176
|
+
return text.slice(start, end + 1);
|
|
177
|
+
}
|
|
178
|
+
function makeOutcome(status, overrides) {
|
|
179
|
+
return {
|
|
180
|
+
phase: "deep",
|
|
181
|
+
status,
|
|
182
|
+
factsCreated: 0,
|
|
183
|
+
factsUpdated: 0,
|
|
184
|
+
conflictsResolved: 0,
|
|
185
|
+
episodesFormed: 0,
|
|
186
|
+
insightsCreated: 0,
|
|
187
|
+
noiseFilteredCount: 0,
|
|
188
|
+
emptyExtractions: 0,
|
|
189
|
+
llmTokensUsed: 0,
|
|
190
|
+
llmCostUsd: null,
|
|
191
|
+
errorMessage: null,
|
|
192
|
+
...overrides
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
//#endregion
|
|
197
|
+
export { runDeepPhase };
|
|
198
|
+
//# sourceMappingURL=deep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep.js","names":["existingText: string | null","response: Awaited<ReturnType<Provider['generate']>>","parsed: unknown"],"sources":["../../../src/consolidator/phases/deep.ts"],"sourcesContent":["/**\n * Deep phase — minimum-viable drain of `conflict_check_pending`. The\n * MVP scope is exactly the RB-02 cross-cut: read every queued\n * candidate, ask the configured deep-tier provider to choose between\n * `dedup` / `supersede` / `admit`, apply the resolution, and clear\n * the row.\n *\n * Reflection / fact evolution / sleeptime updates are out of scope\n * (deferred per Phase 10c § Out of scope).\n *\n * @packageDocumentation\n */\n\nimport type { Provider, ProviderRequest, SessionScope, Tracer } from '@graphorin/core';\nimport { withMemorySpan } from '../../internal/spans.js';\nimport type {\n ConsolidatorMemoryStoreExt,\n MemoryStoreAdapter,\n} from '../../internal/storage-adapter.js';\nimport type { BudgetTracker } from '../budget.js';\nimport type { PhaseOutcome } from '../types.js';\n\n/** Inputs accepted by {@link runDeepPhase}. */\nexport interface DeepPhaseDeps {\n readonly store: MemoryStoreAdapter;\n readonly consolidatorStore: ConsolidatorMemoryStoreExt | null;\n readonly provider: Provider;\n readonly tracer: Tracer;\n readonly scope: SessionScope;\n readonly deepModel: string | null;\n readonly maxConflictsPerRun: number;\n readonly budget: BudgetTracker;\n readonly priceUsage?: (usage: { promptTokens: number; completionTokens: number }) => number;\n readonly tier: 'standard' | 'full' | 'custom';\n /** Override the wall clock — used by tests + the runtime clock seam. */\n readonly now?: () => number;\n}\n\nconst JUDGE_PROMPT = [\n 'You are a conflict-resolution judge for a long-running personal-assistant memory.',\n 'Decide whether a candidate fact contradicts, dedups, or is unrelated to an existing fact.',\n 'Return strictly JSON: { \"decision\": \"supersede\" | \"dedup\" | \"admit\", \"reason\": string }.',\n 'Use \"supersede\" only when the candidate clearly replaces the older fact.',\n 'Use \"dedup\" when both convey the same information.',\n 'Use \"admit\" when the facts are independent or you are unsure.',\n].join(' ');\n\ninterface JudgeOutcome {\n readonly decision: 'supersede' | 'dedup' | 'admit';\n readonly reason: string;\n}\n\n/** Run the deep phase. */\nexport async function runDeepPhase(deps: DeepPhaseDeps): Promise<PhaseOutcome> {\n return withMemorySpan(\n deps.tracer,\n 'memory.consolidate.deep',\n deps.scope,\n {\n 'consolidator.phase': 'deep',\n 'consolidator.tier': deps.tier,\n 'consolidator.deep_model': deps.deepModel ?? deps.provider.modelId,\n },\n async (span) => {\n const startedAt = (typeof deps.now === 'function' ? deps.now : Date.now)();\n const conflicts = deps.store.conflicts;\n if (conflicts === undefined) {\n span.setAttributes({\n 'consolidator.deep.skipped': 'no-conflict-store',\n 'consolidator.duration_ms': Math.max(\n 0,\n (typeof deps.now === 'function' ? deps.now : Date.now)() - startedAt,\n ),\n 'consolidator.facts_extracted': 0,\n 'consolidator.budget_used_usd': 0,\n });\n return makeOutcome('completed', { conflictsResolved: 0 });\n }\n const semantic = deps.store.semantic;\n const pending = await conflicts.listPending(deps.scope, deps.maxConflictsPerRun);\n span.setAttributes({ 'consolidator.deep.pending_count': pending.length });\n if (pending.length === 0) {\n span.setAttributes({\n 'consolidator.duration_ms': Math.max(\n 0,\n (typeof deps.now === 'function' ? deps.now : Date.now)() - startedAt,\n ),\n 'consolidator.facts_extracted': 0,\n 'consolidator.budget_used_usd': 0,\n });\n return makeOutcome('completed', { conflictsResolved: 0 });\n }\n\n let resolved = 0;\n let factsUpdated = 0;\n let totalTokens = 0;\n let totalCost = 0;\n\n // MCON-9: a judge call that throws or returns garbage must not be\n // re-billed forever. First failure stamps `attemptedAt`; a failure\n // on an already-attempted row closes it (`'judge-unparseable'`),\n // so a poisoned row costs at most two calls across all runs.\n const recordJudgeFailure = async (r: (typeof pending)[number]): Promise<void> => {\n if (r.attemptedAt !== null) {\n await conflicts.markResolved(r.id, 'judge-unparseable');\n } else if (typeof conflicts.markAttempted === 'function') {\n await conflicts.markAttempted(r.id);\n }\n };\n\n for (const row of pending) {\n // MCON-9: mirror the standard/reflect phases — once the budget\n // pauses mid-run, stop spending instead of draining up to\n // maxConflictsPerRun calls past the ceiling.\n if (deps.budget.snapshot().paused) break;\n const candidateText = row.candidateText;\n const conflictingId = row.conflictingIds[0] ?? null;\n let existingText: string | null = null;\n if (conflictingId !== null && typeof semantic.get === 'function') {\n const fact = await semantic.get(conflictingId);\n existingText = fact?.text ?? null;\n }\n const request = buildJudgeRequest(deps, candidateText, existingText);\n let response: Awaited<ReturnType<Provider['generate']>>;\n try {\n response = await deps.provider.generate(request);\n } catch (err) {\n span.recordException(err);\n await recordJudgeFailure(row);\n continue;\n }\n const usage = response.usage;\n const tokens =\n (usage.promptTokens ?? 0) + (usage.completionTokens ?? 0) + (usage.reasoningTokens ?? 0);\n const cost =\n deps.priceUsage?.({\n promptTokens: usage.promptTokens,\n completionTokens: usage.completionTokens,\n }) ?? 0;\n totalTokens += tokens;\n totalCost += cost;\n deps.budget.record({ phase: 'deep', tokens, costUsd: cost });\n\n const judge = parseJudge(response.text);\n if (judge === null) {\n await recordJudgeFailure(row);\n continue;\n }\n\n if (judge.decision === 'supersede' && conflictingId !== null) {\n if (typeof semantic.get === 'function') {\n const candidateFact = await semantic.get(row.factId);\n if (candidateFact !== null) {\n await semantic.supersede(conflictingId, candidateFact, judge.reason);\n factsUpdated += 1;\n }\n }\n await conflicts.markResolved(row.id, 'supersede');\n } else if (judge.decision === 'dedup' && conflictingId !== null) {\n if (typeof semantic.purge === 'function') {\n await semantic.purge(row.factId, judge.reason);\n } else if (typeof semantic.forget === 'function') {\n await semantic.forget(row.factId, judge.reason);\n }\n factsUpdated += 1;\n await conflicts.markResolved(row.id, 'dedup');\n } else {\n await conflicts.markResolved(row.id, 'admit');\n }\n resolved += 1;\n }\n\n const snapshot = deps.budget.snapshot();\n span.setAttributes({\n 'consolidator.duration_ms': Math.max(\n 0,\n (typeof deps.now === 'function' ? deps.now : Date.now)() - startedAt,\n ),\n 'consolidator.facts_extracted': factsUpdated,\n 'consolidator.budget_used_usd': totalCost,\n 'consolidator.deep.resolved': resolved,\n 'consolidator.deep.facts_updated': factsUpdated,\n 'consolidator.deep.tokens.total': totalTokens,\n 'consolidator.deep.cost.estimate.usd': totalCost,\n 'consolidator.budget.remaining.tokens': snapshot.tokensRemaining,\n 'consolidator.budget.remaining.usd': snapshot.costRemaining,\n });\n\n return {\n phase: 'deep',\n status: 'completed',\n factsCreated: 0,\n factsUpdated,\n conflictsResolved: resolved,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount: 0,\n emptyExtractions: 0,\n llmTokensUsed: totalTokens,\n llmCostUsd: totalCost,\n errorMessage: null,\n };\n },\n );\n}\n\nfunction buildJudgeRequest(\n deps: DeepPhaseDeps,\n candidateText: string,\n existingText: string | null,\n): ProviderRequest {\n const userBlock = [\n `Candidate fact: ${candidateText}`,\n existingText !== null ? `Existing fact: ${existingText}` : 'Existing fact: (unknown)',\n ].join('\\n');\n return {\n messages: [{ role: 'user', content: userBlock }],\n systemMessage: JUDGE_PROMPT,\n temperature: 0,\n // MCON-14: per-call output cap — the verdict shape is tiny.\n maxTokens: 256,\n metadata: {\n userId: deps.scope.userId,\n ...(deps.scope.sessionId !== undefined ? { sessionId: deps.scope.sessionId } : {}),\n ...(deps.scope.agentId !== undefined ? { agentId: deps.scope.agentId } : {}),\n },\n outputType: { kind: 'structured' },\n };\n}\n\nfunction parseJudge(text: string | undefined): JudgeOutcome | null {\n if (text === undefined || text.length === 0) return null;\n const candidate = stripFence(text).trim();\n if (candidate.length === 0) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(candidate);\n } catch {\n const slice = sliceJsonObject(candidate);\n if (slice === null) return null;\n try {\n parsed = JSON.parse(slice);\n } catch {\n return null;\n }\n }\n if (parsed === null || typeof parsed !== 'object') return null;\n const obj = parsed as { decision?: unknown; reason?: unknown };\n const decision = obj.decision;\n if (decision !== 'supersede' && decision !== 'dedup' && decision !== 'admit') return null;\n const reason = typeof obj.reason === 'string' ? obj.reason : 'unspecified';\n return { decision, reason };\n}\n\nfunction stripFence(text: string): string {\n const match = /^```[^\\n]*\\n([\\s\\S]*?)\\n```/u.exec(text.trim());\n return match?.[1] ?? text;\n}\n\nfunction sliceJsonObject(text: string): string | null {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start < 0 || end < start) return null;\n return text.slice(start, end + 1);\n}\n\nfunction makeOutcome(\n status: PhaseOutcome['status'],\n overrides: Partial<PhaseOutcome>,\n): PhaseOutcome {\n return {\n phase: 'deep',\n status,\n factsCreated: 0,\n factsUpdated: 0,\n conflictsResolved: 0,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount: 0,\n emptyExtractions: 0,\n llmTokensUsed: 0,\n llmCostUsd: null,\n errorMessage: null,\n ...overrides,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Provider, ProviderRequest, RunState } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/consolidator/phases/induce.d.ts
|
|
4
|
+
|
|
5
|
+
/** Output-token ceiling for one induction call. */
|
|
6
|
+
declare const DEFAULT_INDUCTION_MAX_TOKENS = 512;
|
|
7
|
+
/** Hard cap on steps in an induced procedure (defends against runaway output). */
|
|
8
|
+
declare const MAX_PROCEDURE_STEPS = 50;
|
|
9
|
+
/** Upper bound on trajectory steps rendered into the induction prompt. */
|
|
10
|
+
declare const MAX_TRAJECTORY_STEPS_SHOWN = 60;
|
|
11
|
+
/**
|
|
12
|
+
* One distilled step of an agent trajectory — model-agnostic, so the
|
|
13
|
+
* inducer never sees raw provider/tool wire formats.
|
|
14
|
+
*
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
interface TrajectoryStep {
|
|
18
|
+
/** Tool invoked at this step, when the step was a tool call. */
|
|
19
|
+
readonly tool?: string;
|
|
20
|
+
/** Concrete args / observation rendered to text (the value-abstraction source). */
|
|
21
|
+
readonly detail?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The minimal trajectory an inducer needs: the goal, the ordered steps, and
|
|
25
|
+
* whether the run succeeded. Induction fires on **success only** (AWM online
|
|
26
|
+
* mode) — {@link runWorkflowInduction} returns `null` for a failed run.
|
|
27
|
+
*
|
|
28
|
+
* @stable
|
|
29
|
+
*/
|
|
30
|
+
interface Trajectory {
|
|
31
|
+
/** What the run set out to do (typically the first user message). */
|
|
32
|
+
readonly goal: string;
|
|
33
|
+
/** Ordered steps the agent took. */
|
|
34
|
+
readonly steps: ReadonlyArray<TrajectoryStep>;
|
|
35
|
+
/** Whether the run succeeded — the induction gate. */
|
|
36
|
+
readonly succeeded: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A reusable workflow distilled from a successful trajectory.
|
|
40
|
+
*
|
|
41
|
+
* @stable
|
|
42
|
+
*/
|
|
43
|
+
interface InducedProcedure {
|
|
44
|
+
/** Short imperative title / goal of the reusable workflow. */
|
|
45
|
+
readonly title: string;
|
|
46
|
+
/** Ordered, value-abstracted steps (`"search for {product}"`, …). */
|
|
47
|
+
readonly steps: ReadonlyArray<string>;
|
|
48
|
+
/** Variable names abstracted from concrete values (`"product"`, `"day"`). */
|
|
49
|
+
readonly variables: ReadonlyArray<string>;
|
|
50
|
+
/** Voyager-style verifiable success criteria for self-verification on reuse. */
|
|
51
|
+
readonly successCriteria: ReadonlyArray<string>;
|
|
52
|
+
}
|
|
53
|
+
/** Per-call options for an induction request. */
|
|
54
|
+
interface WorkflowInductionOptions {
|
|
55
|
+
/** Output-token ceiling. Default {@link DEFAULT_INDUCTION_MAX_TOKENS}. */
|
|
56
|
+
readonly maxTokens?: number;
|
|
57
|
+
readonly signal?: AbortSignal;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Provider-agnostic seam: turn one successful trajectory into a procedure.
|
|
61
|
+
* Returns `null` when nothing inducible (degraded provider, empty output) —
|
|
62
|
+
* never throws.
|
|
63
|
+
*
|
|
64
|
+
* @stable
|
|
65
|
+
*/
|
|
66
|
+
interface WorkflowInducer {
|
|
67
|
+
induce(trajectory: Trajectory, options?: WorkflowInductionOptions): Promise<InducedProcedure | null>;
|
|
68
|
+
}
|
|
69
|
+
/** Result of self-verifying a reuse against an induced procedure's criteria. */
|
|
70
|
+
interface VerificationResult {
|
|
71
|
+
/** True only when the procedure has criteria and every one is met. */
|
|
72
|
+
readonly verified: boolean;
|
|
73
|
+
/** Criteria not satisfied by the observed signals. */
|
|
74
|
+
readonly unmet: ReadonlyArray<string>;
|
|
75
|
+
}
|
|
76
|
+
declare const INDUCTION_SYSTEM_PROMPT: string;
|
|
77
|
+
/**
|
|
78
|
+
* Build the (pure) induction request. Renders the trajectory as numbered
|
|
79
|
+
* steps; caps the rendered steps to keep the prompt bounded.
|
|
80
|
+
*/
|
|
81
|
+
declare function buildInductionRequest(trajectory: Trajectory, options?: WorkflowInductionOptions): ProviderRequest;
|
|
82
|
+
/**
|
|
83
|
+
* Parse the induction response into an {@link InducedProcedure}, tolerating
|
|
84
|
+
* chatty / fenced output. Returns `null` when absent / unparseable / it
|
|
85
|
+
* yields no steps. **Never throws on model output** — only normalizes.
|
|
86
|
+
*
|
|
87
|
+
* The returned `variables` are reconciled with the steps: every `{name}`
|
|
88
|
+
* placeholder actually present in the steps is guaranteed to appear, so the
|
|
89
|
+
* abstraction is grounded in the procedure rather than in a model-declared
|
|
90
|
+
* list that may drift from it.
|
|
91
|
+
*
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
declare function parseInducedProcedure(text: string | undefined): InducedProcedure | null;
|
|
95
|
+
/**
|
|
96
|
+
* Normalize a raw induced procedure: trim + drop empty entries, cap step
|
|
97
|
+
* count, and reconcile the variable list with the placeholders actually used
|
|
98
|
+
* in the steps. Returns `null` when no usable steps remain (nothing to
|
|
99
|
+
* induce).
|
|
100
|
+
*
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
declare function normalizeInducedProcedure(raw: {
|
|
104
|
+
title: string;
|
|
105
|
+
steps: ReadonlyArray<string>;
|
|
106
|
+
variables: ReadonlyArray<string>;
|
|
107
|
+
successCriteria: ReadonlyArray<string>;
|
|
108
|
+
}): InducedProcedure | null;
|
|
109
|
+
/**
|
|
110
|
+
* Resilient provider-backed inducer. A provider throw or unparseable output
|
|
111
|
+
* degrades to `null` (no procedure) — induction never breaks the write path.
|
|
112
|
+
*/
|
|
113
|
+
declare function createProviderWorkflowInducer(provider: Provider, options?: {
|
|
114
|
+
readonly maxTokens?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Usage callback (MCON-15) — induction is the framework's highest
|
|
117
|
+
* poisoning-risk LLM spend and previously flowed past every budget
|
|
118
|
+
* envelope. `createMemory` wires this into the consolidator budget
|
|
119
|
+
* when one is enabled; standalone callers can record it themselves.
|
|
120
|
+
* Best-effort: a throwing callback never breaks induction.
|
|
121
|
+
*/
|
|
122
|
+
readonly onUsage?: (usage: {
|
|
123
|
+
readonly promptTokens: number;
|
|
124
|
+
readonly completionTokens: number;
|
|
125
|
+
}) => void;
|
|
126
|
+
}): WorkflowInducer;
|
|
127
|
+
/**
|
|
128
|
+
* Pure orchestrator: induce a procedure from a trajectory.
|
|
129
|
+
*
|
|
130
|
+
* **Gate — successful trajectories only** (AWM online mode): a failed /
|
|
131
|
+
* aborted run, or one with no steps, yields `null` without calling the
|
|
132
|
+
* inducer. Otherwise the inducer runs and the result is normalized.
|
|
133
|
+
*/
|
|
134
|
+
declare function runWorkflowInduction(trajectory: Trajectory, inducer: WorkflowInducer, options?: WorkflowInductionOptions): Promise<InducedProcedure | null>;
|
|
135
|
+
/**
|
|
136
|
+
* Distil a {@link RunState} into the minimal {@link Trajectory} the inducer
|
|
137
|
+
* needs. Pure — consumes the agent's already-emitted run state, so capturing
|
|
138
|
+
* a trajectory + its success signal needs no agent-loop change. The success
|
|
139
|
+
* signal is `status === 'completed'`.
|
|
140
|
+
*/
|
|
141
|
+
declare function trajectoryFromRunState(run: RunState): Trajectory;
|
|
142
|
+
/**
|
|
143
|
+
* Self-verify a reuse against an induced procedure's success criteria. A
|
|
144
|
+
* criterion is met when any observed signal contains it (case-insensitive
|
|
145
|
+
* substring) — a deterministic, offline check the agent runtime can feed
|
|
146
|
+
* actual run observations into on reuse. With **no criteria**, the reuse
|
|
147
|
+
* cannot be self-verified, so `verified` is `false`.
|
|
148
|
+
*/
|
|
149
|
+
declare function checkSuccessCriteria(procedure: {
|
|
150
|
+
readonly successCriteria?: ReadonlyArray<string>;
|
|
151
|
+
}, observed: ReadonlyArray<string>): VerificationResult;
|
|
152
|
+
//#endregion
|
|
153
|
+
export { DEFAULT_INDUCTION_MAX_TOKENS, INDUCTION_SYSTEM_PROMPT, InducedProcedure, MAX_PROCEDURE_STEPS, MAX_TRAJECTORY_STEPS_SHOWN, Trajectory, TrajectoryStep, VerificationResult, WorkflowInducer, WorkflowInductionOptions, buildInductionRequest, checkSuccessCriteria, createProviderWorkflowInducer, normalizeInducedProcedure, parseInducedProcedure, runWorkflowInduction, trajectoryFromRunState };
|
|
154
|
+
//# sourceMappingURL=induce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"induce.d.ts","names":[],"sources":["../../../src/consolidator/phases/induce.ts"],"sourcesContent":[],"mappings":";;;;;AAwMmB,cAvKN,4BAAA,GAuKM,GAAA;;AACC,cArKP,mBAAA,GAqKO,EAAA;AAwBpB;AAgDsB,cA1OT,0BAAA,GA0O6B,EAAA;;;;;;;AAuB1B,UAtPC,cAAA,CAsPqB;EAyBtB;EAC0B,SAAA,IAAA,CAAA,EAAA,MAAA;EAC9B;EACT,SAAA,MAAA,CAAA,EAAA,MAAA;;;;;;;;;UApQc,UAAA;;;;kBAIC,cAAc;;;;;;;;;UAUf,gBAAA;;;;kBAIC;;sBAEI;;4BAEM;;;UAIX,wBAAA;;;oBAGG;;;;;;;;;UAUH,eAAA;qBAED,sBACF,2BACT,QAAQ;;;UAII,kBAAA;;;;kBAIC;;cAGL;;;;;iBAeG,qBAAA,aACF,sBACH,2BACR;;;;;;;;;;;;;iBAwCa,qBAAA,4BAAiD;;;;;;;;;iBAoBjD,yBAAA;;SAEP;aACI;mBACM;IACf;;;;;iBAwBY,6BAAA,WACJ;;;;;;;;;;;;;IAeT;;;;;;;;iBAgCmB,oBAAA,aACR,qBACH,2BACA,2BACR,QAAQ;;;;;;;iBAmBK,sBAAA,MAA4B,WAAW;;;;;;;;iBAyBvC,oBAAA;6BAC0B;aAC9B,wBACT"}
|