total-recall-brain 3.6.4 → 3.6.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.
@@ -38,61 +38,81 @@ const RECOMPILE_EVERY_N_TASKS = 20;
38
38
  // Pause between task iterations (ms)
39
39
  const TASK_SLEEP_MS = 10000;
40
40
 
41
- // ─── Categories that require LLM (skipped in deterministic mode) ────────────────
41
+ // ─── Tasks are no longer skipped to conserve resources. We use CLI agents! ───
42
42
 
43
- const LLM_REQUIRED_CATEGORIES = new Set([
44
- 'system2-deliberation',
45
- 'conscious-enforcement',
46
- 'cutoff-audit',
47
- ]);
48
-
49
- /**
50
- * Check if a task requires an LLM to execute.
51
- * These tasks are skipped — the daemon is deterministic.
52
- */
53
- function requiresLLM(task) {
54
- if (LLM_REQUIRED_CATEGORIES.has(task.category)) return true;
55
-
56
- const slug = task.slug;
57
- if (slug.startsWith('research-deliberation-')) return true;
58
- if (slug.startsWith('research-improvement-')) return true;
59
- if (slug.startsWith('research-monitoring-')) return true;
60
- if (slug.startsWith('research-expansion-')) return true;
61
- if (slug.includes('self-diagnosis')) return true;
62
- if (slug.includes('clarity-review')) return true;
63
- if (slug.includes('staleness-check')) return true;
64
- if (slug.includes('inference')) return true;
65
- if (slug.includes('post-mortem')) return true;
66
- if (slug.includes('compliance')) return true;
67
-
68
- return false;
69
- }
70
-
71
- /**
72
- * Dispatch a task to the appropriate engine.
73
- * Only deterministic (non-LLM) tasks are executed.
74
- */
75
43
  async function dispatchTask(task) {
76
44
  const category = task.category;
77
45
 
78
46
  try {
79
- switch (category) {
80
- case 'memory-maintenance':
81
- return await runMaintenanceTask(task);
82
-
83
- case 'proactive-research':
84
- case 'research-acquisition':
85
- return await runResearchTask(task);
86
-
87
- default:
88
- // All other categories (research, deliberation, inference, etc.)
89
- // require LLM and are handled by IDE agents, not the daemon.
90
- logger.info({
91
- subsystem: 'daemon-loop',
92
- message: `Skipping task (not deterministic): ${task.slug} [${category}]`,
93
- });
94
- return { success: true, output: 'Skipped — handled by IDE agents' };
47
+ const { loadRuntimeConfig } = await import('./runtime.mjs');
48
+ const runtimeConfig = await loadRuntimeConfig();
49
+
50
+ // ─── Research & Proactive Tasks (LLM + Fetching) ───
51
+ if (task.slug.startsWith('research-') || category === 'proactive-research' || category === 'research-acquisition') {
52
+ if (task.slug.startsWith('staleness-check-')) {
53
+ const { runStalenessCheck } = await import('./clarity-rewriter.mjs');
54
+ const result = await runStalenessCheck(task.target, { vaultDir: VAULT_DIR, queueDir: QUEUE_DIR, runtimeConfig });
55
+ return { success: true, output: `Staleness check complete: ${result.verdict} (conf: ${result.confidence})` };
56
+ }
57
+ return await runResearchTask(task, runtimeConfig);
58
+ }
59
+
60
+ // ─── System 2 Inference ───
61
+ if (category === 'system2-deliberation') {
62
+ const { runInferenceTask } = await import('./inference-engine.mjs');
63
+ const slugs = task.target ? task.target.split(',') : [];
64
+ const result = await runInferenceTask(slugs, {
65
+ vaultDir: VAULT_DIR,
66
+ inboxDir: path.join(BRAIN_DIR, 'memory-inbox', 'pending'),
67
+ runtimeConfig
68
+ });
69
+ return { success: true, output: `Inference complete: ${result.conclusions?.length || 0} conclusions` };
70
+ }
71
+
72
+ // ─── Cutoff Audit ───
73
+ if (category === 'cutoff-audit' || task.slug.startsWith('cutoff-audit-')) {
74
+ const { runCutoffAudit } = await import('./clarity-rewriter.mjs');
75
+ const result = await runCutoffAudit({ vaultDir: VAULT_DIR, queueDir: QUEUE_DIR, runtimeConfig });
76
+ return { success: true, output: `Cutoff audit complete: ${result.audited} audited, ${result.flagged} flagged, ${result.critical} critical` };
77
+ }
78
+
79
+ // ─── Self Diagnosis & Conscious Enforcement ───
80
+ if (category === 'conscious-enforcement' || task.slug.includes('self-diagnosis')) {
81
+ if (task.slug.startsWith('post-mortem-')) {
82
+ const { runPostMortem } = await import('./post-mortem.mjs');
83
+ const sessionPath = path.join(brainDir, 'sessions', task.target);
84
+ const result = await runPostMortem(sessionPath, { vaultDir: VAULT_DIR, inboxDir: path.join(BRAIN_DIR, 'memory-inbox', 'pending'), runtimeConfig });
85
+ return { success: true, output: `Post-mortem complete: ${result.nodesCreated || 0} nodes extracted` };
86
+ }
87
+
88
+ if (task.slug.includes('self-diagnosis')) {
89
+ const { runSelfDiagnosis } = await import('./fact-seeker.mjs');
90
+ await runSelfDiagnosis({ vaultDir: VAULT_DIR, runtimeConfig });
91
+ return { success: true, output: `Self-diagnosis complete` };
92
+ }
93
+ return { success: true, output: `Conscious enforcement handled` };
95
94
  }
95
+
96
+ // ─── Memory Maintenance ───
97
+ if (category === 'memory-maintenance') {
98
+ if (task.slug.startsWith('clarity-review-')) {
99
+ const { runClarityReview } = await import('./clarity-rewriter.mjs');
100
+ const result = await runClarityReview(task.target, { vaultDir: VAULT_DIR, inboxDir: path.join(BRAIN_DIR, 'memory-inbox', 'pending'), runtimeConfig });
101
+ return { success: true, output: `Clarity review complete: rewrote=${result.rewrote}` };
102
+ }
103
+ if (task.slug.startsWith('memory-compaction-')) {
104
+ const { runMemoryCompaction } = await import('./fact-seeker.mjs');
105
+ const result = await runMemoryCompaction({ vaultDir: VAULT_DIR, inboxDir: path.join(BRAIN_DIR, 'memory-inbox', 'pending'), runtimeConfig });
106
+ return { success: true, output: `Memory compaction complete. Consolidated nodes: ${result.consolidatedCount || 0}` };
107
+ }
108
+ return await runMaintenanceTask(task);
109
+ }
110
+
111
+ logger.info({
112
+ subsystem: 'daemon-loop',
113
+ message: `Skipping unhandled task category: ${task.slug} [${category}]`,
114
+ });
115
+ return { success: true, output: 'Skipped unhandled task' };
96
116
  } catch (err) {
97
117
  logger.info({
98
118
  subsystem: 'daemon-loop',
@@ -104,10 +124,10 @@ async function dispatchTask(task) {
104
124
 
105
125
  // ─── Research Engine (Web Fetching Only — No LLM Synthesis) ─────────────────────
106
126
 
107
- async function runResearchTask(task) {
127
+ async function runResearchTask(task, runtimeConfig) {
108
128
  const inboxDir = path.join(BRAIN_DIR, 'memory-inbox', 'pending');
109
129
 
110
- // Knowledge acquisition — fetch from web, save raw results
130
+ // Phase 1: Knowledge acquisition
111
131
  if (task.slug.startsWith('research-acquisition-') || task.category === 'proactive-research' || task.slug.includes('fact-seeker') || task.slug.includes('knowledge-acquisition')) {
112
132
  const { runKnowledgeAcquisitionCycle } = await import('./fact-seeker.mjs');
113
133
  const forceTopic = task.target || null;
@@ -119,6 +139,7 @@ async function runResearchTask(task) {
119
139
  skillsDir: SKILLS_DIR,
120
140
  derivedDir: DERIVED_DIR,
121
141
  instructionsFile: INSTRUCTIONS_FILE,
142
+ runtimeConfig
122
143
  });
123
144
  if (result.skipped) return { success: true, output: `Knowledge acquisition: ${result.skipped}` };
124
145
  const surfaceNote = result.surfaced ? ' [SURFACED]' : ' [staged]';
@@ -129,8 +150,59 @@ async function runResearchTask(task) {
129
150
  };
130
151
  }
131
152
 
153
+ // Phase 2: Deliberation
154
+ if (task.slug.startsWith('research-deliberation-')) {
155
+ const { runResearchDeliberationCycle } = await import('./fact-seeker.mjs');
156
+ const result = await runResearchDeliberationCycle({
157
+ vaultDir: VAULT_DIR,
158
+ nodeSlug: task._node_slug || 'pending',
159
+ topic: task.title || task.target || 'Unknown Topic',
160
+ runtimeConfig
161
+ });
162
+ return { success: true, output: `Deliberation complete: ${result.slug || 'done'}` };
163
+ }
164
+
165
+ // Phase 3: Improvement
166
+ if (task.slug.startsWith('research-improvement-')) {
167
+ const { runResearchImprovementCycle } = await import('./fact-seeker.mjs');
168
+ const result = await runResearchImprovementCycle({
169
+ vaultDir: VAULT_DIR,
170
+ nodeSlug: task._node_slug || 'pending',
171
+ topic: task.title || task.target || 'Unknown Topic',
172
+ runtimeConfig
173
+ });
174
+ return { success: true, output: `Improvement complete` };
175
+ }
176
+
177
+ // Phase 4: Monitoring
178
+ if (task.slug.startsWith('research-monitoring-')) {
179
+ const { runResearchMonitoringCycle } = await import('./fact-seeker.mjs');
180
+ const result = await runResearchMonitoringCycle({
181
+ vaultDir: VAULT_DIR,
182
+ nodeSlug: task._node_slug || 'pending',
183
+ topic: task.title || task.target || 'Unknown Topic',
184
+ runtimeConfig,
185
+ skillsDir: SKILLS_DIR,
186
+ derivedDir: DERIVED_DIR,
187
+ instructionsFile: INSTRUCTIONS_FILE
188
+ });
189
+ return { success: true, output: `Monitoring complete` };
190
+ }
191
+
192
+ // Phase 5: Expansion
193
+ if (task.slug.startsWith('research-expansion-')) {
194
+ const { runResearchExpansionCycle } = await import('./fact-seeker.mjs');
195
+ const result = await runResearchExpansionCycle({
196
+ vaultDir: VAULT_DIR,
197
+ nodeSlug: task._node_slug || 'pending',
198
+ topic: task.title || task.target || 'Unknown Topic',
199
+ runtimeConfig
200
+ });
201
+ return { success: true, output: `Expansion complete` };
202
+ }
203
+
132
204
  // Conclusion writer — validate pending drafts (deterministic checks)
133
- if (task.slug.includes('validate') || task.category === 'research-acquisition') {
205
+ if (task.slug.includes('validate')) {
134
206
  const { runConclusionWriter } = await import('./conclusion-writer.mjs');
135
207
  const result = await runConclusionWriter({
136
208
  inboxDir,
@@ -309,7 +381,21 @@ async function main() {
309
381
  // Mark complete
310
382
  if ((source === 'explicit' || source === 'idle') && task._filepath) {
311
383
  try {
312
- updateTaskStatus(task, result.success ? 'completed' : 'failed', QUEUE_DIR);
384
+ if (result.skippedLLM) {
385
+ updateTaskStatus(task, 'pending', QUEUE_DIR);
386
+ } else if (result.success) {
387
+ updateTaskStatus(task, 'completed', QUEUE_DIR);
388
+ } else {
389
+ // Dead Letter Queue / Retry Logic
390
+ task.retry_count = (task.retry_count || 0) + 1;
391
+ if (task.retry_count >= 3) {
392
+ logger.info({ subsystem: 'daemon-loop', message: `Task ${task.slug} failed 3 times. Moving to DLQ (failed).` });
393
+ updateTaskStatus(task, 'failed', QUEUE_DIR);
394
+ } else {
395
+ logger.info({ subsystem: 'daemon-loop', message: `Task ${task.slug} failed. Retry ${task.retry_count}/3. Requeueing.` });
396
+ updateTaskStatus(task, 'pending', QUEUE_DIR);
397
+ }
398
+ }
313
399
  } catch (statusErr) {
314
400
  logger.info({
315
401
  subsystem: 'daemon-loop',
@@ -321,15 +407,33 @@ async function main() {
321
407
  try {
322
408
  const patch = {
323
409
  status: result.success ? 'pending' : 'failed',
324
- notes: result.output || result.error || null,
410
+ // Append the output rather than completely overwriting the original rationale
411
+ notes: result.output ? `Phase output: ${result.output}` : (result.error || null),
325
412
  };
326
413
  if (result.factSlug) {
327
414
  patch.node_slug = result.factSlug;
328
415
  }
329
- // Only advance through acquisition phase — skip LLM phases
330
- if (result.success && task._research_phase === 'acquisition' && result.factSlug) {
331
- // Research stays in acquisition — no LLM phases to advance to
332
- patch.status = 'done';
416
+ // If the task was skipped because it requires an LLM, DO NOT complete the research project.
417
+ if (result.skippedLLM) {
418
+ patch.status = 'pending';
419
+ } else if (result.success && result.factSlug) {
420
+ if (task._research_phase === 'acquisition') {
421
+ patch.status = 'pending';
422
+ patch.research_phase = 'deliberation';
423
+ patch.completed_at = new Date().toISOString();
424
+ } else if (task._research_phase === 'deliberation') {
425
+ patch.status = 'pending';
426
+ patch.research_phase = 'improvement';
427
+ } else if (task._research_phase === 'improvement') {
428
+ patch.status = 'pending';
429
+ patch.research_phase = 'monitoring';
430
+ } else if (task._research_phase === 'monitoring') {
431
+ patch.status = 'pending';
432
+ patch.research_phase = 'expansion';
433
+ } else if (task._research_phase === 'expansion') {
434
+ patch.status = 'done';
435
+ patch.completed_at = new Date().toISOString();
436
+ }
333
437
  }
334
438
  updateQueueItem(task._research_id, patch);
335
439
  } catch (err) {
@@ -529,7 +529,8 @@ export function buildMultiNoteGraph(topic, synthesis, parsedSupportingNotes, sta
529
529
  return { slug, body: bodyLines.join('\n'), frontmatter };
530
530
  });
531
531
 
532
- const masterSlug = `fact-master-${crypto.randomBytes(5).toString('hex')}`;
532
+ const topicHash = crypto.createHash('md5').update(topic).digest('hex').slice(0, 10);
533
+ const masterSlug = `fact-master-${topicHash}`;
533
534
 
534
535
  // Bidirectionally link the master node and supporting notes
535
536
  const masterRelated = supportingNodes.map(n => n.slug);
@@ -658,7 +659,8 @@ async function writeAndSurfaceImmediately(topic, synthesis, sourceResults, {
658
659
  const topResults = sourceResults.slice(0, 4);
659
660
  for (const r of topResults) {
660
661
  const summary = await synthesizeSupportingNote(topic, r, runtimeConfig);
661
- const slug = `fact-note-${crypto.randomBytes(5).toString('hex')}`;
662
+ const noteHash = crypto.createHash('md5').update(r.url || r.title || r.snippet).digest('hex').slice(0, 10);
663
+ const slug = `fact-note-${noteHash}`;
662
664
  parsedSupportingNotes.push({ result: r, summary, slug });
663
665
  }
664
666
 
@@ -700,7 +702,8 @@ async function writeDraftToInbox(topic, synthesis, sourceResults, inboxDir, runt
700
702
  const topResults = sourceResults.slice(0, 4);
701
703
  for (const r of topResults) {
702
704
  const summary = await synthesizeSupportingNote(topic, r, runtimeConfig);
703
- const slug = `fact-note-${crypto.randomBytes(5).toString('hex')}`;
705
+ const noteHash = crypto.createHash('md5').update(r.url || r.title || r.snippet).digest('hex').slice(0, 10);
706
+ const slug = `fact-note-${noteHash}`;
704
707
  parsedSupportingNotes.push({ result: r, summary, slug });
705
708
  }
706
709
 
@@ -325,6 +325,14 @@ export function listQueue({ status, query, limit = 100, offset = 0, brainDir: ov
325
325
  */
326
326
  export function addToQueue({ topic, priority = 'medium', notes, brainDir: overrideBrainDir } = {}) {
327
327
  if (!topic) throw new Error('topic is required');
328
+
329
+ const items = loadQueue(overrideBrainDir);
330
+ const normalizedTopic = String(topic).trim().toLowerCase();
331
+ const existing = items.find(i => i.topic.trim().toLowerCase() === normalizedTopic);
332
+ if (existing) {
333
+ return existing;
334
+ }
335
+
328
336
  const item = {
329
337
  id: crypto.randomUUID(),
330
338
  topic: String(topic),
@@ -338,7 +346,6 @@ export function addToQueue({ topic, priority = 'medium', notes, brainDir: overri
338
346
  completed_at: null,
339
347
  };
340
348
  item.summary = compileResearchProjectSummary(item, null);
341
- const items = loadQueue(overrideBrainDir);
342
349
  items.unshift(item);
343
350
  saveQueue(items, overrideBrainDir);
344
351
 
@@ -210,6 +210,7 @@ export function generateIdleTask({ vaultDir, sessionsDir }) {
210
210
  () => generateInferenceTask(vaultDir),
211
211
  () => generatePostMortemTask(sessionsDir),
212
212
  () => generateClarityReviewTask(vaultDir),
213
+ () => generateMemoryCompactionTask(vaultDir),
213
214
  ];
214
215
 
215
216
  const strategy = cleanStrategies[_idleCycleCounter % cleanStrategies.length];
@@ -248,18 +249,34 @@ function generateClarityReviewTask(vaultDir) {
248
249
  if (nodes.length === 0) {
249
250
  return makeFallbackTask('memory-maintenance', 'No active nodes to review');
250
251
  }
251
-
252
- const target = nodes[crypto.randomInt(0, nodes.length)];
252
+ const target = nodes[Math.floor(Math.random() * nodes.length)];
253
253
  return {
254
254
  type: 'task',
255
- slug: `clarity-review-${target.slug}-${Date.now().toString(36)}`,
255
+ slug: `clarity-review-${crypto.randomBytes(4).toString('hex')}`,
256
256
  priority: 30,
257
257
  category: 'memory-maintenance',
258
258
  target: target.slug,
259
259
  status: 'pending',
260
260
  created_by: 'scheduler-idle',
261
- reason: `Idle task: review "${target.title}" for clarity and actionability.`,
262
- body: `## Objective\nReview memory node "${target.slug}" and evaluate:\n1. Is the title descriptive enough for a 7-item skill injection?\n2. Is the body actionable by an AI agent?\n3. Does the frontmatter accurately reflect the content?\n\nIf improvements are needed, generate a rewrite proposal.`,
261
+ reason: `Idle task: selected active node ${target.slug} for clarity review.`,
262
+ body: `Analyze node ${target.slug} for clarity, deduplication, formatting, and structural integrity. Rewrite via SSSS strict compliance if needed.`,
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Generate a memory compaction task to fuse highly overlapping fragmented nodes.
268
+ */
269
+ function generateMemoryCompactionTask(vaultDir) {
270
+ return {
271
+ type: 'task',
272
+ slug: `memory-compaction-${crypto.randomBytes(4).toString('hex')}`,
273
+ priority: 35,
274
+ category: 'memory-maintenance',
275
+ target: 'global',
276
+ status: 'pending',
277
+ created_by: 'scheduler-idle',
278
+ reason: `Idle task: scan vault for fragmented nodes to merge into comprehensive master documents.`,
279
+ body: `Scan for highly overlapping or fragmented memory nodes and fuse them into comprehensive master nodes, archiving the fragments.`,
263
280
  };
264
281
  }
265
282
 
@@ -523,6 +540,7 @@ export function createScheduler({ queueDir, vaultDir, sessionsDir }) {
523
540
  body,
524
541
  _research_id: item.id,
525
542
  _research_phase: phase,
543
+ _node_slug: item.node_slug,
526
544
  });
527
545
  }
528
546
  if (researchItems.length > 0) {
@@ -644,6 +644,28 @@ export const PhoneNumberSchema = z.object({
644
644
  user_id: z.string().optional().nullable(),
645
645
  });
646
646
 
647
+ export const DomainSchema = z.object({
648
+ type: z.literal('domain'),
649
+ domain_name: z.string(),
650
+ registrar: z.string(),
651
+ status: z.string(),
652
+ payment_status: z.string(),
653
+ workspace_id: z.string().optional().nullable(),
654
+ user_id: z.string().optional().nullable(),
655
+ base_price_usd: z.number().optional().nullable(),
656
+ purchase_price_usd: z.number().optional().nullable(),
657
+ annual_renewal_usd: z.number().optional().nullable(),
658
+ markup_amount_usd: z.number().optional().nullable(),
659
+ markup_rate: z.number().optional().nullable(),
660
+ expires_at: z.string().optional().nullable(),
661
+ payment_method: z.string().optional().nullable(),
662
+ vercel_domain_id: z.string().optional().nullable(),
663
+ stripe_payment_intent_id: z.string().optional().nullable(),
664
+ stripe_checkout_session_id: z.string().optional().nullable(),
665
+ credits_charged: z.number().optional().nullable(),
666
+ credit_transaction_id: z.string().optional().nullable(),
667
+ });
668
+
647
669
  export const WorkspaceSchema = z.object({
648
670
  type: z.literal('workspace'),
649
671
  name: z.string(),
@@ -804,6 +826,7 @@ export const SSSS_SCHEMAS = {
804
826
  workspace_transfer: WorkspaceTransferSchema,
805
827
  extension: ExtensionSchema,
806
828
  phone_number: PhoneNumberSchema,
829
+ domain: DomainSchema,
807
830
  workspace: WorkspaceSchema,
808
831
  commerce_catalog: CommerceCatalogSchema,
809
832
  product: ProductSchema,
@@ -138,6 +138,23 @@ app.get('/health', requireAuthOrLocal, async (req, res) => {
138
138
  // Determine overall status
139
139
  const hasCriticalIssue = emergencyAlerts.length > 0 || daemonStatus === 'dead' || cliAgents.length === 0;
140
140
 
141
+ // Check Caddy and Cloudflare status
142
+ let caddyStatus = 'inactive';
143
+ let cloudflareStatus = 'inactive';
144
+ try {
145
+ const { exec } = await import('node:child_process');
146
+ const util = await import('node:util');
147
+ const execAsync = util.promisify(exec);
148
+ try {
149
+ await execAsync('pgrep caddy');
150
+ caddyStatus = 'active';
151
+ } catch { }
152
+ try {
153
+ await execAsync('pgrep cloudflared');
154
+ cloudflareStatus = 'active';
155
+ } catch { }
156
+ } catch { }
157
+
141
158
  res.json({
142
159
  status: hasCriticalIssue ? 'degraded' : 'healthy',
143
160
  version: PACKAGE_VERSION,
@@ -146,6 +163,8 @@ app.get('/health', requireAuthOrLocal, async (req, res) => {
146
163
  disk,
147
164
  cli_agents: cliAgents,
148
165
  daemon: daemonStatus,
166
+ caddy: caddyStatus,
167
+ cloudflare: cloudflareStatus,
149
168
  emergency_alerts: emergencyAlerts || null,
150
169
  vfs: {
151
170
  exists: vaultExists,