@loreai/core 0.10.2 → 0.11.1
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/bun/config.d.ts +8 -0
- package/dist/bun/config.d.ts.map +1 -1
- package/dist/bun/db.d.ts.map +1 -1
- package/dist/bun/distillation.d.ts +74 -2
- package/dist/bun/distillation.d.ts.map +1 -1
- package/dist/bun/embedding.d.ts.map +1 -1
- package/dist/bun/gradient.d.ts +72 -0
- package/dist/bun/gradient.d.ts.map +1 -1
- package/dist/bun/index.d.ts +4 -2
- package/dist/bun/index.d.ts.map +1 -1
- package/dist/bun/index.js +554 -76
- package/dist/bun/index.js.map +4 -4
- package/dist/bun/prompt.d.ts +8 -2
- package/dist/bun/prompt.d.ts.map +1 -1
- package/dist/bun/temporal.d.ts +31 -0
- package/dist/bun/temporal.d.ts.map +1 -1
- package/dist/bun/types.d.ts +9 -0
- package/dist/bun/types.d.ts.map +1 -1
- package/dist/bun/worker-model.d.ts +90 -0
- package/dist/bun/worker-model.d.ts.map +1 -0
- package/dist/node/config.d.ts +8 -0
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/db.d.ts.map +1 -1
- package/dist/node/distillation.d.ts +74 -2
- package/dist/node/distillation.d.ts.map +1 -1
- package/dist/node/embedding.d.ts.map +1 -1
- package/dist/node/gradient.d.ts +72 -0
- package/dist/node/gradient.d.ts.map +1 -1
- package/dist/node/index.d.ts +4 -2
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +554 -76
- package/dist/node/index.js.map +4 -4
- package/dist/node/prompt.d.ts +8 -2
- package/dist/node/prompt.d.ts.map +1 -1
- package/dist/node/temporal.d.ts +31 -0
- package/dist/node/temporal.d.ts.map +1 -1
- package/dist/node/types.d.ts +9 -0
- package/dist/node/types.d.ts.map +1 -1
- package/dist/node/worker-model.d.ts +90 -0
- package/dist/node/worker-model.d.ts.map +1 -0
- package/dist/types/config.d.ts +8 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/db.d.ts.map +1 -1
- package/dist/types/distillation.d.ts +74 -2
- package/dist/types/distillation.d.ts.map +1 -1
- package/dist/types/embedding.d.ts.map +1 -1
- package/dist/types/gradient.d.ts +72 -0
- package/dist/types/gradient.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/prompt.d.ts +8 -2
- package/dist/types/prompt.d.ts.map +1 -1
- package/dist/types/temporal.d.ts +31 -0
- package/dist/types/temporal.d.ts.map +1 -1
- package/dist/types/types.d.ts +9 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/worker-model.d.ts +90 -0
- package/dist/types/worker-model.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/config.ts +53 -6
- package/src/db.ts +68 -6
- package/src/distillation.ts +225 -28
- package/src/embedding.ts +7 -0
- package/src/gradient.ts +305 -17
- package/src/index.ts +16 -0
- package/src/lat-reader.ts +4 -4
- package/src/ltm.ts +17 -17
- package/src/prompt.ts +101 -0
- package/src/recall.ts +4 -4
- package/src/temporal.ts +41 -10
- package/src/types.ts +9 -0
- package/src/worker-model.ts +264 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../../src/temporal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../../src/temporal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQrD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,WAAS,CAAC;AAEvC;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAarD;AAiBD,wBAAgB,KAAK,CAAC,KAAK,EAAE;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,QAqCA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,eAAe,EAAE,CASnB;AAED,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,eAAe,EAAE,CAOnB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAQ1C;AA2BD,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,eAAe,EAAE,CA0CpB;AAED,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,qBAAqB,EAAE,CAgC1B;AAED,wBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAWrE;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAWR;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,8FAA8F;IAC9F,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,WAAW,CA0Ed"}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -36,6 +36,15 @@ export type LoreAssistantMessage = {
|
|
|
36
36
|
modelID: string;
|
|
37
37
|
providerID: string;
|
|
38
38
|
mode: string;
|
|
39
|
+
/**
|
|
40
|
+
* Set to `true` by the OpenCode compaction agent on the assistant
|
|
41
|
+
* message that holds a `/compact` summary (see upstream
|
|
42
|
+
* `compaction.ts:435`). Lore reads this flag in F1b's
|
|
43
|
+
* `findPreviousCompactSummary` to anchor repeat `/compact`
|
|
44
|
+
* invocations to the prior summary. Always undefined for normal
|
|
45
|
+
* assistant turns.
|
|
46
|
+
*/
|
|
47
|
+
summary?: boolean;
|
|
39
48
|
path: {
|
|
40
49
|
cwd: string;
|
|
41
50
|
root: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic worker model selection.
|
|
3
|
+
*
|
|
4
|
+
* Background workers (distillation, curation, query expansion) don't need
|
|
5
|
+
* frontier reasoning. This module discovers cheaper models from the same
|
|
6
|
+
* provider and validates their quality via a two-phase comparison:
|
|
7
|
+
* Phase 1: structural checks (parsability, observation count, token bounds)
|
|
8
|
+
* Phase 2: LLM judge (session model rates candidate output vs reference)
|
|
9
|
+
*
|
|
10
|
+
* Results are persisted in kv_meta and re-evaluated when the model landscape
|
|
11
|
+
* changes (new models, session model switch, model deprecation).
|
|
12
|
+
*/
|
|
13
|
+
/** Minimal model info needed for worker selection — provider-agnostic. */
|
|
14
|
+
export type ModelInfo = {
|
|
15
|
+
id: string;
|
|
16
|
+
providerID: string;
|
|
17
|
+
cost: {
|
|
18
|
+
input: number;
|
|
19
|
+
};
|
|
20
|
+
status: string;
|
|
21
|
+
capabilities: {
|
|
22
|
+
input: {
|
|
23
|
+
text: boolean;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/** Result of a worker model validation stored in kv_meta. */
|
|
28
|
+
export type WorkerModelResult = {
|
|
29
|
+
modelID: string;
|
|
30
|
+
providerID: string;
|
|
31
|
+
fingerprint: string;
|
|
32
|
+
validatedAt: number;
|
|
33
|
+
judgeScore: number | null;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Select worker model candidates from the available models.
|
|
37
|
+
*
|
|
38
|
+
* Returns up to 2 candidates: cheapest overall + one tier below the session
|
|
39
|
+
* model. The session model itself is included (if it's the cheapest, the list
|
|
40
|
+
* has 1 entry and no comparison is needed).
|
|
41
|
+
*/
|
|
42
|
+
export declare function selectWorkerCandidates(sessionModel: {
|
|
43
|
+
id: string;
|
|
44
|
+
providerID: string;
|
|
45
|
+
cost: {
|
|
46
|
+
input: number;
|
|
47
|
+
};
|
|
48
|
+
}, providerModels: ModelInfo[]): ModelInfo[];
|
|
49
|
+
/**
|
|
50
|
+
* Compute a fingerprint from the model landscape. Changes when:
|
|
51
|
+
* - Models are added or removed from the provider
|
|
52
|
+
* - The session model changes
|
|
53
|
+
*/
|
|
54
|
+
export declare function computeModelFingerprint(providerID: string, sessionModelID: string, activeModelIDs: string[]): string;
|
|
55
|
+
export declare function getValidatedWorkerModel(providerID: string): WorkerModelResult | null;
|
|
56
|
+
export declare function storeValidatedWorkerModel(result: WorkerModelResult): void;
|
|
57
|
+
/**
|
|
58
|
+
* Check whether the stored validation is stale (fingerprint mismatch).
|
|
59
|
+
*/
|
|
60
|
+
export declare function isValidationStale(stored: WorkerModelResult | null, currentFingerprint: string): boolean;
|
|
61
|
+
export type StructuralCheckResult = {
|
|
62
|
+
passed: boolean;
|
|
63
|
+
observationCount: number;
|
|
64
|
+
tokenCount: number;
|
|
65
|
+
reason?: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Structural quality check: does the candidate distillation output meet
|
|
69
|
+
* minimum quality thresholds relative to the reference?
|
|
70
|
+
*/
|
|
71
|
+
export declare function structuralCheck(candidateObservations: string | null, referenceObservations: string): StructuralCheckResult;
|
|
72
|
+
export declare const WORKER_JUDGE_SYSTEM = "You are evaluating distillation quality. You will be given a REFERENCE distillation (produced by a capable model) and a CANDIDATE distillation (produced by a cheaper model) of the same conversation segment.\n\nRate the candidate on a scale of 1-5:\n5 = Captures all key facts and decisions, equivalent to reference\n4 = Captures most facts, minor omissions\n3 = Captures the essential facts, some detail loss acceptable\n2 = Missing important facts or technical details\n1 = Significantly incomplete or inaccurate\n\nRespond with ONLY a single digit (1-5).";
|
|
73
|
+
export declare function workerJudgeUser(reference: string, candidate: string): string;
|
|
74
|
+
/** Parse the judge's score from a response. Returns null on parse failure. */
|
|
75
|
+
export declare function parseJudgeScore(response: string): number | null;
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the effective worker model for a given provider.
|
|
78
|
+
* Priority: explicit config > validated auto-selection > session model (fallback).
|
|
79
|
+
*/
|
|
80
|
+
export declare function resolveWorkerModel(providerID: string, configWorkerModel?: {
|
|
81
|
+
providerID: string;
|
|
82
|
+
modelID: string;
|
|
83
|
+
}, configModel?: {
|
|
84
|
+
providerID: string;
|
|
85
|
+
modelID: string;
|
|
86
|
+
}): {
|
|
87
|
+
providerID: string;
|
|
88
|
+
modelID: string;
|
|
89
|
+
} | undefined;
|
|
90
|
+
//# sourceMappingURL=worker-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-model.d.ts","sourceRoot":"","sources":["../../src/worker-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE;QAAE,KAAK,EAAE;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC;CAC5C,CAAC;AAEF,6DAA6D;AAC7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAQF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACzE,cAAc,EAAE,SAAS,EAAE,GAC1B,SAAS,EAAE,CAoCb;AAMD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EAAE,GACvB,MAAM,CAKR;AAMD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GACjB,iBAAiB,GAAG,IAAI,CAU1B;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAQzE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,iBAAiB,GAAG,IAAI,EAChC,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAGT;AAMD,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,qBAAqB,EAAE,MAAM,GAAG,IAAI,EACpC,qBAAqB,EAAE,MAAM,GAC5B,qBAAqB,CAsCvB;AAMD,eAAO,MAAM,mBAAmB,ijBASQ,CAAC;AAEzC,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,CAER;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI/D;AAMD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,iBAAiB,CAAC,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC3D,WAAW,CAAC,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAYrD"}
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -9,22 +9,69 @@ export const LoreConfig = z.object({
|
|
|
9
9
|
modelID: z.string(),
|
|
10
10
|
})
|
|
11
11
|
.optional(),
|
|
12
|
+
/** Explicit worker model override. When set, all background workers (distillation,
|
|
13
|
+
* curation, query expansion) use this model instead of the session model or the
|
|
14
|
+
* auto-selected worker model. Bypasses dynamic worker model selection entirely. */
|
|
15
|
+
workerModel: z
|
|
16
|
+
.object({
|
|
17
|
+
providerID: z.string(),
|
|
18
|
+
modelID: z.string(),
|
|
19
|
+
})
|
|
20
|
+
.optional(),
|
|
12
21
|
budget: z
|
|
13
22
|
.object({
|
|
14
23
|
distilled: z.number().min(0.05).max(0.5).default(0.25),
|
|
15
24
|
raw: z.number().min(0.1).max(0.7).default(0.4),
|
|
16
25
|
output: z.number().min(0.1).max(0.5).default(0.25),
|
|
17
|
-
/** Max fraction of usable context reserved for LTM system-prompt injection. Default: 0.
|
|
18
|
-
ltm: z.number().min(0.02).max(0.3).default(0.
|
|
26
|
+
/** Max fraction of usable context reserved for LTM system-prompt injection. Default: 0.05 (5%). */
|
|
27
|
+
ltm: z.number().min(0.02).max(0.3).default(0.05),
|
|
28
|
+
/** Per-turn cache-read cost target in dollars. Controls when layer 0 (full
|
|
29
|
+
* passthrough) escalates to layer 1 (compressed). The cap is derived as:
|
|
30
|
+
* maxLayer0Tokens = max(target / model.cost.cache.read, 40K).
|
|
31
|
+
* Lower = cheaper but earlier compression. Default: 0.10. Set to 0 to
|
|
32
|
+
* disable cost-aware capping (use the model's full context). */
|
|
33
|
+
targetCacheReadCostPerTurn: z.number().min(0).default(0.10),
|
|
34
|
+
/** Direct override for the layer-0 token cap. When set, bypasses the
|
|
35
|
+
* cost-aware formula from targetCacheReadCostPerTurn. 0 = disabled
|
|
36
|
+
* (no cap, use full context). Default: undefined (use cost-aware auto). */
|
|
37
|
+
maxLayer0Tokens: z.number().min(0).optional(),
|
|
19
38
|
})
|
|
20
|
-
.default({ distilled: 0.25, raw: 0.4, output: 0.25, ltm: 0.10 }),
|
|
39
|
+
.default({ distilled: 0.25, raw: 0.4, output: 0.25, ltm: 0.05, targetCacheReadCostPerTurn: 0.10 }),
|
|
40
|
+
/**
|
|
41
|
+
* Cold-cache idle-resume handling.
|
|
42
|
+
*
|
|
43
|
+
* Anthropic's prompt cache evicts entries after ~5 min (default tier) /
|
|
44
|
+
* ~1 hour (extended tier). When a session resumes after the eviction window,
|
|
45
|
+
* Lore's byte-identity caches (distilled prefix, raw window pin, LTM block)
|
|
46
|
+
* are providing no value because the underlying provider cache is already
|
|
47
|
+
* cold. On detection, Lore refreshes those caches so the next turn can
|
|
48
|
+
* produce a better-fitting window without paying a cache cost it would
|
|
49
|
+
* otherwise be trying to preserve. Reasoning blocks are NOT touched —
|
|
50
|
+
* Anthropic's April 23 postmortem identified dropping reasoning blocks as
|
|
51
|
+
* the root cause of forgetfulness/repetition.
|
|
52
|
+
*
|
|
53
|
+
* `idleResumeMinutes` is the threshold in minutes. Default 60 — matches
|
|
54
|
+
* Anthropic's extended-cache eviction window, conservative across providers.
|
|
55
|
+
* Set to 0 to disable the feature.
|
|
56
|
+
*/
|
|
57
|
+
idleResumeMinutes: z.number().min(0).max(24 * 60).default(60),
|
|
21
58
|
distillation: z
|
|
22
59
|
.object({
|
|
23
|
-
minMessages: z.number().min(3).default(
|
|
24
|
-
maxSegment: z.number().min(5).default(
|
|
60
|
+
minMessages: z.number().min(3).default(5),
|
|
61
|
+
maxSegment: z.number().min(5).default(30),
|
|
25
62
|
metaThreshold: z.number().min(3).default(10),
|
|
63
|
+
/** Max chars per tool output when rendering temporal messages for distillation input.
|
|
64
|
+
* Outputs longer than this are replaced with a compact annotation preserving line
|
|
65
|
+
* count, error signals, and file paths. Default: 2000 (matches upstream OpenCode's
|
|
66
|
+
* TOOL_OUTPUT_MAX_CHARS during compaction). Set to 0 to disable. */
|
|
67
|
+
toolOutputMaxChars: z.number().min(0).default(2_000),
|
|
26
68
|
})
|
|
27
|
-
.default({
|
|
69
|
+
.default({
|
|
70
|
+
minMessages: 5,
|
|
71
|
+
maxSegment: 30,
|
|
72
|
+
metaThreshold: 10,
|
|
73
|
+
toolOutputMaxChars: 2_000,
|
|
74
|
+
}),
|
|
28
75
|
knowledge: z
|
|
29
76
|
.object({
|
|
30
77
|
/** Set to false to disable long-term knowledge storage and system-prompt injection.
|
package/src/db.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Database } from "#db/driver";
|
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
3
|
import { mkdirSync } from "fs";
|
|
4
4
|
|
|
5
|
-
const SCHEMA_VERSION =
|
|
5
|
+
const SCHEMA_VERSION = 11;
|
|
6
6
|
|
|
7
7
|
const MIGRATIONS: string[] = [
|
|
8
8
|
`
|
|
@@ -281,6 +281,58 @@ const MIGRATIONS: string[] = [
|
|
|
281
281
|
PRIMARY KEY (from_id, to_id)
|
|
282
282
|
);
|
|
283
283
|
`,
|
|
284
|
+
`
|
|
285
|
+
-- Version 11: F3b -- unambiguous chunk terminator in temporal_messages.content.
|
|
286
|
+
--
|
|
287
|
+
-- Pre-F3b, partsToText joined chunks with a newline. Tool-output payloads
|
|
288
|
+
-- can contain newlines too, so the boundary between a tool envelope and a
|
|
289
|
+
-- following plain-text or [reasoning] chunk was structurally ambiguous.
|
|
290
|
+
-- This caused two known limitations in the F3 distill-input truncator:
|
|
291
|
+
-- trailing text could be swallowed into a tool payload, and embedded
|
|
292
|
+
-- literal envelope strings inside a payload (e.g. when reading AGENTS.md)
|
|
293
|
+
-- could fabricate fake boundaries.
|
|
294
|
+
--
|
|
295
|
+
-- F3b switches the chunk separator to newline plus ASCII Unit Separator
|
|
296
|
+
-- (char 31). The Unit Separator is non-word so FTS5's unicode61 tokenizer
|
|
297
|
+
-- ignores it (zero BM25 impact). New rows are written via the post-F3b
|
|
298
|
+
-- partsToText. Existing rows are rewritten in place by the UPDATE below,
|
|
299
|
+
-- which uses pure SQL replace() to inject the Unit Separator after every
|
|
300
|
+
-- legacy chunk-prefix sequence -- the same boundary patterns the legacy
|
|
301
|
+
-- F3 reader was already trying to recover.
|
|
302
|
+
--
|
|
303
|
+
-- Trade-off (acceptable): any embedded legacy chunk-prefix sequence
|
|
304
|
+
-- inside a tool payload becomes a structural boundary post-migration.
|
|
305
|
+
-- This matches what the legacy F3 reader did at read-time anyway, baked
|
|
306
|
+
-- into the row permanently. The migration runs once per machine.
|
|
307
|
+
--
|
|
308
|
+
-- Idempotent: a row that already contains the Unit Separator before a
|
|
309
|
+
-- chunk prefix no longer matches the search literal (the separator
|
|
310
|
+
-- interposes), so re-running the UPDATE is a no-op for migrated rows.
|
|
311
|
+
-- (Important: migrate() in db.ts runs each migration via database.exec()
|
|
312
|
+
-- with no explicit BEGIN/COMMIT around the whole loop. SQLite makes this
|
|
313
|
+
-- single UPDATE statement atomic per-statement, so partial progress on
|
|
314
|
+
-- crash is safe to retry thanks to the idempotency above.)
|
|
315
|
+
--
|
|
316
|
+
-- char(10) = newline, char(31) = Unit Separator. SQLite has no native
|
|
317
|
+
-- regex, but two nested replace() calls on the literal prefixes are
|
|
318
|
+
-- sufficient because both legacy chunk prefixes match at line-start.
|
|
319
|
+
--
|
|
320
|
+
-- Each row UPDATE fires the temporal_fts_update trigger once; because
|
|
321
|
+
-- the Unit Separator is a non-word character, the re-indexed content
|
|
322
|
+
-- tokenizes identically -- net no-op for FTS scoring.
|
|
323
|
+
UPDATE temporal_messages
|
|
324
|
+
SET content = replace(
|
|
325
|
+
replace(
|
|
326
|
+
content,
|
|
327
|
+
char(10) || '[tool:',
|
|
328
|
+
char(10) || char(31) || '[tool:'
|
|
329
|
+
),
|
|
330
|
+
char(10) || '[reasoning] ',
|
|
331
|
+
char(10) || char(31) || '[reasoning] '
|
|
332
|
+
)
|
|
333
|
+
WHERE content LIKE '%' || char(10) || '[tool:%'
|
|
334
|
+
OR content LIKE '%' || char(10) || '[reasoning] %';
|
|
335
|
+
`,
|
|
284
336
|
];
|
|
285
337
|
|
|
286
338
|
function dataDir() {
|
|
@@ -307,13 +359,23 @@ export function db(): Database {
|
|
|
307
359
|
// exist, so no special option is needed. (bun:sqlite's `{ create: true }`
|
|
308
360
|
// exists only to opt INTO creation when you want readonly=false — which is
|
|
309
361
|
// already the default for our case.)
|
|
310
|
-
|
|
311
|
-
instance
|
|
312
|
-
|
|
362
|
+
//
|
|
363
|
+
// IMPORTANT: Do NOT assign to `instance` until migrate() succeeds. If
|
|
364
|
+
// migrate() throws (SQLITE_BUSY, partial prior run, disk error), the
|
|
365
|
+
// module-level singleton must remain undefined so the next db() call
|
|
366
|
+
// retries initialization instead of returning an un-migrated handle.
|
|
367
|
+
const database = new Database(path);
|
|
368
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
369
|
+
database.exec("PRAGMA foreign_keys = ON");
|
|
370
|
+
// Retry for up to 5s when another connection holds the write lock (e.g.
|
|
371
|
+
// backgroundDistill's BEGIN IMMEDIATE overlapping with a recall query).
|
|
372
|
+
// Default is 0ms which throws SQLITE_BUSY immediately.
|
|
373
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
313
374
|
// Return freed pages to the OS incrementally on each transaction commit
|
|
314
375
|
// instead of accumulating a free-page list that bloats the file.
|
|
315
|
-
|
|
316
|
-
migrate(
|
|
376
|
+
database.exec("PRAGMA auto_vacuum = INCREMENTAL");
|
|
377
|
+
migrate(database);
|
|
378
|
+
instance = database;
|
|
317
379
|
return instance;
|
|
318
380
|
}
|
|
319
381
|
|
package/src/distillation.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { db, ensureProject } from "./db";
|
|
2
2
|
import { config } from "./config";
|
|
3
3
|
import * as temporal from "./temporal";
|
|
4
|
+
import { CHUNK_TERMINATOR } from "./temporal";
|
|
4
5
|
import * as embedding from "./embedding";
|
|
5
6
|
import * as log from "./log";
|
|
6
7
|
import {
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
RECURSIVE_SYSTEM,
|
|
10
11
|
recursiveUser,
|
|
11
12
|
} from "./prompt";
|
|
12
|
-
import { needsUrgentDistillation } from "./gradient";
|
|
13
|
+
import { needsUrgentDistillation, toolStripAnnotation } from "./gradient";
|
|
13
14
|
import { workerSessionIDs } from "./worker";
|
|
14
15
|
import type { LLMClient } from "./types";
|
|
15
16
|
|
|
@@ -53,9 +54,96 @@ function formatTime(ms: number): string {
|
|
|
53
54
|
return `${h}:${m}`;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
// Chunk separator written by `temporal.partsToText` and recovered by the
|
|
58
|
+
// reader below. As of F3b, chunks are joined with `"\n" + CHUNK_TERMINATOR`
|
|
59
|
+
// — the `\x1f` (Unit Separator) is non-word so FTS5 ignores it, and it
|
|
60
|
+
// cannot legitimately appear in normal content, so the boundary is
|
|
61
|
+
// unambiguous regardless of payload contents.
|
|
62
|
+
//
|
|
63
|
+
// The migration in db.ts (version 11) rewrote pre-F3b rows to the new
|
|
64
|
+
// format, so every `temporal_messages.content` value now uses this
|
|
65
|
+
// separator. Before F3b the structural parser used a heuristic regex
|
|
66
|
+
// over `\n` boundaries which had two ambiguity directions
|
|
67
|
+
// (trailing-text swallow + embedded-envelope fabrication); both are
|
|
68
|
+
// gone for migrated and new rows.
|
|
69
|
+
const CHUNK_SEPARATOR = "\n" + CHUNK_TERMINATOR;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Truncate tool outputs within a `TemporalMessage.content` string (produced
|
|
73
|
+
* by `temporal.partsToText`). Plain text and `[reasoning]` chunks pass
|
|
74
|
+
* through untouched; only `[tool:<name>] <payload>` envelopes whose payload
|
|
75
|
+
* exceeds `maxChars` are replaced with a compact `toolStripAnnotation(...)`
|
|
76
|
+
* marker preserving line count, error flag, and file paths.
|
|
77
|
+
*
|
|
78
|
+
* Annotation matches the style used by the runtime gradient so the
|
|
79
|
+
* distillation LLM sees the same affordance it sees during live turns.
|
|
80
|
+
*
|
|
81
|
+
* Exported primarily for tests. If future renderers need the same
|
|
82
|
+
* semantics (e.g. a recall-time preview), they can reuse this.
|
|
83
|
+
*/
|
|
84
|
+
export function truncateToolOutputsInContent(
|
|
85
|
+
content: string,
|
|
86
|
+
maxChars: number,
|
|
87
|
+
): string {
|
|
88
|
+
if (maxChars <= 0 || content.length === 0) return content;
|
|
89
|
+
|
|
90
|
+
// Fast path: a row with no chunk terminator is single-chunk content.
|
|
91
|
+
// (After the F3b migration, this only happens for messages with exactly
|
|
92
|
+
// one part — including all user messages, single-text assistant
|
|
93
|
+
// responses, and standalone tool outputs.) Truncate as one chunk if
|
|
94
|
+
// it's a tool envelope.
|
|
95
|
+
if (content.indexOf(CHUNK_TERMINATOR) === -1) {
|
|
96
|
+
return truncateSingleChunk(content, maxChars);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const chunks = content.split(CHUNK_SEPARATOR);
|
|
100
|
+
let anyToolChunk = false;
|
|
101
|
+
for (const c of chunks) {
|
|
102
|
+
if (c.startsWith("[tool:")) {
|
|
103
|
+
anyToolChunk = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!anyToolChunk) return content;
|
|
108
|
+
|
|
109
|
+
const out = chunks.map((chunk) => truncateSingleChunk(chunk, maxChars));
|
|
110
|
+
return out.join(CHUNK_SEPARATOR);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Truncate a single chunk if it's an oversized [tool:<name>] envelope.
|
|
114
|
+
// Returns the chunk unchanged if it's plain text, [reasoning], or a tool
|
|
115
|
+
// chunk under the cap. Helper used by both the multi-chunk path and the
|
|
116
|
+
// single-chunk fast path.
|
|
117
|
+
function truncateSingleChunk(chunk: string, maxChars: number): string {
|
|
118
|
+
if (!chunk.startsWith("[tool:")) return chunk;
|
|
119
|
+
const closeBracket = chunk.indexOf("] ");
|
|
120
|
+
if (closeBracket < 0) return chunk; // malformed; leave alone
|
|
121
|
+
const toolName = chunk.slice(6, closeBracket); // 6 = "[tool:".length
|
|
122
|
+
const payload = chunk.slice(closeBracket + 2);
|
|
123
|
+
if (payload.length <= maxChars) return chunk;
|
|
124
|
+
return `[tool:${toolName}] ${toolStripAnnotation(toolName, payload)}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Render a sequence of TemporalMessages as a single string for the distillation
|
|
129
|
+
* LLM. User messages pass through verbatim; assistant and tool messages have
|
|
130
|
+
* oversized tool outputs truncated via {@link truncateToolOutputsInContent}.
|
|
131
|
+
*
|
|
132
|
+
* Exported so tests can verify truncation on realistic message fixtures without
|
|
133
|
+
* spinning up a full distillSegment round trip.
|
|
134
|
+
*/
|
|
135
|
+
export function messagesToText(
|
|
136
|
+
messages: TemporalMessage[],
|
|
137
|
+
toolOutputMaxChars?: number,
|
|
138
|
+
): string {
|
|
139
|
+
const cap = toolOutputMaxChars ?? config().distillation.toolOutputMaxChars;
|
|
57
140
|
return messages
|
|
58
|
-
.map((m) =>
|
|
141
|
+
.map((m) => {
|
|
142
|
+
// User text is always signal — never truncate.
|
|
143
|
+
const body =
|
|
144
|
+
m.role === "user" ? m.content : truncateToolOutputsInContent(m.content, cap);
|
|
145
|
+
return `[${m.role}] (${formatTime(m.created_at)}) ${body}`;
|
|
146
|
+
})
|
|
59
147
|
.join("\n\n");
|
|
60
148
|
}
|
|
61
149
|
|
|
@@ -85,6 +173,48 @@ function latestObservations(
|
|
|
85
173
|
return row?.observations || undefined;
|
|
86
174
|
}
|
|
87
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Return the most recent gen>0 (meta) distillation observations for this
|
|
178
|
+
* session, or undefined when none exists. Used by `metaDistill` as the
|
|
179
|
+
* `<previous-meta-summary>` anchor on second-and-later consolidation rounds:
|
|
180
|
+
* the LLM updates the prior meta in place rather than re-deriving from
|
|
181
|
+
* scratch.
|
|
182
|
+
*
|
|
183
|
+
* Filters on `generation > 0` explicitly — gen-0 rows are raw segment
|
|
184
|
+
* observations and aren't a suitable anchor (same constraint that motivated
|
|
185
|
+
* the F1b SDK live-read for /compact summaries).
|
|
186
|
+
*
|
|
187
|
+
* Exported primarily for tests; `metaDistill` is the only production caller.
|
|
188
|
+
*/
|
|
189
|
+
export function latestMetaObservations(
|
|
190
|
+
projectPath: string,
|
|
191
|
+
sessionID: string,
|
|
192
|
+
): string | undefined {
|
|
193
|
+
return latestMeta(projectPath, sessionID)?.observations;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Internal: like `latestMetaObservations` but also returns the generation
|
|
198
|
+
* number, so `metaDistill` can derive the next gen number for the new row.
|
|
199
|
+
*/
|
|
200
|
+
function latestMeta(
|
|
201
|
+
projectPath: string,
|
|
202
|
+
sessionID: string,
|
|
203
|
+
): { observations: string; generation: number } | undefined {
|
|
204
|
+
const pid = ensureProject(projectPath);
|
|
205
|
+
const row = db()
|
|
206
|
+
.query(
|
|
207
|
+
`SELECT observations, generation FROM distillations
|
|
208
|
+
WHERE project_id = ? AND session_id = ? AND generation > 0
|
|
209
|
+
ORDER BY generation DESC, created_at DESC LIMIT 1`,
|
|
210
|
+
)
|
|
211
|
+
.get(pid, sessionID) as
|
|
212
|
+
| { observations: string; generation: number }
|
|
213
|
+
| null;
|
|
214
|
+
if (!row || !row.observations) return undefined;
|
|
215
|
+
return row;
|
|
216
|
+
}
|
|
217
|
+
|
|
88
218
|
/** Safely parse the source_ids JSON column. Defaults to [] on corrupt data. */
|
|
89
219
|
export function parseSourceIds(raw: string): string[] {
|
|
90
220
|
try {
|
|
@@ -107,16 +237,31 @@ export type Distillation = {
|
|
|
107
237
|
created_at: number;
|
|
108
238
|
};
|
|
109
239
|
|
|
110
|
-
/**
|
|
240
|
+
/**
|
|
241
|
+
* Load distillations for a session, oldest first.
|
|
242
|
+
*
|
|
243
|
+
* By default (`includeArchived = false`) skips rows that have been archived
|
|
244
|
+
* by `archiveDistillations` — typically gen-0 segments that were already
|
|
245
|
+
* consolidated into a gen>0 meta. This honors the docstring contract that
|
|
246
|
+
* archived rows are "excluded from the in-context prefix."
|
|
247
|
+
*
|
|
248
|
+
* Pre-F2, this function did NOT filter `archived` and so leaked merged
|
|
249
|
+
* gen-0 rows into `/compact` and overflow-recovery prompts alongside the
|
|
250
|
+
* meta that consolidated them. The default-false behavior fixes that
|
|
251
|
+
* divergence; `includeArchived: true` preserves the legacy shape for
|
|
252
|
+
* rare callers that explicitly want all rows.
|
|
253
|
+
*/
|
|
111
254
|
export function loadForSession(
|
|
112
255
|
projectPath: string,
|
|
113
256
|
sessionID: string,
|
|
257
|
+
includeArchived = false,
|
|
114
258
|
): Distillation[] {
|
|
115
259
|
const pid = ensureProject(projectPath);
|
|
260
|
+
const sql = includeArchived
|
|
261
|
+
? "SELECT id, project_id, session_id, observations, source_ids, generation, token_count, created_at FROM distillations WHERE project_id = ? AND session_id = ? ORDER BY created_at ASC"
|
|
262
|
+
: "SELECT id, project_id, session_id, observations, source_ids, generation, token_count, created_at FROM distillations WHERE project_id = ? AND session_id = ? AND archived = 0 ORDER BY created_at ASC";
|
|
116
263
|
const rows = db()
|
|
117
|
-
.query(
|
|
118
|
-
"SELECT id, project_id, session_id, observations, source_ids, generation, token_count, created_at FROM distillations WHERE project_id = ? AND session_id = ? ORDER BY created_at ASC",
|
|
119
|
-
)
|
|
264
|
+
.query(sql)
|
|
120
265
|
.all(pid, sessionID) as Array<{
|
|
121
266
|
id: string;
|
|
122
267
|
project_id: string;
|
|
@@ -200,10 +345,13 @@ function loadGen0(projectPath: string, sessionID: string): Distillation[] {
|
|
|
200
345
|
}));
|
|
201
346
|
}
|
|
202
347
|
|
|
203
|
-
// Archive distillations instead of deleting them. Archived entries are
|
|
204
|
-
// from the in-context prefix (loadDistillations filters
|
|
205
|
-
//
|
|
206
|
-
//
|
|
348
|
+
// Archive distillations instead of deleting them. Archived entries are
|
|
349
|
+
// excluded from the in-context prefix (`gradient.loadDistillations` filters
|
|
350
|
+
// `archived = 0`) and from `loadForSession`'s default path (post-F2). They
|
|
351
|
+
// remain searchable via BM25 recall (`search.ts` does not filter archived);
|
|
352
|
+
// vector recall (`embedding.ts`) skips them via `WHERE archived = 0`. This
|
|
353
|
+
// preserves a detailed "zoom-in" layer beneath the compressed gen-1 summary
|
|
354
|
+
// for BM25 callers while keeping the in-context prefix lean.
|
|
207
355
|
// Inspired by Cartridges (Eyuboglu et al., 2025): independently compressed
|
|
208
356
|
// representations remain composable and queryable after consolidation.
|
|
209
357
|
// Reference: https://arxiv.org/abs/2501.17390
|
|
@@ -387,16 +535,41 @@ async function distillSegment(input: {
|
|
|
387
535
|
return result;
|
|
388
536
|
}
|
|
389
537
|
|
|
390
|
-
|
|
538
|
+
/**
|
|
539
|
+
* Consolidate a session's gen-0 distillation segments into a higher-generation
|
|
540
|
+
* meta-distillation. On second-and-later rounds, anchors on the prior meta
|
|
541
|
+
* via `<previous-meta-summary>` so the LLM updates in place rather than
|
|
542
|
+
* re-deriving from scratch.
|
|
543
|
+
*
|
|
544
|
+
* Exported for tests; `run()` is the production entry point.
|
|
545
|
+
*/
|
|
546
|
+
export async function metaDistill(input: {
|
|
391
547
|
llm: LLMClient;
|
|
392
548
|
projectPath: string;
|
|
393
549
|
sessionID: string;
|
|
394
550
|
model?: { providerID: string; modelID: string };
|
|
395
551
|
}): Promise<DistillationResult | null> {
|
|
396
552
|
const existing = loadGen0(input.projectPath, input.sessionID);
|
|
397
|
-
if (existing.length < 3) return null;
|
|
398
553
|
|
|
399
|
-
|
|
554
|
+
// F2 anchor: when a prior gen>0 meta exists for this session, feed it back
|
|
555
|
+
// as <previous-meta-summary> so the LLM updates in place rather than
|
|
556
|
+
// re-deriving from scratch. Mirrors upstream OpenCode's <previous-summary>
|
|
557
|
+
// anchoring at compaction.ts:121-132. The `loadGen0` query already filters
|
|
558
|
+
// archived rows, so `existing` only contains gen-0 distillations created
|
|
559
|
+
// since the last meta-distill — no overlap with the anchor body.
|
|
560
|
+
const priorMeta = latestMeta(input.projectPath, input.sessionID);
|
|
561
|
+
|
|
562
|
+
// Threshold: first meta needs ≥3 gen-0 segments to consolidate. Subsequent
|
|
563
|
+
// anchored metas only need ≥1 new gen-0 since the prior meta already covers
|
|
564
|
+
// earlier history; without this distinction, every meta-distill round would
|
|
565
|
+
// need a fresh pile of segments and we'd lose the incremental-update benefit.
|
|
566
|
+
if (priorMeta) {
|
|
567
|
+
if (existing.length === 0) return null;
|
|
568
|
+
} else {
|
|
569
|
+
if (existing.length < 3) return null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const userContent = recursiveUser(existing, priorMeta?.observations);
|
|
400
573
|
|
|
401
574
|
const model = input.model ?? config().model;
|
|
402
575
|
const responseText = await input.llm.prompt(
|
|
@@ -409,25 +582,49 @@ async function metaDistill(input: {
|
|
|
409
582
|
const result = parseDistillationResult(responseText);
|
|
410
583
|
if (!result) return null;
|
|
411
584
|
|
|
412
|
-
// Store the meta-distillation at generation N+1
|
|
413
|
-
|
|
585
|
+
// Store the meta-distillation at generation N+1, where N is the highest
|
|
586
|
+
// generation in the merged inputs OR the prior meta's generation, whichever
|
|
587
|
+
// is greater. Pre-F2, `existing` was the full history (all gen-0 rows that
|
|
588
|
+
// ever existed for the session, including those merged by prior metas) so
|
|
589
|
+
// its generation max worked. With F2's archive filter, `existing` only
|
|
590
|
+
// covers new gen-0 since the last meta — we must consult the prior meta's
|
|
591
|
+
// generation explicitly to keep the chain monotonic.
|
|
592
|
+
const maxGen = Math.max(
|
|
593
|
+
...existing.map((d) => d.generation),
|
|
594
|
+
priorMeta?.generation ?? 0,
|
|
595
|
+
);
|
|
414
596
|
const allSourceIDs = existing.flatMap((d) => d.source_ids);
|
|
415
|
-
const metaId = storeDistillation({
|
|
416
|
-
projectPath: input.projectPath,
|
|
417
|
-
sessionID: input.sessionID,
|
|
418
|
-
observations: result.observations,
|
|
419
|
-
sourceIDs: allSourceIDs,
|
|
420
|
-
generation: maxGen + 1,
|
|
421
|
-
});
|
|
422
597
|
|
|
423
|
-
//
|
|
598
|
+
// Atomic: store the new meta row + archive the merged gen-0 rows in one
|
|
599
|
+
// transaction. Without this, a crash between the two would leave stale
|
|
600
|
+
// lineage (gen-N+1 meta stored but gen-0 rows un-archived, causing the
|
|
601
|
+
// next run to re-consolidate the same segments into a duplicate meta).
|
|
602
|
+
// Uses manual BEGIN/COMMIT because `bun:sqlite` and `node:sqlite` have
|
|
603
|
+
// incompatible transaction APIs (`.transaction()` vs nothing).
|
|
604
|
+
let metaId: string;
|
|
605
|
+
db().exec("BEGIN IMMEDIATE");
|
|
606
|
+
try {
|
|
607
|
+
metaId = storeDistillation({
|
|
608
|
+
projectPath: input.projectPath,
|
|
609
|
+
sessionID: input.sessionID,
|
|
610
|
+
observations: result.observations,
|
|
611
|
+
sourceIDs: allSourceIDs,
|
|
612
|
+
generation: maxGen + 1,
|
|
613
|
+
});
|
|
614
|
+
// Archive the gen-0 distillations that were merged into gen-1+.
|
|
615
|
+
// They remain searchable via BM25 recall but are excluded from the
|
|
616
|
+
// in-context prefix and (post-F2) from `loadForSession`'s default path.
|
|
617
|
+
archiveDistillations(existing.map((d) => d.id));
|
|
618
|
+
db().exec("COMMIT");
|
|
619
|
+
} catch (e) {
|
|
620
|
+
db().exec("ROLLBACK");
|
|
621
|
+
throw e;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Fire-and-forget OUTSIDE the transaction (async, no rollback needed).
|
|
424
625
|
if (embedding.isAvailable()) {
|
|
425
626
|
embedding.embedDistillation(metaId, result.observations);
|
|
426
627
|
}
|
|
427
628
|
|
|
428
|
-
// Archive the gen-0 distillations that were merged into gen-1+.
|
|
429
|
-
// They remain searchable via recall but excluded from the in-context prefix.
|
|
430
|
-
archiveDistillations(existing.map((d) => d.id));
|
|
431
|
-
|
|
432
629
|
return result;
|
|
433
630
|
}
|