@remnic/core 9.3.629 → 9.3.630

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 (211) hide show
  1. package/dist/access-cli.js +13 -13
  2. package/dist/access-http.d.ts +5 -4
  3. package/dist/access-http.js +6 -6
  4. package/dist/access-mcp.d.ts +5 -4
  5. package/dist/access-mcp.js +5 -5
  6. package/dist/{access-service-BdThkfIE.d.ts → access-service-C4v-eFjB.d.ts} +2 -2
  7. package/dist/access-service.d.ts +5 -4
  8. package/dist/access-service.js +4 -4
  9. package/dist/action-confidence.d.ts +1 -1
  10. package/dist/active-memory-bridge.d.ts +1 -1
  11. package/dist/active-recall.d.ts +1 -1
  12. package/dist/active-recall.js +1 -1
  13. package/dist/behavior-learner.d.ts +1 -1
  14. package/dist/behavior-signals.d.ts +1 -1
  15. package/dist/bootstrap.d.ts +4 -3
  16. package/dist/briefing.d.ts +1 -1
  17. package/dist/briefing.js +2 -2
  18. package/dist/buffer-surprise-report.d.ts +1 -1
  19. package/dist/buffer.d.ts +1 -1
  20. package/dist/calibration.d.ts +1 -1
  21. package/dist/causal-behavior.d.ts +1 -1
  22. package/dist/causal-consolidation.d.ts +1 -1
  23. package/dist/causal-consolidation.js +3 -3
  24. package/dist/{chunk-GE7Q7KXP.js → chunk-2VJ7AJFX.js} +2 -2
  25. package/dist/{chunk-KVFYTRMV.js → chunk-4QEUKASL.js} +2 -2
  26. package/dist/{chunk-KB4MFBF5.js → chunk-5S6IREG3.js} +3 -3
  27. package/dist/{chunk-LQYTQCXM.js → chunk-6LBQL5US.js} +2 -2
  28. package/dist/{chunk-TZDSNIRO.js → chunk-ADOD7PJC.js} +5 -5
  29. package/dist/{chunk-54KDA6UK.js → chunk-BL33LBTN.js} +3 -3
  30. package/dist/{chunk-532VCWYW.js → chunk-BWK5EEKS.js} +2 -2
  31. package/dist/{chunk-KKTXCFD7.js → chunk-EORL2IDM.js} +39 -8
  32. package/dist/{chunk-KKTXCFD7.js.map → chunk-EORL2IDM.js.map} +1 -1
  33. package/dist/{chunk-JXHMAQYT.js → chunk-F6USGHMO.js} +4 -4
  34. package/dist/{chunk-NKCW223V.js → chunk-GXWFZYSR.js} +2 -2
  35. package/dist/{chunk-XXO5TI3B.js → chunk-K3BTOW7N.js} +3 -3
  36. package/dist/{chunk-N5RGXWLQ.js → chunk-MQ24KOOR.js} +2 -2
  37. package/dist/{chunk-3MNBW7R7.js → chunk-NRQJBK36.js} +2 -2
  38. package/dist/{chunk-3R2UZV3U.js → chunk-OOFBE62K.js} +2 -2
  39. package/dist/{chunk-MVQN73GT.js → chunk-OQMR2SDZ.js} +2 -2
  40. package/dist/{chunk-UGHUNQ74.js → chunk-RSKUUEBA.js} +73 -1
  41. package/dist/chunk-RSKUUEBA.js.map +1 -0
  42. package/dist/{chunk-QDV6VAD4.js → chunk-S5W37FPX.js} +2 -2
  43. package/dist/{chunk-57QXN2CS.js → chunk-SACS6KE6.js} +2 -2
  44. package/dist/{chunk-JKCDQBDW.js → chunk-UE57H4MA.js} +2 -2
  45. package/dist/{chunk-OLNNOHBC.js → chunk-VUTPRX7K.js} +20 -14
  46. package/dist/{chunk-OLNNOHBC.js.map → chunk-VUTPRX7K.js.map} +1 -1
  47. package/dist/{chunk-3GLCUPXP.js → chunk-YJOWWRRS.js} +429 -45
  48. package/dist/chunk-YJOWWRRS.js.map +1 -0
  49. package/dist/{chunk-P2D2MM47.js → chunk-ZZSXUZF3.js} +2 -2
  50. package/dist/{cli-DAsHklrf.d.ts → cli-B_6EMiQc.d.ts} +3 -3
  51. package/dist/cli.d.ts +6 -5
  52. package/dist/cli.js +17 -17
  53. package/dist/compounding/engine.d.ts +1 -1
  54. package/dist/compounding/engine.js +2 -2
  55. package/dist/compounding/preference-consolidator.d.ts +1 -1
  56. package/dist/compression-optimizer.d.ts +1 -1
  57. package/dist/config.d.ts +1 -1
  58. package/dist/config.js +1 -1
  59. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  60. package/dist/connectors/codex-materialize-runner.js +2 -2
  61. package/dist/connectors/codex-materialize.d.ts +1 -1
  62. package/dist/connectors/index.d.ts +1 -1
  63. package/dist/connectors/index.js +2 -2
  64. package/dist/consolidation-provenance-check.d.ts +1 -1
  65. package/dist/consolidation-undo.d.ts +1 -1
  66. package/dist/contradiction/index.d.ts +1 -1
  67. package/dist/conversation-index/backend.d.ts +1 -1
  68. package/dist/conversation-index/chunker.d.ts +1 -1
  69. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  70. package/dist/conversation-index/indexer.d.ts +1 -1
  71. package/dist/conversation-index/search.d.ts +1 -1
  72. package/dist/day-summary.d.ts +1 -1
  73. package/dist/delinearize.d.ts +1 -1
  74. package/dist/direct-answer-wiring.d.ts +1 -1
  75. package/dist/direct-answer.d.ts +1 -1
  76. package/dist/embedding-fallback.d.ts +1 -1
  77. package/dist/enrichment/index.d.ts +1 -1
  78. package/dist/entity-retrieval.d.ts +1 -1
  79. package/dist/entity-retrieval.js +2 -2
  80. package/dist/entity-schema.d.ts +1 -1
  81. package/dist/explicit-capture.d.ts +4 -3
  82. package/dist/extraction-judge-telemetry.d.ts +1 -1
  83. package/dist/extraction-judge-training.d.ts +1 -1
  84. package/dist/extraction-judge.d.ts +1 -1
  85. package/dist/extraction.d.ts +1 -1
  86. package/dist/fallback-llm.d.ts +1 -1
  87. package/dist/identity-continuity.d.ts +1 -1
  88. package/dist/importance.d.ts +1 -1
  89. package/dist/index.d.ts +8 -8
  90. package/dist/index.js +22 -22
  91. package/dist/intent.d.ts +1 -1
  92. package/dist/lcm/engine.d.ts +1 -1
  93. package/dist/lcm/index.d.ts +1 -1
  94. package/dist/lcm/tools.d.ts +1 -1
  95. package/dist/lifecycle.d.ts +1 -1
  96. package/dist/live-connectors-runner.d.ts +1 -1
  97. package/dist/local-llm.d.ts +1 -1
  98. package/dist/maintenance/memory-governance.d.ts +1 -1
  99. package/dist/maintenance/memory-governance.js +2 -2
  100. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
  101. package/dist/maintenance/rebuild-memory-projection.js +3 -3
  102. package/dist/mcp-memory-inspector-app.d.ts +5 -4
  103. package/dist/memory-action-policy.d.ts +1 -1
  104. package/dist/memory-cache.d.ts +1 -1
  105. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  106. package/dist/memory-projection-store.d.ts +1 -1
  107. package/dist/memory-provenance.d.ts +1 -1
  108. package/dist/memory-worth-outcomes.d.ts +1 -1
  109. package/dist/models-json.d.ts +1 -1
  110. package/dist/namespaces/migrate.d.ts +1 -1
  111. package/dist/namespaces/migrate.js +3 -3
  112. package/dist/namespaces/principal.d.ts +1 -1
  113. package/dist/namespaces/search.d.ts +1 -1
  114. package/dist/namespaces/storage.d.ts +1 -1
  115. package/dist/namespaces/storage.js +2 -2
  116. package/dist/native-knowledge.d.ts +1 -1
  117. package/dist/operator-toolkit.d.ts +1 -1
  118. package/dist/operator-toolkit.js +6 -6
  119. package/dist/{orchestrator-BexeSJ2j.d.ts → orchestrator-Dlw3ae4B.d.ts} +101 -10
  120. package/dist/orchestrator.d.ts +4 -3
  121. package/dist/orchestrator.js +10 -10
  122. package/dist/patterns-cli.d.ts +1 -1
  123. package/dist/policy-runtime.d.ts +1 -1
  124. package/dist/qmd-recall-cache.d.ts +1 -1
  125. package/dist/qmd.d.ts +1 -1
  126. package/dist/recall-disclosure-escalation.d.ts +1 -1
  127. package/dist/recall-explain-renderer.d.ts +1 -1
  128. package/dist/recall-planner-llm.d.ts +1 -1
  129. package/dist/recall-state.d.ts +1 -1
  130. package/dist/recall-tag-filter.d.ts +1 -1
  131. package/dist/recall-xray-cli.d.ts +1 -1
  132. package/dist/recall-xray-renderer.d.ts +1 -1
  133. package/dist/recall-xray.d.ts +1 -1
  134. package/dist/resolve-auth-token.d.ts +1 -1
  135. package/dist/resume-bundles.js +2 -2
  136. package/dist/retrieval-agents.d.ts +1 -1
  137. package/dist/retrieval-tiers.d.ts +1 -1
  138. package/dist/routing/engine.d.ts +1 -1
  139. package/dist/routing/store.d.ts +1 -1
  140. package/dist/schemas.d.ts +22 -22
  141. package/dist/search/embed-helper.d.ts +1 -1
  142. package/dist/search/factory.d.ts +1 -1
  143. package/dist/search/index.d.ts +1 -1
  144. package/dist/search/lancedb-backend.d.ts +1 -1
  145. package/dist/search/meilisearch-backend.d.ts +1 -1
  146. package/dist/search/noop-backend.d.ts +1 -1
  147. package/dist/search/orama-backend.d.ts +1 -1
  148. package/dist/search/port.d.ts +1 -1
  149. package/dist/search/remote-backend.d.ts +1 -1
  150. package/dist/{semantic-consolidation-PwkzNfdK.d.ts → semantic-consolidation-C4sefXEI.d.ts} +1 -1
  151. package/dist/semantic-consolidation.d.ts +2 -2
  152. package/dist/semantic-consolidation.js +3 -3
  153. package/dist/semantic-rule-promotion.js +2 -2
  154. package/dist/semantic-rule-verifier.d.ts +1 -1
  155. package/dist/semantic-rule-verifier.js +2 -2
  156. package/dist/session-observer-bands.d.ts +1 -1
  157. package/dist/session-observer-state.d.ts +1 -1
  158. package/dist/shared-context/manager.d.ts +1 -1
  159. package/dist/signal.d.ts +1 -1
  160. package/dist/storage.d.ts +38 -2
  161. package/dist/storage.js +5 -3
  162. package/dist/summarizer.d.ts +1 -1
  163. package/dist/summary-snapshot.d.ts +1 -1
  164. package/dist/temporal-supersession.d.ts +1 -1
  165. package/dist/temporal-validity.d.ts +1 -1
  166. package/dist/threading.d.ts +1 -1
  167. package/dist/tier-migration.d.ts +1 -1
  168. package/dist/tier-routing.d.ts +1 -1
  169. package/dist/topics.d.ts +1 -1
  170. package/dist/transcript.d.ts +1 -1
  171. package/dist/transfer/types.d.ts +12 -12
  172. package/dist/{types-BCF2wqKa.d.ts → types-2vqxmO0j.d.ts} +39 -10
  173. package/dist/types.d.ts +1 -1
  174. package/dist/utility-runtime.d.ts +1 -1
  175. package/dist/verified-recall.js +2 -2
  176. package/package.json +1 -1
  177. package/src/orchestrator.ts +16 -0
  178. package/src/storage.ts +100 -0
  179. package/src/wearables/cli.ts +6 -0
  180. package/src/wearables/config.test.ts +33 -4
  181. package/src/wearables/config.ts +39 -7
  182. package/src/wearables/memory-gen.test.ts +416 -1
  183. package/src/wearables/memory-gen.ts +381 -23
  184. package/src/wearables/pipeline.test.ts +309 -1
  185. package/src/wearables/pipeline.ts +131 -9
  186. package/src/wearables/service.test.ts +172 -0
  187. package/src/wearables/service.ts +84 -3
  188. package/src/wearables/storage-io.test.ts +81 -0
  189. package/src/wearables/trust.test.ts +123 -0
  190. package/src/wearables/trust.ts +168 -0
  191. package/src/wearables/types.ts +37 -8
  192. package/dist/chunk-3GLCUPXP.js.map +0 -1
  193. package/dist/chunk-UGHUNQ74.js.map +0 -1
  194. /package/dist/{chunk-GE7Q7KXP.js.map → chunk-2VJ7AJFX.js.map} +0 -0
  195. /package/dist/{chunk-KVFYTRMV.js.map → chunk-4QEUKASL.js.map} +0 -0
  196. /package/dist/{chunk-KB4MFBF5.js.map → chunk-5S6IREG3.js.map} +0 -0
  197. /package/dist/{chunk-LQYTQCXM.js.map → chunk-6LBQL5US.js.map} +0 -0
  198. /package/dist/{chunk-TZDSNIRO.js.map → chunk-ADOD7PJC.js.map} +0 -0
  199. /package/dist/{chunk-54KDA6UK.js.map → chunk-BL33LBTN.js.map} +0 -0
  200. /package/dist/{chunk-532VCWYW.js.map → chunk-BWK5EEKS.js.map} +0 -0
  201. /package/dist/{chunk-JXHMAQYT.js.map → chunk-F6USGHMO.js.map} +0 -0
  202. /package/dist/{chunk-NKCW223V.js.map → chunk-GXWFZYSR.js.map} +0 -0
  203. /package/dist/{chunk-XXO5TI3B.js.map → chunk-K3BTOW7N.js.map} +0 -0
  204. /package/dist/{chunk-N5RGXWLQ.js.map → chunk-MQ24KOOR.js.map} +0 -0
  205. /package/dist/{chunk-3MNBW7R7.js.map → chunk-NRQJBK36.js.map} +0 -0
  206. /package/dist/{chunk-3R2UZV3U.js.map → chunk-OOFBE62K.js.map} +0 -0
  207. /package/dist/{chunk-MVQN73GT.js.map → chunk-OQMR2SDZ.js.map} +0 -0
  208. /package/dist/{chunk-QDV6VAD4.js.map → chunk-S5W37FPX.js.map} +0 -0
  209. /package/dist/{chunk-57QXN2CS.js.map → chunk-SACS6KE6.js.map} +0 -0
  210. /package/dist/{chunk-JKCDQBDW.js.map → chunk-UE57H4MA.js.map} +0 -0
  211. /package/dist/{chunk-P2D2MM47.js.map → chunk-ZZSXUZF3.js.map} +0 -0
@@ -28,7 +28,16 @@
28
28
  */
29
29
 
30
30
  import { scoreImportance } from "../importance.js";
31
+ import type { JudgeBatchResult, JudgeCandidate } from "../extraction-judge.js";
32
+ import { getVerdictKind } from "../extraction-judge.js";
31
33
  import { describeErrorForOperator } from "./errors.js";
34
+ import {
35
+ computeTrustScore,
36
+ decideSmart,
37
+ findCorroboration,
38
+ type CorroborationContext,
39
+ type TrustEvidence,
40
+ } from "./trust.js";
32
41
  import type {
33
42
  BufferTurn,
34
43
  ExtractedFact,
@@ -54,7 +63,7 @@ import type {
54
63
  export function memoryStatusForMode(
55
64
  mode: WearableMemoryMode,
56
65
  ): Extract<MemoryStatus, "active" | "pending_review"> {
57
- return mode === "auto" ? "active" : "pending_review";
66
+ return mode === "auto" || mode === "smart" ? "active" : "pending_review";
58
67
  }
59
68
 
60
69
  /** Narrow writer interface satisfied by `StorageManager`. */
@@ -75,19 +84,70 @@ export interface WearableMemoryWriter {
75
84
  },
76
85
  ): Promise<string>;
77
86
  hasFactContentHash(content: string): Promise<boolean>;
87
+ /**
88
+ * Locate an earlier wearable write of the same content (any status).
89
+ * Optional: enables in-place promotion when re-scoring with stronger
90
+ * evidence.
91
+ */
92
+ findWearableMemoryByContent?(
93
+ content: string,
94
+ ): Promise<{ id: string; status: MemoryStatus | undefined } | null>;
95
+ /**
96
+ * Promote a pending_review wearable memory to active, merging trust
97
+ * evidence. Returns false when missing or no longer pending.
98
+ */
99
+ promoteWearableMemory?(
100
+ id: string,
101
+ attributeUpdates: Record<string, string>,
102
+ confidence?: number,
103
+ ): Promise<boolean>;
104
+ /**
105
+ * Demote a pending_review wearable memory to rejected on an explicit
106
+ * judge-reject re-verdict. Returns false when missing or no longer
107
+ * pending. Active rows are never auto-demoted.
108
+ */
109
+ demoteWearableMemory?(
110
+ id: string,
111
+ attributeUpdates: Record<string, string>,
112
+ ): Promise<boolean>;
78
113
  }
79
114
 
80
115
  export interface WearableMemoryGenDeps {
81
116
  extract(turns: BufferTurn[]): Promise<ExtractionResult>;
82
117
  writer: WearableMemoryWriter;
118
+ /**
119
+ * LLM-as-judge batch evaluation (the existing extraction judge).
120
+ * Absent when no judge is wired (degraded smart mode: trust scoring
121
+ * runs on confidence x sourceTrust + corroboration alone).
122
+ */
123
+ judgeFacts?(candidates: JudgeCandidate[]): Promise<JudgeBatchResult>;
124
+ /**
125
+ * Corroboration evidence for smart mode: other sources' same-day
126
+ * transcript tokens + existing active memories. Absent disables
127
+ * corroboration boosts.
128
+ */
129
+ corroboration?: CorroborationContext;
83
130
  }
84
131
 
85
132
  export interface WearableMemoryGenResult {
86
133
  created: number;
134
+ /** Earlier borderline writes promoted to active by new evidence. */
135
+ promoted: number;
136
+ /** Earlier pending writes retired by a fresh judge-reject verdict. */
137
+ demoted: number;
87
138
  skipped: number;
88
139
  skippedByReason: Record<string, number>;
89
140
  /** Non-fatal problems (e.g. the extraction engine erroring). */
90
141
  warnings: string[];
142
+ /**
143
+ * True when every conversation was extracted (the pass may still
144
+ * carry degraded-mode warnings, e.g. judge unavailable). False only
145
+ * when extraction itself aborted mid-day — the signal callers use to
146
+ * re-run the pass on the next sync. A degraded-but-complete pass
147
+ * must NOT re-run forever: its facts are already written and dedup
148
+ * would suppress improvements anyway.
149
+ */
150
+ completed: boolean;
91
151
  }
92
152
 
93
153
  export const WEARABLE_SOURCE_PREFIX = "wearable";
@@ -171,6 +231,70 @@ interface GatedCandidate {
171
231
  conversation: WearableConversation;
172
232
  }
173
233
 
234
+ interface ScoredCandidate {
235
+ trust: number;
236
+ verdict?: "accept" | "reject" | "defer";
237
+ evidence: TrustEvidence;
238
+ }
239
+
240
+ /**
241
+ * Smart-mode scoring: one judge batch call for the whole day, then
242
+ * per-fact trust = confidence x sourceTrust + judge/corroboration
243
+ * boosts. A judge failure degrades gracefully (warned once; scoring
244
+ * continues on confidence + corroboration alone).
245
+ */
246
+ async function scoreCandidates(
247
+ novel: GatedCandidate[],
248
+ settings: WearableSourceSettings,
249
+ deps: WearableMemoryGenDeps,
250
+ result: WearableMemoryGenResult,
251
+ ): Promise<Map<number, ScoredCandidate>> {
252
+ const scored = new Map<number, ScoredCandidate>();
253
+ if (novel.length === 0) return scored;
254
+
255
+ let verdicts: Map<number, "accept" | "reject" | "defer"> | undefined;
256
+ if (deps.judgeFacts) {
257
+ const judgeCandidates: JudgeCandidate[] = novel.map((candidate) => ({
258
+ text: candidate.fact.content,
259
+ category: candidate.fact.category,
260
+ confidence:
261
+ typeof candidate.fact.confidence === "number"
262
+ ? candidate.fact.confidence
263
+ : 0.7,
264
+ tags: candidate.fact.tags ?? [],
265
+ importanceLevel: candidate.importance.level,
266
+ }));
267
+ try {
268
+ const judgeResult = await deps.judgeFacts(judgeCandidates);
269
+ verdicts = new Map();
270
+ for (const [index, verdict] of judgeResult.verdicts) {
271
+ verdicts.set(index, getVerdictKind(verdict));
272
+ }
273
+ } catch (err) {
274
+ result.warnings.push(
275
+ `extraction judge unavailable for this pass: ${describeErrorForOperator(err)} — trust scoring continued without judge verdicts`,
276
+ );
277
+ }
278
+ }
279
+
280
+ const corroboration: CorroborationContext = deps.corroboration ?? {
281
+ otherSourceDayTokens: new Map(),
282
+ existingMemories: [],
283
+ };
284
+ novel.forEach((candidate, index) => {
285
+ const evidence = findCorroboration(candidate.fact.content, corroboration);
286
+ const verdict = verdicts?.get(index);
287
+ const trust = computeTrustScore({
288
+ extractionConfidence: candidate.fact.confidence,
289
+ sourceTrust: settings.sourceTrust,
290
+ judgeVerdict: verdict,
291
+ evidence,
292
+ });
293
+ scored.set(index, { trust, ...(verdict !== undefined ? { verdict } : {}), evidence });
294
+ });
295
+ return scored;
296
+ }
297
+
174
298
  /**
175
299
  * Run extraction + gates over a day's conversations and persist the
176
300
  * survivors. Returns counts for the sync summary.
@@ -185,9 +309,12 @@ export async function generateWearableMemories(
185
309
  ): Promise<WearableMemoryGenResult> {
186
310
  const result: WearableMemoryGenResult = {
187
311
  created: 0,
312
+ promoted: 0,
313
+ demoted: 0,
188
314
  skipped: 0,
189
315
  skippedByReason: {},
190
316
  warnings: [],
317
+ completed: true,
191
318
  };
192
319
  if (settings.memoryMode === "off") return result;
193
320
 
@@ -215,6 +342,7 @@ export async function generateWearableMemories(
215
342
  result.warnings.push(
216
343
  `extraction failed for ${sourceId}/${date} (conversation ${conversation.id}): ${describeErrorForOperator(err)} — the memory pass for this day retries on the next sync`,
217
344
  );
345
+ result.completed = false;
218
346
  break;
219
347
  }
220
348
  for (const fact of extraction.facts) {
@@ -227,7 +355,11 @@ export async function generateWearableMemories(
227
355
  skip("unsupported-category");
228
356
  continue;
229
357
  }
358
+ // In smart mode the trust bands subsume the hard confidence
359
+ // floor — a borderline fact belongs in the review band, not on
360
+ // the floor. The pre-filter applies to review/auto modes only.
230
361
  if (
362
+ settings.memoryMode !== "smart" &&
231
363
  typeof fact.confidence === "number" &&
232
364
  fact.confidence < settings.minConfidence
233
365
  ) {
@@ -254,33 +386,177 @@ export async function generateWearableMemories(
254
386
 
255
387
  // Drop candidates that already exist in storage BEFORE applying the
256
388
  // day cap so duplicates never consume cap slots that novel,
257
- // lower-scoring candidates should get (Codex P2 on PR #1458).
389
+ // lower-scoring candidates should get (Codex P2 on PR #1458). In
390
+ // smart mode a duplicate of a PENDING_REVIEW write is kept aside as
391
+ // a promotion candidate — corroboration that arrives after the
392
+ // original borderline write (another device syncing the same day)
393
+ // must be able to promote it in place (Cursor review on PR #1462).
258
394
  const novel: GatedCandidate[] = [];
395
+ const promotable: GatedCandidate[] = [];
259
396
  for (const candidate of candidates) {
260
397
  if (await deps.writer.hasFactContentHash(candidate.fact.content)) {
261
- skip("duplicate-existing");
398
+ if (
399
+ settings.memoryMode === "smart" &&
400
+ deps.writer.findWearableMemoryByContent !== undefined &&
401
+ deps.writer.promoteWearableMemory !== undefined
402
+ ) {
403
+ promotable.push(candidate);
404
+ } else {
405
+ skip("duplicate-existing");
406
+ }
262
407
  continue;
263
408
  }
264
409
  novel.push(candidate);
265
410
  }
266
411
 
267
- // Day cap: keep the most important candidates. Stable ordering —
268
- // score desc, then content asc so equal scores compare 0-consistent.
269
- novel.sort((a, b) => {
270
- if (a.importance.score > b.importance.score) return -1;
271
- if (a.importance.score < b.importance.score) return 1;
272
- if (a.fact.content < b.fact.content) return -1;
273
- if (a.fact.content > b.fact.content) return 1;
412
+ // Re-score promotion candidates with TODAY'S evidence; promote the
413
+ // ones that now clear the auto threshold. Never consumes day-cap
414
+ // slots (no new memory is written).
415
+ if (promotable.length > 0) {
416
+ const promoteScores = await scoreCandidates(promotable, settings, deps, result);
417
+ for (const [index, candidate] of promotable.entries()) {
418
+ const scored = promoteScores.get(index);
419
+ const decision = scored
420
+ ? decideSmart(scored.trust, scored.verdict, settings)
421
+ : undefined;
422
+ if (!scored || !decision) {
423
+ skip("duplicate-existing");
424
+ continue;
425
+ }
426
+ // A fresh judge-REJECT retires the stored row — but only a
427
+ // pending_review one. Active rows are never auto-demoted: an
428
+ // operator approval or accrued recall signals must not be
429
+ // overturned by one later LLM verdict; contradiction scans and
430
+ // supersession own active-row retirement (Cursor review on PR
431
+ // #1462, round 7).
432
+ if (scored.verdict === "reject") {
433
+ if (deps.writer.demoteWearableMemory !== undefined) {
434
+ const existingForDemote = await deps.writer.findWearableMemoryByContent!(
435
+ candidate.fact.content,
436
+ );
437
+ if (
438
+ existingForDemote &&
439
+ existingForDemote.status === "pending_review" &&
440
+ (await deps.writer.demoteWearableMemory(existingForDemote.id, {
441
+ trustScore: scored.trust.toFixed(3),
442
+ trustDecision: "demoted-by-rejection",
443
+ judgeVerdict: "reject",
444
+ }))
445
+ ) {
446
+ result.demoted += 1;
447
+ continue;
448
+ }
449
+ }
450
+ skip("duplicate-existing");
451
+ continue;
452
+ }
453
+ if (decision.outcome !== "active") {
454
+ skip("duplicate-existing");
455
+ continue;
456
+ }
457
+ const existing = await deps.writer.findWearableMemoryByContent!(
458
+ candidate.fact.content,
459
+ );
460
+ if (!existing || existing.status !== "pending_review") {
461
+ skip("duplicate-existing");
462
+ continue;
463
+ }
464
+ const promoted = await deps.writer.promoteWearableMemory!(
465
+ existing.id,
466
+ {
467
+ trustScore: scored.trust.toFixed(3),
468
+ trustDecision: "promoted-by-corroboration",
469
+ ...(scored.verdict !== undefined ? { judgeVerdict: scored.verdict } : {}),
470
+ ...(scored.evidence.corroboratedBySources.length > 0
471
+ ? { corroboratedBySources: scored.evidence.corroboratedBySources.join(",") }
472
+ : {}),
473
+ ...(scored.evidence.supportingMemoryId !== undefined
474
+ ? { supportingMemoryId: scored.evidence.supportingMemoryId }
475
+ : {}),
476
+ },
477
+ scored.trust,
478
+ );
479
+ if (promoted) {
480
+ result.promoted += 1;
481
+ } else {
482
+ skip("duplicate-existing");
483
+ }
484
+ }
485
+ }
486
+
487
+ // Smart mode: judge + trust scoring decide active/review/drop per
488
+ // fact. The judge runs ONE batch call for the whole day; trust
489
+ // combines extraction confidence x sourceTrust with corroboration
490
+ // boosts (cross-device agreement, existing-memory support).
491
+ let trustById = new Map<number, ScoredCandidate>();
492
+ if (settings.memoryMode === "smart") {
493
+ trustById = await scoreCandidates(novel, settings, deps, result);
494
+ }
495
+
496
+ // Smart decisions run BEFORE the day cap so dropped facts (judge
497
+ // rejections, below-trust) never consume cap slots that surviving
498
+ // candidates ranked past position N should get (Cursor review on PR
499
+ // #1462).
500
+ interface Writable {
501
+ candidate: GatedCandidate;
502
+ index: number;
503
+ status: MemoryStatus;
504
+ trustAttributes: Record<string, string>;
505
+ }
506
+ const modeStatus = memoryStatusForMode(settings.memoryMode);
507
+ const writable: Writable[] = [];
508
+ novel.forEach((candidate, index) => {
509
+ if (settings.memoryMode !== "smart") {
510
+ writable.push({ candidate, index, status: modeStatus, trustAttributes: {} });
511
+ return;
512
+ }
513
+ const scored = trustById.get(index);
514
+ if (!scored) return;
515
+ const decision = decideSmart(scored.trust, scored.verdict, settings);
516
+ if (decision.outcome === "drop") {
517
+ skip(decision.reason);
518
+ return;
519
+ }
520
+ writable.push({
521
+ candidate,
522
+ index,
523
+ status: decision.outcome === "active" ? "active" : "pending_review",
524
+ trustAttributes: {
525
+ trustScore: scored.trust.toFixed(3),
526
+ trustDecision: decision.reason,
527
+ ...(scored.verdict !== undefined ? { judgeVerdict: scored.verdict } : {}),
528
+ ...(scored.evidence.corroboratedBySources.length > 0
529
+ ? { corroboratedBySources: scored.evidence.corroboratedBySources.join(",") }
530
+ : {}),
531
+ ...(scored.evidence.supportingMemoryId !== undefined
532
+ ? { supportingMemoryId: scored.evidence.supportingMemoryId }
533
+ : {}),
534
+ },
535
+ });
536
+ });
537
+
538
+ // Day cap over the SURVIVORS: strongest by trust in smart mode, by
539
+ // importance otherwise. Stable ordering with a content tiebreak.
540
+ const strength = (entry: Writable): number =>
541
+ settings.memoryMode === "smart"
542
+ ? trustById.get(entry.index)?.trust ?? 0
543
+ : entry.candidate.importance.score;
544
+ writable.sort((a, b) => {
545
+ const sa = strength(a);
546
+ const sb = strength(b);
547
+ if (sa > sb) return -1;
548
+ if (sa < sb) return 1;
549
+ if (a.candidate.fact.content < b.candidate.fact.content) return -1;
550
+ if (a.candidate.fact.content > b.candidate.fact.content) return 1;
274
551
  return 0;
275
552
  });
276
553
  const cap = settings.maxMemoriesPerDay;
277
- const kept = cap > 0 ? novel.slice(0, cap) : novel;
278
- if (novel.length > kept.length) {
279
- skip("over-day-cap", novel.length - kept.length);
554
+ const kept = cap > 0 ? writable.slice(0, cap) : writable;
555
+ if (writable.length > kept.length) {
556
+ skip("over-day-cap", writable.length - kept.length);
280
557
  }
281
558
 
282
- const status = memoryStatusForMode(settings.memoryMode);
283
- for (const candidate of kept) {
559
+ for (const { candidate, index, status, trustAttributes } of kept) {
284
560
  const tags = [
285
561
  ...new Set([
286
562
  ...(candidate.fact.tags ?? []),
@@ -290,7 +566,10 @@ export async function generateWearableMemories(
290
566
  ]),
291
567
  ];
292
568
  await deps.writer.writeMemory(candidate.fact.category, candidate.fact.content, {
293
- confidence: candidate.fact.confidence,
569
+ confidence:
570
+ settings.memoryMode === "smart"
571
+ ? trustById.get(index)?.trust
572
+ : candidate.fact.confidence,
294
573
  tags,
295
574
  source: wearableSourceLabel(sourceId),
296
575
  importance: candidate.importance,
@@ -300,6 +579,7 @@ export async function generateWearableMemories(
300
579
  wearableSource: sourceId,
301
580
  wearableDate: date,
302
581
  wearableConversationId: candidate.conversation.id,
582
+ ...trustAttributes,
303
583
  },
304
584
  contentHashSource: candidate.fact.content,
305
585
  status,
@@ -365,15 +645,26 @@ export async function writeDailyDigestMemory(
365
645
  * review queue. Always `pending_review` regardless of memoryMode — the
366
646
  * provider's extraction quality is outside Remnic's control.
367
647
  */
648
+ /** Native-source trust prior reduction (provider extraction quality). */
649
+ const NATIVE_TRUST_FACTOR = 0.9;
650
+
368
651
  export async function importNativeMemories(
369
652
  sourceId: string,
370
653
  memories: WearableNativeMemory[],
371
654
  alreadyImportedIds: ReadonlySet<string>,
372
- writer: WearableMemoryWriter,
373
- ): Promise<{ imported: number; importedIds: string[] }> {
655
+ settings: WearableSourceSettings,
656
+ deps: WearableMemoryGenDeps,
657
+ ): Promise<{ imported: number; importedIds: string[]; warnings: string[] }> {
374
658
  let imported = 0;
375
659
  const importedIds: string[] = [];
660
+ const warnings: string[] = [];
376
661
  const seenContent = new Set<string>();
662
+ const smart = settings.importNativeMemories === "smart";
663
+
664
+ // Smart path: one judge batch over the novel items, like transcript
665
+ // facts, with a reduced source prior — provider extraction quality is
666
+ // outside Remnic's control.
667
+ const novel: WearableNativeMemory[] = [];
377
668
  for (const memory of memories) {
378
669
  const content = memory.content?.trim();
379
670
  if (!content) continue;
@@ -381,13 +672,79 @@ export async function importNativeMemories(
381
672
  // Intra-run + cross-run dedup: the storage hash index only learns a
382
673
  // fact after its write lands, so same-content items within one page
383
674
  // batch need the local set.
384
- if (seenContent.has(content) || (await writer.hasFactContentHash(content))) {
675
+ if (seenContent.has(content) || (await deps.writer.hasFactContentHash(content))) {
385
676
  importedIds.push(memory.id);
386
677
  continue;
387
678
  }
388
679
  seenContent.add(content);
389
- await writer.writeMemory("fact", content, {
390
- confidence: 0.6,
680
+ novel.push({ ...memory, content });
681
+ }
682
+
683
+ let verdicts: Map<number, "accept" | "reject" | "defer"> | undefined;
684
+ if (smart && deps.judgeFacts && novel.length > 0) {
685
+ try {
686
+ const judgeResult = await deps.judgeFacts(
687
+ novel.map((memory) => ({
688
+ text: memory.content,
689
+ category: "fact",
690
+ confidence: 0.7,
691
+ tags: memory.tags ?? [],
692
+ })),
693
+ );
694
+ verdicts = new Map();
695
+ for (const [index, verdict] of judgeResult.verdicts) {
696
+ verdicts.set(index, getVerdictKind(verdict));
697
+ }
698
+ } catch (err) {
699
+ warnings.push(
700
+ `extraction judge unavailable for native import: ${describeErrorForOperator(err)} — trust scoring continued without judge verdicts`,
701
+ );
702
+ }
703
+ }
704
+ const corroboration: CorroborationContext = deps.corroboration ?? {
705
+ otherSourceDayTokens: new Map(),
706
+ existingMemories: [],
707
+ };
708
+
709
+ for (const [index, memory] of novel.entries()) {
710
+ const content = memory.content;
711
+ let status: MemoryStatus = "pending_review";
712
+ let trustAttributes: Record<string, string> = {};
713
+ let confidence = 0.6;
714
+ if (smart) {
715
+ const evidence = findCorroboration(content, corroboration);
716
+ const verdict = verdicts?.get(index);
717
+ const trust = computeTrustScore({
718
+ extractionConfidence: undefined,
719
+ sourceTrust: settings.sourceTrust * NATIVE_TRUST_FACTOR,
720
+ judgeVerdict: verdict,
721
+ evidence,
722
+ });
723
+ const decision = decideSmart(trust, verdict, settings);
724
+ if (decision.outcome === "drop") {
725
+ // Deliberately NOT recorded in importedIds: a dropped native
726
+ // fact re-fetches and re-scores on later syncs, so corpus or
727
+ // corroboration support that arrives later can still admit it.
728
+ // The judge verdict cache keeps repeated rejections cheap
729
+ // (Cursor review on PR #1462).
730
+ continue;
731
+ }
732
+ status = decision.outcome === "active" ? "active" : "pending_review";
733
+ confidence = trust;
734
+ trustAttributes = {
735
+ trustScore: trust.toFixed(3),
736
+ trustDecision: decision.reason,
737
+ ...(verdict !== undefined ? { judgeVerdict: verdict } : {}),
738
+ ...(evidence.corroboratedBySources.length > 0
739
+ ? { corroboratedBySources: evidence.corroboratedBySources.join(",") }
740
+ : {}),
741
+ ...(evidence.supportingMemoryId !== undefined
742
+ ? { supportingMemoryId: evidence.supportingMemoryId }
743
+ : {}),
744
+ };
745
+ }
746
+ await deps.writer.writeMemory("fact", content, {
747
+ confidence,
391
748
  tags: [
392
749
  ...new Set([
393
750
  ...(memory.tags ?? []),
@@ -402,12 +759,13 @@ export async function importNativeMemories(
402
759
  structuredAttributes: {
403
760
  wearableSource: sourceId,
404
761
  wearableNativeId: memory.id,
762
+ ...trustAttributes,
405
763
  },
406
764
  contentHashSource: content,
407
- status: "pending_review",
765
+ status,
408
766
  });
409
767
  imported += 1;
410
768
  importedIds.push(memory.id);
411
769
  }
412
- return { imported, importedIds };
770
+ return { imported, importedIds, warnings };
413
771
  }