@loreai/core 0.10.2 → 0.11.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.
Files changed (72) hide show
  1. package/dist/bun/config.d.ts +8 -0
  2. package/dist/bun/config.d.ts.map +1 -1
  3. package/dist/bun/db.d.ts.map +1 -1
  4. package/dist/bun/distillation.d.ts +74 -2
  5. package/dist/bun/distillation.d.ts.map +1 -1
  6. package/dist/bun/embedding.d.ts.map +1 -1
  7. package/dist/bun/gradient.d.ts +72 -0
  8. package/dist/bun/gradient.d.ts.map +1 -1
  9. package/dist/bun/index.d.ts +4 -2
  10. package/dist/bun/index.d.ts.map +1 -1
  11. package/dist/bun/index.js +530 -67
  12. package/dist/bun/index.js.map +4 -4
  13. package/dist/bun/prompt.d.ts +8 -2
  14. package/dist/bun/prompt.d.ts.map +1 -1
  15. package/dist/bun/temporal.d.ts +31 -0
  16. package/dist/bun/temporal.d.ts.map +1 -1
  17. package/dist/bun/types.d.ts +9 -0
  18. package/dist/bun/types.d.ts.map +1 -1
  19. package/dist/bun/worker-model.d.ts +90 -0
  20. package/dist/bun/worker-model.d.ts.map +1 -0
  21. package/dist/node/config.d.ts +8 -0
  22. package/dist/node/config.d.ts.map +1 -1
  23. package/dist/node/db.d.ts.map +1 -1
  24. package/dist/node/distillation.d.ts +74 -2
  25. package/dist/node/distillation.d.ts.map +1 -1
  26. package/dist/node/embedding.d.ts.map +1 -1
  27. package/dist/node/gradient.d.ts +72 -0
  28. package/dist/node/gradient.d.ts.map +1 -1
  29. package/dist/node/index.d.ts +4 -2
  30. package/dist/node/index.d.ts.map +1 -1
  31. package/dist/node/index.js +530 -67
  32. package/dist/node/index.js.map +4 -4
  33. package/dist/node/prompt.d.ts +8 -2
  34. package/dist/node/prompt.d.ts.map +1 -1
  35. package/dist/node/temporal.d.ts +31 -0
  36. package/dist/node/temporal.d.ts.map +1 -1
  37. package/dist/node/types.d.ts +9 -0
  38. package/dist/node/types.d.ts.map +1 -1
  39. package/dist/node/worker-model.d.ts +90 -0
  40. package/dist/node/worker-model.d.ts.map +1 -0
  41. package/dist/types/config.d.ts +8 -0
  42. package/dist/types/config.d.ts.map +1 -1
  43. package/dist/types/db.d.ts.map +1 -1
  44. package/dist/types/distillation.d.ts +74 -2
  45. package/dist/types/distillation.d.ts.map +1 -1
  46. package/dist/types/embedding.d.ts.map +1 -1
  47. package/dist/types/gradient.d.ts +72 -0
  48. package/dist/types/gradient.d.ts.map +1 -1
  49. package/dist/types/index.d.ts +4 -2
  50. package/dist/types/index.d.ts.map +1 -1
  51. package/dist/types/prompt.d.ts +8 -2
  52. package/dist/types/prompt.d.ts.map +1 -1
  53. package/dist/types/temporal.d.ts +31 -0
  54. package/dist/types/temporal.d.ts.map +1 -1
  55. package/dist/types/types.d.ts +9 -0
  56. package/dist/types/types.d.ts.map +1 -1
  57. package/dist/types/worker-model.d.ts +90 -0
  58. package/dist/types/worker-model.d.ts.map +1 -0
  59. package/package.json +1 -1
  60. package/src/config.ts +53 -6
  61. package/src/db.ts +57 -1
  62. package/src/distillation.ts +225 -28
  63. package/src/embedding.ts +7 -0
  64. package/src/gradient.ts +262 -8
  65. package/src/index.ts +16 -0
  66. package/src/lat-reader.ts +4 -4
  67. package/src/ltm.ts +17 -17
  68. package/src/prompt.ts +101 -0
  69. package/src/recall.ts +4 -4
  70. package/src/temporal.ts +41 -10
  71. package/src/types.ts +9 -0
  72. 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;AAsCrD,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"}
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"}
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loreai/core",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Shared memory engine for Lore — three-tier storage, distillation, gradient context management",
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.10 (10%). */
18
- ltm: z.number().min(0.02).max(0.3).default(0.10),
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(8),
24
- maxSegment: z.number().min(5).default(50),
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({ minMessages: 8, maxSegment: 50, metaThreshold: 10 }),
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 = 10;
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() {
@@ -310,6 +362,10 @@ export function db(): Database {
310
362
  instance = new Database(path);
311
363
  instance.exec("PRAGMA journal_mode = WAL");
312
364
  instance.exec("PRAGMA foreign_keys = ON");
365
+ // Retry for up to 5s when another connection holds the write lock (e.g.
366
+ // backgroundDistill's BEGIN IMMEDIATE overlapping with a recall query).
367
+ // Default is 0ms which throws SQLITE_BUSY immediately.
368
+ instance.exec("PRAGMA busy_timeout = 5000");
313
369
  // Return freed pages to the OS incrementally on each transaction commit
314
370
  // instead of accumulating a free-page list that bloats the file.
315
371
  instance.exec("PRAGMA auto_vacuum = INCREMENTAL");
@@ -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
- function messagesToText(messages: TemporalMessage[]): string {
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) => `[${m.role}] (${formatTime(m.created_at)}) ${m.content}`)
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
- /** Load all distillations for a session, oldest first. */
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 excluded
204
- // from the in-context prefix (loadDistillations filters them out) but remain
205
- // searchable via the recall tool (searchDistillations includes them). This
206
- // preserves a detailed "zoom-in" layer beneath the compressed gen-1 summary.
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
- async function metaDistill(input: {
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
- const userContent = recursiveUser(existing);
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
- const maxGen = Math.max(...existing.map((d) => d.generation));
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
- // Fire-and-forget: embed the meta-distillation for vector search
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
  }
package/src/embedding.ts CHANGED
@@ -12,6 +12,11 @@ import { db } from "./db";
12
12
  import { config } from "./config";
13
13
  import * as log from "./log";
14
14
 
15
+ /** Timeout for embedding API fetch calls (ms). Prevents a hanging API from
16
+ * blocking the recall tool indefinitely. 10s is generous for typical 100-500ms
17
+ * embedding calls but bounded enough to avoid minutes-long hangs. */
18
+ const EMBED_TIMEOUT_MS = 10_000;
19
+
15
20
  // ---------------------------------------------------------------------------
16
21
  // Provider interface
17
22
  // ---------------------------------------------------------------------------
@@ -58,6 +63,7 @@ class VoyageProvider implements EmbeddingProvider {
58
63
  input_type: inputType,
59
64
  output_dimension: this.dimensions,
60
65
  }),
66
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
61
67
  });
62
68
 
63
69
  if (!res.ok) {
@@ -112,6 +118,7 @@ class OpenAIProvider implements EmbeddingProvider {
112
118
  Authorization: `Bearer ${this.apiKey}`,
113
119
  },
114
120
  body: JSON.stringify(body),
121
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
115
122
  });
116
123
 
117
124
  if (!res.ok) {