total-recall-brain 3.13.0 → 3.13.2
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.
- package/README.md +91 -108
- package/bin/antigravity.mjs +81 -52
- package/bin/total-recall.mjs +32 -57
- package/extension/background.js +1 -1
- package/extension/options/options.js +9 -3
- package/extension/sidepanel/sidepanel.js +0 -2
- package/frontend/index.html +4 -3
- package/frontend/package.json +1 -0
- package/frontend/src/App.tsx +109 -152
- package/frontend/src/api.ts +605 -23
- package/frontend/src/components/BrainSelector.tsx +211 -199
- package/frontend/src/components/DocumentEditorModal.tsx +57 -0
- package/frontend/src/components/DocumentTable.tsx +47 -0
- package/frontend/src/components/Graph3D.tsx +120 -71
- package/frontend/src/components/UsageChart.tsx +3 -3
- package/frontend/src/components/brand/BrandMark.tsx +95 -0
- package/frontend/src/index.css +286 -86
- package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
- package/frontend/src/pages/ChatPage.tsx +103 -45
- package/frontend/src/pages/DesignDocsPage.tsx +5 -5
- package/frontend/src/pages/FilesPage.tsx +10 -6
- package/frontend/src/pages/GraphPage.tsx +5 -3
- package/frontend/src/pages/HelpPage.tsx +2 -2
- package/frontend/src/pages/InboxPage.tsx +82 -0
- package/frontend/src/pages/InstructionsPage.tsx +1 -1
- package/frontend/src/pages/IntegrationsPage.tsx +21 -10
- package/frontend/src/pages/LoginPage.tsx +95 -77
- package/frontend/src/pages/MemoryPage.tsx +31 -8
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +412 -323
- package/frontend/src/pages/SettingsPage.tsx +825 -922
- package/frontend/src/pages/SkillsPage.tsx +272 -147
- package/frontend/src/pages/UsagePage.tsx +23 -31
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +13 -0
- package/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
- package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
- package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
- package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
- package/src/cli/brain.mjs +63 -5
- package/src/cli/connect.mjs +11 -19
- package/src/cli/daemon.mjs +5 -4
- package/src/cli/deploy-ui.mjs +8 -3
- package/src/cli/deploy.mjs +84 -27
- package/src/cli/ingest/index.mjs +1 -1
- package/src/cli/init.mjs +63 -34
- package/src/cli/remember.mjs +3 -3
- package/src/cli/secret.mjs +812 -0
- package/src/cli/setup.mjs +34 -7
- package/src/cli/share.mjs +2 -2
- package/src/cli/skill.mjs +396 -35
- package/src/cli/sync.mjs +4 -4
- package/src/cli/task.mjs +287 -0
- package/src/core/clarity-rewriter.mjs +5 -5
- package/src/core/config.mjs +44 -2
- package/src/core/conflict-detector.mjs +5 -5
- package/src/core/crons.mjs +54 -0
- package/src/core/daemon-loop.mjs +87 -235
- package/src/core/dream.mjs +170 -10
- package/src/core/env-import.mjs +456 -0
- package/src/core/fact-seeker.mjs +14 -14
- package/src/core/import-rules.mjs +20 -7
- package/src/core/inference-engine.mjs +3 -3
- package/src/core/migrate.mjs +14 -8
- package/src/core/okf-adapter.mjs +2 -2
- package/src/core/operation-validator.mjs +39 -331
- package/src/core/optimizer.mjs +7 -0
- package/src/core/post-mortem.mjs +2 -2
- package/src/core/project-brain.mjs +349 -0
- package/src/core/provider-catalog.mjs +296 -0
- package/src/core/quick-capture.mjs +1 -1
- package/src/core/remote-vault-sync.mjs +194 -0
- package/src/core/research-queue.mjs +7 -11
- package/src/core/runtime.mjs +33 -9
- package/src/core/scheduler.mjs +59 -5
- package/src/core/schema.mjs +14 -0
- package/src/core/search.mjs +1 -1
- package/src/core/secrets-env-export.mjs +323 -0
- package/src/core/secrets-rotate.mjs +245 -0
- package/src/core/secrets-store.mjs +645 -0
- package/src/core/semantic-index.mjs +3 -3
- package/src/core/skills-registry.mjs +1062 -0
- package/src/core/source-adapters.mjs +4 -1
- package/src/core/ssss-host-extension.mjs +94 -0
- package/src/core/ssss-kernel-bridge.mjs +530 -0
- package/src/core/surface.mjs +14 -0
- package/src/core/task-envelope.mjs +298 -0
- package/src/core/task-executors.mjs +600 -0
- package/src/core/validated-write.mjs +124 -56
- package/src/core/vault.mjs +21 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +26 -16
- package/src/server/auth.mjs +51 -0
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +198 -25
- package/src/server/route-manifest.json +156 -0
- package/src/server/routes/_shared.mjs +40 -1
- package/src/server/routes/docs.mjs +260 -0
- package/src/server/routes/memory.mjs +145 -32
- package/src/server/routes/research.mjs +30 -0
- package/src/server/routes/secrets.mjs +470 -0
- package/src/server/routes/share.mjs +2 -2
- package/src/server/routes/skills.mjs +452 -54
- package/src/server/routes/sync.mjs +48 -0
- package/src/server/routes/webauthn.mjs +184 -0
- package/src/server/update-route-manifest.mjs +39 -0
- package/templates/onboarding-interview.md +32 -22
- package/templates/openwiki/README.md +10 -0
- package/templates/openwiki/architecture.md +5 -0
- package/templates/openwiki/ide.md +10 -0
- package/templates/openwiki/memory.md +11 -0
- package/templates/openwiki/secrets.md +7 -0
- package/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
- /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
package/src/core/daemon-loop.mjs
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Total Recall Active Intelligence Daemon Loop —
|
|
2
|
+
* Total Recall Active Intelligence Daemon Loop — v3
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Durable worker over the open task envelope queue.
|
|
5
|
+
* - Dispatches via executor registry (fail-loud on unknown)
|
|
6
|
+
* - Does NOT invent idle work unless TR_IDLE_TASKS=1
|
|
7
|
+
* - Periodically runs dream (system memory consolidation)
|
|
8
|
+
* - Ingests sessions on boot; recompiles surfaces every N ticks
|
|
6
9
|
*
|
|
7
|
-
*
|
|
8
|
-
* 1. Ingest new IDE conversation logs
|
|
9
|
-
* 2. Load/refresh task queue from disk
|
|
10
|
-
* 3. Dispatch next task (deterministic only — no LLM tasks)
|
|
11
|
-
* 4. Periodically recompile surfaces
|
|
12
|
-
*
|
|
13
|
-
* This script is spawned by `total-recall daemon start` and runs until killed.
|
|
10
|
+
* Spawned by `total-recall daemon start`.
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
import path from 'path';
|
|
17
14
|
import fs from 'fs';
|
|
18
|
-
import { createScheduler, updateTaskStatus } from './scheduler.mjs';
|
|
15
|
+
import { createScheduler, updateTaskStatus, persistTaskToDisk } from './scheduler.mjs';
|
|
19
16
|
import { scanAndIngest } from './session-watcher.mjs';
|
|
20
17
|
import { logger } from './logger.mjs';
|
|
21
|
-
import { updateQueueItem
|
|
22
|
-
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { updateQueueItem } from './research-queue.mjs';
|
|
23
19
|
import { agentDir, brainDir } from './config.mjs';
|
|
20
|
+
import { runCrons } from './crons.mjs';
|
|
21
|
+
import { dispatchTask as registryDispatch } from './task-executors.mjs';
|
|
22
|
+
import { buildTaskEnvelope } from './task-envelope.mjs';
|
|
24
23
|
|
|
25
24
|
const AGENT_DIR = agentDir;
|
|
26
25
|
const BRAIN_DIR = brainDir;
|
|
@@ -35,84 +34,32 @@ const INSTRUCTIONS_FILE = path.join(BRAIN_DIR, 'INSTRUCTIONS.md');
|
|
|
35
34
|
// How often to recompile surfaces (every N task ticks)
|
|
36
35
|
const RECOMPILE_EVERY_N_TASKS = 20;
|
|
37
36
|
|
|
38
|
-
//
|
|
37
|
+
// Empty-queue sleep (ms) — longer when idle so we don't spin
|
|
39
38
|
const TASK_SLEEP_MS = 10000;
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const EMPTY_SLEEP_MS = 30000;
|
|
40
|
+
|
|
41
|
+
// Run a system dream every N empty ticks (memory consolidation without busy-work)
|
|
42
|
+
const DREAM_EVERY_N_EMPTY = 20;
|
|
43
|
+
|
|
44
|
+
function executorContext(runtimeConfig) {
|
|
45
|
+
return {
|
|
46
|
+
brainDir: BRAIN_DIR,
|
|
47
|
+
vaultDir: VAULT_DIR,
|
|
48
|
+
skillsDir: SKILLS_DIR,
|
|
49
|
+
derivedDir: DERIVED_DIR,
|
|
50
|
+
sessionsDir: SESSIONS_DIR,
|
|
51
|
+
queueDir: QUEUE_DIR,
|
|
52
|
+
conflictsDir: CONFLICTS_DIR,
|
|
53
|
+
instructionsFile: INSTRUCTIONS_FILE,
|
|
54
|
+
runtimeConfig,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
42
57
|
|
|
43
58
|
async function dispatchTask(task) {
|
|
44
|
-
const category = task.category;
|
|
45
|
-
|
|
46
59
|
try {
|
|
47
60
|
const { loadRuntimeConfig } = await import('./runtime.mjs');
|
|
48
61
|
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` };
|
|
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' };
|
|
62
|
+
return await registryDispatch(task, executorContext(runtimeConfig));
|
|
116
63
|
} catch (err) {
|
|
117
64
|
logger.info({
|
|
118
65
|
subsystem: 'daemon-loop',
|
|
@@ -122,137 +69,6 @@ async function dispatchTask(task) {
|
|
|
122
69
|
}
|
|
123
70
|
}
|
|
124
71
|
|
|
125
|
-
// ─── Research Engine (Web Fetching Only — No LLM Synthesis) ─────────────────────
|
|
126
|
-
|
|
127
|
-
async function runResearchTask(task, runtimeConfig) {
|
|
128
|
-
const inboxDir = path.join(BRAIN_DIR, 'memory-inbox', 'pending');
|
|
129
|
-
|
|
130
|
-
// Phase 1: Knowledge acquisition
|
|
131
|
-
if (task.slug.startsWith('research-acquisition-') || task.category === 'proactive-research' || task.slug.includes('fact-seeker') || task.slug.includes('knowledge-acquisition')) {
|
|
132
|
-
const { runKnowledgeAcquisitionCycle } = await import('./fact-seeker.mjs');
|
|
133
|
-
const forceTopic = task.target || null;
|
|
134
|
-
const result = await runKnowledgeAcquisitionCycle({
|
|
135
|
-
vaultDir: VAULT_DIR,
|
|
136
|
-
inboxDir,
|
|
137
|
-
queueDir: QUEUE_DIR,
|
|
138
|
-
forceTopic,
|
|
139
|
-
skillsDir: SKILLS_DIR,
|
|
140
|
-
derivedDir: DERIVED_DIR,
|
|
141
|
-
instructionsFile: INSTRUCTIONS_FILE,
|
|
142
|
-
runtimeConfig
|
|
143
|
-
});
|
|
144
|
-
if (result.skipped) return { success: true, output: `Knowledge acquisition: ${result.skipped}` };
|
|
145
|
-
const surfaceNote = result.surfaced ? ' [SURFACED]' : ' [staged]';
|
|
146
|
-
return {
|
|
147
|
-
success: true,
|
|
148
|
-
output: `Researched "${result.topic}": ${result.sources?.join(', ')} | confidence: ${result.confidence || 'n/a'} | slug: ${result.factSlug}${surfaceNote}`,
|
|
149
|
-
factSlug: result.factSlug,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
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
|
-
|
|
204
|
-
// Conclusion writer — validate pending drafts (deterministic checks)
|
|
205
|
-
if (task.slug.includes('validate')) {
|
|
206
|
-
const { runConclusionWriter } = await import('./conclusion-writer.mjs');
|
|
207
|
-
const result = await runConclusionWriter({
|
|
208
|
-
inboxDir,
|
|
209
|
-
vaultDir: VAULT_DIR,
|
|
210
|
-
quarantineDir: path.join(BRAIN_DIR, 'memory-inbox', 'quarantine'),
|
|
211
|
-
});
|
|
212
|
-
if (result.skipped) return { success: true, output: `Conclusion writer: ${result.skipped}` };
|
|
213
|
-
return {
|
|
214
|
-
success: true,
|
|
215
|
-
output: `Validated ${result.processed} drafts: ${result.approved} approved, ${result.rejected} rejected`,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return { success: true, output: 'No-op research task' };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ─── Maintenance Engine (Deterministic Only) ────────────────────────────────────
|
|
223
|
-
|
|
224
|
-
async function runMaintenanceTask(task) {
|
|
225
|
-
// Lease vacuuming — purely filesystem, no LLM
|
|
226
|
-
try {
|
|
227
|
-
const leasesDir = path.join(BRAIN_DIR, 'leases');
|
|
228
|
-
if (fs.existsSync(leasesDir)) {
|
|
229
|
-
const workspaces = fs.readdirSync(leasesDir);
|
|
230
|
-
for (const ws of workspaces) {
|
|
231
|
-
const wsDir = path.join(leasesDir, ws);
|
|
232
|
-
if (!fs.statSync(wsDir).isDirectory()) continue;
|
|
233
|
-
const files = fs.readdirSync(wsDir);
|
|
234
|
-
for (const file of files) {
|
|
235
|
-
if (!file.endsWith('.lease.json')) continue;
|
|
236
|
-
const fp = path.join(wsDir, file);
|
|
237
|
-
try {
|
|
238
|
-
const lease = JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
239
|
-
if (new Date(lease.expires_at) < new Date()) {
|
|
240
|
-
fs.unlinkSync(fp);
|
|
241
|
-
logger.info({ subsystem: 'daemon-loop', message: `Vacuumed expired lease: ${fp}` });
|
|
242
|
-
}
|
|
243
|
-
} catch {
|
|
244
|
-
try { fs.unlinkSync(fp); } catch {}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
} catch (err) {
|
|
250
|
-
logger.info({ subsystem: 'daemon-loop', message: `Lease vacuuming failed: ${err.message}` });
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return { success: true, output: 'Maintenance complete' };
|
|
254
|
-
}
|
|
255
|
-
|
|
256
72
|
// ─── Interrupt Writer ───────────────────────────────────────────────────────────
|
|
257
73
|
|
|
258
74
|
/**
|
|
@@ -304,7 +120,7 @@ process.on('unhandledRejection', (reason, promise) => {
|
|
|
304
120
|
async function main() {
|
|
305
121
|
logger.info({
|
|
306
122
|
subsystem: 'daemon-loop',
|
|
307
|
-
message: `
|
|
123
|
+
message: `Daemon starting (open task envelope). Vault: ${VAULT_DIR} idle=${process.env.TR_IDLE_TASKS === '1' ? 'on' : 'off'}`,
|
|
308
124
|
});
|
|
309
125
|
|
|
310
126
|
// Initial session ingest
|
|
@@ -318,6 +134,8 @@ async function main() {
|
|
|
318
134
|
logger.info({ subsystem: 'daemon-loop', message: `Boot ingest failed: ${err.message}` });
|
|
319
135
|
}
|
|
320
136
|
|
|
137
|
+
let emptyTicks = 0;
|
|
138
|
+
|
|
321
139
|
while (running) {
|
|
322
140
|
try {
|
|
323
141
|
// Periodically recompile surfaces
|
|
@@ -335,7 +153,6 @@ async function main() {
|
|
|
335
153
|
logger.info({ subsystem: 'daemon-loop', message: `Surface recompile error: ${err.message}` });
|
|
336
154
|
}
|
|
337
155
|
|
|
338
|
-
// Compact any append logs with accumulated dirty entries
|
|
339
156
|
try {
|
|
340
157
|
const { compactAppendLogs } = await import('./append-log.mjs');
|
|
341
158
|
const compactResult = compactAppendLogs();
|
|
@@ -345,25 +162,63 @@ async function main() {
|
|
|
345
162
|
} catch (err) {
|
|
346
163
|
logger.info({ subsystem: 'daemon-loop', message: `Append log compaction error: ${err.message}` });
|
|
347
164
|
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await runCrons({ vaultDir: VAULT_DIR, skillsDir: SKILLS_DIR, brainDir: BRAIN_DIR });
|
|
168
|
+
} catch (err) {
|
|
169
|
+
logger.error({ subsystem: 'daemon-loop', message: `Cron execution failed: ${err.message}` });
|
|
170
|
+
}
|
|
348
171
|
}
|
|
349
172
|
|
|
350
|
-
// Refresh scheduler from disk on each iteration
|
|
351
173
|
const scheduler = createScheduler({
|
|
352
174
|
queueDir: QUEUE_DIR,
|
|
353
175
|
vaultDir: VAULT_DIR,
|
|
354
176
|
sessionsDir: SESSIONS_DIR,
|
|
355
177
|
});
|
|
356
178
|
|
|
357
|
-
|
|
179
|
+
let { task, source } = scheduler.next();
|
|
180
|
+
|
|
181
|
+
// Empty queue: optional system dream, then sleep (no busy-work invention)
|
|
182
|
+
if (!task) {
|
|
183
|
+
emptyTicks++;
|
|
184
|
+
if (emptyTicks % DREAM_EVERY_N_EMPTY === 0) {
|
|
185
|
+
const dreamEnv = buildTaskEnvelope({
|
|
186
|
+
intent: 'Scheduled system dream cycle',
|
|
187
|
+
slug: `dream-system-${Date.now().toString(36)}`,
|
|
188
|
+
kind: 'system',
|
|
189
|
+
executor: 'dream',
|
|
190
|
+
category: 'memory-maintenance',
|
|
191
|
+
priority: 70,
|
|
192
|
+
system: true,
|
|
193
|
+
origin: { agent: 'daemon', created_by: 'daemon-system' },
|
|
194
|
+
capabilities: ['vault:read', 'vault:write'],
|
|
195
|
+
});
|
|
196
|
+
task = persistTaskToDisk(dreamEnv, QUEUE_DIR);
|
|
197
|
+
source = 'system';
|
|
198
|
+
logger.info({
|
|
199
|
+
subsystem: 'daemon-loop',
|
|
200
|
+
message: `Empty queue → enqueue system dream (${emptyTicks} empty ticks)`,
|
|
201
|
+
});
|
|
202
|
+
} else {
|
|
203
|
+
logger.info({
|
|
204
|
+
subsystem: 'daemon-loop',
|
|
205
|
+
message: `Queue empty (tick ${emptyTicks}) — sleeping ${EMPTY_SLEEP_MS}ms`,
|
|
206
|
+
});
|
|
207
|
+
await new Promise((r) => setTimeout(r, EMPTY_SLEEP_MS));
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
emptyTicks = 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
358
214
|
taskCount++;
|
|
359
215
|
|
|
360
216
|
logger.info({
|
|
361
217
|
subsystem: 'daemon-loop',
|
|
362
|
-
message: `Task #${taskCount} [${source}] ${task.category}: ${task.slug}`,
|
|
218
|
+
message: `Task #${taskCount} [${source}] ${task.category || task.kind}: ${task.slug}`,
|
|
363
219
|
});
|
|
364
220
|
|
|
365
|
-
|
|
366
|
-
if ((source === 'explicit' || source === 'idle') && task._filepath) {
|
|
221
|
+
if ((source === 'explicit' || source === 'idle' || source === 'system') && task._filepath) {
|
|
367
222
|
try {
|
|
368
223
|
updateTaskStatus(task, 'in-progress', QUEUE_DIR);
|
|
369
224
|
} catch (statusErr) {
|
|
@@ -386,22 +241,23 @@ async function main() {
|
|
|
386
241
|
|
|
387
242
|
const result = await dispatchTask(task);
|
|
388
243
|
|
|
389
|
-
|
|
390
|
-
if ((source === 'explicit' || source === 'idle') && task._filepath) {
|
|
244
|
+
if ((source === 'explicit' || source === 'idle' || source === 'system') && task._filepath) {
|
|
391
245
|
try {
|
|
392
246
|
if (result.skippedLLM) {
|
|
393
247
|
updateTaskStatus(task, 'pending', QUEUE_DIR);
|
|
394
248
|
} else if (result.success) {
|
|
395
249
|
updateTaskStatus(task, 'completed', QUEUE_DIR);
|
|
396
250
|
} else {
|
|
397
|
-
// Dead Letter Queue / Retry Logic
|
|
398
251
|
task.retry_count = (task.retry_count || 0) + 1;
|
|
252
|
+
task.last_error = result.error || 'Unknown error';
|
|
399
253
|
if (task.retry_count >= 3) {
|
|
400
|
-
logger.info({ subsystem: 'daemon-loop', message: `Task ${task.slug} failed
|
|
401
|
-
updateTaskStatus(task, 'failed', QUEUE_DIR);
|
|
254
|
+
logger.info({ subsystem: 'daemon-loop', message: `Task ${task.slug} failed ${task.retry_count} times. Moving to DLQ (failed). Last error: ${task.last_error}` });
|
|
255
|
+
updateTaskStatus(task, 'failed', QUEUE_DIR, result.error || 'Unknown error');
|
|
402
256
|
} else {
|
|
403
|
-
|
|
404
|
-
|
|
257
|
+
const backoffMs = Math.min(Math.pow(2, task.retry_count) * 1000, 60000);
|
|
258
|
+
logger.info({ subsystem: 'daemon-loop', message: `Task ${task.slug} failed. Retry ${task.retry_count}/3 after ${backoffMs}ms backoff. Error: ${task.last_error}` });
|
|
259
|
+
updateTaskStatus(task, 'pending', QUEUE_DIR, result.error || 'Unknown error');
|
|
260
|
+
await new Promise(r => setTimeout(r, backoffMs));
|
|
405
261
|
}
|
|
406
262
|
}
|
|
407
263
|
} catch (statusErr) {
|
|
@@ -415,13 +271,11 @@ async function main() {
|
|
|
415
271
|
try {
|
|
416
272
|
const patch = {
|
|
417
273
|
status: result.success ? 'pending' : 'failed',
|
|
418
|
-
// Append the output rather than completely overwriting the original rationale
|
|
419
274
|
notes: result.output ? `Phase output: ${result.output}` : (result.error || null),
|
|
420
275
|
};
|
|
421
276
|
if (result.factSlug) {
|
|
422
277
|
patch.node_slug = result.factSlug;
|
|
423
278
|
}
|
|
424
|
-
// If the task was skipped because it requires an LLM, DO NOT complete the research project.
|
|
425
279
|
if (result.skippedLLM) {
|
|
426
280
|
patch.status = 'pending';
|
|
427
281
|
} else if (result.success) {
|
|
@@ -460,11 +314,9 @@ async function main() {
|
|
|
460
314
|
message: `Task #${taskCount} done: ${result.output || result.error || 'ok'}`,
|
|
461
315
|
});
|
|
462
316
|
|
|
463
|
-
// Throttle to protect local resources
|
|
464
317
|
await new Promise(r => setTimeout(r, TASK_SLEEP_MS));
|
|
465
318
|
|
|
466
319
|
} catch (loopErr) {
|
|
467
|
-
// Individual task failures must NEVER kill the daemon.
|
|
468
320
|
logger.info({
|
|
469
321
|
subsystem: 'daemon-loop',
|
|
470
322
|
message: `Task loop iteration crashed (non-fatal, continuing): ${loopErr.message}`,
|
|
@@ -473,7 +325,7 @@ async function main() {
|
|
|
473
325
|
}
|
|
474
326
|
}
|
|
475
327
|
|
|
476
|
-
logger.info({ subsystem: 'daemon-loop', message: '
|
|
328
|
+
logger.info({ subsystem: 'daemon-loop', message: 'Daemon stopped.' });
|
|
477
329
|
}
|
|
478
330
|
|
|
479
331
|
main().catch(async (err) => {
|
package/src/core/dream.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import matter from 'gray-matter';
|
|
4
6
|
import { writeNode, atomicWrite, walkMd } from './vault.mjs';
|
|
5
7
|
import { getNodes } from './vault-cache.mjs';
|
|
6
8
|
import { logger } from './logger.mjs';
|
|
@@ -231,6 +233,139 @@ export function scanModifiedVault(vaultDir, sinceHours = 24) {
|
|
|
231
233
|
});
|
|
232
234
|
}
|
|
233
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Load REM candidates from memory-inbox (pending + capture drafts).
|
|
238
|
+
*/
|
|
239
|
+
export function loadCandidatesFromInbox(brainDirPath) {
|
|
240
|
+
const candidates = [];
|
|
241
|
+
const roots = [
|
|
242
|
+
path.join(brainDirPath, 'memory-inbox', 'pending'),
|
|
243
|
+
path.join(brainDirPath, 'memory-inbox', 'capture'),
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
for (const dir of roots) {
|
|
247
|
+
if (!fs.existsSync(dir)) continue;
|
|
248
|
+
let files = [];
|
|
249
|
+
try {
|
|
250
|
+
files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
|
|
251
|
+
} catch {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
for (const file of files) {
|
|
255
|
+
try {
|
|
256
|
+
const raw = fs.readFileSync(path.join(dir, file), 'utf8');
|
|
257
|
+
const { data, content } = matter(raw);
|
|
258
|
+
if (!data || (!data.slug && !data.title && !String(content || '').trim())) continue;
|
|
259
|
+
const status = data.status || 'draft';
|
|
260
|
+
if (status === 'archived' || status === 'active') continue;
|
|
261
|
+
candidates.push({
|
|
262
|
+
...data,
|
|
263
|
+
slug: data.slug || path.basename(file, '.md'),
|
|
264
|
+
category: data.category || 'facts',
|
|
265
|
+
title: data.title || data.slug || file,
|
|
266
|
+
status: 'draft',
|
|
267
|
+
confidence: data.confidence ?? 0.55,
|
|
268
|
+
importance: data.importance ?? 2,
|
|
269
|
+
evidence_count: data.evidence_count || data.source?.evidence_count || 1,
|
|
270
|
+
body: content,
|
|
271
|
+
_inbox_path: path.join(dir, file),
|
|
272
|
+
});
|
|
273
|
+
} catch (err) {
|
|
274
|
+
logger.warn('dream', `Inbox candidate skip ${file}: ${err.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return candidates;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Lightweight session → candidate extraction (deterministic heuristics).
|
|
283
|
+
* User lines that signal durable preferences/rules become draft candidates.
|
|
284
|
+
*/
|
|
285
|
+
export function loadCandidatesFromSessions(sessionsDir, { maxFiles = 8, maxCandidates = 20 } = {}) {
|
|
286
|
+
const candidates = [];
|
|
287
|
+
if (!fs.existsSync(sessionsDir)) return candidates;
|
|
288
|
+
|
|
289
|
+
const signal =
|
|
290
|
+
/\b(always|never|prefer|remember|must not|must|invariant|don't|do not|from now on)\b/i;
|
|
291
|
+
|
|
292
|
+
let files = [];
|
|
293
|
+
try {
|
|
294
|
+
files = fs
|
|
295
|
+
.readdirSync(sessionsDir)
|
|
296
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
297
|
+
.map((f) => ({
|
|
298
|
+
name: f,
|
|
299
|
+
mtime: fs.statSync(path.join(sessionsDir, f)).mtimeMs,
|
|
300
|
+
}))
|
|
301
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
302
|
+
.slice(0, maxFiles);
|
|
303
|
+
} catch {
|
|
304
|
+
return candidates;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const file of files) {
|
|
308
|
+
let lines;
|
|
309
|
+
try {
|
|
310
|
+
lines = fs.readFileSync(path.join(sessionsDir, file.name), 'utf8').split('\n').filter(Boolean);
|
|
311
|
+
} catch {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
for (const line of lines) {
|
|
315
|
+
if (candidates.length >= maxCandidates) return candidates;
|
|
316
|
+
let entry;
|
|
317
|
+
try {
|
|
318
|
+
entry = JSON.parse(line);
|
|
319
|
+
} catch {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const content = entry.content || entry.text || '';
|
|
323
|
+
if (!content || content.length < 24 || content.length > 2000) continue;
|
|
324
|
+
if (entry.type === 'tool_call') continue;
|
|
325
|
+
const role = entry.role;
|
|
326
|
+
if (role && role !== 'user' && role !== 'human') continue;
|
|
327
|
+
if (!signal.test(content)) continue;
|
|
328
|
+
|
|
329
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex').slice(0, 10);
|
|
330
|
+
candidates.push({
|
|
331
|
+
slug: `session-extract-${hash}`,
|
|
332
|
+
category: 'preferences',
|
|
333
|
+
title: content.slice(0, 80).replace(/\s+/g, ' '),
|
|
334
|
+
status: 'draft',
|
|
335
|
+
confidence: 0.55,
|
|
336
|
+
importance: 3,
|
|
337
|
+
evidence_count: 1,
|
|
338
|
+
modality: /must not|never|don't|do not/i.test(content) ? 'must_not' : 'should',
|
|
339
|
+
subject: 'user',
|
|
340
|
+
predicate: 'prefers',
|
|
341
|
+
object: 'behavior',
|
|
342
|
+
tags: ['session-extract', 'dream-rem'],
|
|
343
|
+
source: {
|
|
344
|
+
type: 'session',
|
|
345
|
+
session_id: path.basename(file.name, '.jsonl'),
|
|
346
|
+
evidence_count: 1,
|
|
347
|
+
},
|
|
348
|
+
body: content,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return candidates;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Collect all REM candidates (inbox + sessions). Dedupe by slug.
|
|
357
|
+
*/
|
|
358
|
+
export function collectRemCandidates({ brainDirPath, sessionsDir }) {
|
|
359
|
+
const bySlug = new Map();
|
|
360
|
+
for (const c of loadCandidatesFromInbox(brainDirPath)) {
|
|
361
|
+
bySlug.set(c.slug, c);
|
|
362
|
+
}
|
|
363
|
+
for (const c of loadCandidatesFromSessions(sessionsDir)) {
|
|
364
|
+
if (!bySlug.has(c.slug)) bySlug.set(c.slug, c);
|
|
365
|
+
}
|
|
366
|
+
return [...bySlug.values()];
|
|
367
|
+
}
|
|
368
|
+
|
|
234
369
|
/**
|
|
235
370
|
* Phase 2: REM Sleep. Score nodes, check conflicts, promote/decay.
|
|
236
371
|
*/
|
|
@@ -285,17 +420,43 @@ export async function runDreamCycle({
|
|
|
285
420
|
const modified = scanModifiedVault(vaultDir);
|
|
286
421
|
logger.info('dream', `Modified vault files: ${modified.length}`);
|
|
287
422
|
|
|
288
|
-
|
|
289
|
-
|
|
423
|
+
const candidates = collectRemCandidates({
|
|
424
|
+
brainDirPath: brainDir,
|
|
425
|
+
sessionsDir,
|
|
426
|
+
});
|
|
427
|
+
logger.info('dream', `REM candidates collected: ${candidates.length}`);
|
|
290
428
|
|
|
291
429
|
logger.info('dream', 'PHASE 2 — REM (Pattern Recognition)');
|
|
292
430
|
const existingNodes = getNodes(vaultDir);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
431
|
+
const existingSlugs = new Set(existingNodes.map((n) => n.slug));
|
|
432
|
+
// Skip candidates already active in vault
|
|
433
|
+
const fresh = candidates.filter((c) => !existingSlugs.has(c.slug) || c.status === 'draft');
|
|
434
|
+
|
|
435
|
+
let promotedCount = 0;
|
|
436
|
+
let conflictedCount = 0;
|
|
437
|
+
if (fresh.length > 0) {
|
|
438
|
+
const { promoted, conflicted } = evaluateCandidates(fresh, existingNodes, conflictsDir);
|
|
439
|
+
promotedCount = promoted.length;
|
|
440
|
+
conflictedCount = conflicted.length;
|
|
296
441
|
logger.info('dream', `Promoted: ${promoted.length} | Conflicts: ${conflicted.length}`);
|
|
297
442
|
for (const node of promoted) {
|
|
298
|
-
|
|
443
|
+
try {
|
|
444
|
+
// Strip inbox-only fields before vault write
|
|
445
|
+
const { body, _inbox_path, ...nodeFields } = node;
|
|
446
|
+
const toWrite = {
|
|
447
|
+
...nodeFields,
|
|
448
|
+
status: 'active',
|
|
449
|
+
content: body || nodeFields.content,
|
|
450
|
+
};
|
|
451
|
+
await writeNode(toWrite, vaultDir);
|
|
452
|
+
if (_inbox_path && fs.existsSync(_inbox_path)) {
|
|
453
|
+
try {
|
|
454
|
+
fs.unlinkSync(_inbox_path);
|
|
455
|
+
} catch {}
|
|
456
|
+
}
|
|
457
|
+
} catch (err) {
|
|
458
|
+
logger.warn('dream', `Promote write failed for ${node.slug}: ${err.message}`);
|
|
459
|
+
}
|
|
299
460
|
}
|
|
300
461
|
} else {
|
|
301
462
|
logger.info('dream', 'No new candidates to evaluate.');
|
|
@@ -312,9 +473,6 @@ export async function runDreamCycle({
|
|
|
312
473
|
logger.info('dream', 'PHASE 4 — Lucid Dreaming (Optimizer Proposals)');
|
|
313
474
|
let proposalCount = 0;
|
|
314
475
|
try {
|
|
315
|
-
const proposalsDir = path.join(vaultDir, 'proposals');
|
|
316
|
-
if (!fs.existsSync(proposalsDir)) fs.mkdirSync(proposalsDir, { recursive: true });
|
|
317
|
-
|
|
318
476
|
const cleanupProposals = await generateMemoryCleanupProposals(vaultDir);
|
|
319
477
|
const staleProposals = await generateStaleKnowledgeRefreshProposals(vaultDir);
|
|
320
478
|
const allProposals = [...cleanupProposals, ...staleProposals];
|
|
@@ -324,7 +482,8 @@ export async function runDreamCycle({
|
|
|
324
482
|
for (const p of allProposals) {
|
|
325
483
|
const passed = await evaluateProposalGate(p, null);
|
|
326
484
|
logger.info('dream', `Proposal [${p.category}]: ${p.summary} -> ${passed ? 'ACCEPTED' : 'REJECTED'}`);
|
|
327
|
-
|
|
485
|
+
// Contract write under vault root → proposals/<slug>.md
|
|
486
|
+
await writeNode(p, vaultDir);
|
|
328
487
|
if (passed) proposalCount++;
|
|
329
488
|
}
|
|
330
489
|
} else {
|
|
@@ -339,6 +498,7 @@ export async function runDreamCycle({
|
|
|
339
498
|
const existingNodes = getNodes(vaultDir);
|
|
340
499
|
writeDailyNote(vaultDir, [
|
|
341
500
|
`Modified vault files scanned: ${modified.length}`,
|
|
501
|
+
`REM candidates: ${candidates.length} (promoted ${promotedCount}, conflicted ${conflictedCount})`,
|
|
342
502
|
`Active nodes: ${existingNodes.filter(n => n.status === 'active').length}`,
|
|
343
503
|
`Proposals accepted: ${proposalCount}`,
|
|
344
504
|
]);
|