@remnic/core 9.3.727 → 9.3.728
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/dist/access-boundary.js +6 -6
- package/dist/access-cli.js +11 -11
- package/dist/access-http.js +10 -10
- package/dist/access-mcp.js +9 -9
- package/dist/access-operations-batch.js +7 -7
- package/dist/access-operations.d.ts +1 -1
- package/dist/access-operations.js +8 -8
- package/dist/access-schema.d.ts +4 -4
- package/dist/access-service.js +5 -5
- package/dist/capabilities.d.ts +43 -1
- package/dist/capabilities.js +5 -3
- package/dist/{chunk-DVRHLN2O.js → chunk-3RCBIOOJ.js} +18 -2
- package/dist/{chunk-DVRHLN2O.js.map → chunk-3RCBIOOJ.js.map} +1 -1
- package/dist/{chunk-R7IYU5DZ.js → chunk-4ESAR77N.js} +2 -2
- package/dist/{chunk-SNKPUJHR.js → chunk-76NHR6ZL.js} +3 -3
- package/dist/{chunk-6PY5YJAV.js → chunk-BQCHEV7O.js} +8 -3
- package/dist/{chunk-6PY5YJAV.js.map → chunk-BQCHEV7O.js.map} +1 -1
- package/dist/{chunk-ZA2S2VLL.js → chunk-C2PG7IEH.js} +5 -2
- package/dist/chunk-C2PG7IEH.js.map +1 -0
- package/dist/{chunk-MJIHJ7OG.js → chunk-H5BWGCRZ.js} +11 -10
- package/dist/chunk-H5BWGCRZ.js.map +1 -0
- package/dist/{chunk-N3VNUM4Z.js → chunk-HGNOCPZX.js} +4 -4
- package/dist/{chunk-CSRXQ6NB.js → chunk-I5REUWLH.js} +10 -4
- package/dist/chunk-I5REUWLH.js.map +1 -0
- package/dist/{chunk-NSA5ZRUL.js → chunk-M4AYVEWS.js} +3 -3
- package/dist/{chunk-OEEW4KJA.js → chunk-O6YH5F47.js} +3 -3
- package/dist/{chunk-XPHVAOBT.js → chunk-QNEUZIAH.js} +45 -36
- package/dist/chunk-QNEUZIAH.js.map +1 -0
- package/dist/{chunk-27QQC7ZX.js → chunk-UM5AWJNQ.js} +6 -5
- package/dist/chunk-UM5AWJNQ.js.map +1 -0
- package/dist/{chunk-2KAYTPPT.js → chunk-UTHQPSRC.js} +2 -2
- package/dist/{chunk-CTOQEZSN.js → chunk-V4GNSDFH.js} +2 -2
- package/dist/{chunk-EHISUJFN.js → chunk-WAS3NV46.js} +6 -3
- package/dist/chunk-WAS3NV46.js.map +1 -0
- package/dist/{chunk-DKJCRGWM.js → chunk-YGIQORJ3.js} +2 -2
- package/dist/cli.js +13 -13
- package/dist/embedding-fallback.js +2 -1
- package/dist/extraction.js +2 -1
- package/dist/{first-start-migration-PG5HBC3K.js → first-start-migration-22LPFHD5.js} +6 -2
- package/dist/first-start-migration-22LPFHD5.js.map +1 -0
- package/dist/index.js +16 -16
- package/dist/namespaces/migrate.js +5 -4
- package/dist/namespaces/search.js +4 -3
- package/dist/operator-toolkit.js +6 -6
- package/dist/orchestrator.js +7 -7
- package/dist/schemas.d.ts +22 -22
- package/dist/search/embed-helper.js +2 -1
- package/dist/search/factory.js +3 -2
- package/dist/search/index.js +3 -2
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +2 -2
- package/src/access-service.ts +2 -1
- package/src/capabilities.test.ts +155 -0
- package/src/capabilities.ts +91 -0
- package/src/chat/chat-engine.test.ts +39 -1
- package/src/chat/chat-session.ts +25 -2
- package/src/cli.ts +3 -2
- package/src/embedding-fallback.ts +2 -1
- package/src/extraction.ts +7 -3
- package/src/maintenance/first-start-migration.ts +3 -1
- package/src/orchestrator.ts +38 -26
- package/src/search/embed-helper.ts +3 -2
- package/dist/chunk-27QQC7ZX.js.map +0 -1
- package/dist/chunk-CSRXQ6NB.js.map +0 -1
- package/dist/chunk-EHISUJFN.js.map +0 -1
- package/dist/chunk-MJIHJ7OG.js.map +0 -1
- package/dist/chunk-XPHVAOBT.js.map +0 -1
- package/dist/chunk-ZA2S2VLL.js.map +0 -1
- package/dist/first-start-migration-PG5HBC3K.js.map +0 -1
- /package/dist/{chunk-R7IYU5DZ.js.map → chunk-4ESAR77N.js.map} +0 -0
- /package/dist/{chunk-SNKPUJHR.js.map → chunk-76NHR6ZL.js.map} +0 -0
- /package/dist/{chunk-N3VNUM4Z.js.map → chunk-HGNOCPZX.js.map} +0 -0
- /package/dist/{chunk-NSA5ZRUL.js.map → chunk-M4AYVEWS.js.map} +0 -0
- /package/dist/{chunk-OEEW4KJA.js.map → chunk-O6YH5F47.js.map} +0 -0
- /package/dist/{chunk-2KAYTPPT.js.map → chunk-UTHQPSRC.js.map} +0 -0
- /package/dist/{chunk-CTOQEZSN.js.map → chunk-V4GNSDFH.js.map} +0 -0
- /package/dist/{chunk-DKJCRGWM.js.map → chunk-YGIQORJ3.js.map} +0 -0
package/src/capabilities.ts
CHANGED
|
@@ -203,3 +203,94 @@ export function resolveAccessSetupCapabilities(
|
|
|
203
203
|
recallAuditAnomalyDetection: config.recallAuditAnomalyDetectionEnabled === true,
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Memory-lifecycle capability set (issue #1523 batch 3).
|
|
209
|
+
//
|
|
210
|
+
// The flags below gate memory WRITE/EXTRACTION (extraction judge, scope
|
|
211
|
+
// classification, dedupe, telemetry prefilter), temporal supersession of
|
|
212
|
+
// stored facts, and the lifecycle policy pass (promotion / decay / stale
|
|
213
|
+
// filtering). They are read on the persistExtraction write path, the recall
|
|
214
|
+
// candidate-filter path, and the maintenance / lifecycle-pass path — so, like
|
|
215
|
+
// the graph-construction set above, they get their own projection resolved
|
|
216
|
+
// ONCE at each operation entry that reads them (never re-derived from raw
|
|
217
|
+
// config mid-operation — the gate-divergence defect class #1523 targets).
|
|
218
|
+
//
|
|
219
|
+
// Every field projects from an already-resolved PluginConfig boolean (defaults
|
|
220
|
+
// are applied at the config-parse boundary, rule 36), so the resolver is a
|
|
221
|
+
// pure projection — no `!== false` / `=== true` re-coercion here.
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Frozen projection of memory-lifecycle feature gates (issue #1523 batch 3).
|
|
226
|
+
*
|
|
227
|
+
* Resolved once per write / recall-filter / maintenance operation and threaded
|
|
228
|
+
* down. Composition lives ONLY in {@link resolveMemoryLifecycleCapabilities}.
|
|
229
|
+
*/
|
|
230
|
+
export interface MemoryLifecycleCapabilitySet {
|
|
231
|
+
/** `temporalSupersessionEnabled` — supersede stale structured facts on write, filter on recall. */
|
|
232
|
+
readonly temporalSupersession: boolean;
|
|
233
|
+
/** `temporalMemoryTreeEnabled` — temporal-memory-tree build + recall tier. */
|
|
234
|
+
readonly temporalMemoryTree: boolean;
|
|
235
|
+
/** `lifecyclePolicyEnabled` — lifecycle promotion / decay metadata pass. */
|
|
236
|
+
readonly lifecyclePolicy: boolean;
|
|
237
|
+
/** `lifecycleFilterStaleEnabled` — filter stale-lifecycle memories from recall. */
|
|
238
|
+
readonly lifecycleFilterStale: boolean;
|
|
239
|
+
/** `lifecycleMetricsEnabled` — emit lifecycle-pass metrics. */
|
|
240
|
+
readonly lifecycleMetrics: boolean;
|
|
241
|
+
/** `extractionScopeClassificationEnabled` — LLM classifies each fact's scope. */
|
|
242
|
+
readonly extractionScopeClassification: boolean;
|
|
243
|
+
/** `extractionJudgeEnabled` — LLM-as-judge fact-worthiness gate. */
|
|
244
|
+
readonly extractionJudge: boolean;
|
|
245
|
+
/** `extractionDedupeEnabled` — dedupe against recent extractions. */
|
|
246
|
+
readonly extractionDedupe: boolean;
|
|
247
|
+
/** `extractionTelemetryPrefilterEnabled` — skip mechanical-telemetry transcripts. */
|
|
248
|
+
readonly extractionTelemetryPrefilter: boolean;
|
|
249
|
+
/** `extractionJudgeTelemetryEnabled` — collect judge verdict telemetry. */
|
|
250
|
+
readonly extractionJudgeTelemetry: boolean;
|
|
251
|
+
/** `embeddingFallbackEnabled` — semantic-dedup / archive-search embedding fallback. */
|
|
252
|
+
readonly embeddingFallback: boolean;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Config projection consumed by {@link resolveMemoryLifecycleCapabilities}.
|
|
257
|
+
*/
|
|
258
|
+
export type MemoryLifecycleConfigProjection = Pick<
|
|
259
|
+
PluginConfig,
|
|
260
|
+
| "temporalSupersessionEnabled"
|
|
261
|
+
| "temporalMemoryTreeEnabled"
|
|
262
|
+
| "lifecyclePolicyEnabled"
|
|
263
|
+
| "lifecycleFilterStaleEnabled"
|
|
264
|
+
| "lifecycleMetricsEnabled"
|
|
265
|
+
| "extractionScopeClassificationEnabled"
|
|
266
|
+
| "extractionJudgeEnabled"
|
|
267
|
+
| "extractionDedupeEnabled"
|
|
268
|
+
| "extractionTelemetryPrefilterEnabled"
|
|
269
|
+
| "extractionJudgeTelemetryEnabled"
|
|
270
|
+
| "embeddingFallbackEnabled"
|
|
271
|
+
>;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Resolve the {@link MemoryLifecycleCapabilitySet} from parsed config.
|
|
275
|
+
*
|
|
276
|
+
* Call this ONCE per write / recall-filter / maintenance operation entry and
|
|
277
|
+
* thread the result down — exactly the pattern {@link resolveCapabilities}
|
|
278
|
+
* established for recall.
|
|
279
|
+
*/
|
|
280
|
+
export function resolveMemoryLifecycleCapabilities(
|
|
281
|
+
config: MemoryLifecycleConfigProjection,
|
|
282
|
+
): MemoryLifecycleCapabilitySet {
|
|
283
|
+
return Object.freeze({
|
|
284
|
+
temporalSupersession: config.temporalSupersessionEnabled,
|
|
285
|
+
temporalMemoryTree: config.temporalMemoryTreeEnabled,
|
|
286
|
+
lifecyclePolicy: config.lifecyclePolicyEnabled,
|
|
287
|
+
lifecycleFilterStale: config.lifecycleFilterStaleEnabled,
|
|
288
|
+
lifecycleMetrics: config.lifecycleMetricsEnabled,
|
|
289
|
+
extractionScopeClassification: config.extractionScopeClassificationEnabled,
|
|
290
|
+
extractionJudge: config.extractionJudgeEnabled,
|
|
291
|
+
extractionDedupe: config.extractionDedupeEnabled,
|
|
292
|
+
extractionTelemetryPrefilter: config.extractionTelemetryPrefilterEnabled,
|
|
293
|
+
extractionJudgeTelemetry: config.extractionJudgeTelemetryEnabled,
|
|
294
|
+
embeddingFallback: config.embeddingFallbackEnabled,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { strict as assert } from "node:assert";
|
|
10
|
-
import { mkdir, rm, readFile, utimes, unlink } from "node:fs/promises";
|
|
10
|
+
import { mkdir, rm, readFile, utimes, unlink, appendFile } from "node:fs/promises";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { test } from "node:test";
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
sessionBelongsToPrincipal,
|
|
23
23
|
chatSessionFile,
|
|
24
24
|
cleanupExpiredChatSessions,
|
|
25
|
+
__resetTranscriptSeqForTest,
|
|
25
26
|
} from "./chat-session.js";
|
|
26
27
|
import type { ChatSessionState } from "./chat-types.js";
|
|
27
28
|
import type { ChatToolExecutor } from "./chat-engine.js";
|
|
@@ -335,3 +336,40 @@ test("appendTranscriptEntry refuses to recreate a swept session (chat_session_ex
|
|
|
335
336
|
/chat_session_expired/,
|
|
336
337
|
);
|
|
337
338
|
});
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
test("loadChatSession raises the seq counter above the on-disk max on backward clock skew (issue #1718)", async () => {
|
|
342
|
+
const dir = await makeTempDir();
|
|
343
|
+
const session = await createChatSession(dir, { principal: "skew" });
|
|
344
|
+
// Simulate a previous process that ran with a forward-skewed clock: append a
|
|
345
|
+
// transcript line whose seq is far in the future relative to the current
|
|
346
|
+
// Date.now(). A later restart with a normal (or backward-stepped) clock must
|
|
347
|
+
// still issue seqs strictly greater than this persisted line.
|
|
348
|
+
const futureSeq = Date.now() + 10_000_000;
|
|
349
|
+
await appendFile(
|
|
350
|
+
chatSessionFile(dir, session.id),
|
|
351
|
+
JSON.stringify({
|
|
352
|
+
seq: futureSeq,
|
|
353
|
+
ts: new Date().toISOString(),
|
|
354
|
+
role: "user",
|
|
355
|
+
content: "future-dated line from a forward-skewed prior process",
|
|
356
|
+
}) + "\n",
|
|
357
|
+
"utf8",
|
|
358
|
+
);
|
|
359
|
+
// Simulate a process restart whose module-level counter starts below the
|
|
360
|
+
// persisted on-disk seq (the backward-clock-skew window).
|
|
361
|
+
__resetTranscriptSeqForTest();
|
|
362
|
+
// Resume the session: loadChatSession must raise the counter above the
|
|
363
|
+
// on-disk max so the next append is strictly greater, regardless of the
|
|
364
|
+
// current Date.now().
|
|
365
|
+
const loaded = await loadChatSession(dir, session.id);
|
|
366
|
+
assert.ok(loaded, "session must load");
|
|
367
|
+
const next = await appendTranscriptEntry(dir, session.id, {
|
|
368
|
+
role: "assistant",
|
|
369
|
+
content: "resumed after backward clock skew",
|
|
370
|
+
});
|
|
371
|
+
assert.ok(
|
|
372
|
+
next.seq > futureSeq,
|
|
373
|
+
`seq ${next.seq} must exceed the on-disk max ${futureSeq} after a backward clock skew (issue #1718)`,
|
|
374
|
+
);
|
|
375
|
+
});
|
package/src/chat/chat-session.ts
CHANGED
|
@@ -50,8 +50,10 @@ export function chatSessionFile(memoryDir: string, chatSessionId: string): strin
|
|
|
50
50
|
* incremented when two appends land in the same millisecond, so `seq` is
|
|
51
51
|
* strictly increasing within a process (review thread: `Date.now()` alone is
|
|
52
52
|
* not monotonic — same-ms entries would share a seq and break the SSE
|
|
53
|
-
* reconnect dedup-by-seq contract). Across process restarts
|
|
54
|
-
*
|
|
53
|
+
* reconnect dedup-by-seq contract). Across process restarts the counter is
|
|
54
|
+
* raised above the on-disk max by `loadChatSession` (issue #1718), so a
|
|
55
|
+
* backward clock step (NTP, VM migration, manual change) cannot re-seed it
|
|
56
|
+
* below lines already persisted for a resumed session.
|
|
55
57
|
*/
|
|
56
58
|
let lastTranscriptSeq = 0;
|
|
57
59
|
|
|
@@ -61,6 +63,14 @@ function nextTranscriptSeq(): number {
|
|
|
61
63
|
return lastTranscriptSeq;
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Test-only: reset the module-level transcript seq counter so a regression
|
|
68
|
+
* test can deterministically simulate a fresh process restart (issue #1718).
|
|
69
|
+
*/
|
|
70
|
+
export function __resetTranscriptSeqForTest(): void {
|
|
71
|
+
lastTranscriptSeq = 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
/**
|
|
65
75
|
* Create a new chat session. The session binds the caller's principal and
|
|
66
76
|
* sessionKey at creation (rule 42); every tool call flows through
|
|
@@ -175,6 +185,19 @@ export async function loadChatSession(
|
|
|
175
185
|
// tolerated, never crash the engine).
|
|
176
186
|
}
|
|
177
187
|
}
|
|
188
|
+
// Issue #1718 — backward-clock-skew robustness: raise the module-level seq
|
|
189
|
+
// counter so the next append is strictly greater than any line already on
|
|
190
|
+
// disk for this session, regardless of clock direction across restarts
|
|
191
|
+
// (NTP step, VM migration, manual change). Without this, a backward clock
|
|
192
|
+
// skew would re-seed the counter below the on-disk max and the SSE
|
|
193
|
+
// reconnect dedup-by-seq contract could drop freshly-appended entries as
|
|
194
|
+
// duplicates. The transcript is already fully parsed above, so this is a
|
|
195
|
+
// single pass over the in-memory array.
|
|
196
|
+
for (const e of transcript) {
|
|
197
|
+
if (typeof e.seq === "number" && e.seq + 1 > lastTranscriptSeq) {
|
|
198
|
+
lastTranscriptSeq = e.seq + 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
178
201
|
return {
|
|
179
202
|
id: chatSessionId,
|
|
180
203
|
principal,
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { access, lstat, readFile, readdir, realpath, unlink } from "node:fs/prom
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import type { Readable, Writable } from "node:stream";
|
|
6
6
|
import type { Orchestrator } from "./orchestrator.js";
|
|
7
|
+
import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
|
|
7
8
|
import { ThreadingManager } from "./threading.js";
|
|
8
9
|
import { utcDayRange } from "./transcript.js";
|
|
9
10
|
import { runWearablesCliCommand } from "./wearables/cli.js";
|
|
@@ -3728,7 +3729,7 @@ export function registerCli(
|
|
|
3728
3729
|
console.log("=== Extraction Judge Verdict Stats ===\n");
|
|
3729
3730
|
console.log(`Total verdicts: ${stats.total}`);
|
|
3730
3731
|
if (stats.total === 0) {
|
|
3731
|
-
if (!orchestrator.config.
|
|
3732
|
+
if (!resolveMemoryLifecycleCapabilities(orchestrator.config).extractionJudgeTelemetry) {
|
|
3732
3733
|
console.log(
|
|
3733
3734
|
"\nNote: extractionJudgeTelemetryEnabled is OFF. Enable it in plugin config to collect verdict telemetry.",
|
|
3734
3735
|
);
|
|
@@ -8541,7 +8542,7 @@ export function registerCli(
|
|
|
8541
8542
|
config: orchestrator.config,
|
|
8542
8543
|
memoryDir: orchestrator.config.memoryDir,
|
|
8543
8544
|
embeddingLookupFactory: (storage) => {
|
|
8544
|
-
if (!orchestrator.config.
|
|
8545
|
+
if (!resolveMemoryLifecycleCapabilities(orchestrator.config).embeddingFallback) return undefined;
|
|
8545
8546
|
return async (content: string, limit: number) => {
|
|
8546
8547
|
try {
|
|
8547
8548
|
return await orchestrator.semanticDedupLookup(content, limit, storage);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { log } from "./logger.js";
|
|
4
|
+
import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
|
|
4
5
|
import { readEnvVar } from "./runtime/env.js";
|
|
5
6
|
import type { PluginConfig } from "./types.js";
|
|
6
7
|
import {
|
|
@@ -397,7 +398,7 @@ export class EmbeddingFallback {
|
|
|
397
398
|
private async resolveProvider(
|
|
398
399
|
options: { includeHost?: boolean } = {},
|
|
399
400
|
): Promise<ProviderConfig | null> {
|
|
400
|
-
if (!this.config.
|
|
401
|
+
if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return null;
|
|
401
402
|
|
|
402
403
|
if (
|
|
403
404
|
options.includeHost !== false &&
|
package/src/extraction.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { normalizeProcedureSteps } from "./procedural/procedure-types.js";
|
|
|
38
38
|
import { normalizeReasoningTrace } from "./reasoning-trace-types.js";
|
|
39
39
|
import { looksLikeMechanicalTelemetryTranscript } from "./telemetry-transcript.js";
|
|
40
40
|
import { buildFactProvenance, type ProvenanceTurnInput } from "./provenance.js";
|
|
41
|
+
import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
|
|
41
42
|
|
|
42
43
|
type ExtractionQuestion = ExtractionResult["questions"][number];
|
|
43
44
|
type ExtractedFactResult = ExtractionResult["facts"][number];
|
|
@@ -1055,6 +1056,7 @@ export class ExtractionEngine {
|
|
|
1055
1056
|
}
|
|
1056
1057
|
|
|
1057
1058
|
async extract(turns: BufferTurn[], existingEntities?: string[]): Promise<ExtractionResult> {
|
|
1059
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
1058
1060
|
|
|
1059
1061
|
// Guard: skip if buffer is empty or all turns are whitespace-only
|
|
1060
1062
|
const substantiveTurns = turns.filter((t) => t.content.trim().length > 0);
|
|
@@ -1083,7 +1085,7 @@ export class ExtractionEngine {
|
|
|
1083
1085
|
return { facts: [], profileUpdates: [], entities: [], questions: [] };
|
|
1084
1086
|
}
|
|
1085
1087
|
if (
|
|
1086
|
-
|
|
1088
|
+
lifecycleCaps.extractionTelemetryPrefilter &&
|
|
1087
1089
|
looksLikeMechanicalTelemetryTranscript(conversation)
|
|
1088
1090
|
) {
|
|
1089
1091
|
log.debug("extraction skipped — mechanical action/state telemetry without durable-memory cues");
|
|
@@ -1331,6 +1333,7 @@ export class ExtractionEngine {
|
|
|
1331
1333
|
* Uses a minimal prompt to fit within local model context limits (typically 4k-8k).
|
|
1332
1334
|
*/
|
|
1333
1335
|
private async extractWithLocalLlm(conversation: string, existingEntities?: string[]): Promise<ExtractionResult | null> {
|
|
1336
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
1334
1337
|
log.debug(
|
|
1335
1338
|
`extractWithLocalLlm: starting extraction, localLlmEnabled=${this.shouldUseLocalLlm}, model=${this.config.localLlmModel}`,
|
|
1336
1339
|
);
|
|
@@ -1406,7 +1409,7 @@ These are durable insights - capture them:
|
|
|
1406
1409
|
- CRITICAL: Use canonical hyphenated entity names (e.g., "jane-doe" not "janedoe")
|
|
1407
1410
|
- CRITICAL: NEVER extract the same fact twice - check for duplicates before adding to facts array
|
|
1408
1411
|
- CRITICAL: NEVER extract cron job schedules, automation configurations, or system monitoring details (these are operational noise)
|
|
1409
|
-
- If uncertain about relevance, prefer NOT extracting${
|
|
1412
|
+
- If uncertain about relevance, prefer NOT extracting${lifecycleCaps.extractionScopeClassification ? `
|
|
1410
1413
|
- For each fact, set "scope" to "global" (cross-project knowledge: framework bugs, library behavior, user preferences, tool configs, general patterns) or "project" (codebase-specific: file paths, env configs, deployment details, project workarounds). When in doubt, prefer "project".` : ""}
|
|
1411
1414
|
|
|
1412
1415
|
=== Structured Attributes ===
|
|
@@ -1645,6 +1648,7 @@ Omit "eventTime" when the fact has no explicit temporal anchor — do NOT guess
|
|
|
1645
1648
|
* Build extraction instructions shared between local and cloud LLM.
|
|
1646
1649
|
*/
|
|
1647
1650
|
private buildExtractionInstructions(existingEntities?: string[]): string {
|
|
1651
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
1648
1652
|
return `You are a memory extraction system. Analyze the following conversation and extract durable, reusable memories.
|
|
1649
1653
|
|
|
1650
1654
|
Memory categories:
|
|
@@ -1681,7 +1685,7 @@ Rules:
|
|
|
1681
1685
|
* Inferred (0.40-0.69): Pattern recognition — reasonable guess from limited evidence
|
|
1682
1686
|
* Speculative (0.00-0.39): Tentative hypothesis — weak signal, needs future confirmation. Speculative memories auto-expire after 30 days if not confirmed.${this.config.provenance?.enabled ? `
|
|
1683
1687
|
- Source quotes: For each fact, include a "quote" field containing the EXACT verbatim words from the conversation that support the fact. Copy a contiguous span from a single speaker turn (not a paraphrase, not a summary). Cap at ~300 characters. This grounds every memory in the literal utterance that created it.` : ""}
|
|
1684
|
-
- For commitments: include any deadline or timeframe mentioned${
|
|
1688
|
+
- For commitments: include any deadline or timeframe mentioned${lifecycleCaps.extractionScopeClassification ? `
|
|
1685
1689
|
|
|
1686
1690
|
Scope classification:
|
|
1687
1691
|
For each fact, set "scope" to one of:
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { access, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
21
|
+
import { resolveMemoryLifecycleCapabilities } from "../capabilities.js";
|
|
21
22
|
import type { StorageManager } from "../storage.js";
|
|
22
23
|
import type { PluginConfig } from "../types.js";
|
|
23
24
|
import {
|
|
@@ -179,6 +180,7 @@ export async function runFirstStartMigration(
|
|
|
179
180
|
signal,
|
|
180
181
|
} = options;
|
|
181
182
|
const now = (options.now ?? (() => new Date()))();
|
|
183
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(config);
|
|
182
184
|
const abortedResult = (candidateCount = 0, demotedCount = 0, failureCount = 0): FirstStartMigrationResult => ({
|
|
183
185
|
skipped: candidateCount === 0 && demotedCount === 0 && failureCount === 0,
|
|
184
186
|
skipReason: candidateCount === 0 && demotedCount === 0 && failureCount === 0 ? "aborted" : undefined,
|
|
@@ -193,7 +195,7 @@ export async function runFirstStartMigration(
|
|
|
193
195
|
return abortedResult();
|
|
194
196
|
}
|
|
195
197
|
|
|
196
|
-
if (!
|
|
198
|
+
if (!lifecycleCaps.lifecyclePolicy) {
|
|
197
199
|
return {
|
|
198
200
|
skipped: true,
|
|
199
201
|
skipReason: "lifecyclePolicyEnabled is false",
|
package/src/orchestrator.ts
CHANGED
|
@@ -269,8 +269,10 @@ import { tryDirectAnswer, type DirectAnswerSources } from "./direct-answer-wirin
|
|
|
269
269
|
import {
|
|
270
270
|
resolveCapabilities,
|
|
271
271
|
resolveGraphConstructionCapabilities,
|
|
272
|
+
resolveMemoryLifecycleCapabilities,
|
|
272
273
|
type CapabilitySet,
|
|
273
274
|
type GraphConstructionCapabilitySet,
|
|
275
|
+
type MemoryLifecycleCapabilitySet,
|
|
274
276
|
} from "./capabilities.js";
|
|
275
277
|
import { DEFAULT_TAXONOMY } from "./taxonomy/index.js";
|
|
276
278
|
import {
|
|
@@ -3094,9 +3096,10 @@ export class Orchestrator {
|
|
|
3094
3096
|
);
|
|
3095
3097
|
// BoxBuilders are created per-namespace on first use in runExtraction().
|
|
3096
3098
|
|
|
3099
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(config);
|
|
3097
3100
|
// Temporal Memory Tree (v8.2) — lazy build during consolidation
|
|
3098
3101
|
this.tmtBuilder = new TmtBuilder(config.memoryDir, {
|
|
3099
|
-
temporalMemoryTreeEnabled:
|
|
3102
|
+
temporalMemoryTreeEnabled: lifecycleCaps.temporalMemoryTree,
|
|
3100
3103
|
tmtHourlyMinMemories: config.tmtHourlyMinMemories,
|
|
3101
3104
|
tmtSummaryMaxTokens: config.tmtSummaryMaxTokens,
|
|
3102
3105
|
});
|
|
@@ -3654,6 +3657,7 @@ export class Orchestrator {
|
|
|
3654
3657
|
}
|
|
3655
3658
|
|
|
3656
3659
|
private async deferredInitialize(signal: AbortSignal): Promise<void> {
|
|
3660
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
3657
3661
|
|
|
3658
3662
|
// Sync QMD index with current disk state so recall finds recently-written
|
|
3659
3663
|
// facts. Without this, the index stays stale from the last extraction-
|
|
@@ -3707,7 +3711,7 @@ export class Orchestrator {
|
|
|
3707
3711
|
}),
|
|
3708
3712
|
);
|
|
3709
3713
|
}
|
|
3710
|
-
if (this.config.
|
|
3714
|
+
if (resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) {
|
|
3711
3715
|
warmupPromises.push(
|
|
3712
3716
|
this.embeddingFallback
|
|
3713
3717
|
.isAvailable()
|
|
@@ -3792,7 +3796,7 @@ export class Orchestrator {
|
|
|
3792
3796
|
// sweep (capped at 50 demotions) so the hot tier isn't flooded on the
|
|
3793
3797
|
// first real cron pass. Non-fatal — a failure here must not break init.
|
|
3794
3798
|
if (signal.aborted) return;
|
|
3795
|
-
if (
|
|
3799
|
+
if (lifecycleCaps.lifecyclePolicy && this.config.qmdTierMigrationEnabled) {
|
|
3796
3800
|
try {
|
|
3797
3801
|
const { runFirstStartMigration } = await import(
|
|
3798
3802
|
"./maintenance/first-start-migration.js"
|
|
@@ -4117,6 +4121,7 @@ export class Orchestrator {
|
|
|
4117
4121
|
* default storage.
|
|
4118
4122
|
*/
|
|
4119
4123
|
async runLifecyclePolicyFanout(): Promise<NamespaceMaintenanceSummary> {
|
|
4124
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
4120
4125
|
return this.runNamespaceMaintenanceFanoutForJob(
|
|
4121
4126
|
"lifecycle",
|
|
4122
4127
|
async (ctx) => {
|
|
@@ -4125,7 +4130,7 @@ export class Orchestrator {
|
|
|
4125
4130
|
const assessed = await this.runLifecyclePolicyPass(corpus, storage);
|
|
4126
4131
|
return { itemCount: assessed };
|
|
4127
4132
|
},
|
|
4128
|
-
{ enabled:
|
|
4133
|
+
{ enabled: lifecycleCaps.lifecyclePolicy },
|
|
4129
4134
|
);
|
|
4130
4135
|
}
|
|
4131
4136
|
|
|
@@ -7438,6 +7443,7 @@ export class Orchestrator {
|
|
|
7438
7443
|
options: RecallInvocationOptions = {},
|
|
7439
7444
|
caps: CapabilitySet = resolveCapabilities(this.config),
|
|
7440
7445
|
graphCaps: GraphConstructionCapabilitySet = resolveGraphConstructionCapabilities(this.config),
|
|
7446
|
+
lifecycleCaps: MemoryLifecycleCapabilitySet = resolveMemoryLifecycleCapabilities(this.config),
|
|
7441
7447
|
): Promise<string> {
|
|
7442
7448
|
const recallStart = Date.now();
|
|
7443
7449
|
// Backend degradations observed by this recall's QMD searches (#1536):
|
|
@@ -10845,9 +10851,9 @@ export class Orchestrator {
|
|
|
10845
10851
|
if (
|
|
10846
10852
|
this.isRecallSectionEnabled(
|
|
10847
10853
|
"temporal-memory-tree",
|
|
10848
|
-
|
|
10854
|
+
lifecycleCaps.temporalMemoryTree,
|
|
10849
10855
|
) &&
|
|
10850
|
-
|
|
10856
|
+
lifecycleCaps.temporalMemoryTree &&
|
|
10851
10857
|
recallMode !== "minimal" &&
|
|
10852
10858
|
(recallMode as RecallPlanMode) !== "no_recall"
|
|
10853
10859
|
) {
|
|
@@ -11707,7 +11713,7 @@ export class Orchestrator {
|
|
|
11707
11713
|
// Using the shared gate fixes both Finding 2 and Finding 3 from
|
|
11708
11714
|
// PR #402 (round 6).
|
|
11709
11715
|
const supersessionOptions = {
|
|
11710
|
-
enabled:
|
|
11716
|
+
enabled: lifecycleCaps.temporalSupersession,
|
|
11711
11717
|
includeInRecall: this.config.temporalSupersessionIncludeInRecall,
|
|
11712
11718
|
};
|
|
11713
11719
|
// Cursor Medium on PR #713: when `as_of` is active, the
|
|
@@ -13231,7 +13237,8 @@ export class Orchestrator {
|
|
|
13231
13237
|
turns: BufferTurn[],
|
|
13232
13238
|
options: { commit?: boolean; bufferKey?: string } = {},
|
|
13233
13239
|
): boolean {
|
|
13234
|
-
|
|
13240
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
13241
|
+
if (!lifecycleCaps.extractionDedupe) return true;
|
|
13235
13242
|
if (!Array.isArray(turns) || turns.length === 0) return false;
|
|
13236
13243
|
|
|
13237
13244
|
const bufferKey = options.bufferKey ?? turns[0]?.sessionKey ?? "default";
|
|
@@ -14090,6 +14097,7 @@ export class Orchestrator {
|
|
|
14090
14097
|
/** Verbatim source turn text the facts were extracted from (faithfulness gate #1576). */
|
|
14091
14098
|
sourceText?: string,
|
|
14092
14099
|
graphCaps: GraphConstructionCapabilitySet = resolveGraphConstructionCapabilities(this.config),
|
|
14100
|
+
lifecycleCaps: MemoryLifecycleCapabilitySet = resolveMemoryLifecycleCapabilities(this.config),
|
|
14093
14101
|
): Promise<string[]> {
|
|
14094
14102
|
// Inline source attribution (issue #369). When enabled, every extracted
|
|
14095
14103
|
// fact is rewritten to carry a compact provenance tag inside its body so
|
|
@@ -14388,7 +14396,7 @@ export class Orchestrator {
|
|
|
14388
14396
|
},
|
|
14389
14397
|
);
|
|
14390
14398
|
if (
|
|
14391
|
-
|
|
14399
|
+
lifecycleCaps.temporalSupersession &&
|
|
14392
14400
|
options.category === "fact" &&
|
|
14393
14401
|
options.entityRef &&
|
|
14394
14402
|
options.structuredAttributes &&
|
|
@@ -14552,7 +14560,7 @@ export class Orchestrator {
|
|
|
14552
14560
|
// would have run post-writeMemory. This is a best-effort / fail-open
|
|
14553
14561
|
// step — if the lookup fails we skip silently (same as the normal path).
|
|
14554
14562
|
if (
|
|
14555
|
-
|
|
14563
|
+
lifecycleCaps.temporalSupersession &&
|
|
14556
14564
|
options.entityRef &&
|
|
14557
14565
|
options.structuredAttributes &&
|
|
14558
14566
|
Object.keys(options.structuredAttributes).length > 0
|
|
@@ -14714,7 +14722,7 @@ export class Orchestrator {
|
|
|
14714
14722
|
// shared recall continues returning the stale state. Reuses the same
|
|
14715
14723
|
// applyTemporalSupersession helper — no logic duplication.
|
|
14716
14724
|
if (
|
|
14717
|
-
|
|
14725
|
+
lifecycleCaps.temporalSupersession &&
|
|
14718
14726
|
options.entityRef &&
|
|
14719
14727
|
options.structuredAttributes &&
|
|
14720
14728
|
Object.keys(options.structuredAttributes).length > 0
|
|
@@ -15024,7 +15032,7 @@ export class Orchestrator {
|
|
|
15024
15032
|
// persistExtraction call so stale state from a prior call cannot leak
|
|
15025
15033
|
// into the caller's buffer-retention decision.
|
|
15026
15034
|
this.lastPersistExtractionDeferredCount = 0;
|
|
15027
|
-
if (
|
|
15035
|
+
if (lifecycleCaps.extractionJudge) {
|
|
15028
15036
|
// #1669 P1: pre-filter redacted facts from judge candidates so never-store
|
|
15029
15037
|
// content is not persisted as judge training data.
|
|
15030
15038
|
let preJudgeRedactionRules: CompiledRedactionRule[] = [];
|
|
@@ -15090,7 +15098,7 @@ export class Orchestrator {
|
|
|
15090
15098
|
// off; the combined callback itself is undefined when both are
|
|
15091
15099
|
// disabled so there is zero overhead in the default configuration.
|
|
15092
15100
|
const judgeTelemetryOpts = {
|
|
15093
|
-
enabled:
|
|
15101
|
+
enabled: lifecycleCaps.extractionJudgeTelemetry,
|
|
15094
15102
|
memoryDir: this.config.memoryDir,
|
|
15095
15103
|
};
|
|
15096
15104
|
const judgeTrainingOpts = {
|
|
@@ -15301,7 +15309,7 @@ export class Orchestrator {
|
|
|
15301
15309
|
// do not block scope routing). Rule 30: gated by
|
|
15302
15310
|
// extractionScopeClassificationEnabled.
|
|
15303
15311
|
if (
|
|
15304
|
-
|
|
15312
|
+
lifecycleCaps.extractionScopeClassification &&
|
|
15305
15313
|
this.config.namespacesEnabled &&
|
|
15306
15314
|
fact.scope === "global" &&
|
|
15307
15315
|
!routedNamespaceExplicit
|
|
@@ -15954,7 +15962,7 @@ export class Orchestrator {
|
|
|
15954
15962
|
// #1578 r3: an extracted end-only bound (validFrom absent) is
|
|
15955
15963
|
// historical, not a new authoritative state — never let it
|
|
15956
15964
|
// supersede a later active fact (codex P1 on :15534).
|
|
15957
|
-
enabled:
|
|
15965
|
+
enabled: lifecycleCaps.temporalSupersession &&
|
|
15958
15966
|
!(biTemporal && !biTemporal.validFrom),
|
|
15959
15967
|
});
|
|
15960
15968
|
} catch (err) {
|
|
@@ -16190,7 +16198,7 @@ export class Orchestrator {
|
|
|
16190
16198
|
entityRef: supersessionEntityRef,
|
|
16191
16199
|
structuredAttributes: fact.structuredAttributes,
|
|
16192
16200
|
createdAt: supersessionOrderingAt(biTemporal?.validFrom ?? sourceContext?.validAt),
|
|
16193
|
-
enabled:
|
|
16201
|
+
enabled: lifecycleCaps.temporalSupersession &&
|
|
16194
16202
|
!(biTemporal && !biTemporal.validFrom),
|
|
16195
16203
|
});
|
|
16196
16204
|
} catch (err) {
|
|
@@ -16557,7 +16565,7 @@ export class Orchestrator {
|
|
|
16557
16565
|
storage: StorageManager,
|
|
16558
16566
|
memoryId: string,
|
|
16559
16567
|
): Promise<void> {
|
|
16560
|
-
if (!this.config.
|
|
16568
|
+
if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return;
|
|
16561
16569
|
if (!(await this.embeddingFallback.isAvailable())) return;
|
|
16562
16570
|
const memory = await storage.getMemoryById(memoryId);
|
|
16563
16571
|
if (!memory) return;
|
|
@@ -16749,6 +16757,7 @@ export class Orchestrator {
|
|
|
16749
16757
|
merged: number;
|
|
16750
16758
|
invalidated: number;
|
|
16751
16759
|
}> {
|
|
16760
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
16752
16761
|
log.info("running consolidation pass");
|
|
16753
16762
|
let merged = 0;
|
|
16754
16763
|
let invalidated = 0;
|
|
@@ -16985,7 +16994,7 @@ export class Orchestrator {
|
|
|
16985
16994
|
}
|
|
16986
16995
|
|
|
16987
16996
|
// v8.3 Lifecycle policy pass — deterministic promotion/decay metadata
|
|
16988
|
-
if (
|
|
16997
|
+
if (lifecycleCaps.lifecyclePolicy) {
|
|
16989
16998
|
try {
|
|
16990
16999
|
const lightSleepStartedAt = new Date().toISOString();
|
|
16991
17000
|
const lifecycleCorpus = await this.storage.readAllMemories();
|
|
@@ -17173,7 +17182,7 @@ export class Orchestrator {
|
|
|
17173
17182
|
await this.storage.saveMeta(meta);
|
|
17174
17183
|
|
|
17175
17184
|
// Temporal Memory Tree (v8.2) — rebuild nodes from all memories, fail-open
|
|
17176
|
-
if (
|
|
17185
|
+
if (lifecycleCaps.temporalMemoryTree) {
|
|
17177
17186
|
try {
|
|
17178
17187
|
const tmtEntries = allMemories
|
|
17179
17188
|
.filter(
|
|
@@ -17715,6 +17724,7 @@ export class Orchestrator {
|
|
|
17715
17724
|
let updatedCount = 0;
|
|
17716
17725
|
let disputedCount = 0;
|
|
17717
17726
|
let evaluatedCount = 0;
|
|
17727
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
17718
17728
|
|
|
17719
17729
|
const thresholds = this.effectiveLifecycleThresholds();
|
|
17720
17730
|
const policy = {
|
|
@@ -17780,7 +17790,7 @@ export class Orchestrator {
|
|
|
17780
17790
|
|
|
17781
17791
|
// Report how many memories had frontmatter rewritten so callers can record a
|
|
17782
17792
|
// catalog write touch for lifecycle-only passes (codex NR-tS).
|
|
17783
|
-
if (!
|
|
17793
|
+
if (!lifecycleCaps.lifecycleMetrics) return updatedCount;
|
|
17784
17794
|
|
|
17785
17795
|
const total = evaluatedCount;
|
|
17786
17796
|
const metrics = {
|
|
@@ -19226,7 +19236,7 @@ export class Orchestrator {
|
|
|
19226
19236
|
// • search() throws → re-throw (network/provider error).
|
|
19227
19237
|
// • search() returns [] → return [] (empty index, not a
|
|
19228
19238
|
// backend failure; decideSemanticDedup reports no_candidates).
|
|
19229
|
-
if (!this.config.
|
|
19239
|
+
if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) {
|
|
19230
19240
|
throw new Error("semantic dedup: embedding backend not configured");
|
|
19231
19241
|
}
|
|
19232
19242
|
if (!(await this.embeddingFallback.isAvailable())) {
|
|
@@ -19300,7 +19310,7 @@ export class Orchestrator {
|
|
|
19300
19310
|
query: string,
|
|
19301
19311
|
limit: number,
|
|
19302
19312
|
): Promise<QmdSearchResult[]> {
|
|
19303
|
-
if (!this.config.
|
|
19313
|
+
if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return [];
|
|
19304
19314
|
if (!(await this.embeddingFallback.isAvailable())) return [];
|
|
19305
19315
|
const hits = await this.embeddingFallback.search(query, limit);
|
|
19306
19316
|
if (hits.length === 0) return [];
|
|
@@ -20010,6 +20020,7 @@ export class Orchestrator {
|
|
|
20010
20020
|
blockedPaths?: Set<string>;
|
|
20011
20021
|
},
|
|
20012
20022
|
): QmdSearchResult[] {
|
|
20023
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
20013
20024
|
let lifecycleFilteredCount = 0;
|
|
20014
20025
|
let temporalSupersededFilteredCount = 0;
|
|
20015
20026
|
let biTemporalExpiredFilteredCount = 0;
|
|
@@ -20043,9 +20054,9 @@ export class Orchestrator {
|
|
|
20043
20054
|
if (
|
|
20044
20055
|
options?.allowLifecycleFiltered !== true &&
|
|
20045
20056
|
shouldFilterLifecycleRecallCandidate(memory.frontmatter, {
|
|
20046
|
-
lifecyclePolicyEnabled:
|
|
20057
|
+
lifecyclePolicyEnabled: lifecycleCaps.lifecyclePolicy,
|
|
20047
20058
|
lifecycleFilterStaleEnabled:
|
|
20048
|
-
|
|
20059
|
+
lifecycleCaps.lifecycleFilterStale,
|
|
20049
20060
|
})
|
|
20050
20061
|
) {
|
|
20051
20062
|
lifecycleFilteredCount += 1;
|
|
@@ -20079,7 +20090,7 @@ export class Orchestrator {
|
|
|
20079
20090
|
// half-open `[valid_at, invalid_at)` evaluation in isValidAsOf
|
|
20080
20091
|
// is the authoritative gate for historical recall.
|
|
20081
20092
|
shouldFilterSupersededFromRecall(memory.frontmatter, {
|
|
20082
|
-
enabled:
|
|
20093
|
+
enabled: lifecycleCaps.temporalSupersession,
|
|
20083
20094
|
includeInRecall: this.config.temporalSupersessionIncludeInRecall,
|
|
20084
20095
|
})
|
|
20085
20096
|
) {
|
|
@@ -20223,6 +20234,7 @@ export class Orchestrator {
|
|
|
20223
20234
|
asOfMs?: number;
|
|
20224
20235
|
},
|
|
20225
20236
|
): Promise<QmdSearchResult[]> {
|
|
20237
|
+
const lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config);
|
|
20226
20238
|
if (results.length === 0) return results;
|
|
20227
20239
|
|
|
20228
20240
|
const safety = await this.filterSearchResultsForRecall(
|
|
@@ -20397,7 +20409,7 @@ export class Orchestrator {
|
|
|
20397
20409
|
const lifecycleDelta = lifecycleRecallScoreAdjustment(
|
|
20398
20410
|
memory.frontmatter,
|
|
20399
20411
|
{
|
|
20400
|
-
lifecyclePolicyEnabled:
|
|
20412
|
+
lifecyclePolicyEnabled: lifecycleCaps.lifecyclePolicy,
|
|
20401
20413
|
},
|
|
20402
20414
|
);
|
|
20403
20415
|
score += applyUtilityRankingRuntimeDelta(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { log } from "../logger.js";
|
|
2
|
+
import { resolveMemoryLifecycleCapabilities } from "../capabilities.js";
|
|
2
3
|
import type { PluginConfig } from "../types.js";
|
|
3
4
|
import { isAbortError } from "../abort-error.js";
|
|
4
5
|
import { withTimeoutSignal } from "./abort.js";
|
|
@@ -209,7 +210,7 @@ export class EmbedHelper {
|
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
private resolveProvider(options: { includeHost?: boolean } = {}): ProviderConfig | null {
|
|
212
|
-
if (!this.config.
|
|
213
|
+
if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return null;
|
|
213
214
|
|
|
214
215
|
if (
|
|
215
216
|
options.includeHost !== false &&
|
|
@@ -244,7 +245,7 @@ export class EmbedHelper {
|
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
private resolveFallbackProviderForIdentity(identity: EmbedProviderIdentity): ProviderConfig | null {
|
|
247
|
-
if (!this.config.
|
|
248
|
+
if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return null;
|
|
248
249
|
const separator = identity.indexOf(":");
|
|
249
250
|
if (separator <= 0 || separator === identity.length - 1) return null;
|
|
250
251
|
const type = identity.slice(0, separator);
|