@graphorin/memory 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/README.md +24 -7
- 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 +17 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -7
- 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 +6 -0
- package/dist/package.js.map +1 -0
- 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,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost-budget enforcement for the consolidator. The tracker is a
|
|
3
|
+
* pure in-memory state machine: it owns the day-bucketed counters
|
|
4
|
+
* (token + cost), the UTC reset semantics, and the `onExceed`
|
|
5
|
+
* dispatch.
|
|
6
|
+
*
|
|
7
|
+
* The tracker does not perform any I/O - operators surface the live
|
|
8
|
+
* counters via `Consolidator.status()` and the persisted snapshot is
|
|
9
|
+
* derived from `consolidator_runs` rows.
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { BudgetExceededError } from './errors.js';
|
|
15
|
+
import type { ConsolidatorPhase, OnBudgetExceed } from './types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
export interface BudgetSnapshot {
|
|
21
|
+
readonly tokensUsedToday: number;
|
|
22
|
+
readonly costUsedToday: number;
|
|
23
|
+
readonly tokensRemaining: number;
|
|
24
|
+
readonly costRemaining: number;
|
|
25
|
+
readonly resetAt: string;
|
|
26
|
+
readonly paused: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @stable
|
|
31
|
+
*/
|
|
32
|
+
export interface BudgetCheck {
|
|
33
|
+
readonly allowed: boolean;
|
|
34
|
+
readonly reason?: 'paused' | 'tokens-exceeded' | 'cost-exceeded';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @stable
|
|
39
|
+
*/
|
|
40
|
+
export interface BudgetTrackerOptions {
|
|
41
|
+
readonly maxTokensPerDay: number;
|
|
42
|
+
readonly maxCostPerDay: number;
|
|
43
|
+
readonly onExceed: OnBudgetExceed;
|
|
44
|
+
readonly resetSemantics: 'utc' | 'local' | 'sliding-24h';
|
|
45
|
+
readonly now?: () => number;
|
|
46
|
+
/**
|
|
47
|
+
* Sink for the `onExceed: 'log'` WARN (memory-consolidation-02).
|
|
48
|
+
* Defaults to `process.stderr`. One WARN per resource per budget
|
|
49
|
+
* window - the shipped standard/full presets use `'log'`, so without
|
|
50
|
+
* it a breached ceiling was completely silent.
|
|
51
|
+
*/
|
|
52
|
+
readonly logger?: (message: string) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Per-instance budget tracker. The runtime creates one tracker per
|
|
57
|
+
* consolidator and resets the day counters lazily on the next phase
|
|
58
|
+
* invocation.
|
|
59
|
+
*
|
|
60
|
+
* @stable
|
|
61
|
+
*/
|
|
62
|
+
export class BudgetTracker {
|
|
63
|
+
readonly #now: () => number;
|
|
64
|
+
readonly #resetSemantics: 'utc' | 'local' | 'sliding-24h';
|
|
65
|
+
readonly #onExceed: OnBudgetExceed;
|
|
66
|
+
readonly #logger: (message: string) => void;
|
|
67
|
+
#maxTokensPerDay: number;
|
|
68
|
+
#maxCostPerDay: number;
|
|
69
|
+
#bucketStart: number;
|
|
70
|
+
#tokens = 0;
|
|
71
|
+
#cost = 0;
|
|
72
|
+
#paused = false;
|
|
73
|
+
#pausedReason: 'tokens-exceeded' | 'cost-exceeded' | null = null;
|
|
74
|
+
/** Resources already WARNed this window under `onExceed: 'log'` (memory-consolidation-02). */
|
|
75
|
+
#warnedThisWindow = new Set<'tokens' | 'cost'>();
|
|
76
|
+
/**
|
|
77
|
+
* Timestamped spend ledger for `sliding-24h` only. `#maybeReset` trims it to
|
|
78
|
+
* the trailing 24h window and recomputes `#tokens` / `#cost` from it, so the
|
|
79
|
+
* counters reflect a true rolling window instead of being zeroed on every
|
|
80
|
+
* check (MCON-3). Unused - and never appended to - under `utc` / `local`.
|
|
81
|
+
*/
|
|
82
|
+
#ledger: Array<{ at: number; tokens: number; cost: number }> = [];
|
|
83
|
+
|
|
84
|
+
constructor(opts: BudgetTrackerOptions) {
|
|
85
|
+
this.#now = opts.now ?? Date.now;
|
|
86
|
+
this.#resetSemantics = opts.resetSemantics;
|
|
87
|
+
this.#onExceed = opts.onExceed;
|
|
88
|
+
this.#logger =
|
|
89
|
+
opts.logger ??
|
|
90
|
+
((message: string): void => {
|
|
91
|
+
process.stderr.write(`${message}\n`);
|
|
92
|
+
});
|
|
93
|
+
this.#maxTokensPerDay = opts.maxTokensPerDay;
|
|
94
|
+
this.#maxCostPerDay = opts.maxCostPerDay;
|
|
95
|
+
this.#bucketStart = bucketStart(this.#now(), this.#resetSemantics);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Replace the active ceilings. Used by `Consolidator.setTier(...)`.
|
|
100
|
+
*
|
|
101
|
+
* @stable
|
|
102
|
+
*/
|
|
103
|
+
reconfigure(opts: { maxTokensPerDay: number; maxCostPerDay: number }): void {
|
|
104
|
+
this.#maxTokensPerDay = opts.maxTokensPerDay;
|
|
105
|
+
this.#maxCostPerDay = opts.maxCostPerDay;
|
|
106
|
+
if (this.#paused) {
|
|
107
|
+
const tokensOk = this.#tokens <= this.#maxTokensPerDay;
|
|
108
|
+
const costOk = this.#cost <= this.#maxCostPerDay;
|
|
109
|
+
if (tokensOk && costOk) {
|
|
110
|
+
this.#paused = false;
|
|
111
|
+
this.#pausedReason = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Return whether the supplied phase may run right now.
|
|
118
|
+
*
|
|
119
|
+
* @stable
|
|
120
|
+
*/
|
|
121
|
+
precheck(phase: ConsolidatorPhase): BudgetCheck {
|
|
122
|
+
this.#maybeReset();
|
|
123
|
+
if (phase === 'light') return { allowed: true };
|
|
124
|
+
if (this.#paused) {
|
|
125
|
+
const reason = this.#pausedReason ?? 'cost-exceeded';
|
|
126
|
+
return { allowed: false, reason };
|
|
127
|
+
}
|
|
128
|
+
if (this.#maxTokensPerDay <= 0) {
|
|
129
|
+
return { allowed: false, reason: 'tokens-exceeded' };
|
|
130
|
+
}
|
|
131
|
+
if (this.#maxCostPerDay <= 0) {
|
|
132
|
+
return { allowed: false, reason: 'cost-exceeded' };
|
|
133
|
+
}
|
|
134
|
+
return { allowed: true };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Record consumption. Returns the post-record state - `paused` is
|
|
139
|
+
* `true` when the spend pushed past a ceiling under
|
|
140
|
+
* `onExceed: 'pause'`.
|
|
141
|
+
*
|
|
142
|
+
* Throws {@link BudgetExceededError} when the configured behaviour
|
|
143
|
+
* is `'throw'`.
|
|
144
|
+
*
|
|
145
|
+
* @stable
|
|
146
|
+
*/
|
|
147
|
+
record(args: { phase: ConsolidatorPhase; tokens: number; costUsd: number }): BudgetSnapshot {
|
|
148
|
+
this.#maybeReset();
|
|
149
|
+
const tokens = Math.max(0, args.tokens);
|
|
150
|
+
const cost = Math.max(0, args.costUsd);
|
|
151
|
+
if (this.#resetSemantics === 'sliding-24h') {
|
|
152
|
+
this.#ledger.push({ at: this.#now(), tokens, cost });
|
|
153
|
+
}
|
|
154
|
+
this.#tokens += tokens;
|
|
155
|
+
this.#cost += cost;
|
|
156
|
+
if (this.#tokens > this.#maxTokensPerDay) {
|
|
157
|
+
this.#handleBreach(args.phase, 'tokens', this.#tokens, this.#maxTokensPerDay);
|
|
158
|
+
}
|
|
159
|
+
if (this.#cost > this.#maxCostPerDay) {
|
|
160
|
+
this.#handleBreach(args.phase, 'cost', this.#cost, this.#maxCostPerDay);
|
|
161
|
+
}
|
|
162
|
+
return this.snapshot();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Read-only snapshot. Surfaced through `Consolidator.status()`.
|
|
167
|
+
*
|
|
168
|
+
* @stable
|
|
169
|
+
*/
|
|
170
|
+
snapshot(): BudgetSnapshot {
|
|
171
|
+
this.#maybeReset();
|
|
172
|
+
// For the rolling window the "reset" is continuous - the boundary is when
|
|
173
|
+
// the oldest in-window spend ages out (24h after it landed).
|
|
174
|
+
const resetAt =
|
|
175
|
+
this.#resetSemantics === 'sliding-24h'
|
|
176
|
+
? (this.#ledger[0]?.at ?? this.#now()) + DAY_MS
|
|
177
|
+
: nextBucketStart(this.#bucketStart, this.#resetSemantics);
|
|
178
|
+
return Object.freeze({
|
|
179
|
+
tokensUsedToday: this.#tokens,
|
|
180
|
+
costUsedToday: this.#cost,
|
|
181
|
+
tokensRemaining: Math.max(0, this.#maxTokensPerDay - this.#tokens),
|
|
182
|
+
costRemaining: Math.max(0, this.#maxCostPerDay - this.#cost),
|
|
183
|
+
resetAt: new Date(resetAt).toISOString(),
|
|
184
|
+
paused: this.#paused,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Force a reset. Used by tests + manual operator action.
|
|
190
|
+
*
|
|
191
|
+
* @stable
|
|
192
|
+
*/
|
|
193
|
+
reset(): void {
|
|
194
|
+
this.#tokens = 0;
|
|
195
|
+
this.#cost = 0;
|
|
196
|
+
this.#paused = false;
|
|
197
|
+
this.#pausedReason = null;
|
|
198
|
+
this.#ledger = [];
|
|
199
|
+
this.#warnedThisWindow.clear();
|
|
200
|
+
this.#bucketStart = bucketStart(this.#now(), this.#resetSemantics);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#handleBreach(
|
|
204
|
+
phase: ConsolidatorPhase,
|
|
205
|
+
resource: 'tokens' | 'cost',
|
|
206
|
+
actual: number,
|
|
207
|
+
budget: number,
|
|
208
|
+
): void {
|
|
209
|
+
if (this.#onExceed === 'throw') {
|
|
210
|
+
throw new BudgetExceededError({ phase, resource, actual, budget });
|
|
211
|
+
}
|
|
212
|
+
if (this.#onExceed === 'pause') {
|
|
213
|
+
this.#paused = true;
|
|
214
|
+
this.#pausedReason = resource === 'tokens' ? 'tokens-exceeded' : 'cost-exceeded';
|
|
215
|
+
}
|
|
216
|
+
if (this.#onExceed === 'log' && !this.#warnedThisWindow.has(resource)) {
|
|
217
|
+
// memory-consolidation-02: the type doc always promised "'log'
|
|
218
|
+
// keeps running with a WARN" - the WARN finally exists. Once per
|
|
219
|
+
// resource per budget window, so a breached daily ceiling is
|
|
220
|
+
// visible without flooding.
|
|
221
|
+
this.#warnedThisWindow.add(resource);
|
|
222
|
+
this.#logger(
|
|
223
|
+
`[graphorin/memory] consolidator ${resource} budget exceeded in phase '${phase}': ` +
|
|
224
|
+
`${resource === 'cost' ? `$${actual.toFixed(4)} > $${budget.toFixed(4)}` : `${actual} > ${budget}`} ` +
|
|
225
|
+
`(onExceed: 'log' - continuing; switch to 'pause'/'throw' to enforce).`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#maybeReset(): void {
|
|
231
|
+
const now = this.#now();
|
|
232
|
+
if (this.#resetSemantics === 'sliding-24h') {
|
|
233
|
+
// Rolling window: drop spends older than the trailing 24h and recompute
|
|
234
|
+
// the totals from what remains, rather than zeroing on every check.
|
|
235
|
+
const cutoff = now - DAY_MS;
|
|
236
|
+
if (
|
|
237
|
+
this.#ledger.length > 0 &&
|
|
238
|
+
this.#ledger[0] !== undefined &&
|
|
239
|
+
this.#ledger[0].at <= cutoff
|
|
240
|
+
) {
|
|
241
|
+
this.#ledger = this.#ledger.filter((entry) => entry.at > cutoff);
|
|
242
|
+
}
|
|
243
|
+
this.#tokens = this.#ledger.reduce((sum, entry) => sum + entry.tokens, 0);
|
|
244
|
+
this.#cost = this.#ledger.reduce((sum, entry) => sum + entry.cost, 0);
|
|
245
|
+
// Auto-unpause once the window has dropped back under both ceilings.
|
|
246
|
+
if (
|
|
247
|
+
this.#paused &&
|
|
248
|
+
this.#tokens <= this.#maxTokensPerDay &&
|
|
249
|
+
this.#cost <= this.#maxCostPerDay
|
|
250
|
+
) {
|
|
251
|
+
this.#paused = false;
|
|
252
|
+
this.#pausedReason = null;
|
|
253
|
+
}
|
|
254
|
+
if (this.#tokens <= this.#maxTokensPerDay && this.#cost <= this.#maxCostPerDay) {
|
|
255
|
+
this.#warnedThisWindow.clear();
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// utc / local: zero the counters when the calendar bucket rolls over.
|
|
260
|
+
const currentBucket = bucketStart(now, this.#resetSemantics);
|
|
261
|
+
if (currentBucket !== this.#bucketStart) {
|
|
262
|
+
this.#bucketStart = currentBucket;
|
|
263
|
+
this.#tokens = 0;
|
|
264
|
+
this.#cost = 0;
|
|
265
|
+
this.#paused = false;
|
|
266
|
+
this.#pausedReason = null;
|
|
267
|
+
this.#warnedThisWindow.clear();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Compute the start of the current bucket (ms epoch) for the
|
|
276
|
+
* supplied reset semantics. UTC midnight is the production default
|
|
277
|
+
* per ADR-038.
|
|
278
|
+
*
|
|
279
|
+
* @internal
|
|
280
|
+
*/
|
|
281
|
+
export function bucketStart(now: number, semantics: 'utc' | 'local' | 'sliding-24h'): number {
|
|
282
|
+
switch (semantics) {
|
|
283
|
+
case 'utc': {
|
|
284
|
+
const d = new Date(now);
|
|
285
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
|
|
286
|
+
}
|
|
287
|
+
case 'local': {
|
|
288
|
+
const d = new Date(now);
|
|
289
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
290
|
+
}
|
|
291
|
+
case 'sliding-24h':
|
|
292
|
+
return now;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Compute the next reset boundary (ms epoch).
|
|
298
|
+
*
|
|
299
|
+
* @internal
|
|
300
|
+
*/
|
|
301
|
+
export function nextBucketStart(
|
|
302
|
+
bucket: number,
|
|
303
|
+
semantics: 'utc' | 'local' | 'sliding-24h',
|
|
304
|
+
): number {
|
|
305
|
+
switch (semantics) {
|
|
306
|
+
case 'utc':
|
|
307
|
+
case 'local':
|
|
308
|
+
return bucket + DAY_MS;
|
|
309
|
+
case 'sliding-24h':
|
|
310
|
+
return bucket + DAY_MS;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ebbinghaus retention curve for the light phase. Pure functions so
|
|
3
|
+
* the formula is testable without touching storage.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default time constant for the retention curve. The plan calls for
|
|
10
|
+
* `tau = 7` days; consumers may override per-consolidator via
|
|
11
|
+
* `decayTauDays`.
|
|
12
|
+
*
|
|
13
|
+
* @stable
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_DECAY_TAU_DAYS = 7;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default archive threshold - facts whose retention falls below this
|
|
19
|
+
* value are soft-archived in the light phase. `0.05` matches the
|
|
20
|
+
* documented forgetting policy for the memory system.
|
|
21
|
+
*
|
|
22
|
+
* @stable
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_DECAY_ARCHIVE_THRESHOLD = 0.05;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute the retention score for a fact given its age, last-access
|
|
28
|
+
* recency, and accumulated `strength`. Larger `strength` means the
|
|
29
|
+
* fact has been accessed more often, so the retention curve flattens
|
|
30
|
+
* - `score = base * exp(-elapsedDays / (tau * strength))`.
|
|
31
|
+
*
|
|
32
|
+
* @stable
|
|
33
|
+
*/
|
|
34
|
+
export function retention(args: {
|
|
35
|
+
/** Reference time (ms epoch). */
|
|
36
|
+
readonly now: number;
|
|
37
|
+
/** Last access timestamp (ms epoch); falls back to `createdAt` when null. */
|
|
38
|
+
readonly lastAccessedAt: number | null;
|
|
39
|
+
/** Creation timestamp (ms epoch). */
|
|
40
|
+
readonly createdAt: number;
|
|
41
|
+
/** Strength multiplier (default 1.0). */
|
|
42
|
+
readonly strength: number;
|
|
43
|
+
/** Time constant in days. */
|
|
44
|
+
readonly tauDays: number;
|
|
45
|
+
}): number {
|
|
46
|
+
const reference = args.lastAccessedAt ?? args.createdAt;
|
|
47
|
+
const elapsedMs = Math.max(0, args.now - reference);
|
|
48
|
+
const elapsedDays = elapsedMs / (1000 * 60 * 60 * 24);
|
|
49
|
+
const tau = Math.max(0.1, args.tauDays * Math.max(0.5, args.strength));
|
|
50
|
+
return Math.exp(-elapsedDays / tau);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `true` when the fact's retention curve has fallen below the
|
|
55
|
+
* archive threshold.
|
|
56
|
+
*
|
|
57
|
+
* @stable
|
|
58
|
+
*/
|
|
59
|
+
export function shouldArchive(args: {
|
|
60
|
+
readonly now: number;
|
|
61
|
+
readonly lastAccessedAt: number | null;
|
|
62
|
+
readonly createdAt: number;
|
|
63
|
+
readonly strength: number;
|
|
64
|
+
readonly tauDays: number;
|
|
65
|
+
readonly archiveThreshold: number;
|
|
66
|
+
}): boolean {
|
|
67
|
+
const score = retention({
|
|
68
|
+
now: args.now,
|
|
69
|
+
lastAccessedAt: args.lastAccessedAt,
|
|
70
|
+
createdAt: args.createdAt,
|
|
71
|
+
strength: args.strength,
|
|
72
|
+
tauDays: args.tauDays,
|
|
73
|
+
});
|
|
74
|
+
return score < args.archiveThreshold;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Neutral importance used when a fact carries no importance hint -
|
|
79
|
+
* the midpoint of the `[0, 1]` range, so an unscored fact contributes
|
|
80
|
+
* an importance factor of exactly `1.0` (`salience === retention`).
|
|
81
|
+
*
|
|
82
|
+
* @stable
|
|
83
|
+
*/
|
|
84
|
+
export const NEUTRAL_IMPORTANCE = 0.5;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Tunable weights for the multi-signal {@link salience} score (X-1).
|
|
88
|
+
* Each weight is the *magnitude* of the corresponding signal's pull on
|
|
89
|
+
* the retention curve; all default to values chosen so the ordering is
|
|
90
|
+
* sensible without ever inverting it.
|
|
91
|
+
*
|
|
92
|
+
* @stable
|
|
93
|
+
*/
|
|
94
|
+
export interface SalienceWeights {
|
|
95
|
+
/**
|
|
96
|
+
* How strongly importance (P1-2) stretches retention. At the default
|
|
97
|
+
* `0.6`, importance `1.0` multiplies retention by `1.3` and importance
|
|
98
|
+
* `0.0` by `0.7`; neutral importance leaves it unchanged.
|
|
99
|
+
*/
|
|
100
|
+
readonly importance: number;
|
|
101
|
+
/**
|
|
102
|
+
* Penalty applied to a **quarantined** fact (P1-4) - the explicit
|
|
103
|
+
* security-risk negative term. At the default `0.7`, a quarantined
|
|
104
|
+
* fact keeps only `0.3` of its retention, so it is evicted first under
|
|
105
|
+
* capacity pressure. Never a hard delete: the fact is archived,
|
|
106
|
+
* recoverable, and quarantine still gates it out of recall meanwhile.
|
|
107
|
+
*/
|
|
108
|
+
readonly quarantine: number;
|
|
109
|
+
/**
|
|
110
|
+
* Mild penalty for a fact with non-first-party provenance (P1-4) -
|
|
111
|
+
* e.g. `'tool'` / `'imported'` content that did not originate with the
|
|
112
|
+
* user. At the default `0.2` such a fact keeps `0.8` of its retention.
|
|
113
|
+
*/
|
|
114
|
+
readonly foreignProvenance: number;
|
|
115
|
+
/**
|
|
116
|
+
* Retrieval-frequency reinforcement (D3) - the use-it-or-lose-it
|
|
117
|
+
* signal. How strongly the monotonic access counter stretches
|
|
118
|
+
* retention: the factor is
|
|
119
|
+
* `1 + weight * min(1, log1p(count) / log1p(saturation))`, saturating
|
|
120
|
+
* at {@link ACCESS_REINFORCEMENT_SATURATION} accesses. At the default
|
|
121
|
+
* `0` the factor is exactly `1` - behaviour is byte-identical until an
|
|
122
|
+
* operator opts in (e.g. `0.3` ⇒ a heavily-used fact keeps up to 1.3x
|
|
123
|
+
* its retention). Optional so existing weight literals stay valid.
|
|
124
|
+
*/
|
|
125
|
+
readonly accessReinforcement?: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Access count at which retrieval-frequency reinforcement saturates
|
|
130
|
+
* (D3). `log1p`-scaled, so the first few accesses matter most and
|
|
131
|
+
* anything past this count contributes the full weight.
|
|
132
|
+
*
|
|
133
|
+
* @stable
|
|
134
|
+
*/
|
|
135
|
+
export const ACCESS_REINFORCEMENT_SATURATION = 32;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Default {@link SalienceWeights}. Chosen so that an active,
|
|
139
|
+
* first-party, unscored fact has `salience === retention` (the X-1
|
|
140
|
+
* change is invisible until a fact carries an importance hint, is
|
|
141
|
+
* quarantined, or has foreign provenance).
|
|
142
|
+
*
|
|
143
|
+
* @stable
|
|
144
|
+
*/
|
|
145
|
+
export const DEFAULT_SALIENCE_WEIGHTS: SalienceWeights = Object.freeze({
|
|
146
|
+
importance: 0.6,
|
|
147
|
+
quarantine: 0.7,
|
|
148
|
+
foreignProvenance: 0.2,
|
|
149
|
+
// D3: reinforcement is opt-in - the default weight 0 keeps salience
|
|
150
|
+
// byte-identical for every fact regardless of its access count.
|
|
151
|
+
accessReinforcement: 0,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Multi-signal salience for capacity-bounded forgetting (X-1). Combines
|
|
156
|
+
* the Ebbinghaus {@link retention} curve (temporal relevance + access
|
|
157
|
+
* frequency via `strength`) with the P1-2 importance hint and a P1-4
|
|
158
|
+
* security-risk negative term:
|
|
159
|
+
*
|
|
160
|
+
* ```
|
|
161
|
+
* salience = retention × importanceFactor × securityFactor
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* Sold as **cost / staleness control, not accuracy**: it only orders
|
|
165
|
+
* what gets archived first when storage is bounded - it never gates
|
|
166
|
+
* recall. With neutral importance, an active fact, and first-party
|
|
167
|
+
* provenance the factors are all `1`, so `salience === retention`.
|
|
168
|
+
*
|
|
169
|
+
* @stable
|
|
170
|
+
*/
|
|
171
|
+
export function salience(args: {
|
|
172
|
+
readonly now: number;
|
|
173
|
+
readonly lastAccessedAt: number | null;
|
|
174
|
+
readonly createdAt: number;
|
|
175
|
+
readonly strength: number;
|
|
176
|
+
readonly tauDays: number;
|
|
177
|
+
/** Importance hint in `[0, 1]`; `null` → {@link NEUTRAL_IMPORTANCE}. */
|
|
178
|
+
readonly importance: number | null;
|
|
179
|
+
/** P1-4: a quarantined fact gets the {@link SalienceWeights.quarantine} penalty. */
|
|
180
|
+
readonly quarantined: boolean;
|
|
181
|
+
/** P1-4: a non-first-party fact gets the {@link SalienceWeights.foreignProvenance} penalty. */
|
|
182
|
+
readonly foreignProvenance: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Monotonic retrieval-access count (D3); `null` / absent ⇒ `0`.
|
|
185
|
+
* Contributes only when {@link SalienceWeights.accessReinforcement}
|
|
186
|
+
* is non-zero (the default `0` keeps salience unchanged).
|
|
187
|
+
*/
|
|
188
|
+
readonly accessCount?: number | null;
|
|
189
|
+
/** Defaults to {@link DEFAULT_SALIENCE_WEIGHTS}. */
|
|
190
|
+
readonly weights?: SalienceWeights;
|
|
191
|
+
}): number {
|
|
192
|
+
const weights = args.weights ?? DEFAULT_SALIENCE_WEIGHTS;
|
|
193
|
+
const base = retention({
|
|
194
|
+
now: args.now,
|
|
195
|
+
lastAccessedAt: args.lastAccessedAt,
|
|
196
|
+
createdAt: args.createdAt,
|
|
197
|
+
strength: args.strength,
|
|
198
|
+
tauDays: args.tauDays,
|
|
199
|
+
});
|
|
200
|
+
const importance = clamp01(args.importance ?? NEUTRAL_IMPORTANCE);
|
|
201
|
+
const importanceFactor = Math.max(0, 1 + weights.importance * (importance - NEUTRAL_IMPORTANCE));
|
|
202
|
+
const securityFactor = args.quarantined
|
|
203
|
+
? Math.max(0, 1 - weights.quarantine)
|
|
204
|
+
: args.foreignProvenance
|
|
205
|
+
? Math.max(0, 1 - weights.foreignProvenance)
|
|
206
|
+
: 1;
|
|
207
|
+
// D3: use-it-or-lose-it reinforcement. log1p-saturating in the access
|
|
208
|
+
// count so early accesses matter most; identity at the default weight 0.
|
|
209
|
+
const reinforcementWeight = weights.accessReinforcement ?? 0;
|
|
210
|
+
const count = Math.max(0, args.accessCount ?? 0);
|
|
211
|
+
const reinforcementFactor =
|
|
212
|
+
reinforcementWeight === 0
|
|
213
|
+
? 1
|
|
214
|
+
: 1 +
|
|
215
|
+
reinforcementWeight *
|
|
216
|
+
Math.min(1, Math.log1p(count) / Math.log1p(ACCESS_REINFORCEMENT_SATURATION));
|
|
217
|
+
return Math.max(0, base * importanceFactor * securityFactor * reinforcementFactor);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Capacity-bounded eviction selector (X-1). Given facts scored by
|
|
222
|
+
* {@link salience}, return the ids of the lowest-salience ones to
|
|
223
|
+
* archive so that at most `capacity` remain. Pure and deterministic:
|
|
224
|
+
* ties break by id so a given batch always evicts the same set.
|
|
225
|
+
*
|
|
226
|
+
* Returns `[]` when the batch already fits (`length <= capacity`).
|
|
227
|
+
* `capacity <= 0` evicts the whole batch.
|
|
228
|
+
*
|
|
229
|
+
* @stable
|
|
230
|
+
*/
|
|
231
|
+
export function selectForCapacityEviction(
|
|
232
|
+
scored: ReadonlyArray<{ readonly id: string; readonly salience: number }>,
|
|
233
|
+
capacity: number,
|
|
234
|
+
): ReadonlyArray<string> {
|
|
235
|
+
const evictCount = scored.length - Math.max(0, capacity);
|
|
236
|
+
if (evictCount <= 0) return [];
|
|
237
|
+
return [...scored]
|
|
238
|
+
.sort((a, b) => a.salience - b.salience || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
|
|
239
|
+
.slice(0, evictCount)
|
|
240
|
+
.map((row) => row.id);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function clamp01(value: number): number {
|
|
244
|
+
if (Number.isNaN(value)) return NEUTRAL_IMPORTANCE;
|
|
245
|
+
return Math.min(1, Math.max(0, value));
|
|
246
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead-letter queue helpers - pure functions used by the standard
|
|
3
|
+
* + deep phases. The actual persistence lives in the storage
|
|
4
|
+
* adapter's `enqueueFailedBatch / claimReadyBatches / ...` surface.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Compute the next retry delay given the failure count + retry
|
|
11
|
+
* policy. Implements full-jitter exponential backoff capped by
|
|
12
|
+
* `maxBackoffMs`.
|
|
13
|
+
*
|
|
14
|
+
* The formula is `delay = random(0, min(maxBackoffMs, baseMs * 2^retryCount))`
|
|
15
|
+
* where `random` is the optional `jitter` callback (defaults to
|
|
16
|
+
* `Math.random()`).
|
|
17
|
+
*
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
export function nextBackoffMs(args: {
|
|
21
|
+
readonly retryCount: number;
|
|
22
|
+
readonly baseMs: number;
|
|
23
|
+
readonly maxMs: number;
|
|
24
|
+
readonly jitter?: () => number;
|
|
25
|
+
}): number {
|
|
26
|
+
const cap = Math.min(args.maxMs, args.baseMs * 2 ** Math.max(0, args.retryCount));
|
|
27
|
+
const jitter = args.jitter ?? Math.random;
|
|
28
|
+
return Math.max(0, Math.floor(cap * jitter()));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Classify a thrown error into the lowercase `error_kind` value
|
|
33
|
+
* recorded against `consolidator_failed_batches`. Used by the phases
|
|
34
|
+
* so the DLQ row carries a stable discriminator instead of free-form
|
|
35
|
+
* prose.
|
|
36
|
+
*
|
|
37
|
+
* @stable
|
|
38
|
+
*/
|
|
39
|
+
export function classifyError(err: unknown): string {
|
|
40
|
+
if (err === null || err === undefined) return 'unknown';
|
|
41
|
+
const message = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
|
|
42
|
+
if (message.includes('rate limit') || message.includes('429')) return 'rate_limit';
|
|
43
|
+
if (message.includes('5xx') || message.includes('500') || message.includes('502')) return '5xx';
|
|
44
|
+
if (message.includes('timeout')) return 'timeout';
|
|
45
|
+
if (message.includes('embedder') || message.includes('embedding')) return 'embedder_failed';
|
|
46
|
+
if (message.includes('invalid') || message.includes('parse')) return 'invalid_response';
|
|
47
|
+
if (message.includes('budget')) return 'budget';
|
|
48
|
+
return 'unknown';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Best-effort error message extraction.
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export function describeError(err: unknown): string {
|
|
57
|
+
if (err === null || err === undefined) return 'unknown error';
|
|
58
|
+
if (err instanceof Error) return err.message;
|
|
59
|
+
if (typeof err === 'string') return err;
|
|
60
|
+
return String(err);
|
|
61
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors raised by the Phase 10c consolidator runtime. Every
|
|
3
|
+
* error carries a stable lowercase `kind` discriminator so downstream
|
|
4
|
+
* tooling can pattern-match without parsing the prose.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { GraphorinMemoryError } from '../errors/index.js';
|
|
10
|
+
import type { ConsolidatorPhase } from './types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Raised when the consolidator's daily budget envelope is exhausted
|
|
14
|
+
* and `onExceed: 'throw'` is in effect.
|
|
15
|
+
*
|
|
16
|
+
* @stable
|
|
17
|
+
*/
|
|
18
|
+
export class BudgetExceededError extends GraphorinMemoryError {
|
|
19
|
+
override readonly name = 'BudgetExceededError';
|
|
20
|
+
readonly kind = 'budget-exceeded' as const;
|
|
21
|
+
readonly phase: ConsolidatorPhase;
|
|
22
|
+
readonly budget: number;
|
|
23
|
+
readonly actual: number;
|
|
24
|
+
readonly resource: 'tokens' | 'cost';
|
|
25
|
+
|
|
26
|
+
constructor(args: {
|
|
27
|
+
phase: ConsolidatorPhase;
|
|
28
|
+
budget: number;
|
|
29
|
+
actual: number;
|
|
30
|
+
resource: 'tokens' | 'cost';
|
|
31
|
+
}) {
|
|
32
|
+
super(
|
|
33
|
+
`[graphorin/memory] consolidator budget exceeded - phase=${args.phase} resource=${args.resource} actual=${args.actual} budget=${args.budget}`,
|
|
34
|
+
{ hint: 'Pass `onExceed: "log"` to keep running, or upgrade the tier ceilings.' },
|
|
35
|
+
);
|
|
36
|
+
this.phase = args.phase;
|
|
37
|
+
this.budget = args.budget;
|
|
38
|
+
this.actual = args.actual;
|
|
39
|
+
this.resource = args.resource;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Raised when `tier: 'custom'` is selected without explicit
|
|
45
|
+
* `ceilings.maxTokensPerDay` / `ceilings.maxCostPerDay`. Custom is
|
|
46
|
+
* an explicit-only escape hatch.
|
|
47
|
+
*
|
|
48
|
+
* @stable
|
|
49
|
+
*/
|
|
50
|
+
export class CustomTierMisconfiguredError extends GraphorinMemoryError {
|
|
51
|
+
override readonly name = 'CustomTierMisconfiguredError';
|
|
52
|
+
readonly kind = 'custom-tier-misconfigured' as const;
|
|
53
|
+
readonly missing: ReadonlyArray<string>;
|
|
54
|
+
|
|
55
|
+
constructor(missing: ReadonlyArray<string>) {
|
|
56
|
+
super(
|
|
57
|
+
`[graphorin/memory] tier='custom' requires explicit ceilings - missing: ${missing.join(', ')}`,
|
|
58
|
+
{
|
|
59
|
+
hint: 'Set `ceilings.maxTokensPerDay` + `ceilings.maxCostPerDay` (and `cheapModel` / `deepModel` if those phases are enabled).',
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
this.missing = Object.freeze([...missing]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Raised when the standard / deep phase attempts an LLM call without
|
|
68
|
+
* a configured provider. Surfaces as a typed failure that the DLQ
|
|
69
|
+
* pipeline can attribute correctly.
|
|
70
|
+
*
|
|
71
|
+
* @stable
|
|
72
|
+
*/
|
|
73
|
+
export class ProviderNotConfiguredError extends GraphorinMemoryError {
|
|
74
|
+
override readonly name = 'ProviderNotConfiguredError';
|
|
75
|
+
readonly kind = 'provider-not-configured' as const;
|
|
76
|
+
readonly phase: ConsolidatorPhase;
|
|
77
|
+
|
|
78
|
+
constructor(phase: ConsolidatorPhase) {
|
|
79
|
+
super(
|
|
80
|
+
`[graphorin/memory] consolidator phase '${phase}' requires a provider but none was supplied.`,
|
|
81
|
+
{
|
|
82
|
+
hint: 'Pass `provider` to createConsolidator({...}) or pin tier="free" to skip LLM phases.',
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
this.phase = phase;
|
|
86
|
+
}
|
|
87
|
+
}
|