mixdog 1.0.4 → 1.0.6

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 (158) hide show
  1. package/README.md +117 -434
  2. package/package.json +18 -17
  3. package/scripts/gate-local.mjs +93 -0
  4. package/scripts/lib/gate-scheduler.mjs +24 -0
  5. package/scripts/lib/run-node-tests.mjs +5 -1
  6. package/scripts/release-paths.mjs +22 -1
  7. package/scripts/test-direct.mjs +2 -1
  8. package/scripts/test.mjs +3 -1
  9. package/src/defaults/agents.json +0 -12
  10. package/src/defaults/mixdog-config.template.json +1 -2
  11. package/src/defaults/skills/docx/SKILL.md +7 -4
  12. package/src/defaults/skills/docx/references/html.md +46 -0
  13. package/src/defaults/skills/docx/references/native-authoring.md +18 -4
  14. package/src/defaults/skills/pdf/SKILL.md +10 -2
  15. package/src/defaults/skills/pdf/references/html.md +64 -0
  16. package/src/defaults/skills/pptx/references/html.md +1 -1
  17. package/src/defaults/skills/xlsx/SKILL.md +7 -3
  18. package/src/defaults/skills/xlsx/references/html.md +40 -0
  19. package/src/defaults/skills/xlsx/references/report-design.md +21 -4
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -1
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  22. package/src/runtime/agent/orchestrator/internal-agents.mjs +1 -1
  23. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +6 -5
  24. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +6 -3
  25. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +6 -2
  26. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +50 -9
  27. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +28 -1
  28. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +4 -1
  29. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +34 -8
  30. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +10 -2
  31. package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
  32. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -0
  33. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +3 -1
  34. package/src/runtime/attachments/pdf-extract.mjs +7 -3
  35. package/src/runtime/computer-bridge/client.mjs +1 -1
  36. package/src/runtime/memory/index.mjs +4 -15
  37. package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
  38. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +0 -2
  39. package/src/runtime/memory/lib/cycle-scheduler/backlog-probe.mjs +3 -11
  40. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +1 -3
  41. package/src/runtime/memory/lib/cycle-scheduler/scheduled-enqueue.mjs +1 -11
  42. package/src/runtime/memory/lib/cycle-scheduler/tick-loop.mjs +0 -2
  43. package/src/runtime/memory/lib/cycle-scheduler.mjs +4 -13
  44. package/src/runtime/memory/lib/cycle-signatures.mjs +0 -6
  45. package/src/runtime/memory/lib/cycle1/cycle1-rows.mjs +1 -1
  46. package/src/runtime/memory/lib/cycle1/cycle1-window.mjs +2 -2
  47. package/src/runtime/memory/lib/embedding-reindex.mjs +1 -1
  48. package/src/runtime/memory/lib/http-router/lifecycle-routes.mjs +0 -1
  49. package/src/runtime/memory/lib/memory-action-handlers/cycle-actions.mjs +7 -52
  50. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +6 -22
  51. package/src/runtime/memory/lib/memory-action-handlers/cycle-rebuild-action.mjs +10 -17
  52. package/src/runtime/memory/lib/memory-action-handlers/maintenance-actions.mjs +1 -3
  53. package/src/runtime/memory/lib/memory-action-handlers/manage-actions.mjs +2 -4
  54. package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -4
  55. package/src/runtime/memory/lib/memory-cycle-requests.mjs +1 -1
  56. package/src/runtime/memory/lib/{memory-cycle2-shared.mjs → memory-cycle-shared.mjs} +14 -16
  57. package/src/runtime/memory/lib/memory-cycle.mjs +3 -2
  58. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  59. package/src/runtime/memory/lib/memory-embed.mjs +14 -1
  60. package/src/runtime/memory/lib/memory-ops-policy.mjs +8 -11
  61. package/src/runtime/memory/lib/memory-schema/entries.mjs +0 -1
  62. package/src/runtime/memory/lib/memory.mjs +2 -6
  63. package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
  64. package/src/runtime/memory/lib/query-handlers.mjs +1 -1
  65. package/src/runtime/memory/lib/query-hybrid-search.mjs +1 -1
  66. package/src/runtime/memory/lib/query-maintenance-handlers.mjs +0 -6
  67. package/src/runtime/memory/lib/recall-format.mjs +1 -1
  68. package/src/runtime/office/authoring/docx-html-build.mjs +375 -0
  69. package/src/runtime/office/authoring/docx-html-measure.mjs +561 -0
  70. package/src/runtime/office/authoring/docx-html-runner.mjs +102 -0
  71. package/src/runtime/office/authoring/html-browser.mjs +61 -0
  72. package/src/runtime/office/authoring/html-document-author.mjs +181 -0
  73. package/src/runtime/office/authoring/html-source-drift.mjs +36 -0
  74. package/src/runtime/office/authoring/pdf-author-action.mjs +266 -0
  75. package/src/runtime/office/authoring/pdf-html-charts.mjs +379 -0
  76. package/src/runtime/office/authoring/pdf-html-runner.mjs +317 -0
  77. package/src/runtime/office/authoring/pptx-author-action.mjs +44 -22
  78. package/src/runtime/office/authoring/pptx-author-session.mjs +12 -6
  79. package/src/runtime/office/authoring/pptx-html-build.mjs +23 -4
  80. package/src/runtime/office/authoring/pptx-html-measure.mjs +38 -44
  81. package/src/runtime/office/authoring/pptx-html-runner.mjs +18 -3
  82. package/src/runtime/office/authoring/pptx-receipt.mjs +39 -31
  83. package/src/runtime/office/authoring/pptx-review-artifacts.mjs +9 -1
  84. package/src/runtime/office/authoring/pptx-script-runner.mjs +6 -2
  85. package/src/runtime/office/authoring/xlsx-html-build.mjs +365 -0
  86. package/src/runtime/office/authoring/xlsx-html-measure.mjs +257 -0
  87. package/src/runtime/office/authoring/xlsx-html-runner.mjs +139 -0
  88. package/src/runtime/office/capabilities-catalog.mjs +8 -0
  89. package/src/runtime/office/capabilities-signatures.mjs +18 -6
  90. package/src/runtime/office/capabilities.mjs +26 -1
  91. package/src/runtime/office/com/office-com-host.ps1 +133 -17
  92. package/src/runtime/office/core/office-actions-batch.mjs +16 -1
  93. package/src/runtime/office/core/office-actions-inspect.mjs +3 -2
  94. package/src/runtime/office/core/office-actions-lifecycle.mjs +6 -2
  95. package/src/runtime/office/core/office-actions-render.mjs +4 -1
  96. package/src/runtime/office/core/office-finalize/review-stage.mjs +15 -2
  97. package/src/runtime/office/core/office-qa/design-review-stage.mjs +9 -0
  98. package/src/runtime/office/core/office-session-dispatch.mjs +3 -0
  99. package/src/runtime/office/core/office-sessionless-actions.mjs +7 -2
  100. package/src/runtime/office/core/office-sessions.mjs +19 -2
  101. package/src/runtime/office/pdf/pdf-writer.mjs +1 -1
  102. package/src/runtime/office/portable/docx-formatting.mjs +21 -0
  103. package/src/runtime/office/portable/ooxml-validator.mjs +22 -1
  104. package/src/runtime/office/portable/portable-chart.mjs +10 -2
  105. package/src/runtime/office/portable/portable-docx-edits.mjs +12 -6
  106. package/src/runtime/office/portable/portable-docx-operations.mjs +75 -1
  107. package/src/runtime/office/portable/portable-docx-xml.mjs +49 -6
  108. package/src/runtime/office/portable/portable-docx.mjs +2 -0
  109. package/src/runtime/office/portable/portable-snapshot-docx.mjs +2 -0
  110. package/src/runtime/office/portable/portable-validation.mjs +6 -3
  111. package/src/runtime/office/portable/portable-xlsx-cell-values.mjs +87 -0
  112. package/src/runtime/office/portable/portable-xlsx-charts.mjs +55 -13
  113. package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +1 -1
  114. package/src/runtime/office/portable/xlsx-contract.mjs +82 -3
  115. package/src/runtime/office/quality/assurance-checklist.mjs +2 -2
  116. package/src/runtime/office/quality/assurance-rendered.mjs +27 -1
  117. package/src/runtime/office/quality/assurance-structure-docx.mjs +44 -0
  118. package/src/runtime/office/quality/assurance-structure-pptx.mjs +24 -6
  119. package/src/runtime/office/quality/assurance-structure-xlsx.mjs +50 -1
  120. package/src/runtime/office/quality/design-review-critique.mjs +3 -2
  121. package/src/runtime/office/quality/document-brief.mjs +44 -0
  122. package/src/runtime/office/quality/inline-audit.mjs +7 -2
  123. package/src/runtime/office/quality/quality-pipeline.mjs +9 -1
  124. package/src/runtime/office/tool-defs.mjs +2 -2
  125. package/src/runtime/shared/llm/quota-value-estimate.mjs +317 -0
  126. package/src/runtime/shared/llm/usage-ledger-quota.mjs +266 -63
  127. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +35 -0
  128. package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
  129. package/src/runtime/shared/llm/usage-ledger.mjs +10 -1
  130. package/src/runtime/shared/provider-accounts.mjs +11 -1
  131. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -4
  132. package/src/runtime/shared/tool-execution-contract.mjs +1 -1
  133. package/src/runtime/shared/wait-until.test-support.mjs +33 -0
  134. package/src/session-runtime/boot/apis.mjs +1 -1
  135. package/src/session-runtime/services/channel-admin.mjs +4 -3
  136. package/src/session-runtime/services/usage-stats-model.mjs +22 -0
  137. package/src/session-runtime/setup-tool/settings-contract.mjs +6 -1
  138. package/src/session-runtime/turn/session-ops.mjs +15 -9
  139. package/src/session-runtime/usage-stats-api.mjs +34 -2
  140. package/src/standalone/agent-dispatch-broker.mjs +1 -1
  141. package/src/standalone/local-session-runtime.mjs +488 -0
  142. package/src/standalone/session-protocol.mjs +1 -0
  143. package/src/standalone/session-runtime-inline-host.mjs +1 -1
  144. package/src/tui/hooks/useSession.mjs +1 -1
  145. package/src/tui/session/session-api/commands.mjs +1 -1
  146. package/src/tui/session/session-api/integrations.mjs +3 -0
  147. package/src/tui/session/session-flow/auto-clear.mjs +6 -7
  148. package/src/tui/session/tool-card-results.mjs +1 -1
  149. package/src/tui/session/turn.mjs +5 -0
  150. package/src/tui/session-local.mjs +3 -488
  151. package/src/tui/session.mjs +1 -1
  152. package/src/ui/statusline-agents.mjs +0 -1
  153. package/src/rules/agent/41-cycle2-agent.md +0 -24
  154. package/src/runtime/memory/lib/cycle-scheduler/cycle2-runs.mjs +0 -120
  155. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +0 -110
  156. package/src/runtime/memory/lib/memory-cycle2-quarantine.mjs +0 -99
  157. package/src/runtime/memory/lib/memory-cycle2-review.mjs +0 -240
  158. package/src/runtime/memory/lib/memory-cycle2.mjs +0 -191
@@ -48,13 +48,17 @@ function pageBodyText(content) {
48
48
  if (typeof item?.str !== 'string') continue;
49
49
  if (item.str) {
50
50
  const placement = runPlacement(item);
51
+ // A run on another baseline is another line even when the writer marked no line end (a browser's
52
+ // print does not): "784" over its label read as "784office". A baseline shift under half the
53
+ // smaller run's size (a superscript, a mixed-size run) stays on the line.
54
+ const shift = placement && cursor ? Math.abs(placement.line - cursor.line) : 0;
55
+ const newLine = shift > Math.min(placement?.size || 0, cursor?.size || 0) * 0.5;
56
+ if (newLine && body && !body.endsWith('\n')) body += '\n';
51
57
  // A writer splits one word into several runs for kerning, and a
52
58
  // Korean line into a run per token; joining those with a space
53
59
  // invents "2026 년". Only a gap the page itself leaves is a space.
54
60
  const separated =
55
- placement && cursor && placement.line === cursor.line
56
- ? placement.start - cursor.end > cursor.size * 0.22
57
- : !placement || !cursor;
61
+ placement && cursor && !newLine ? placement.start - cursor.end > cursor.size * 0.22 : !placement || !cursor;
58
62
  const glued = !body || body.endsWith('\n') || body.endsWith(' ') || item.str.startsWith(' ') || !separated;
59
63
  body += (glued ? '' : ' ') + item.str;
60
64
  cursor = placement;
@@ -411,7 +411,7 @@ function computerToolResult(value, args) {
411
411
  }
412
412
  return {
413
413
  content,
414
- ...(canonicalComputerResultIsError(text, args) ? { isError: true } : {}),
414
+ ...(value.isError === true || canonicalComputerResultIsError(text, args) ? { isError: true } : {}),
415
415
  };
416
416
  }
417
417
 
@@ -51,7 +51,7 @@ import {
51
51
  shutdownEmbeddingProvider,
52
52
  warmupEmbeddingProvider,
53
53
  } from './lib/embedding-provider.mjs';
54
- import { runCycle1, runCycle2, parseInterval, flushEmbeddingDirty, flushRawEmbeddings } from './lib/memory-cycle.mjs';
54
+ import { runCycle1, parseInterval, flushEmbeddingDirty, flushRawEmbeddings } from './lib/memory-cycle.mjs';
55
55
  import { callAgentDispatch } from './lib/agent-ipc.mjs';
56
56
  import {
57
57
  cancelCoalescedCycleRetries,
@@ -73,7 +73,7 @@ import {
73
73
  import { writeJsonAtomicSync } from '../shared/atomic-file.mjs';
74
74
  import { safeIpcSend } from '../shared/safe-ipc-send.mjs';
75
75
  import { resolvePluginData, mixdogHome } from '../shared/plugin-paths.mjs';
76
- import { scheduledCycle1Signature, scheduledCycle2Signature } from './lib/cycle-signatures.mjs';
76
+ import { scheduledCycle1Signature } from './lib/cycle-signatures.mjs';
77
77
  import { createTranscriptIngest } from './lib/transcript-ingest.mjs';
78
78
  import { createCycleLlmAdapters } from './lib/cycle-llm-adapters.mjs';
79
79
  import { createCycleScheduler } from './lib/cycle-scheduler.mjs';
@@ -333,20 +333,15 @@ async function getCycleLastRun() {
333
333
  const obj = JSON.parse(raw);
334
334
  return {
335
335
  cycle1: Number(obj.cycle1) || 0,
336
- cycle2: Number(obj.cycle2) || 0,
337
336
  // Heartbeat (every attempt, success or skip) is tracked separately from
338
337
  // the success timestamps above so a long string of failed/skipped runs
339
338
  // cannot disguise itself as a healthy keeper.
340
339
  cycle1_heartbeat: Number(obj.cycle1_heartbeat) || 0,
341
- // Last cycle2 failure message; cleared to '' on success.
342
- cycle2_last_error: typeof obj.cycle2_last_error === 'string' ? obj.cycle2_last_error : '',
343
340
  };
344
341
  } catch {
345
342
  return {
346
343
  cycle1: 0,
347
- cycle2: 0,
348
344
  cycle1_heartbeat: 0,
349
- cycle2_last_error: '',
350
345
  };
351
346
  }
352
347
  }
@@ -373,7 +368,7 @@ async function refreshCoreMemorySnapshot(reason = 'mutation') {
373
368
  const CYCLE_STATE_FILE = path.join(DATA_DIR, 'memory-cycle-state.json');
374
369
 
375
370
  const _cycleLlmAdapters = createCycleLlmAdapters({ callAgentDispatch });
376
- const { getCycle1CallLlm, getCycle2CallLlm } = _cycleLlmAdapters;
371
+ const { getCycle1CallLlm } = _cycleLlmAdapters;
377
372
 
378
373
  const _cycleScheduler = createCycleScheduler({
379
374
  getDb: () => db,
@@ -388,9 +383,7 @@ const _cycleScheduler = createCycleScheduler({
388
383
  readMainConfig,
389
384
  memoryCyclesEnabled,
390
385
  getCycle1CallLlm,
391
- getCycle2CallLlm,
392
386
  runCycle1,
393
- runCycle2,
394
387
  parseInterval,
395
388
  flushRawEmbeddings,
396
389
  claimAndMarkScheduledCycle,
@@ -398,13 +391,11 @@ const _cycleScheduler = createCycleScheduler({
398
391
  scheduleCoalescedCycleRetry,
399
392
  cancelCoalescedCycleRetries,
400
393
  scheduledCycle1Signature,
401
- scheduledCycle2Signature,
402
394
  cycleStateFile: CYCLE_STATE_FILE,
403
395
  });
404
- // Cycle1 run primitives + cycle2 finalize used by MCP action handlers below.
396
+ // Cycle1 run primitives used by MCP action handlers below.
405
397
  const _startCycle1Run = _cycleScheduler.startCycle1Run;
406
398
  const _awaitCycle1Run = _cycleScheduler.awaitCycle1Run;
407
- const _finalizeCycle2Run = _cycleScheduler.finalizeCycle2Run;
408
399
 
409
400
  // Transcript watcher lifecycle stays in the facade (owns _transcriptIngest);
410
401
  // the cycle tick loop start/stop is delegated to the scheduler.
@@ -509,10 +500,8 @@ const _actionHandlers = createMemoryActionHandlers({
509
500
  dumpSessionRootChunks,
510
501
  awaitCycle1Run: _awaitCycle1Run,
511
502
  startCycle1Run: _startCycle1Run,
512
- finalizeCycle2Run: _finalizeCycle2Run,
513
503
  refreshCoreMemoryFile: refreshCoreMemorySnapshot,
514
504
  getSchedulerCycle1InFlight: () => _cycleScheduler.getCycle1InFlight(),
515
- getCycle2CallLlm,
516
505
  ingestTranscriptFile,
517
506
  cwdFromTranscriptPath,
518
507
  });
@@ -9,7 +9,7 @@ import { getDatabase, embeddingToSql } from './memory.mjs';
9
9
  import { VALID_CATEGORY } from './memory-categories.mjs';
10
10
  import { cachedEmbedTextBatch } from './memory-embed.mjs';
11
11
  import { checkedConnect } from './pg/adapter.mjs';
12
- import { throwIfAborted } from './memory-cycle2-shared.mjs';
12
+ import { throwIfAborted } from './memory-cycle-shared.mjs';
13
13
  import { findCoreKeyRows } from './core-memory-uniqueness.mjs';
14
14
 
15
15
  const CORE_ELEMENT_DERIVE_LENGTH = 40;
@@ -30,9 +30,7 @@ export function createCycleLlmAdapters({ callAgentDispatch }) {
30
30
  // unhandled rejection that killed the memory runtime. Keep the factory
31
31
  // contract: each getter returns the (memoized) adapter function.
32
32
  const cycle1 = buildAdapter('cycle1-agent');
33
- const cycle2 = buildAdapter('cycle2-agent');
34
33
  return {
35
34
  getCycle1CallLlm: () => cycle1,
36
- getCycle2CallLlm: () => cycle2,
37
35
  };
38
36
  }
@@ -6,13 +6,6 @@ async function countRows(db, sql, params) {
6
6
  return Number((await db.query(sql, params)).rows[0]?.c ?? 0);
7
7
  }
8
8
 
9
- export function countPendingCycle2Roots(db) {
10
- return countRows(
11
- db,
12
- `SELECT COUNT(*) c FROM entries WHERE is_root = 1 AND cycle2_reviewed_at IS NULL AND duplicate_of IS NULL`
13
- );
14
- }
15
-
16
9
  // Per-tick backlog counts for the state file / statusline, the backlog
17
10
  // warning, and the opportunistic raw-embedding flush while rows are unchunked.
18
11
  export function createBacklogProbe({ getDb, ledger, log, flushRawEmbeddings }) {
@@ -46,10 +39,9 @@ export function createBacklogProbe({ getDb, ledger, log, flushRawEmbeddings }) {
46
39
  AND (reviewed_at IS NULL OR reviewed_at < $1)`,
47
40
  [now - CYCLE1_OMITTED_COOLDOWN_MS]
48
41
  );
49
- const cycle2Pending = await countPendingCycle2Roots(db);
50
- ledger.setBacklog({ unchunked, unchunked_eligible: unchunkedEligible, cycle2_pending: cycle2Pending, at: now });
51
- if (unchunked > BACKLOG_WARN_PENDING || cycle2Pending > BACKLOG_WARN_PENDING) {
52
- ledger.warn(`backlog unchunked=${unchunked} eligible=${unchunkedEligible} cycle2_pending=${cycle2Pending}`);
42
+ ledger.setBacklog({ unchunked, unchunked_eligible: unchunkedEligible, at: now });
43
+ if (unchunked > BACKLOG_WARN_PENDING) {
44
+ ledger.warn(`backlog unchunked=${unchunked} eligible=${unchunkedEligible}`);
53
45
  }
54
46
  if (unchunked > 0) flushRawEmbeddingsOnce(db);
55
47
  } catch {
@@ -9,10 +9,9 @@ const BACKLOG_WARN_FAILURES = 5;
9
9
  export function createCycleHealthLedger({ cycleStateFile, log }) {
10
10
  const health = {
11
11
  cycle1: { last_success_at: 0, last_error_at: 0, last_error: null, consecutive_failures: 0 },
12
- cycle2: { last_success_at: 0, last_error_at: 0, last_error: null, consecutive_failures: 0 },
13
12
  };
14
13
  let running = null; // { cycle, started_at, pid }
15
- let backlog = { unchunked: 0, cycle2_pending: 0, at: 0 };
14
+ let backlog = { unchunked: 0, at: 0 };
16
15
  let lastWarnAt = 0;
17
16
 
18
17
  function write() {
@@ -74,7 +73,6 @@ export function createCycleHealthLedger({ cycleStateFile, log }) {
74
73
  // state file reports 0 until the next run.
75
74
  function hydrateSuccess(last) {
76
75
  if (last?.cycle1 > 0 && !health.cycle1.last_success_at) health.cycle1.last_success_at = last.cycle1;
77
- if (last?.cycle2 > 0 && !health.cycle2.last_success_at) health.cycle2.last_success_at = last.cycle2;
78
76
  write();
79
77
  }
80
78
 
@@ -5,9 +5,7 @@ export function createScheduledEnqueue({
5
5
  getConfig,
6
6
  claimAndMarkScheduledCycle,
7
7
  scheduledCycle1Signature,
8
- scheduledCycle2Signature,
9
8
  scheduleScheduledCycle1,
10
- scheduleScheduledCycle2,
11
9
  }) {
12
10
  function periodicCycle1Config() {
13
11
  return {
@@ -38,13 +36,5 @@ export function createScheduledEnqueue({
38
36
  }
39
37
  }
40
38
 
41
- async function enqueueScheduledCycle2(intervalMs) {
42
- const config = getConfig()?.cycle2 || {};
43
- const signature = scheduledCycle2Signature(config);
44
- if (await claimScheduledSlot('cycle2', intervalMs, signature, config)) {
45
- scheduleScheduledCycle2(config, signature);
46
- }
47
- }
48
-
49
- return { periodicCycle1Config, enqueueScheduledCycle1, enqueueScheduledCycle2 };
39
+ return { periodicCycle1Config, enqueueScheduledCycle1 };
50
40
  }
@@ -30,12 +30,10 @@ export function createCycleTickLoop({
30
30
  setConfig(mainConfig);
31
31
  const cyclesOn = memoryCyclesEnabled();
32
32
  const cycle1Ms = parseInterval(mainConfig?.cycle1?.interval || '10m');
33
- const cycle2Ms = parseInterval(mainConfig?.cycle2?.interval || '1h');
34
33
  const now = Date.now();
35
34
  const last = await getCycleLastRun();
36
35
  if (cyclesOn) {
37
36
  if (periodicCycleDue(last.cycle1, startedAt, cycle1Ms, now)) await enqueue.enqueueScheduledCycle1(cycle1Ms);
38
- if (periodicCycleDue(last.cycle2, startedAt, cycle2Ms, now)) await enqueue.enqueueScheduledCycle2(cycle2Ms);
39
37
  }
40
38
  await probeBacklog(now);
41
39
  }
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Owns the mutually-referential cycle machinery: the cycle-health ledger and
4
4
  // run-state file, the cycle1 outer coalesce layer, the scheduled enqueue /
5
- // retry paths for cycle1/2, checkCycles(), and the self-rescheduling tick
5
+ // retry paths for cycle1, checkCycles(), and the self-rescheduling tick
6
6
  // loop. The phases live under cycle-scheduler/. index.mjs keeps lifecycle
7
7
  // ownership by injecting live getters (getDb/getConfig/setConfig) plus the
8
8
  // cycle runners and LLM adapters.
@@ -15,15 +15,14 @@
15
15
  // log -> __mixdogMemoryLog
16
16
  // getCycleLastRun / setCycleLastRun -> meta-backed cycle timestamps
17
17
  // readMainConfig / memoryCyclesEnabled -> config-flag helpers
18
- // getCycle{1,2}CallLlm -> in-process LLM adapters
19
- // runCycle1 / runCycle2 / parseInterval / flushRawEmbeddings
18
+ // getCycle1CallLlm -> in-process LLM adapter
19
+ // runCycle1 / parseInterval / flushRawEmbeddings
20
20
  // claimAndMarkScheduledCycle / resolveCoalesceMaxRetries /
21
21
  // scheduleCoalescedCycleRetry -> coalesced queue primitives
22
- // scheduledCycle{1,2}Signature -> queue signatures
22
+ // scheduledCycle1Signature -> queue signature
23
23
  // cycleStateFile -> path to memory-cycle-state.json
24
24
  import { createCycleHealthLedger } from './cycle-scheduler/health-ledger.mjs';
25
25
  import { createCycle1Runs } from './cycle-scheduler/cycle1-runs.mjs';
26
- import { createCycle2Runs } from './cycle-scheduler/cycle2-runs.mjs';
27
26
  import { createScheduledEnqueue } from './cycle-scheduler/scheduled-enqueue.mjs';
28
27
  import { createBacklogProbe } from './cycle-scheduler/backlog-probe.mjs';
29
28
  import { createCycleTickLoop } from './cycle-scheduler/tick-loop.mjs';
@@ -42,9 +41,7 @@ export function createCycleScheduler(deps) {
42
41
  readMainConfig,
43
42
  memoryCyclesEnabled,
44
43
  getCycle1CallLlm,
45
- getCycle2CallLlm,
46
44
  runCycle1,
47
- runCycle2,
48
45
  parseInterval,
49
46
  flushRawEmbeddings,
50
47
  claimAndMarkScheduledCycle,
@@ -52,7 +49,6 @@ export function createCycleScheduler(deps) {
52
49
  scheduleCoalescedCycleRetry,
53
50
  cancelCoalescedCycleRetries,
54
51
  scheduledCycle1Signature,
55
- scheduledCycle2Signature,
56
52
  cycleStateFile,
57
53
  } = deps;
58
54
 
@@ -68,15 +64,12 @@ export function createCycleScheduler(deps) {
68
64
  setCycleLastRun,
69
65
  ...queue,
70
66
  });
71
- const cycle2 = createCycle2Runs({ ledger, getDb, log, getCycle2CallLlm, runCycle2, setCycleLastRun, ...queue });
72
67
  const enqueue = createScheduledEnqueue({
73
68
  getDb,
74
69
  getConfig,
75
70
  claimAndMarkScheduledCycle,
76
71
  scheduledCycle1Signature,
77
- scheduledCycle2Signature,
78
72
  scheduleScheduledCycle1: cycle1.scheduleScheduledCycle1,
79
- scheduleScheduledCycle2: cycle2.scheduleScheduledCycle2,
80
73
  });
81
74
  const backlog = createBacklogProbe({ getDb, ledger, log, flushRawEmbeddings });
82
75
  const tick = createCycleTickLoop({
@@ -97,7 +90,6 @@ export function createCycleScheduler(deps) {
97
90
  // running state.
98
91
  function resetInFlight() {
99
92
  cycle1.reset();
100
- cycle2.reset();
101
93
  backlog.reset();
102
94
  tick.reset();
103
95
  ledger.resetRunning();
@@ -113,7 +105,6 @@ export function createCycleScheduler(deps) {
113
105
  // run primitives used by MCP action handlers
114
106
  startCycle1Run: cycle1.startCycle1Run,
115
107
  awaitCycle1Run: cycle1.awaitCycle1Run,
116
- finalizeCycle2Run: cycle2.finalizeCycle2Run,
117
108
  periodicCycle1Config: enqueue.periodicCycle1Config,
118
109
  // lifecycle
119
110
  startCycles: tick.start,
@@ -11,9 +11,3 @@ export function scheduledCycle1Signature(config) {
11
11
  maxConcurrent: undefined,
12
12
  });
13
13
  }
14
-
15
- export function scheduledCycle2Signature(config) {
16
- return makeCycleRequestSignature('cycle2', config, {
17
- concurrency: undefined,
18
- });
19
- }
@@ -3,7 +3,7 @@
3
3
  // session fetch with its starvation backfill, session grouping, and the
4
4
  // terminal / omitted sentinel updates.
5
5
  import { __mixdogMemoryLog } from '../memory-log.mjs';
6
- import { throwIfAborted } from '../memory-cycle2-shared.mjs';
6
+ import { throwIfAborted } from '../memory-cycle-shared.mjs';
7
7
  import { CYCLE1_OMITTED_COOLDOWN_MS } from './cycle1-plan.mjs';
8
8
 
9
9
  export function isStructurallyUnchunkableInput(row) {
@@ -3,7 +3,7 @@
3
3
  // through the LLM, commit each chunk as one transaction, and mark what was
4
4
  // left out. Rows within the window are one session in chronological order.
5
5
  import { __mixdogMemoryLog } from '../memory-log.mjs';
6
- import { throwIfAborted } from '../memory-cycle2-shared.mjs';
6
+ import { throwIfAborted } from '../memory-cycle-shared.mjs';
7
7
  import { assessChunkQuality, generateCycle1Chunks } from '../memory-chunk-quality.mjs';
8
8
  import { inferChunkProjectId, syncRootEmbedding } from '../memory-embed.mjs';
9
9
  import { isStructurallyUnchunkableInput, markOmittedRows, markTerminalRows, selectRootId } from './cycle1-rows.mjs';
@@ -42,7 +42,7 @@ async function commitCycle1Chunk(db, chunk, rootId, memberIds) {
42
42
  `UPDATE entries
43
43
  SET chunk_root = $1, is_root = 1, element = $2, category = $3, summary = $4,
44
44
  status = 'pending', project_id = $5,
45
- last_seen_at = $7, chunk_quality = $8::jsonb, cycle2_reviewed_at = NULL, duplicate_of = NULL
45
+ last_seen_at = $7, chunk_quality = $8::jsonb, duplicate_of = NULL
46
46
  WHERE id = $6`,
47
47
  [rootId, element, category, summary, projectId, rootId, Date.now(), JSON.stringify(quality)]
48
48
  );
@@ -1,4 +1,4 @@
1
- import { throwIfAborted } from './memory-cycle2-shared.mjs';
1
+ import { throwIfAborted } from './memory-cycle-shared.mjs';
2
2
 
3
3
  const DEFAULT_PASS_PAUSE_MS = 1_000;
4
4
 
@@ -60,7 +60,6 @@ export function createLifecycleRoutes({
60
60
  active_roots: stats.active_roots,
61
61
  archived_roots: stats.archived_roots,
62
62
  unchunked_leaves: stats.unchunked_leaves,
63
- cycle2_pending_roots: stats.cycle2_pending_roots,
64
63
  core_entries: stats.core_entries,
65
64
  core_embed_null: stats.core_embed_null,
66
65
  cycle_running: cycleScheduler.getCycleRunning(),
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * memory-action-handlers/cycle-actions.mjs — the cycle-driven memory actions:
3
- * cycle1, cycle2/sleep, flush, rebuild and backfill. Cycle scheduler
4
- * primitives and the cycle2 LLM adapter are injected; the whole-action
3
+ * cycle1, flush, rebuild and backfill. Cycle scheduler
4
+ * primitives are injected; the whole-action
5
5
  * backfill mutex lives here since it only guards this module's backfill path.
6
6
  */
7
- import { runCycle2 } from '../memory-cycle.mjs';
8
- import { throwIfAborted } from '../memory-cycle2-shared.mjs';
7
+ import { flushSearchEmbeddings } from '../memory-cycle.mjs';
8
+ import { throwIfAborted } from '../memory-cycle-shared.mjs';
9
9
  import { createBackfillAction } from './cycle-backfill-action.mjs';
10
10
  import { createRebuildAction } from './cycle-rebuild-action.mjs';
11
11
 
@@ -56,41 +56,15 @@ function cycle1SummaryText(result) {
56
56
  );
57
57
  }
58
58
 
59
- function cycle2SummaryResult(result) {
60
- const counts = {
61
- processed: result?.processed || 0,
62
- merged: result?.merged || 0,
63
- linked: result?.linked || 0,
64
- kept: result?.kept || 0,
65
- held: result?.held || 0,
66
- deferred: result?.deferred || 0,
67
- };
68
- const parts = Object.entries(counts)
69
- .filter(([, v]) => v > 0)
70
- .map(([k, v]) => `${k}=${v}`);
71
- if (result?.ok === false)
72
- return { text: `cycle2 failed: ${result.error || 'unknown'} ${parts.join(' ')}`.trim(), isError: true };
73
- if (parts.length) return { text: `cycle2 ${parts.join(' ')}` };
74
- // No applied counts — distinguish an in-flight skip from an empty queue.
75
- let cause = '';
76
- if (result?.skippedInFlight) cause = ' (skipped: in-flight)';
77
- return { text: `cycle2 noop${cause}` };
78
- }
79
-
80
59
  export function createCycleActions({
81
60
  getDb,
82
61
  dataDir,
83
62
  awaitCycle1Run,
84
63
  startCycle1Run,
85
- finalizeCycle2Run,
86
64
  getSchedulerCycle1InFlight,
87
- getCycle2CallLlm,
88
65
  ingestTranscriptFile,
89
66
  cwdFromTranscriptPath,
90
67
  }) {
91
- const withCycle2Llm = (options) =>
92
- typeof options.callLlm === 'function' ? options : { ...options, callLlm: getCycle2CallLlm() };
93
-
94
68
  async function cycle1(args, config, signal) {
95
69
  const cycle1Config = cycle1ConfigFromArgs(args, config?.cycle1 || {});
96
70
  const callerDeadlineMs = Number(args?._callerDeadlineMs) || 0;
@@ -104,50 +78,31 @@ export function createCycleActions({
104
78
  return { ...result, text: cycle1SummaryText(result) };
105
79
  }
106
80
 
107
- async function cycle2(args, config, signal) {
108
- const db = getDb();
109
- throwIfAborted(signal);
110
- const cycle2Config = { ...(config?.cycle2 || {}) };
111
- if (Number.isFinite(Number(args?.batch_size))) {
112
- cycle2Config.batch_size = Math.max(1, Math.floor(Number(args.batch_size)));
113
- }
114
- const result = await runCycle2(db, cycle2Config, withCycle2Llm({ signal }));
115
- throwIfAborted(signal);
116
- await finalizeCycle2Run(result);
117
- return cycle2SummaryResult(result);
118
- }
119
-
120
81
  async function flush(_args, config, signal) {
121
82
  const db = getDb();
122
83
  throwIfAborted(signal);
123
84
  const r1 = await awaitCycle1Run(config?.cycle1 || {}, { signal });
124
85
  throwIfAborted(signal);
125
- const r2 = await runCycle2(db, config?.cycle2 || {}, withCycle2Llm({ signal }));
86
+ const embeddings = await flushSearchEmbeddings(db, { signal });
126
87
  throwIfAborted(signal);
127
- await finalizeCycle2Run(r2);
128
88
  return {
129
- text: `flush: cycle1 chunks=${r1.chunks} processed=${r1.processed}, cycle2 ${JSON.stringify(r2)}`,
130
- isError: r2.ok === false,
89
+ text: `flush: cycle1 chunks=${r1.chunks} processed=${r1.processed}, embeddings=${embeddings.succeeded}`,
131
90
  };
132
91
  }
133
92
 
134
93
  const rebuild = createRebuildAction({
135
94
  getDb,
136
95
  startCycle1Run,
137
- finalizeCycle2Run,
138
96
  getSchedulerCycle1InFlight,
139
- withCycle2Llm,
140
97
  });
141
98
 
142
99
  const backfill = createBackfillAction({
143
100
  getDb,
144
101
  dataDir,
145
102
  awaitCycle1Run,
146
- finalizeCycle2Run,
147
- withCycle2Llm,
148
103
  ingestTranscriptFile,
149
104
  cwdFromTranscriptPath,
150
105
  });
151
106
 
152
- return { cycle1, cycle2, flush, rebuild, backfill };
107
+ return { cycle1, flush, rebuild, backfill };
153
108
  }
@@ -1,24 +1,21 @@
1
1
  /**
2
2
  * memory-action-handlers/cycle-backfill-action.mjs — the backfill action: one
3
3
  * driver run over a transcript window, with the whole-action mutex and the
4
- * per-iteration cancel checkpoints the driver's cycle1/cycle2 callbacks need.
4
+ * per-iteration cancel checkpoints the driver's cycle1 callback needs.
5
5
  */
6
- import { runCycle2 } from '../memory-cycle.mjs';
7
6
  import { runFullBackfill } from '../memory-ops-policy.mjs';
8
- import { throwIfAborted } from '../memory-cycle2-shared.mjs';
7
+ import { throwIfAborted } from '../memory-cycle-shared.mjs';
9
8
 
10
9
  export function createBackfillAction({
11
10
  getDb,
12
11
  dataDir,
13
12
  awaitCycle1Run,
14
- finalizeCycle2Run,
15
- withCycle2Llm,
16
13
  ingestTranscriptFile,
17
14
  cwdFromTranscriptPath,
18
15
  }) {
19
16
  // Whole-action backfill mutex (transport-agnostic). memory-cycle1's
20
17
  // _runCycle1InFlight only protects cycle1; ingest workers
21
- // (memory-ops-policy.mjs) and cycle2 can still overlap if a second backfill
18
+ // (memory-ops-policy.mjs) can still overlap if a second backfill
22
19
  // kicks in (timeout-retry, parallel callers, /api/tool vs /mcp vs
23
20
  // /admin/backfill). Track the in-flight promise here and reject overlaps
24
21
  // with 409.
@@ -35,10 +32,6 @@ export function createBackfillAction({
35
32
  const window = args.window != null ? String(args.window) : '7d';
36
33
  const scope = args.scope != null ? String(args.scope) : 'all';
37
34
  const limit = args.limit != null ? Math.max(1, Number(args.limit)) : null;
38
- // Capture the cycle2 envelope so it routes through finalizeCycle2Run
39
- // (which records cycle2_last_error and clears scheduler delay only on
40
- // ok:true) rather than stamping cycle2 unconditionally afterward.
41
- let capturedCycle2;
42
35
  const promise = runFullBackfill(db, {
43
36
  signal,
44
37
  window,
@@ -48,21 +41,15 @@ export function createBackfillAction({
48
41
  dataDir,
49
42
  ingestTranscriptFile,
50
43
  cwdFromTranscriptPath,
51
- // Re-check the IPC cancel signal at every cycle1/cycle2 iteration the
44
+ // Re-check the IPC cancel signal at every cycle1 iteration the
52
45
  // backfill driver dispatches. handleMemoryAction only checks once
53
46
  // before dispatch; without per-iteration checkpoints a long-running
54
- // backfill keeps spinning through ingest + cycle1 + cycle2 batches
47
+ // backfill keeps spinning through ingest + cycle1 batches
55
48
  // after the proxy has already responded "cancelled" to the caller.
56
49
  runCycle1: (_dbArg, cycle1Config = {}, options = {}, _dir) => {
57
50
  throwIfAborted(signal);
58
51
  return awaitCycle1Run(cycle1Config, { ...options, signal });
59
52
  },
60
- runCycle2: async (dbArg, c2Config, c2Options) => {
61
- throwIfAborted(signal);
62
- const r2 = await runCycle2(dbArg, c2Config, withCycle2Llm({ ...c2Options, signal }));
63
- capturedCycle2 = r2;
64
- return r2;
65
- },
66
53
  });
67
54
  backfillInFlight = promise;
68
55
  let result;
@@ -72,11 +59,8 @@ export function createBackfillAction({
72
59
  if (backfillInFlight === promise) backfillInFlight = null;
73
60
  }
74
61
  throwIfAborted(signal);
75
- if (capturedCycle2) {
76
- await finalizeCycle2Run(capturedCycle2);
77
- }
78
62
  return {
79
- text: `backfill: window=${result.window} scope=${result.scope} files=${result.files} ingested=${result.ingested} cycle1_iters=${result.cycle1_iters} reviewed=${result.reviewed} unclassified=${result.unclassified}${result.error ? ` error=${result.error}` : ''}`,
63
+ text: `backfill: window=${result.window} scope=${result.scope} files=${result.files} ingested=${result.ingested} cycle1_iters=${result.cycle1_iters} unclassified=${result.unclassified}${result.error ? ` error=${result.error}` : ''}`,
80
64
  isError: result.ok === false,
81
65
  };
82
66
  };
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * memory-action-handlers/cycle-rebuild-action.mjs — the destructive rebuild:
3
3
  * drain whatever cycle1 is still running, truncate every classification
4
- * column in one transaction, then re-run cycle1 and cycle2 over the reset
4
+ * column in one transaction, then re-run cycle1 over the reset
5
5
  * rows. The confirm phrase is the only guard the caller gets.
6
6
  */
7
- import { runCycle2 } from '../memory-cycle.mjs';
7
+ import { flushSearchEmbeddings } from '../memory-cycle.mjs';
8
8
  import { getInFlightCycle1 } from '../memory-cycle1.mjs';
9
- import { throwIfAborted } from '../memory-cycle2-shared.mjs';
9
+ import { throwIfAborted } from '../memory-cycle-shared.mjs';
10
10
 
11
11
  // Every root-classification column, cleared before demotion. Cleanup must run
12
12
  // BEFORE demotion: demoting normal roots (chunk_root = id) first and then
@@ -18,7 +18,7 @@ const REBUILD_RESET_ROOTS = `
18
18
  SET element = NULL, category = NULL, summary = NULL,
19
19
  status = 'pending', score = NULL, last_seen_at = NULL,
20
20
  embedding = NULL, summary_hash = NULL,
21
- reviewed_at = NULL, cycle2_reviewed_at = NULL, duplicate_of = NULL,
21
+ reviewed_at = NULL, duplicate_of = NULL,
22
22
  error_count = 0
23
23
  WHERE is_root = 1
24
24
  `;
@@ -28,7 +28,7 @@ const REBUILD_RESET_LEAVES = `
28
28
  element = NULL, category = NULL, summary = NULL,
29
29
  score = NULL, last_seen_at = NULL,
30
30
  embedding = NULL, summary_hash = NULL,
31
- reviewed_at = NULL, cycle2_reviewed_at = NULL, duplicate_of = NULL,
31
+ reviewed_at = NULL, duplicate_of = NULL,
32
32
  error_count = 0
33
33
  WHERE is_root = 0
34
34
  `;
@@ -52,18 +52,12 @@ async function drainCycle1(db, getSchedulerCycle1InFlight) {
52
52
  }
53
53
  }
54
54
 
55
- export function createRebuildAction({
56
- getDb,
57
- startCycle1Run,
58
- finalizeCycle2Run,
59
- getSchedulerCycle1InFlight,
60
- withCycle2Llm,
61
- }) {
55
+ export function createRebuildAction({ getDb, startCycle1Run, getSchedulerCycle1InFlight }) {
62
56
  return async function rebuild(args, config, signal) {
63
57
  const db = getDb();
64
58
  if (args.confirm !== 'REBUILD MEMORY') {
65
59
  return {
66
- text: 'rebuild requires confirm: "REBUILD MEMORY" (truncates classification columns and re-runs cycles)',
60
+ text: 'rebuild requires confirm: "REBUILD MEMORY" (truncates classification columns and re-runs summarization)',
67
61
  isError: true,
68
62
  };
69
63
  }
@@ -84,11 +78,10 @@ export function createRebuildAction({
84
78
  // and guarantees the newly demoted rows are read.
85
79
  const r1 = await startCycle1Run(config?.cycle1 || {}, { signal });
86
80
  throwIfAborted(signal);
87
- const r2 = await runCycle2(db, config?.cycle2 || {}, withCycle2Llm({ signal }));
88
- await finalizeCycle2Run(r2);
81
+ const embeddings = await flushSearchEmbeddings(db, { signal });
82
+ throwIfAborted(signal);
89
83
  return {
90
- text: `rebuild: cycle1 chunks=${r1.chunks} processed=${r1.processed}, cycle2 ${JSON.stringify(r2)}`,
91
- isError: r2.ok === false,
84
+ text: `rebuild: cycle1 chunks=${r1.chunks} processed=${r1.processed}, embeddings=${embeddings.succeeded}`,
92
85
  };
93
86
  };
94
87
  }
@@ -29,15 +29,13 @@ export function createMaintenanceActions({ getDb, entryStats, getCycleLastRun })
29
29
  const { dims, dimsErr } = await embeddingDims(db);
30
30
  const bootstrapComplete = await isBootstrapComplete(db);
31
31
  const lines = [
32
- `entries: total=${stats.total} roots=${stats.roots} cycle1_raw=${stats.unchunked_leaves} (unchunked leaves) cycle2_pending=${stats.cycle2_pending_roots} (awaiting cycle2 review)`,
32
+ `entries: total=${stats.total} roots=${stats.roots} cycle1_raw=${stats.unchunked_leaves} (unchunked leaves)`,
33
33
  `status: ${stats.byStatus.map((r) => `${r.status ?? '?'}:${r.c}`).join(', ') || 'empty'}`,
34
34
  `categories: ${stats.byCategory.map((r) => `${r.category ?? 'NULL'}:${r.c}`).join(', ') || 'empty'}`,
35
35
  `core_memory: user=${stats.core_entries} embed_null=${stats.core_embed_null}`,
36
36
  `embedding_index: ready dims=${dims}${dimsErr ? ` (meta_read_error: ${dimsErr})` : ''}`,
37
37
  `bootstrap: ${bootstrapComplete ? 'complete' : 'incomplete'}`,
38
38
  `last_cycle1: ${agoText(last.cycle1)}`,
39
- `last_cycle2: ${agoText(last.cycle2)}`,
40
- ...(last.cycle2_last_error ? [`last_cycle2_error: ${last.cycle2_last_error}`] : []),
41
39
  ];
42
40
  return { text: lines.join('\n') };
43
41
  }
@@ -154,14 +154,12 @@ export function createManageActions({ getDb, log }) {
154
154
  await db.transaction(async (tx) => {
155
155
  // Editing either side invalidates the duplicate equivalence, not
156
156
  // the original chunks or their lineage.
157
- await tx.query(`UPDATE entries SET duplicate_of = NULL, cycle2_reviewed_at = NULL WHERE duplicate_of = $1`, [
158
- id,
159
- ]);
157
+ await tx.query(`UPDATE entries SET duplicate_of = NULL WHERE duplicate_of = $1`, [id]);
160
158
  await tx.query(
161
159
  `
162
160
  UPDATE entries
163
161
  SET element = $1, summary = $2, category = $3, score = $4,
164
- last_seen_at = $5, content = $6, cycle2_reviewed_at = NULL, duplicate_of = NULL
162
+ last_seen_at = $5, content = $6, duplicate_of = NULL
165
163
  WHERE id = $7
166
164
  `,
167
165
  [finalElement, finalSummary, finalCategory, score, nowMs, composedContent, id]