recallnest 1.4.1 → 2.2.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.
Files changed (233) hide show
  1. package/.env +4 -0
  2. package/CLAUDE.md +7 -3
  3. package/README.md +202 -130
  4. package/README_CN.md +263 -190
  5. package/bin/recallnest.mjs +62 -0
  6. package/config.json +55 -0
  7. package/daily-recall.sh +23 -0
  8. package/integrations/setup-all.sh +31 -0
  9. package/logs/daily-recall.error.log +0 -0
  10. package/logs/daily-recall.log +30 -0
  11. package/logs/ingest-2026-04-11.log +110 -0
  12. package/logs/ingest-2026-04-12.log +90 -0
  13. package/logs/launchagent-stderr.log +0 -0
  14. package/logs/launchagent-stdout.log +0 -0
  15. package/package.json +10 -3
  16. package/src/access-tracker.ts +3 -2
  17. package/src/activity-counter.ts +109 -0
  18. package/src/adaptive-retrieval.ts +6 -5
  19. package/src/admission-control.ts +163 -0
  20. package/src/anchor-generator.ts +80 -0
  21. package/src/api-server.ts +10 -0
  22. package/src/audit-log.ts +122 -0
  23. package/src/auto-gc.ts +98 -29
  24. package/src/auto-recall.ts +33 -4
  25. package/src/capture-engine.ts +372 -23
  26. package/src/cascade-forget.ts +3 -2
  27. package/src/cli.ts +107 -5
  28. package/src/compat.ts +22 -0
  29. package/src/confidence-tracker.ts +210 -47
  30. package/src/conflict-store.ts +12 -3
  31. package/src/consolidation-engine.ts +296 -8
  32. package/src/context-collapse-renderer.ts +222 -0
  33. package/src/context-composer-stable-selection.ts +5 -1
  34. package/src/context-composer.ts +239 -4
  35. package/src/context-reconstructor.ts +404 -0
  36. package/src/conversation-importer.ts +441 -0
  37. package/src/data-checkup.ts +232 -0
  38. package/src/decay-engine.ts +84 -0
  39. package/src/distill-lock.ts +122 -0
  40. package/src/doctor.ts +8 -10
  41. package/src/dream-pipeline.ts +227 -0
  42. package/src/emotion-detector.ts +90 -0
  43. package/src/eval.ts +3 -2
  44. package/src/forget-engine.ts +269 -0
  45. package/src/frequency-tracker.ts +175 -0
  46. package/src/graph-export.ts +644 -0
  47. package/src/ingest.ts +36 -6
  48. package/src/interference-detector.ts +187 -0
  49. package/src/kg-extractor.ts +15 -24
  50. package/src/kg-store.ts +2 -1
  51. package/src/language-hook.ts +67 -0
  52. package/src/llm-client.ts +289 -6
  53. package/src/mcp-server.ts +707 -80
  54. package/src/memory-assets.ts +22 -5
  55. package/src/memory-evolution.ts +365 -0
  56. package/src/memory-lint.ts +429 -0
  57. package/src/memory-output.ts +69 -0
  58. package/src/memory-schema.ts +74 -2
  59. package/src/narrative-schema.ts +79 -0
  60. package/src/narrative-tagger.ts +164 -0
  61. package/src/pii-detector.ts +98 -0
  62. package/src/post-retrieval-filter.ts +76 -0
  63. package/src/prediction-engine.ts +253 -0
  64. package/src/preference-slots.ts +65 -2
  65. package/src/prospective-memory.ts +215 -0
  66. package/src/query-expander.ts +67 -0
  67. package/src/recall-governor.ts +183 -0
  68. package/src/retention-policy.ts +90 -0
  69. package/src/retriever.ts +828 -55
  70. package/src/rif.ts +34 -8
  71. package/src/runtime-config.ts +23 -8
  72. package/src/scope-inventory.ts +4 -3
  73. package/src/scope-policy.ts +4 -0
  74. package/src/session-distiller.ts +307 -0
  75. package/src/session-output.ts +40 -0
  76. package/src/session-schema.ts +37 -0
  77. package/src/session-store.ts +7 -2
  78. package/src/skill-engine.ts +208 -0
  79. package/src/skill-promotion.ts +293 -0
  80. package/src/skill-schema.ts +39 -0
  81. package/src/store.ts +96 -10
  82. package/src/topic-tag.ts +66 -0
  83. package/src/tracker.ts +3 -1
  84. package/src/ui-server.ts +80 -8
  85. package/src/workflow-observation-scope-review.ts +2 -1
  86. package/src/workflow-observation-store.ts +2 -1
  87. package/src/write-verifier.ts +126 -0
  88. package/.claude-plugin/CLAUDE.md +0 -29
  89. package/.claude-plugin/marketplace.json +0 -18
  90. package/.github/workflows/ci.yml +0 -28
  91. package/.mcp.json +0 -9
  92. package/CHANGELOG.md +0 -51
  93. package/DEMO.md +0 -94
  94. package/EVAL.md +0 -89
  95. package/FAILURES.md +0 -75
  96. package/OPERATIONS.md +0 -59
  97. package/ROADMAP.md +0 -198
  98. package/assets/recallnest-workbench.png +0 -0
  99. package/assets/ui/app.js +0 -807
  100. package/assets/ui/index.html +0 -154
  101. package/assets/ui/styles.css +0 -605
  102. package/assets/wechat-qr.jpg +0 -0
  103. package/daily-reflections/2026-03-05.md +0 -15
  104. package/daily-reflections/2026-03-06.md +0 -15
  105. package/daily-reflections/2026-03-07.md +0 -7
  106. package/daily-reflections/2026-03-08.md +0 -34
  107. package/daily-reflections/2026-03-09.md +0 -35
  108. package/daily-reflections/2026-03-10.md +0 -36
  109. package/daily-reflections/2026-03-11.md +0 -35
  110. package/daily-reflections/2026-03-12.md +0 -36
  111. package/daily-reflections/2026-03-13.md +0 -35
  112. package/daily-reflections/2026-03-14.md +0 -35
  113. package/daily-reflections/2026-03-15.md +0 -35
  114. package/daily-reflections/2026-03-16.md +0 -41
  115. package/daily-reflections/2026-03-17.md +0 -10
  116. package/daily-reflections/2026-03-18.md +0 -10
  117. package/daily-reflections/2026-03-19.md +0 -10
  118. package/daily-reflections/2026-03-20.md +0 -10
  119. package/daily-reflections/2026-03-21.md +0 -10
  120. package/daily-reflections/2026-03-22.md +0 -10
  121. package/daily-reflections/2026-03-23.md +0 -10
  122. package/daily-reflections/2026-03-24.md +0 -10
  123. package/daily-reflections/2026-03-25.md +0 -32
  124. package/daily-reflections/2026-03-26.md +0 -37
  125. package/daily-reflections/2026-03-27.md +0 -35
  126. package/daily-reflections/2026-03-28.md +0 -35
  127. package/daily-reflections/2026-03-29.md +0 -35
  128. package/daily-reflections/2026-03-30.md +0 -36
  129. package/daily-reflections/2026-03-31.md +0 -35
  130. package/daily-reflections/2026-04-01.md +0 -35
  131. package/docs/api-reference.md +0 -1291
  132. package/docs/architecture.md +0 -168
  133. package/docs/memory-boundary-contract.md +0 -226
  134. package/docs/memory-categories.md +0 -119
  135. package/docs/pipeline-audit-ultramemory.md +0 -177
  136. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  137. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  138. package/docs/plans/mcp-tiering-plan.md +0 -126
  139. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  140. package/docs/self-evolution.md +0 -198
  141. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  142. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  143. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  144. package/eval/continuity/results-2026-03-16.md +0 -117
  145. package/eval/continuity/results-2026-03-19.md +0 -453
  146. package/eval/continuity/runbook.md +0 -110
  147. package/eval/continuity/seed-cases.md +0 -54
  148. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  149. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  150. package/eval/reports/2026-03-06-baseline.md +0 -82
  151. package/eval/reports/query-expansion.md +0 -138
  152. package/lm +0 -19
  153. package/plan-v1.2-distributable.md +0 -105
  154. package/scripts/archive-distilled-junk.ts +0 -116
  155. package/scripts/backfill-l0l1l2.ts +0 -98
  156. package/scripts/blind-scan.ts +0 -588
  157. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  158. package/scripts/cleanup-step2-template-dups.ts +0 -113
  159. package/scripts/cleanup-step3-stale-content.ts +0 -149
  160. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  161. package/scripts/delete-distilled-junk.ts +0 -88
  162. package/scripts/distill-facts.ts +0 -305
  163. package/scripts/health-check.ts +0 -343
  164. package/scripts/incremental-ingest.sh +0 -71
  165. package/scripts/mlp-health-check.ts +0 -382
  166. package/scripts/pull-claude-desktop.ts +0 -515
  167. package/scripts/re-ingest-cc.sh +0 -66
  168. package/scripts/rebalance-memory-health.ts +0 -181
  169. package/scripts/run-desktop-ingest.sh +0 -11
  170. package/scripts/start-server.sh +0 -12
  171. package/scripts/viz-dashboard.html +0 -423
  172. package/scripts/viz-export.ts +0 -703
  173. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  174. package/src/__tests__/auto-consolidation.test.ts +0 -168
  175. package/src/__tests__/auto-recall.test.ts +0 -131
  176. package/src/__tests__/batch-dedup.test.ts +0 -65
  177. package/src/__tests__/capture-engine.test.ts +0 -1142
  178. package/src/__tests__/capture-heuristic.test.ts +0 -171
  179. package/src/__tests__/cascade-forget.test.ts +0 -210
  180. package/src/__tests__/category-min-scores.test.ts +0 -33
  181. package/src/__tests__/confidence-tracker.test.ts +0 -196
  182. package/src/__tests__/conflict-advisor.test.ts +0 -206
  183. package/src/__tests__/conflict-escalation.test.ts +0 -149
  184. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  185. package/src/__tests__/conflict-output.test.ts +0 -85
  186. package/src/__tests__/conflict-store.test.ts +0 -374
  187. package/src/__tests__/consolidation-engine.test.ts +0 -181
  188. package/src/__tests__/context-composer-pins.test.ts +0 -64
  189. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  190. package/src/__tests__/context-renderer.test.ts +0 -91
  191. package/src/__tests__/continuity-eval.test.ts +0 -275
  192. package/src/__tests__/continuity-schema.test.ts +0 -97
  193. package/src/__tests__/core-recall-eval.test.ts +0 -235
  194. package/src/__tests__/core-summary.test.ts +0 -109
  195. package/src/__tests__/dedup-actions.test.ts +0 -58
  196. package/src/__tests__/deterministic-id.test.ts +0 -39
  197. package/src/__tests__/doctor.test.ts +0 -303
  198. package/src/__tests__/drill-down.test.ts +0 -108
  199. package/src/__tests__/embedder.test.ts +0 -122
  200. package/src/__tests__/entity-resolver.test.ts +0 -58
  201. package/src/__tests__/event-segmenter.test.ts +0 -153
  202. package/src/__tests__/hotness-blend.test.ts +0 -76
  203. package/src/__tests__/ingest.test.ts +0 -360
  204. package/src/__tests__/kg-extractor.test.ts +0 -184
  205. package/src/__tests__/kg-store.test.ts +0 -198
  206. package/src/__tests__/llm-consolidation.test.ts +0 -226
  207. package/src/__tests__/memory-boundaries.test.ts +0 -237
  208. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  209. package/src/__tests__/memory-output.test.ts +0 -215
  210. package/src/__tests__/multi-vector.test.ts +0 -242
  211. package/src/__tests__/ppr-traversal.test.ts +0 -256
  212. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  213. package/src/__tests__/preference-matcher.test.ts +0 -230
  214. package/src/__tests__/preference-slots.test.ts +0 -106
  215. package/src/__tests__/prospective-memory.test.ts +0 -283
  216. package/src/__tests__/query-entity-detector.test.ts +0 -171
  217. package/src/__tests__/query-expander.test.ts +0 -34
  218. package/src/__tests__/recall-mode.test.ts +0 -64
  219. package/src/__tests__/result-synthesizer.test.ts +0 -198
  220. package/src/__tests__/resume-context.test.ts +0 -4743
  221. package/src/__tests__/retrieval-trace.test.ts +0 -213
  222. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  223. package/src/__tests__/retroactive-boost.test.ts +0 -223
  224. package/src/__tests__/scope-inventory.test.ts +0 -200
  225. package/src/__tests__/scope-policy.test.ts +0 -69
  226. package/src/__tests__/session-checkpoint.test.ts +0 -259
  227. package/src/__tests__/session-gc.test.ts +0 -215
  228. package/src/__tests__/store-scope-audit.test.ts +0 -81
  229. package/src/__tests__/temporal-parser.test.ts +0 -241
  230. package/src/__tests__/term-registry.test.ts +0 -121
  231. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  232. package/src/__tests__/version-manager.test.ts +0 -213
  233. package/src/__tests__/workflow-observation.test.ts +0 -400
@@ -13,7 +13,7 @@ import {
13
13
  buildTaskResultSections,
14
14
  } from "./context-composer-task-results.js";
15
15
  import type { RetrievalContext, RetrievalResult } from "./retriever.js";
16
- import type { ResumeContextResponse, SessionCheckpointRecord } from "./session-schema.js";
16
+ import type { EssentialContext, ResumeContextResponse, SessionCheckpointRecord } from "./session-schema.js";
17
17
  import { ResumeContextRequestSchema, ResumeContextResponseSchema } from "./session-schema.js";
18
18
  import { formatCheckpointRecallSummary } from "./session-output.js";
19
19
  import {
@@ -24,6 +24,10 @@ import {
24
24
  } from "./term-registry.js";
25
25
  import { synthesizeSection } from "./result-synthesizer.js";
26
26
  import type { LLMClient } from "./llm-client.js";
27
+ import { collapseResults, estimateTokens, type CollapseInput } from "./context-collapse-renderer.js";
28
+ import { filterByRelevance } from "./post-retrieval-filter.js";
29
+ import { reconstruct as runReconstruction } from "./context-reconstructor.js";
30
+ import { parseNarrative, isNarrativeModeEnabled } from "./narrative-schema.js";
27
31
  type ResumeCategory = "profile" | "preferences" | "entities" | "patterns" | "cases";
28
32
 
29
33
  interface ResumeRetriever {
@@ -154,6 +158,142 @@ function buildSummary(params: {
154
158
  return cleanText(parts.join(" "), 800);
155
159
  }
156
160
 
161
+ // ---------------------------------------------------------------------------
162
+ // CC-8: Post-Compact Reconstruction — assemble essential context
163
+ // ---------------------------------------------------------------------------
164
+
165
+ const ESSENTIAL_CONTEXT_TOKEN_BUDGET = 2000;
166
+
167
+ function buildEssentialContext(params: {
168
+ pinAssets: Array<PinAsset & { path: string }>;
169
+ patternResults: RetrievalResult[];
170
+ latestCheckpoint: SessionCheckpointRecord | null;
171
+ }): EssentialContext | undefined {
172
+ let tokensUsed = 0;
173
+
174
+ // 1. Pinned memories: take up to 3 most recent pins' summaries
175
+ const pinnedMemories: string[] = [];
176
+ for (const pin of params.pinAssets.slice(0, 3)) {
177
+ const text = pin.summary || pin.title;
178
+ const cost = estimateTokens(text);
179
+ if (tokensUsed + cost > ESSENTIAL_CONTEXT_TOKEN_BUDGET) break;
180
+ pinnedMemories.push(text);
181
+ tokensUsed += cost;
182
+ }
183
+
184
+ // 2. Active patterns: take top 1-2 by score
185
+ const activePatterns: string[] = [];
186
+ const sortedPatterns = [...params.patternResults].sort((a, b) => b.score - a.score);
187
+ for (const pattern of sortedPatterns.slice(0, 2)) {
188
+ const text = cleanText(pattern.entry.text, 200);
189
+ const cost = estimateTokens(text);
190
+ if (tokensUsed + cost > ESSENTIAL_CONTEXT_TOKEN_BUDGET) break;
191
+ activePatterns.push(text);
192
+ tokensUsed += cost;
193
+ }
194
+
195
+ // 3. Open loops from latest checkpoint
196
+ const openLoops: string[] = [];
197
+ if (params.latestCheckpoint?.openLoops) {
198
+ for (const loop of params.latestCheckpoint.openLoops.slice(0, 3)) {
199
+ const cost = estimateTokens(loop);
200
+ if (tokensUsed + cost > ESSENTIAL_CONTEXT_TOKEN_BUDGET) break;
201
+ openLoops.push(loop);
202
+ tokensUsed += cost;
203
+ }
204
+ }
205
+
206
+ // Return undefined if nothing was collected
207
+ if (pinnedMemories.length === 0 && activePatterns.length === 0 && openLoops.length === 0) {
208
+ return undefined;
209
+ }
210
+
211
+ return {
212
+ ...(pinnedMemories.length > 0 ? { pinnedMemories } : {}),
213
+ ...(activePatterns.length > 0 ? { activePatterns } : {}),
214
+ ...(openLoops.length > 0 ? { openLoops } : {}),
215
+ };
216
+ }
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // MP-3: Ultra-Light Wake-up — <300 token resume for low-budget terminals
220
+ // ---------------------------------------------------------------------------
221
+
222
+ const LIGHT_TOKEN_BUDGET = 300;
223
+ const LIGHT_MEMORY_CHAR_LIMIT = 200; // ~50 tokens
224
+
225
+ export interface LightResumeResult {
226
+ text: string;
227
+ resolvedScope?: string;
228
+ generatedAt: string;
229
+ }
230
+
231
+ export async function composeLightResumeContext(
232
+ deps: ResumeContextDeps,
233
+ rawInput: unknown,
234
+ ): Promise<LightResumeResult> {
235
+ const input = ResumeContextRequestSchema.parse(rawInput);
236
+ const latestCheckpoint = await resolveLatestCheckpoint(deps.checkpointStore, {
237
+ includeLatestCheckpoint: true,
238
+ sessionId: input.sessionId,
239
+ scope: input.scope,
240
+ });
241
+
242
+ const resolvedScope = input.scope || latestCheckpoint?.resolvedScope;
243
+
244
+ // Retrieve top 3 stable memories (profile + preferences + entities combined)
245
+ const stableResults = await retrieveCandidates(deps.retriever, {
246
+ query: input.task || "identity key facts and preferences",
247
+ limit: 3,
248
+ scope: resolvedScope,
249
+ });
250
+
251
+ // Pin assets provide high-signal stable context
252
+ const pinAssets = (deps.listPins || listPinAssets)(3);
253
+
254
+ const parts: string[] = [];
255
+ let tokensUsed = 0;
256
+
257
+ // 1. Checkpoint summary (1 sentence, ~30 tokens)
258
+ if (latestCheckpoint) {
259
+ const cpLine = `Last session (${latestCheckpoint.sessionId}): ${cleanText(latestCheckpoint.summary, 120)}`;
260
+ parts.push(cpLine);
261
+ tokensUsed += estimateTokens(cpLine);
262
+ }
263
+
264
+ // 2. Top pinned memories (most reliable stable context)
265
+ if (pinAssets.length > 0) {
266
+ for (const pin of pinAssets) {
267
+ if (tokensUsed >= LIGHT_TOKEN_BUDGET - 40) break;
268
+ const line = `- ${cleanText(pin.summary || pin.title, LIGHT_MEMORY_CHAR_LIMIT)}`;
269
+ parts.push(line);
270
+ tokensUsed += estimateTokens(line);
271
+ }
272
+ }
273
+
274
+ // 3. Top stable memories (fill remaining budget)
275
+ if (stableResults.length > 0 && tokensUsed < LIGHT_TOKEN_BUDGET - 40) {
276
+ const pinTexts = new Set(pinAssets.map(p => (p.summary || p.title).toLowerCase()));
277
+ for (const r of stableResults) {
278
+ if (tokensUsed >= LIGHT_TOKEN_BUDGET - 40) break;
279
+ const textLower = r.entry.text.toLowerCase().slice(0, 100);
280
+ if (pinTexts.has(textLower)) continue;
281
+ const line = `- ${cleanText(r.entry.text, LIGHT_MEMORY_CHAR_LIMIT)}`;
282
+ parts.push(line);
283
+ tokensUsed += estimateTokens(line);
284
+ }
285
+ }
286
+
287
+ // 4. Upgrade hint
288
+ parts.push("\nFor complete context, call resume_context(mode='full').");
289
+
290
+ return {
291
+ text: parts.join("\n"),
292
+ resolvedScope,
293
+ generatedAt: new Date().toISOString(),
294
+ };
295
+ }
296
+
157
297
  export async function composeResumeContext(
158
298
  deps: ResumeContextDeps,
159
299
  rawInput: unknown,
@@ -221,6 +361,17 @@ export async function composeResumeContext(
221
361
  const preferenceResults = mergeRetrievalResults(preferenceResultSets, Math.max(4, stableLimit * 3));
222
362
  const entityResults = mergeRetrievalResults(entityResultSets, Math.max(4, stableLimit * 3));
223
363
 
364
+ // E-2: Post-retrieval LLM relevance filter for pattern/case results.
365
+ // Only fires when LLM is available; stable context (profile/preferences/entities) skipped
366
+ // because those are identity-level and already curated by the selection pipeline.
367
+ const llm = deps.llm ?? null;
368
+ const [filteredPatterns, filteredCases] = llm && taskSeed
369
+ ? await Promise.all([
370
+ filterByRelevance(patternResults, taskSeed, llm),
371
+ filterByRelevance(caseResults, taskSeed, llm),
372
+ ])
373
+ : [patternResults, caseResults];
374
+
224
375
  const continuityTask = looksLikeContinuityTask(taskSeed);
225
376
  const pinAssets = (deps.listPins || listPinAssets)(Math.max(4, stableLimit * 2));
226
377
  const {
@@ -246,8 +397,8 @@ export async function composeResumeContext(
246
397
  limit,
247
398
  scope,
248
399
  }),
249
- patternResults,
250
- caseResults,
400
+ patternResults: filteredPatterns,
401
+ caseResults: filteredCases,
251
402
  continuityTask,
252
403
  hasLatestCheckpoint: Boolean(latestCheckpoint),
253
404
  taskLimit,
@@ -258,7 +409,6 @@ export async function composeResumeContext(
258
409
 
259
410
  // Tier 3.5: Optionally synthesize sections into coherent narratives via LLM.
260
411
  // Only activates when RECALLNEST_SYNTHESIZE=true and llm is provided.
261
- const llm = deps.llm ?? null;
262
412
  const queryHint = taskSeed || resolvedScope || "general";
263
413
  const [synthStable, synthPatterns, synthCases] = await Promise.all([
264
414
  synthesizeSection(stableContext, queryHint, llm),
@@ -266,6 +416,83 @@ export async function composeResumeContext(
266
416
  synthesizeSection(recentCases, queryHint, llm),
267
417
  ]);
268
418
 
419
+ // HP-narrative: Group recalled items by life period for narrative context
420
+ let narrativeGroups: Array<{ period: string; items: string[] }> | undefined;
421
+ if (isNarrativeModeEnabled()) {
422
+ const allNarrativeResults = [...profileResults, ...preferenceResults, ...entityResults, ...filteredPatterns, ...filteredCases];
423
+ const periodMap = new Map<string, string[]>();
424
+ for (const r of allNarrativeResults) {
425
+ const narrative = parseNarrative(r.entry.metadata);
426
+ if (!narrative) continue;
427
+ const items = periodMap.get(narrative.lifePeriodLabel) ?? [];
428
+ items.push(cleanText(r.entry.text, 120));
429
+ periodMap.set(narrative.lifePeriodLabel, items);
430
+ }
431
+ if (periodMap.size > 0) {
432
+ narrativeGroups = [...periodMap.entries()]
433
+ .map(([period, items]) => ({ period, items: items.slice(0, 5) }));
434
+ }
435
+ }
436
+
437
+ // CC-7: Collapse rendering — build mixed-granularity view of all recalled items.
438
+ // Gathers all retrieval results, deduplicates, and renders at L0/L1/L2 based on score.
439
+ const allResults: RetrievalResult[] = [];
440
+ const seenIds = new Set<string>();
441
+ for (const r of [...profileResults, ...preferenceResults, ...entityResults, ...filteredPatterns, ...filteredCases]) {
442
+ if (!seenIds.has(r.entry.id)) {
443
+ seenIds.add(r.entry.id);
444
+ allResults.push(r);
445
+ }
446
+ }
447
+ const collapseInput: CollapseInput[] = allResults.map(r => ({
448
+ entryId: r.entry.id,
449
+ text: r.entry.text,
450
+ metadata: r.entry.metadata,
451
+ score: r.score,
452
+ timestamp: r.entry.timestamp,
453
+ }));
454
+ const collapsedItems = collapseInput.length > 0
455
+ ? collapseResults(collapseInput)
456
+ : undefined;
457
+
458
+ // CC-8: Build essential context from pinned memories, top patterns, and open loops.
459
+ const essentialContext = buildEssentialContext({
460
+ pinAssets,
461
+ patternResults: filteredPatterns,
462
+ latestCheckpoint,
463
+ });
464
+
465
+ // Phase 4: Constructive retrieval reconstruction for resume context
466
+ // Pass checkpoint openLoops/nextActions/scope into reconstruction prompt
467
+ let reconstructedContext: string | undefined;
468
+ let reconstructionConfidence: number | undefined;
469
+ let reconstructionContradictions: Array<{ memoryIds: [string, string]; description: string }> | undefined;
470
+ const constructiveFlag = process.env.RECALLNEST_CONSTRUCTIVE_RETRIEVAL === "true";
471
+ if (constructiveFlag && deps.llm?.isAvailable?.()) {
472
+ const allReconResults = [...profileResults, ...preferenceResults, ...entityResults, ...filteredPatterns, ...filteredCases];
473
+ if (allReconResults.length >= 3) {
474
+ try {
475
+ const taskQuery = latestCheckpoint?.summary ?? taskSeed ?? "general context";
476
+ const checkpointContext = latestCheckpoint ? {
477
+ openLoops: latestCheckpoint.openLoops,
478
+ nextActions: latestCheckpoint.nextActions,
479
+ scope: resolvedScope,
480
+ } : resolvedScope ? { scope: resolvedScope } : undefined;
481
+ const recon = await runReconstruction(
482
+ { query: taskQuery, results: allReconResults, mode: "resume", maxTokens: 600, checkpointContext },
483
+ deps.llm,
484
+ );
485
+ if (recon.reconstructed) {
486
+ reconstructedContext = recon.reconstructed;
487
+ reconstructionConfidence = recon.confidence;
488
+ }
489
+ if (recon.contradictions.length > 0) {
490
+ reconstructionContradictions = recon.contradictions;
491
+ }
492
+ } catch { /* silent fallback */ }
493
+ }
494
+ }
495
+
269
496
  const response = {
270
497
  summary: buildSummary({
271
498
  stableContext: synthStable,
@@ -277,6 +504,8 @@ export async function composeResumeContext(
277
504
  stableContext: synthStable,
278
505
  relevantPatterns: synthPatterns,
279
506
  recentCases: synthCases,
507
+ collapsedItems: collapsedItems && collapsedItems.length > 0 ? collapsedItems : undefined,
508
+ essentialContext,
280
509
  latestCheckpoint: latestCheckpoint
281
510
  ? {
282
511
  sessionId: latestCheckpoint.sessionId,
@@ -285,6 +514,8 @@ export async function composeResumeContext(
285
514
  updatedAt: latestCheckpoint.updatedAt,
286
515
  }
287
516
  : undefined,
517
+ injectionHint: "user_attachment" as const,
518
+ ephemeral: true,
288
519
  responseMode: recallOnlyTask ? "recall-only" as const : "default" as const,
289
520
  responseGuidance: recallOnlyTask
290
521
  ? (
@@ -293,6 +524,10 @@ export async function composeResumeContext(
293
524
  : "Recall-only mode: answer only from the recalled stable context items. Keep the reply brief and do not expand into extra rules, examples, or local writing docs unless the user explicitly asks."
294
525
  )
295
526
  : undefined,
527
+ reconstructedContext,
528
+ reconstructionConfidence,
529
+ reconstructionContradictions,
530
+ narrativeGroups,
296
531
  generatedAt: new Date().toISOString(),
297
532
  };
298
533