@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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooperation contract between the {@link ContextEngine} and the
|
|
3
|
+
* outbound prompt-redaction middleware (D3, see ADR-045 / DEC-157)
|
|
4
|
+
* + the inbound tool-result sanitization middleware (D4, see
|
|
5
|
+
* RB-43 / DEC-159).
|
|
6
|
+
*
|
|
7
|
+
* Every {@link MessageContent} part assembled by the ContextEngine
|
|
8
|
+
* carries two **independent** annotations:
|
|
9
|
+
*
|
|
10
|
+
* - `graphorin.content.origin` - where the content came from. Used
|
|
11
|
+
* by D3 (`withRedaction`) under `scanScope: 'untrusted'` to
|
|
12
|
+
* decide whether to re-scan a part that already passed the D2
|
|
13
|
+
* sensitivity-tier filter.
|
|
14
|
+
* - `graphorin.content.inbound.trust` - what trust class the source
|
|
15
|
+
* carries. Used by D4 (`withInboundSanitization`) and by Phase 12
|
|
16
|
+
* (agent runtime) to gate the per-step preamble injection.
|
|
17
|
+
*
|
|
18
|
+
* Both annotations live as **span attributes only** (observability)
|
|
19
|
+
* - they are never serialized to the wire payload. The wire-stable
|
|
20
|
+
* `ProviderRequest` shape is unchanged by Phase 10d.
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Span-attribute key for the origin axis.
|
|
27
|
+
*
|
|
28
|
+
* @stable
|
|
29
|
+
*/
|
|
30
|
+
export const CONTENT_ORIGIN_ATTR = 'graphorin.content.origin' as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Span-attribute key for the inbound-trust axis.
|
|
34
|
+
*
|
|
35
|
+
* @stable
|
|
36
|
+
*/
|
|
37
|
+
export const INBOUND_TRUST_ATTR = 'graphorin.content.inbound.trust' as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Origin discriminator for an assembled message-content part. The
|
|
41
|
+
* non-ContextEngine origins (`'user:input'`, `'tool:result'`,
|
|
42
|
+
* `'mcp:response'`, `'tool-call:args'`) are tagged by Phase 12
|
|
43
|
+
* (agent runtime) when the corresponding payload enters
|
|
44
|
+
* `session_messages`; the ContextEngine then propagates the tag
|
|
45
|
+
* through the assembled message list.
|
|
46
|
+
*
|
|
47
|
+
* @stable
|
|
48
|
+
*/
|
|
49
|
+
export type ContentOrigin =
|
|
50
|
+
| 'memory:tier-filtered'
|
|
51
|
+
| 'system:framework'
|
|
52
|
+
| 'agent:instructions'
|
|
53
|
+
| 'skill:content'
|
|
54
|
+
| 'user:input'
|
|
55
|
+
| 'tool:result'
|
|
56
|
+
| 'mcp:response'
|
|
57
|
+
| 'tool-call:args';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Trust-class discriminator for an assembled message-content part.
|
|
61
|
+
* Sibling axis to {@link ContentOrigin}; the two are independent.
|
|
62
|
+
*
|
|
63
|
+
* - `'trusted'` - built-in framework tools + trusted-skill-bundled
|
|
64
|
+
* tools; D4 preamble does NOT fire on steps containing only
|
|
65
|
+
* these parts.
|
|
66
|
+
* - `'user-defined'` - tools registered via `tool({...})` from user
|
|
67
|
+
* application code; D4 preamble fires.
|
|
68
|
+
* - `'untrusted-skill'` - tools bundled by an untrusted skill; D4
|
|
69
|
+
* preamble fires; default policy is strip-and-wrap.
|
|
70
|
+
* - `'mcp'` - every `Tool` produced by `MCPClient.toTools(...)`; D4
|
|
71
|
+
* preamble fires; default policy is strip-and-wrap.
|
|
72
|
+
* - `'web-search'` - built-in `web_search` adapter; D4 preamble
|
|
73
|
+
* fires; default policy is strip-and-wrap.
|
|
74
|
+
* - `'n/a'` - non-tool-result parts for which the inbound-trust
|
|
75
|
+
* axis is meaningless (`'user:input'`, `'memory:tier-filtered'`,
|
|
76
|
+
* `'system:framework'`, `'agent:instructions'`,
|
|
77
|
+
* `'tool-call:args'`).
|
|
78
|
+
*
|
|
79
|
+
* @stable
|
|
80
|
+
*/
|
|
81
|
+
export type InboundTrust =
|
|
82
|
+
| 'trusted'
|
|
83
|
+
| 'user-defined'
|
|
84
|
+
| 'untrusted-skill'
|
|
85
|
+
| 'mcp'
|
|
86
|
+
| 'web-search'
|
|
87
|
+
| 'n/a';
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Origins for which the inbound-trust axis is meaningless and is
|
|
91
|
+
* always set to `'n/a'`. Surfaced as a frozen constant so consumers
|
|
92
|
+
* can introspect the contract without re-implementing the rule.
|
|
93
|
+
*
|
|
94
|
+
* @stable
|
|
95
|
+
*/
|
|
96
|
+
export const NON_INBOUND_ORIGINS: ReadonlySet<ContentOrigin> = Object.freeze(
|
|
97
|
+
new Set<ContentOrigin>([
|
|
98
|
+
'memory:tier-filtered',
|
|
99
|
+
'system:framework',
|
|
100
|
+
'agent:instructions',
|
|
101
|
+
'user:input',
|
|
102
|
+
'tool-call:args',
|
|
103
|
+
]),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Single typed annotation for an assembled message-content part.
|
|
108
|
+
*
|
|
109
|
+
* @stable
|
|
110
|
+
*/
|
|
111
|
+
export interface ContentAnnotation {
|
|
112
|
+
readonly origin: ContentOrigin;
|
|
113
|
+
readonly inboundTrust: InboundTrust;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build an annotation, enforcing the rule that
|
|
118
|
+
* non-tool-result origins always carry `inboundTrust: 'n/a'`. A
|
|
119
|
+
* caller that requests a non-`'n/a'` value for a non-inbound origin
|
|
120
|
+
* is silently corrected (defense-in-depth: the rule is enforced
|
|
121
|
+
* here so callers cannot accidentally violate it).
|
|
122
|
+
*
|
|
123
|
+
* @stable
|
|
124
|
+
*/
|
|
125
|
+
export function annotate(origin: ContentOrigin, inboundTrust: InboundTrust): ContentAnnotation {
|
|
126
|
+
if (NON_INBOUND_ORIGINS.has(origin)) {
|
|
127
|
+
return Object.freeze({ origin, inboundTrust: 'n/a' });
|
|
128
|
+
}
|
|
129
|
+
return Object.freeze({ origin, inboundTrust });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Convert a {@link ContentAnnotation} to a span-attributes record
|
|
134
|
+
* suitable for `AISpan.setAttributes(...)`.
|
|
135
|
+
*
|
|
136
|
+
* @stable
|
|
137
|
+
*/
|
|
138
|
+
export function toSpanAttributes(annotation: ContentAnnotation): Readonly<Record<string, string>> {
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
[CONTENT_ORIGIN_ATTR]: annotation.origin,
|
|
141
|
+
[INBOUND_TRUST_ATTR]: annotation.inboundTrust,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Decide whether the per-step inbound-sanitization preamble (D4)
|
|
147
|
+
* should fire for an assembled message list. The preamble fires
|
|
148
|
+
* iff at least one part carries an inbound-trust value other than
|
|
149
|
+
* `'trusted'` and `'n/a'`. Trusted-only steps skip the preamble for
|
|
150
|
+
* cache-friendliness; preamble is emitted exactly once per step
|
|
151
|
+
* regardless of how many untrusted parts the step carries.
|
|
152
|
+
*
|
|
153
|
+
* @stable
|
|
154
|
+
*/
|
|
155
|
+
export function shouldFireInboundPreamble(annotations: ReadonlyArray<ContentAnnotation>): boolean {
|
|
156
|
+
for (const annotation of annotations) {
|
|
157
|
+
const trust = annotation.inboundTrust;
|
|
158
|
+
if (trust !== 'trusted' && trust !== 'n/a') return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-recall heuristic - opt-in regex-based pre-call that injects
|
|
3
|
+
* the top-K facts into Layer 6 when the last user message matches one
|
|
4
|
+
* of the configured trigger phrases. (Episode auto-recall is not
|
|
5
|
+
* implemented; episodes are reachable via the `recall_episodes` tool.)
|
|
6
|
+
*
|
|
7
|
+
* The framework ships English defaults; per-locale trigger
|
|
8
|
+
* phrases extend the default via {@link defineContextLocalePack}.
|
|
9
|
+
* Application code can also register a fully-custom strategy via
|
|
10
|
+
* {@link defineAutoRecallStrategy}.
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ContextLocalePack } from './locale-packs/index.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Per-call scope passed to a custom strategy.
|
|
19
|
+
*
|
|
20
|
+
* @stable
|
|
21
|
+
*/
|
|
22
|
+
export interface AutoRecallStrategyContext {
|
|
23
|
+
readonly locale: string;
|
|
24
|
+
readonly lastUserMessage: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Outcome of a strategy invocation.
|
|
29
|
+
*
|
|
30
|
+
* @stable
|
|
31
|
+
*/
|
|
32
|
+
export interface AutoRecallTriggerResult {
|
|
33
|
+
readonly factsTriggered: boolean;
|
|
34
|
+
/** Optional reason surfaced in spans / metadata. */
|
|
35
|
+
readonly reason?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Pluggable strategy signature.
|
|
40
|
+
*
|
|
41
|
+
* @stable
|
|
42
|
+
*/
|
|
43
|
+
export type AutoRecallStrategy = (ctx: AutoRecallStrategyContext) => AutoRecallTriggerResult;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build a locale-driven heuristic strategy. The default for every
|
|
47
|
+
* configured locale.
|
|
48
|
+
*
|
|
49
|
+
* @stable
|
|
50
|
+
*/
|
|
51
|
+
export function defaultLocaleHeuristicStrategy(pack: ContextLocalePack): AutoRecallStrategy {
|
|
52
|
+
const factTriggers = pack.autoRecallTriggers.factTriggers;
|
|
53
|
+
return (ctx) => {
|
|
54
|
+
const text = ctx.lastUserMessage ?? '';
|
|
55
|
+
if (text.length === 0) {
|
|
56
|
+
return { factsTriggered: false };
|
|
57
|
+
}
|
|
58
|
+
const factsTriggered = factTriggers.some((re) => re.test(text));
|
|
59
|
+
if (factsTriggered) {
|
|
60
|
+
return { factsTriggered, reason: 'locale-heuristic-trigger-matched' };
|
|
61
|
+
}
|
|
62
|
+
return { factsTriggered: false };
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Builder for application-supplied custom strategies. Accepts a
|
|
68
|
+
* raw function and returns a tagged version so the engine can
|
|
69
|
+
* surface the strategy name on spans.
|
|
70
|
+
*
|
|
71
|
+
* @stable
|
|
72
|
+
*/
|
|
73
|
+
export function defineAutoRecallStrategy(opts: {
|
|
74
|
+
readonly id: string;
|
|
75
|
+
readonly evaluate: AutoRecallStrategy;
|
|
76
|
+
}): AutoRecallStrategy & { readonly id: string } {
|
|
77
|
+
if (typeof opts.id !== 'string' || opts.id.length === 0) {
|
|
78
|
+
throw new TypeError(
|
|
79
|
+
'[graphorin/memory] defineAutoRecallStrategy: `id` must be a non-empty string.',
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (typeof opts.evaluate !== 'function') {
|
|
83
|
+
throw new TypeError(
|
|
84
|
+
'[graphorin/memory] defineAutoRecallStrategy: `evaluate` must be a function.',
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const wrapped = ((ctx: AutoRecallStrategyContext) => opts.evaluate(ctx)) as AutoRecallStrategy & {
|
|
88
|
+
readonly id: string;
|
|
89
|
+
};
|
|
90
|
+
Object.defineProperty(wrapped, 'id', { value: opts.id, enumerable: true });
|
|
91
|
+
return wrapped;
|
|
92
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOTA-1: zero-LLM tool-result clearing - the cheapest pre-compaction tier.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Anthropic context editing (`clear_tool_uses_20250919`): when the
|
|
5
|
+
* buffer crosses the compaction threshold, replace the oldest tool results with
|
|
6
|
+
* a compact placeholder BEFORE paying for a summarizer LLM call. The most recent
|
|
7
|
+
* `keepToolUses` results stay verbatim, `excludeTools` are never touched, and
|
|
8
|
+
* clearing is skipped entirely when it would reclaim fewer than `clearAtLeast`
|
|
9
|
+
* tokens (not worth the context churn). Pure + deterministic - no provider call.
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Message, ToolMessage } from '@graphorin/core';
|
|
15
|
+
import {
|
|
16
|
+
type ContextTokenCounter,
|
|
17
|
+
countMessageTokens,
|
|
18
|
+
renderMessageText,
|
|
19
|
+
} from '../token-counter.js';
|
|
20
|
+
|
|
21
|
+
/** Placeholder prefix on a cleared tool result - makes clearing idempotent. */
|
|
22
|
+
export const CLEARED_TOOL_RESULT_MARKER = '[cleared tool result';
|
|
23
|
+
|
|
24
|
+
/** Default count of most-recent tool results kept verbatim. */
|
|
25
|
+
export const DEFAULT_KEEP_TOOL_USES = 3;
|
|
26
|
+
|
|
27
|
+
/** Knobs for {@link clearOldToolResults} (mirrors the strategy variant). */
|
|
28
|
+
export interface ClearToolResultsOptions {
|
|
29
|
+
/** Most-recent tool results kept verbatim (default `DEFAULT_KEEP_TOOL_USES`). */
|
|
30
|
+
readonly keepToolUses?: number;
|
|
31
|
+
/** Only clear if at least this many tokens are reclaimable; else leave untouched (default 0). */
|
|
32
|
+
readonly clearAtLeast?: number;
|
|
33
|
+
/** Tool names whose results are never cleared. */
|
|
34
|
+
readonly excludeTools?: ReadonlyArray<string>;
|
|
35
|
+
/** Placeholder builder; defaults to a one-line `[cleared tool result …]` marker. */
|
|
36
|
+
readonly placeholder?: (info: {
|
|
37
|
+
readonly toolCallId: string;
|
|
38
|
+
readonly toolName?: string;
|
|
39
|
+
readonly clearedTokens: number;
|
|
40
|
+
}) => string;
|
|
41
|
+
/**
|
|
42
|
+
* A6 / SOTA-2 - recoverable clearing. When provided, the original tool-result
|
|
43
|
+
* text of each cleared message is handed to this callback (wire it to a spill
|
|
44
|
+
* store / the `read_result` handle registry) and the placeholder references the
|
|
45
|
+
* returned handle id + preview, so the model can re-fetch the full result via
|
|
46
|
+
* `read_result` rather than losing it. Invoked only for clears that actually
|
|
47
|
+
* commit (after the `clearAtLeast` floor), so a rejected clearing never spills.
|
|
48
|
+
* Omitted ⇒ the bare `placeholder` (irrecoverable, byte-identical default).
|
|
49
|
+
*/
|
|
50
|
+
readonly externalize?: (
|
|
51
|
+
content: string,
|
|
52
|
+
info: {
|
|
53
|
+
readonly toolCallId: string;
|
|
54
|
+
readonly toolName?: string;
|
|
55
|
+
readonly clearedTokens: number;
|
|
56
|
+
},
|
|
57
|
+
) => Promise<{ readonly handleId: string; readonly preview?: string }>;
|
|
58
|
+
/**
|
|
59
|
+
* C4 (context-engine-11): the tool the externalized-handle placeholder
|
|
60
|
+
* advertises. The memory package cannot know whether the agent
|
|
61
|
+
* registered `read_result` (that depends on spill wiring), so callers
|
|
62
|
+
* whose runtime does NOT expose it pass `null` and the placeholder
|
|
63
|
+
* degrades to a tool-neutral phrasing instead of promising a tool the
|
|
64
|
+
* model cannot call. Default `'read_result'` (the agent built-in).
|
|
65
|
+
*/
|
|
66
|
+
readonly readResultToolName?: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* C4 (clear_tool_uses_20250919 parity): additionally blank the PAIRED
|
|
69
|
+
* assistant message's tool-call arguments for every cleared result,
|
|
70
|
+
* reclaiming the input side of verbose calls too. Default `false`.
|
|
71
|
+
*/
|
|
72
|
+
readonly clearToolInputs?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Result of a clearing pass. `clearedIndices` empty ⇒ nothing changed. */
|
|
76
|
+
export interface ClearToolResultsOutcome {
|
|
77
|
+
readonly messages: ReadonlyArray<Message>;
|
|
78
|
+
readonly clearedIndices: ReadonlyArray<number>;
|
|
79
|
+
readonly reclaimedTokens: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function defaultPlaceholder(info: { toolName?: string; clearedTokens: number }): string {
|
|
83
|
+
return `${CLEARED_TOOL_RESULT_MARKER} · ${info.toolName ?? 'tool'} · ${info.clearedTokens} tokens elided · re-run the tool if needed]`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A6: a placeholder that points at the externalized handle (recoverable via read_result). */
|
|
87
|
+
function handlePlaceholder(info: {
|
|
88
|
+
toolName?: string;
|
|
89
|
+
clearedTokens: number;
|
|
90
|
+
handleId: string;
|
|
91
|
+
preview?: string;
|
|
92
|
+
readToolName?: string | null;
|
|
93
|
+
}): string {
|
|
94
|
+
const preview = info.preview !== undefined && info.preview.length > 0 ? ` · ${info.preview}` : '';
|
|
95
|
+
// C4 (context-engine-11): only advertise a retrieval tool the caller
|
|
96
|
+
// vouches exists; `null` degrades to a tool-neutral phrasing.
|
|
97
|
+
const retrieval =
|
|
98
|
+
info.readToolName === null
|
|
99
|
+
? `full result externalized to handle: ${info.handleId}`
|
|
100
|
+
: `full result via ${info.readToolName ?? 'read_result'} handle: ${info.handleId}`;
|
|
101
|
+
return `${CLEARED_TOOL_RESULT_MARKER} · ${info.toolName ?? 'tool'} · ${info.clearedTokens} tokens · ${retrieval}${preview}]`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isAlreadyCleared(content: Message['content']): boolean {
|
|
105
|
+
return typeof content === 'string' && content.startsWith(CLEARED_TOOL_RESULT_MARKER);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Replace the oldest clearable tool results with placeholders. Returns the new
|
|
110
|
+
* buffer (same length - content is replaced in place, never removed) plus the
|
|
111
|
+
* cleared indices and reclaimed token count. Idempotent: already-cleared
|
|
112
|
+
* placeholders are skipped on a second pass.
|
|
113
|
+
*/
|
|
114
|
+
export async function clearOldToolResults(
|
|
115
|
+
messages: ReadonlyArray<Message>,
|
|
116
|
+
options: ClearToolResultsOptions,
|
|
117
|
+
counter: ContextTokenCounter,
|
|
118
|
+
): Promise<ClearToolResultsOutcome> {
|
|
119
|
+
const keep = options.keepToolUses ?? DEFAULT_KEEP_TOOL_USES;
|
|
120
|
+
const exclude = new Set(options.excludeTools ?? []);
|
|
121
|
+
const placeholder = options.placeholder ?? defaultPlaceholder;
|
|
122
|
+
|
|
123
|
+
// toolCallId → toolName from every assistant tool-call in the buffer.
|
|
124
|
+
const toolNameById = new Map<string, string>();
|
|
125
|
+
for (const m of messages) {
|
|
126
|
+
if (m.role === 'assistant' && m.toolCalls) {
|
|
127
|
+
for (const tc of m.toolCalls) toolNameById.set(tc.toolCallId, tc.toolName);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Indices of all not-yet-cleared tool-result messages, oldest first.
|
|
132
|
+
const toolIdx: number[] = [];
|
|
133
|
+
for (let i = 0; i < messages.length; i++) {
|
|
134
|
+
const m = messages[i];
|
|
135
|
+
if (m?.role === 'tool' && !isAlreadyCleared(m.content)) toolIdx.push(i);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Keep the most-recent `keep`; older ones are candidates unless excluded.
|
|
139
|
+
const keptCutoff = toolIdx.length - Math.max(0, keep);
|
|
140
|
+
const replacements = new Map<number, ToolMessage>();
|
|
141
|
+
const clearedTokensByIdx = new Map<number, number>();
|
|
142
|
+
let reclaimable = 0;
|
|
143
|
+
for (let j = 0; j < toolIdx.length && j < keptCutoff; j++) {
|
|
144
|
+
const idx = toolIdx[j];
|
|
145
|
+
if (idx === undefined) continue;
|
|
146
|
+
const tm = messages[idx] as ToolMessage;
|
|
147
|
+
const name = toolNameById.get(tm.toolCallId);
|
|
148
|
+
if (name !== undefined && exclude.has(name)) continue;
|
|
149
|
+
const originalTokens = await countMessageTokens([tm], counter);
|
|
150
|
+
const replaced: ToolMessage = {
|
|
151
|
+
role: 'tool',
|
|
152
|
+
toolCallId: tm.toolCallId,
|
|
153
|
+
content: placeholder({
|
|
154
|
+
toolCallId: tm.toolCallId,
|
|
155
|
+
...(name !== undefined ? { toolName: name } : {}),
|
|
156
|
+
clearedTokens: originalTokens,
|
|
157
|
+
}),
|
|
158
|
+
};
|
|
159
|
+
const placeholderTokens = await countMessageTokens([replaced], counter);
|
|
160
|
+
reclaimable += Math.max(0, originalTokens - placeholderTokens);
|
|
161
|
+
replacements.set(idx, replaced);
|
|
162
|
+
clearedTokensByIdx.set(idx, originalTokens);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const clearAtLeast = options.clearAtLeast ?? 0;
|
|
166
|
+
if (replacements.size === 0 || reclaimable < clearAtLeast) {
|
|
167
|
+
return { messages, clearedIndices: [], reclaimedTokens: 0 };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// A6: only now that the clearing commits, externalize each cleared result to a
|
|
171
|
+
// recoverable handle and rewrite its placeholder to reference it. Skipped
|
|
172
|
+
// clears (above) never reach here, so the spill never fires for nothing.
|
|
173
|
+
const externalize = options.externalize;
|
|
174
|
+
if (externalize !== undefined) {
|
|
175
|
+
for (const idx of replacements.keys()) {
|
|
176
|
+
const tm = messages[idx] as ToolMessage;
|
|
177
|
+
const name = toolNameById.get(tm.toolCallId);
|
|
178
|
+
const clearedTokens = clearedTokensByIdx.get(idx) ?? 0;
|
|
179
|
+
const content = typeof tm.content === 'string' ? tm.content : renderMessageText(tm);
|
|
180
|
+
const handle = await externalize(content, {
|
|
181
|
+
toolCallId: tm.toolCallId,
|
|
182
|
+
...(name !== undefined ? { toolName: name } : {}),
|
|
183
|
+
clearedTokens,
|
|
184
|
+
});
|
|
185
|
+
replacements.set(idx, {
|
|
186
|
+
role: 'tool',
|
|
187
|
+
toolCallId: tm.toolCallId,
|
|
188
|
+
content: handlePlaceholder({
|
|
189
|
+
...(name !== undefined ? { toolName: name } : {}),
|
|
190
|
+
clearedTokens,
|
|
191
|
+
handleId: handle.handleId,
|
|
192
|
+
...(handle.preview !== undefined ? { preview: handle.preview } : {}),
|
|
193
|
+
...(options.readResultToolName !== undefined
|
|
194
|
+
? { readToolName: options.readResultToolName }
|
|
195
|
+
: {}),
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let out: Message[] = messages.map((m, i) => replacements.get(i) ?? m);
|
|
202
|
+
|
|
203
|
+
// C4 (clear_tool_uses parity): blank the paired assistant tool-call
|
|
204
|
+
// ARGUMENTS for every cleared result. The call's name and id survive
|
|
205
|
+
// (the transcript stays well-formed); only the argument payload is
|
|
206
|
+
// replaced with a marker object.
|
|
207
|
+
if (options.clearToolInputs === true && replacements.size > 0) {
|
|
208
|
+
const clearedCallIds = new Set<string>();
|
|
209
|
+
for (const idx of replacements.keys()) {
|
|
210
|
+
const tm = messages[idx] as ToolMessage;
|
|
211
|
+
clearedCallIds.add(tm.toolCallId);
|
|
212
|
+
}
|
|
213
|
+
out = out.map((m) => {
|
|
214
|
+
if (m.role !== 'assistant' || m.toolCalls === undefined) return m;
|
|
215
|
+
if (!m.toolCalls.some((tc) => clearedCallIds.has(tc.toolCallId))) return m;
|
|
216
|
+
return {
|
|
217
|
+
...m,
|
|
218
|
+
toolCalls: m.toolCalls.map((tc) =>
|
|
219
|
+
clearedCallIds.has(tc.toolCallId)
|
|
220
|
+
? { ...tc, args: { cleared: '[tool input elided by context clearing]' } }
|
|
221
|
+
: tc,
|
|
222
|
+
),
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
messages: out,
|
|
229
|
+
clearedIndices: [...replacements.keys()],
|
|
230
|
+
reclaimedTokens: reclaimable,
|
|
231
|
+
};
|
|
232
|
+
}
|