cogmem 3.6.3 → 3.6.5
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/CHANGELOG.md +19 -0
- package/README.md +67 -16
- package/RELEASE_CHECKLIST.md +12 -4
- package/dist/agent/AgentMemoryBackend.d.ts +6 -1
- package/dist/agent/AgentMemoryBackend.js +51 -1
- package/dist/agent/AgentRecallQueryCompiler.d.ts +1 -1
- package/dist/agent/AgentRecallQueryCompiler.js +12 -0
- package/dist/atlas/MemoryAtlasService.d.ts +1 -0
- package/dist/atlas/MemoryAtlasService.js +39 -5
- package/dist/atlas/MemoryAtlasTypes.d.ts +14 -0
- package/dist/bin/connect.js +115 -31
- package/dist/bin/import-support.d.ts +1 -0
- package/dist/bin/import-support.js +15 -2
- package/dist/bin/mcp.js +2 -1
- package/dist/bin/memory.js +217 -17
- package/dist/episode/EpisodeStore.d.ts +7 -0
- package/dist/episode/EpisodeStore.js +75 -6
- package/dist/factory.d.ts +2 -0
- package/dist/factory.js +38 -18
- package/dist/host/openclaw/AutoMemoryPluginInstaller.js +6 -2
- package/dist/mcp/server.js +1 -1
- package/dist/store/EventStore.d.ts +6 -0
- package/dist/store/EventStore.js +28 -2
- package/dist/types/index.d.ts +3 -0
- package/examples/hermes-backend/AGENTS.md +36 -16
- package/examples/hermes-backend/README.md +25 -13
- package/examples/hermes-backend/SKILL.md +72 -22
- package/examples/hermes-backend/references/operations.md +50 -13
- package/examples/openclaw-backend/AGENTS.md +33 -7
- package/examples/openclaw-backend/README.md +27 -12
- package/examples/openclaw-backend/SKILL.md +65 -19
- package/examples/openclaw-backend/references/operations.md +53 -12
- package/package.json +1 -1
package/dist/bin/connect.js
CHANGED
|
@@ -90,22 +90,92 @@ function defaultSkillPath(agent, workspaceRoot) {
|
|
|
90
90
|
return join(process.env.HOME || homedir(), '.hermes', 'skills', 'cogmem-memory', 'SKILL.md');
|
|
91
91
|
}
|
|
92
92
|
function nextCommands(agent) {
|
|
93
|
+
return nextSteps(agent)
|
|
94
|
+
.filter((step) => step.safeForAutomation && step.actor === 'agent')
|
|
95
|
+
.map((step) => step.command);
|
|
96
|
+
}
|
|
97
|
+
function nextSteps(agent) {
|
|
98
|
+
const project = agent === 'openclaw' ? 'openclaw' : 'hermes';
|
|
99
|
+
const importCommand = agent === 'openclaw' ? 'import-openclaw' : 'import-hermes';
|
|
100
|
+
const steps = [
|
|
101
|
+
{
|
|
102
|
+
id: 'doctor',
|
|
103
|
+
actor: 'agent',
|
|
104
|
+
safeForAutomation: true,
|
|
105
|
+
command: './node_modules/.bin/cogmem doctor',
|
|
106
|
+
when: 'after installation or update',
|
|
107
|
+
purpose: 'verify package, config, and database access',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'import_dry_run',
|
|
111
|
+
actor: 'agent',
|
|
112
|
+
safeForAutomation: true,
|
|
113
|
+
command: `./node_modules/.bin/cogmem ${importCommand} --workspace . --project ${project} --dry-run --json`,
|
|
114
|
+
when: 'before importing legacy memory',
|
|
115
|
+
purpose: 'preview source discovery and import counts',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'import_apply',
|
|
119
|
+
actor: 'agent',
|
|
120
|
+
safeForAutomation: true,
|
|
121
|
+
command: `./node_modules/.bin/cogmem ${importCommand} --workspace . --project ${project} --json`,
|
|
122
|
+
when: 'after dry-run looks correct',
|
|
123
|
+
purpose: 'idempotently import legacy memory into Cogmem',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'memory_plan',
|
|
127
|
+
actor: 'agent',
|
|
128
|
+
safeForAutomation: true,
|
|
129
|
+
command: `./node_modules/.bin/cogmem memory plan --project ${project} --json`,
|
|
130
|
+
when: 'after import, update, or user asks for governance progress',
|
|
131
|
+
purpose: 'read agent-safe next actions instead of guessing from raw counters',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'memory_status',
|
|
135
|
+
actor: 'agent',
|
|
136
|
+
safeForAutomation: true,
|
|
137
|
+
command: `./node_modules/.bin/cogmem memory status --project ${project} --json`,
|
|
138
|
+
when: 'for read-only health checks',
|
|
139
|
+
purpose: 'inspect raw ledger, vector fallback, dream backlog, and queue summary',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'interactive_init',
|
|
143
|
+
actor: 'operator',
|
|
144
|
+
safeForAutomation: false,
|
|
145
|
+
command: `./node_modules/.bin/cogmem init --agent ${agent} --scope project`,
|
|
146
|
+
when: 'only if no Cogmem config exists and a human operator is present',
|
|
147
|
+
purpose: 'interactive first-time config wizard; agents must not run this unattended',
|
|
148
|
+
},
|
|
149
|
+
];
|
|
93
150
|
if (agent === 'openclaw') {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
'
|
|
97
|
-
|
|
98
|
-
'./node_modules/.bin/cogmem
|
|
99
|
-
'
|
|
100
|
-
|
|
151
|
+
steps.splice(1, 0, {
|
|
152
|
+
id: 'openclaw_diagnose',
|
|
153
|
+
actor: 'agent',
|
|
154
|
+
safeForAutomation: true,
|
|
155
|
+
command: './node_modules/.bin/cogmem openclaw diagnose --workspace . --json',
|
|
156
|
+
when: 'after connect --auto --force or before investigating injection failures',
|
|
157
|
+
purpose: 'verify generated plugin files, audit log, queue lock, and hook diagnostics',
|
|
158
|
+
});
|
|
159
|
+
steps.push({
|
|
160
|
+
id: 'restart_gateway',
|
|
161
|
+
actor: 'host',
|
|
162
|
+
safeForAutomation: false,
|
|
163
|
+
command: 'openclaw gateway restart',
|
|
164
|
+
when: 'after plugin files or OpenClaw config changed',
|
|
165
|
+
purpose: 'reload OpenClaw hooks so the refreshed Cogmem plugin is active',
|
|
166
|
+
});
|
|
101
167
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
168
|
+
else {
|
|
169
|
+
steps.push({
|
|
170
|
+
id: 'reload_hermes',
|
|
171
|
+
actor: 'host',
|
|
172
|
+
safeForAutomation: false,
|
|
173
|
+
command: 'restart or reload Hermes MCP server',
|
|
174
|
+
when: 'after Hermes MCP config changed',
|
|
175
|
+
purpose: 'reload the cogmem MCP server entry',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return steps;
|
|
109
179
|
}
|
|
110
180
|
function usage() {
|
|
111
181
|
return [
|
|
@@ -138,33 +208,45 @@ function hostConfigSnippet(agent, workspaceRoot, auto) {
|
|
|
138
208
|
'// Add host config only after installing a real OpenClaw plugin wrapper with a valid manifest/schema.',
|
|
139
209
|
].join('\n');
|
|
140
210
|
}
|
|
141
|
-
const
|
|
211
|
+
const mcpServer = resolveCogmemMcpServer(workspaceRoot);
|
|
142
212
|
return [
|
|
143
213
|
'mcp_servers:',
|
|
144
214
|
' cogmem:',
|
|
145
|
-
` command: "${
|
|
146
|
-
|
|
215
|
+
` command: "${mcpServer.command}"`,
|
|
216
|
+
` args: ${JSON.stringify(mcpServer.args)}`,
|
|
147
217
|
' enabled: true',
|
|
148
218
|
' tools:',
|
|
149
219
|
' include:',
|
|
150
220
|
...HERMES_COGMEM_TOOLS.map((tool) => ` - ${tool}`),
|
|
151
221
|
].join('\n');
|
|
152
222
|
}
|
|
153
|
-
function
|
|
223
|
+
function resolveCogmemMcpServer(workspaceRoot) {
|
|
154
224
|
if (process.env.COGMEM_MCP_BIN)
|
|
155
|
-
return process.env.COGMEM_MCP_BIN;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
225
|
+
return { command: process.env.COGMEM_MCP_BIN, args: [] };
|
|
226
|
+
if (process.env.COGMEM_BIN)
|
|
227
|
+
return { command: process.env.COGMEM_BIN, args: ['mcp'] };
|
|
228
|
+
const workspaceCli = join(workspaceRoot, 'node_modules', '.bin', 'cogmem');
|
|
229
|
+
if (existsSync(workspaceCli))
|
|
230
|
+
return { command: workspaceCli, args: ['mcp'] };
|
|
159
231
|
const pathValue = process.env.PATH || '';
|
|
232
|
+
for (const segment of pathValue.split(':')) {
|
|
233
|
+
if (!segment)
|
|
234
|
+
continue;
|
|
235
|
+
const candidate = join(segment, 'cogmem');
|
|
236
|
+
if (existsSync(candidate))
|
|
237
|
+
return { command: candidate, args: ['mcp'] };
|
|
238
|
+
}
|
|
239
|
+
const workspaceMcp = join(workspaceRoot, 'node_modules', '.bin', 'cogmem-mcp');
|
|
240
|
+
if (existsSync(workspaceMcp))
|
|
241
|
+
return { command: workspaceMcp, args: [] };
|
|
160
242
|
for (const segment of pathValue.split(':')) {
|
|
161
243
|
if (!segment)
|
|
162
244
|
continue;
|
|
163
245
|
const candidate = join(segment, 'cogmem-mcp');
|
|
164
246
|
if (existsSync(candidate))
|
|
165
|
-
return candidate;
|
|
247
|
+
return { command: candidate, args: [] };
|
|
166
248
|
}
|
|
167
|
-
return 'cogmem
|
|
249
|
+
return { command: 'cogmem', args: ['mcp'] };
|
|
168
250
|
}
|
|
169
251
|
function installSkill(args) {
|
|
170
252
|
if (!args.agent)
|
|
@@ -227,6 +309,7 @@ function installSkill(args) {
|
|
|
227
309
|
installed: !args.dryRun && !alreadyCurrent,
|
|
228
310
|
alreadyCurrent,
|
|
229
311
|
nextCommands: nextCommands(args.agent),
|
|
312
|
+
nextSteps: nextSteps(args.agent),
|
|
230
313
|
hostConfigSnippet: hostConfigSnippet(args.agent, args.workspaceRoot, args.auto),
|
|
231
314
|
autoMemory,
|
|
232
315
|
hermesMcp,
|
|
@@ -237,9 +320,9 @@ function defaultHermesConfigPath(env = process.env) {
|
|
|
237
320
|
}
|
|
238
321
|
function installHermesMcpConfig(input) {
|
|
239
322
|
const configPath = resolve(input.configPath || defaultHermesConfigPath());
|
|
240
|
-
const
|
|
323
|
+
const server = resolveCogmemMcpServer(resolve(input.workspaceRoot));
|
|
241
324
|
const original = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
|
|
242
|
-
const patched = patchHermesMcpConfig(original,
|
|
325
|
+
const patched = patchHermesMcpConfig(original, server.command, server.args);
|
|
243
326
|
const changed = patched !== original;
|
|
244
327
|
let backupPath;
|
|
245
328
|
if (!input.dryRun && (changed || input.force)) {
|
|
@@ -253,21 +336,22 @@ function installHermesMcpConfig(input) {
|
|
|
253
336
|
return {
|
|
254
337
|
enabled: true,
|
|
255
338
|
configPath,
|
|
256
|
-
serverCommand,
|
|
339
|
+
serverCommand: server.command,
|
|
340
|
+
serverArgs: server.args,
|
|
257
341
|
dryRun: input.dryRun,
|
|
258
342
|
configUpdated: changed || input.force,
|
|
259
343
|
backupPath,
|
|
260
344
|
};
|
|
261
345
|
}
|
|
262
|
-
function patchHermesMcpConfig(original, serverCommand) {
|
|
263
|
-
if (/^\s+cogmem\s*:/m.test(original)
|
|
346
|
+
function patchHermesMcpConfig(original, serverCommand, serverArgs) {
|
|
347
|
+
if (/^\s+cogmem\s*:/m.test(original)) {
|
|
264
348
|
return patchExistingHermesCogmemConfig(original);
|
|
265
349
|
}
|
|
266
350
|
const lines = original.replace(/\r\n/g, '\n').split('\n');
|
|
267
351
|
const serverBlock = [
|
|
268
352
|
' cogmem:',
|
|
269
353
|
` command: "${serverCommand}"`,
|
|
270
|
-
|
|
354
|
+
` args: ${JSON.stringify(serverArgs)}`,
|
|
271
355
|
' enabled: true',
|
|
272
356
|
' tools:',
|
|
273
357
|
' include:',
|
|
@@ -366,7 +450,7 @@ function printHuman(result) {
|
|
|
366
450
|
console.log('');
|
|
367
451
|
console.log('Hermes MCP integration:');
|
|
368
452
|
console.log(` config: ${result.hermesMcp.configPath}`);
|
|
369
|
-
console.log(` command: ${result.hermesMcp.serverCommand}
|
|
453
|
+
console.log(` command: ${result.hermesMcp.serverCommand} ${result.hermesMcp.serverArgs.join(' ')}`.trimEnd());
|
|
370
454
|
if (result.hermesMcp.backupPath)
|
|
371
455
|
console.log(` backup: ${result.hermesMcp.backupPath}`);
|
|
372
456
|
console.log(' reload: /reload-mcp');
|
|
@@ -152,6 +152,7 @@ function previewSources(input) {
|
|
|
152
152
|
recordsIngested: 0,
|
|
153
153
|
skippedRecords: 0,
|
|
154
154
|
rawRecordsAnchored: 0,
|
|
155
|
+
emptyEpisodesSkipped: 0,
|
|
155
156
|
reindexRaw: false,
|
|
156
157
|
processedSourceIds: [],
|
|
157
158
|
diagnostics,
|
|
@@ -161,6 +162,7 @@ function previewSources(input) {
|
|
|
161
162
|
async function importSources(input) {
|
|
162
163
|
const opened = openKernel(input.args, input.workspaceRoot);
|
|
163
164
|
const importedEpisodeIds = new Set();
|
|
165
|
+
let emptyEpisodesSkipped = 0;
|
|
164
166
|
const processor = new InstalledBatchProcessor({
|
|
165
167
|
cursorStore: opened.kernel.cursorStore,
|
|
166
168
|
ingestBatch: async (items) => {
|
|
@@ -190,7 +192,7 @@ async function importSources(input) {
|
|
|
190
192
|
for (const episodeId of importedEpisodeIds) {
|
|
191
193
|
const episode = opened.kernel.getEpisode(episodeId);
|
|
192
194
|
if (episode?.status !== 'sealed') {
|
|
193
|
-
opened.kernel
|
|
195
|
+
emptyEpisodesSkipped += sealImportedEpisodeIfReady(opened.kernel, episodeId, `${input.agent}_import_batch_boundary`) ? 0 : 1;
|
|
194
196
|
}
|
|
195
197
|
}
|
|
196
198
|
return {
|
|
@@ -207,6 +209,7 @@ async function importSources(input) {
|
|
|
207
209
|
recordsIngested: summary.recordsIngested,
|
|
208
210
|
skippedRecords: summary.skippedRecords,
|
|
209
211
|
rawRecordsAnchored: summary.recordsIngested,
|
|
212
|
+
emptyEpisodesSkipped,
|
|
210
213
|
reindexRaw: false,
|
|
211
214
|
processedSourceIds: summary.processedSourceIds,
|
|
212
215
|
diagnostics: summary.adapterDiagnostics,
|
|
@@ -238,6 +241,7 @@ async function reindexRawSources(input) {
|
|
|
238
241
|
let rawRecordsAnchored = 0;
|
|
239
242
|
let skippedRecords = 0;
|
|
240
243
|
const importedEpisodeIds = new Set();
|
|
244
|
+
let emptyEpisodesSkipped = 0;
|
|
241
245
|
try {
|
|
242
246
|
for (const source of input.sources) {
|
|
243
247
|
const adapter = adapters.get(source.adapterKind);
|
|
@@ -279,7 +283,7 @@ async function reindexRawSources(input) {
|
|
|
279
283
|
for (const episodeId of importedEpisodeIds) {
|
|
280
284
|
const episode = opened.kernel.getEpisode(episodeId);
|
|
281
285
|
if (episode?.status !== 'sealed') {
|
|
282
|
-
opened.kernel
|
|
286
|
+
emptyEpisodesSkipped += sealImportedEpisodeIfReady(opened.kernel, episodeId, `${input.agent}_reindex_batch_boundary`) ? 0 : 1;
|
|
283
287
|
}
|
|
284
288
|
}
|
|
285
289
|
return {
|
|
@@ -297,6 +301,7 @@ async function reindexRawSources(input) {
|
|
|
297
301
|
recordsIngested: rawRecordsAnchored,
|
|
298
302
|
skippedRecords,
|
|
299
303
|
rawRecordsAnchored,
|
|
304
|
+
emptyEpisodesSkipped,
|
|
300
305
|
processedSourceIds,
|
|
301
306
|
diagnostics,
|
|
302
307
|
sourceResults,
|
|
@@ -307,6 +312,12 @@ async function reindexRawSources(input) {
|
|
|
307
312
|
opened.kernel.close();
|
|
308
313
|
}
|
|
309
314
|
}
|
|
315
|
+
function sealImportedEpisodeIfReady(kernel, episodeId, reason) {
|
|
316
|
+
if (kernel.listEpisodeEventLinks(episodeId).length === 0)
|
|
317
|
+
return false;
|
|
318
|
+
kernel.sealImportedEpisode(episodeId, { reason });
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
310
321
|
async function recordRawImportedEvidence(kernel, projectId, envelope) {
|
|
311
322
|
const sourceRef = envelope.ingestInput.sourceRefs?.[0];
|
|
312
323
|
const record = envelope.record;
|
|
@@ -483,6 +494,8 @@ function printHumanSummary(result) {
|
|
|
483
494
|
console.log(`records ${action}: ${result.dryRun ? result.recordsWouldIngest : result.recordsIngested}`);
|
|
484
495
|
if (result.rawRecordsAnchored !== undefined)
|
|
485
496
|
console.log(`raw ledger anchors: ${result.rawRecordsAnchored}`);
|
|
497
|
+
if (result.emptyEpisodesSkipped)
|
|
498
|
+
console.log(`empty episodes skipped: ${result.emptyEpisodesSkipped}`);
|
|
486
499
|
console.log(`records skipped: ${result.skippedRecords}`);
|
|
487
500
|
if (result.diagnostics.length > 0) {
|
|
488
501
|
console.log('diagnostics:');
|
package/dist/bin/mcp.js
CHANGED
|
@@ -29,7 +29,8 @@ function stringArg(values, key) {
|
|
|
29
29
|
}
|
|
30
30
|
function usage() {
|
|
31
31
|
return [
|
|
32
|
-
'Usage: cogmem
|
|
32
|
+
'Usage: cogmem mcp [--db <memory.db>|--config <config.toml>] [--cwd <dir>]',
|
|
33
|
+
' cogmem-mcp [--db <memory.db>|--config <config.toml>] [--cwd <dir>]',
|
|
33
34
|
'',
|
|
34
35
|
'Starts a stdio MCP server exposing recall, strategy, episode append/import/status/seal, conditional dream tick/status, memory map, maintenance, and prospective-memory tools.',
|
|
35
36
|
].join('\n');
|
package/dist/bin/memory.js
CHANGED
|
@@ -55,6 +55,8 @@ function readArgs(argv) {
|
|
|
55
55
|
before: numberArg(values, 'before'),
|
|
56
56
|
after: numberArg(values, 'after'),
|
|
57
57
|
sinceGlobalSeq: numberArg(values, 'since') ?? numberArg(values, 'since-global-seq'),
|
|
58
|
+
untilGlobalSeq: numberArg(values, 'until') ?? numberArg(values, 'until-global-seq'),
|
|
59
|
+
order: orderArg(values, 'order'),
|
|
58
60
|
intervalMs: numberArg(values, 'interval-ms'),
|
|
59
61
|
maxRuns: numberArg(values, 'max-runs'),
|
|
60
62
|
promoteLimit: numberArg(values, 'promote-limit'),
|
|
@@ -75,6 +77,7 @@ function usage() {
|
|
|
75
77
|
'',
|
|
76
78
|
'Commands:',
|
|
77
79
|
' status summarize raw ledger, vector, and dream backlog state',
|
|
80
|
+
' plan summarize status as agent-safe next actions',
|
|
78
81
|
' list list raw ledger events with source anchors',
|
|
79
82
|
' search --query <q> search raw ledger text without requiring hot vectors',
|
|
80
83
|
' recall --query <q> run agent-facing governed recall with source context',
|
|
@@ -101,7 +104,9 @@ function usage() {
|
|
|
101
104
|
' --thread <id> scope to one thread',
|
|
102
105
|
' --session <id> scope to one session',
|
|
103
106
|
' --limit <n> result limit, default 20',
|
|
104
|
-
' --since <globalSeq>
|
|
107
|
+
' --since <globalSeq> list/bind events at or after a global sequence',
|
|
108
|
+
' --until <globalSeq> list events at or before a global sequence',
|
|
109
|
+
' --order <asc|desc> list raw ledger order, default desc',
|
|
105
110
|
' --status <status> candidate queue status, default candidate',
|
|
106
111
|
' --id <candidate> candidate id for review',
|
|
107
112
|
' --action <action> review action: approve, reject, defer, supersede, or relink',
|
|
@@ -117,7 +122,7 @@ function usage() {
|
|
|
117
122
|
' --interval-ms <n> watch sleep interval, default 300000',
|
|
118
123
|
' --max-runs <n> stop watch after n iterations; omit for long-running worker',
|
|
119
124
|
' --agent <id> agent id for governed recall, default openclaw',
|
|
120
|
-
' --intent <intent> memory_recall, previous_session_summary, or
|
|
125
|
+
' --intent <intent> memory_recall, previous_session_summary, forensic_quote, or historical_discussion',
|
|
121
126
|
' --db <memory.db> open an explicit database path',
|
|
122
127
|
' --config <toml> open a cogmem TOML config',
|
|
123
128
|
' --include-evidence include bounded raw excerpts; event ids are always returned',
|
|
@@ -135,6 +140,7 @@ function usage() {
|
|
|
135
140
|
}
|
|
136
141
|
function isMemoryCommand(value) {
|
|
137
142
|
return value === 'status'
|
|
143
|
+
|| value === 'plan'
|
|
138
144
|
|| value === 'list'
|
|
139
145
|
|| value === 'search'
|
|
140
146
|
|| value === 'recall'
|
|
@@ -166,9 +172,17 @@ function recallIntentArg(values, key) {
|
|
|
166
172
|
const raw = stringArg(values, key);
|
|
167
173
|
if (!raw)
|
|
168
174
|
return undefined;
|
|
169
|
-
if (raw === 'memory_recall' || raw === 'previous_session_summary' || raw === 'forensic_quote')
|
|
175
|
+
if (raw === 'memory_recall' || raw === 'previous_session_summary' || raw === 'forensic_quote' || raw === 'historical_discussion')
|
|
170
176
|
return raw;
|
|
171
|
-
throw new Error(`--${key} must be one of memory_recall, previous_session_summary, forensic_quote`);
|
|
177
|
+
throw new Error(`--${key} must be one of memory_recall, previous_session_summary, forensic_quote, historical_discussion`);
|
|
178
|
+
}
|
|
179
|
+
function orderArg(values, key) {
|
|
180
|
+
const raw = stringArg(values, key);
|
|
181
|
+
if (!raw)
|
|
182
|
+
return undefined;
|
|
183
|
+
if (raw === 'asc' || raw === 'desc')
|
|
184
|
+
return raw;
|
|
185
|
+
throw new Error(`--${key} must be one of asc, desc`);
|
|
172
186
|
}
|
|
173
187
|
function stringArg(values, key) {
|
|
174
188
|
const value = values[key];
|
|
@@ -219,30 +233,32 @@ function inspectionDbPath(args) {
|
|
|
219
233
|
function runReadOnlyInspection(args) {
|
|
220
234
|
const inspection = new MemoryInspectionStore(inspectionDbPath(args));
|
|
221
235
|
try {
|
|
222
|
-
if (args.command === 'status') {
|
|
236
|
+
if (args.command === 'status' || args.command === 'plan') {
|
|
223
237
|
const payload = inspection.status({
|
|
224
238
|
projectId: args.projectId, workspaceId: args.workspaceId,
|
|
225
239
|
threadId: args.threadId, sessionId: args.sessionId,
|
|
226
240
|
});
|
|
241
|
+
const queueSummary = buildCandidateQueueSummary(inspection, args.projectId, args.limit || 5000);
|
|
242
|
+
const plan = buildMemoryPlan(payload, queueSummary, args.projectId);
|
|
243
|
+
const enrichedPayload = args.command === 'plan'
|
|
244
|
+
? plan
|
|
245
|
+
: { ...payload, queueSummary, nextActions: plan.nextActions, blocking: plan.blocking, nonBlocking: plan.nonBlocking };
|
|
227
246
|
if (args.json) {
|
|
228
|
-
printCliJson(
|
|
247
|
+
printCliJson(`memory.${args.command}`, enrichedPayload, {
|
|
229
248
|
queue: payload.dreamCandidateQueue,
|
|
230
249
|
beliefs: payload.activeBeliefs,
|
|
231
250
|
});
|
|
232
251
|
}
|
|
233
252
|
else
|
|
234
|
-
printHuman(
|
|
253
|
+
printHuman(args.command, enrichedPayload);
|
|
235
254
|
return;
|
|
236
255
|
}
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
limit: args.limit || 50,
|
|
242
|
-
});
|
|
243
|
-
const payload = { total: candidates.length, status, candidates: candidates.map(candidateToJson) };
|
|
256
|
+
const queueSummary = buildCandidateQueueSummary(inspection, args.projectId, args.limit || 50);
|
|
257
|
+
const payload = args.status
|
|
258
|
+
? buildSpecificCandidatePayload(inspection, args.projectId, args.status, args.limit || 50, queueSummary)
|
|
259
|
+
: buildGroupedCandidatePayload(queueSummary);
|
|
244
260
|
if (args.json)
|
|
245
|
-
printCliJson('memory.candidates', payload);
|
|
261
|
+
printCliJson('memory.candidates', payload, { queue: queueSummary });
|
|
246
262
|
else
|
|
247
263
|
printHuman('candidates', payload);
|
|
248
264
|
}
|
|
@@ -262,6 +278,7 @@ function eventText(event) {
|
|
|
262
278
|
}
|
|
263
279
|
function eventToJson(event) {
|
|
264
280
|
const text = eventText(event);
|
|
281
|
+
const locator = eventSourceLocator(event, 2, 2);
|
|
265
282
|
return {
|
|
266
283
|
eventId: event.eventId,
|
|
267
284
|
label: memoryEventLabel(event),
|
|
@@ -294,6 +311,21 @@ function eventToJson(event) {
|
|
|
294
311
|
causalityType: event.causalityType,
|
|
295
312
|
orderingConfidence: event.orderingConfidence,
|
|
296
313
|
},
|
|
314
|
+
sourceLocator: locator,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function eventSourceLocator(event, before, after) {
|
|
318
|
+
const projectArg = event.projectId ? ` --project ${cliArg(event.projectId)}` : '';
|
|
319
|
+
const base = `cogmem memory show --event ${cliArg(event.eventId)}${projectArg}`;
|
|
320
|
+
return {
|
|
321
|
+
eventId: event.eventId,
|
|
322
|
+
globalSeq: event.globalSeq,
|
|
323
|
+
projectId: event.projectId,
|
|
324
|
+
threadId: event.threadId,
|
|
325
|
+
sessionId: event.sessionId,
|
|
326
|
+
localDate: event.localDate,
|
|
327
|
+
command: `${base} --before ${before} --after ${after} --json`,
|
|
328
|
+
contextCommand: `${base} --before 3 --after 3 --json`,
|
|
297
329
|
};
|
|
298
330
|
}
|
|
299
331
|
function candidateToJson(candidate) {
|
|
@@ -313,6 +345,141 @@ function candidateToJson(candidate) {
|
|
|
313
345
|
updatedAt: candidate.updatedAt,
|
|
314
346
|
};
|
|
315
347
|
}
|
|
348
|
+
function buildCandidateQueueSummary(inspection, projectId, limit) {
|
|
349
|
+
const status = inspection.status({ projectId });
|
|
350
|
+
const queue = status.dreamCandidateQueue;
|
|
351
|
+
const candidateRows = inspection.listCandidates({ projectId, status: 'candidate', limit });
|
|
352
|
+
const needsRows = inspection.listCandidates({ projectId, status: 'needs_confirmation', limit: Math.max(limit, 5000) });
|
|
353
|
+
const now = Date.now();
|
|
354
|
+
const deferredRows = needsRows.filter((candidate) => candidate.reviewAfter !== undefined && candidate.reviewAfter > now);
|
|
355
|
+
const activeNeedsRows = needsRows.filter((candidate) => !(candidate.reviewAfter !== undefined && candidate.reviewAfter > now));
|
|
356
|
+
return {
|
|
357
|
+
candidate: queue.candidate,
|
|
358
|
+
needs_confirmation: queue.needsConfirmation,
|
|
359
|
+
activeNeedsConfirmation: Math.max(0, queue.needsConfirmation - deferredRows.length),
|
|
360
|
+
deferredNeedsConfirmation: deferredRows.length,
|
|
361
|
+
promoted: queue.promoted,
|
|
362
|
+
rejected: queue.rejected,
|
|
363
|
+
superseded: queue.superseded,
|
|
364
|
+
shadow: queue.shadow,
|
|
365
|
+
groups: {
|
|
366
|
+
candidate: candidateRows.slice(0, limit).map(candidateToJson),
|
|
367
|
+
needs_confirmation: activeNeedsRows.slice(0, limit).map(candidateToJson),
|
|
368
|
+
deferred: deferredRows.slice(0, limit).map(candidateToJson),
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function buildSpecificCandidatePayload(inspection, projectId, status, limit, queueSummary) {
|
|
373
|
+
const candidates = inspection.listCandidates({ projectId, status, limit });
|
|
374
|
+
const payload = {
|
|
375
|
+
total: candidates.length,
|
|
376
|
+
status,
|
|
377
|
+
candidates: candidates.map(candidateToJson),
|
|
378
|
+
queueSummary,
|
|
379
|
+
};
|
|
380
|
+
if (status === 'candidate') {
|
|
381
|
+
payload.warning = 'This lists only status=candidate. Use memory candidates without --status for grouped candidate and needs_confirmation queues.';
|
|
382
|
+
}
|
|
383
|
+
else if (status === 'needs_confirmation') {
|
|
384
|
+
payload.warning = 'memory govern does not process needs_confirmation. Use memory review with explicit user evidence, or defer with review_after.';
|
|
385
|
+
}
|
|
386
|
+
return payload;
|
|
387
|
+
}
|
|
388
|
+
function buildGroupedCandidatePayload(queueSummary) {
|
|
389
|
+
return {
|
|
390
|
+
total: queueSummary.groups.candidate.length + queueSummary.groups.needs_confirmation.length + queueSummary.groups.deferred.length,
|
|
391
|
+
status: 'grouped',
|
|
392
|
+
queueSummary,
|
|
393
|
+
groups: queueSummary.groups,
|
|
394
|
+
nextActions: candidateNextActions(queueSummary, undefined),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function buildMemoryPlan(status, queueSummary, projectId) {
|
|
398
|
+
const nextActions = [];
|
|
399
|
+
const blocking = [];
|
|
400
|
+
const nonBlocking = [];
|
|
401
|
+
const projectArg = projectId ? ` --project ${cliArg(projectId)}` : '';
|
|
402
|
+
const episodeDream = status.episodeDream;
|
|
403
|
+
const pendingEpisodes = asCount(episodeDream.pending) + asCount(episodeDream.retryScheduled);
|
|
404
|
+
const undreamedRawCount = asCount(status.undreamedRawCount);
|
|
405
|
+
if (pendingEpisodes > 0) {
|
|
406
|
+
nextActions.push({
|
|
407
|
+
priority: 'high',
|
|
408
|
+
type: 'dream_tick',
|
|
409
|
+
safeForAutomation: true,
|
|
410
|
+
reason: `${pendingEpisodes} sealed episode Dream jobs can be processed`,
|
|
411
|
+
command: `cogmem dream tick${projectArg} --mode auto --max-episodes 20 --json`,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (undreamedRawCount > 0) {
|
|
415
|
+
nonBlocking.push({
|
|
416
|
+
type: 'raw_dream_ledger_lag',
|
|
417
|
+
count: undreamedRawCount,
|
|
418
|
+
episodeDreamPending: pendingEpisodes,
|
|
419
|
+
resolvableByDreamTick: false,
|
|
420
|
+
safeForAutomation: false,
|
|
421
|
+
reason: pendingEpisodes > 0
|
|
422
|
+
? 'Raw ledger dream coverage is tracked separately from sealed episode Dream jobs; dream tick may process episodes but does not guarantee this counter will clear.'
|
|
423
|
+
: 'Raw ledger dream coverage is behind, but there are no sealed episode Dream jobs. Do not run dream tick for this signal alone.',
|
|
424
|
+
command: `cogmem memory list${projectArg} --order asc --limit 20 --json`,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
nextActions.push(...candidateNextActions(queueSummary, projectId));
|
|
428
|
+
if (queueSummary.activeNeedsConfirmation > 0) {
|
|
429
|
+
blocking.push({
|
|
430
|
+
type: 'needs_confirmation',
|
|
431
|
+
count: queueSummary.activeNeedsConfirmation,
|
|
432
|
+
reason: 'Manual review requires explicit user evidence; memory govern will not process these candidates.',
|
|
433
|
+
command: `cogmem memory candidates${projectArg} --status needs_confirmation --json`,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (queueSummary.deferredNeedsConfirmation > 0) {
|
|
437
|
+
nonBlocking.push({
|
|
438
|
+
type: 'deferred_confirmation',
|
|
439
|
+
count: queueSummary.deferredNeedsConfirmation,
|
|
440
|
+
reason: 'Deferred needs_confirmation remains in the review queue until review_after.',
|
|
441
|
+
command: `cogmem memory candidates${projectArg} --json`,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
healthy: nextActions.length === 0 && blocking.length === 0,
|
|
446
|
+
projectId,
|
|
447
|
+
queueSummary,
|
|
448
|
+
dreamBacklog: status.dreamBacklog,
|
|
449
|
+
episodeDream: status.episodeDream,
|
|
450
|
+
vectorState: status.vectorState,
|
|
451
|
+
nextActions,
|
|
452
|
+
blocking,
|
|
453
|
+
nonBlocking,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function candidateNextActions(queueSummary, projectId) {
|
|
457
|
+
const projectArg = projectId ? ` --project ${cliArg(projectId)}` : '';
|
|
458
|
+
const actions = [];
|
|
459
|
+
if (queueSummary.candidate > 0) {
|
|
460
|
+
actions.push({
|
|
461
|
+
priority: 'high',
|
|
462
|
+
type: 'govern',
|
|
463
|
+
safeForAutomation: true,
|
|
464
|
+
reason: `${queueSummary.candidate} ordinary candidates are ready for deterministic governance`,
|
|
465
|
+
command: `cogmem memory govern${projectArg} --limit 100 --json`,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (queueSummary.activeNeedsConfirmation > 0) {
|
|
469
|
+
actions.push({
|
|
470
|
+
priority: 'medium',
|
|
471
|
+
type: 'review_needs_confirmation',
|
|
472
|
+
safeForAutomation: false,
|
|
473
|
+
reason: `${queueSummary.activeNeedsConfirmation} candidates need explicit operator/user confirmation`,
|
|
474
|
+
command: `cogmem memory candidates${projectArg} --status needs_confirmation --json`,
|
|
475
|
+
reviewCommand: `cogmem memory review${projectArg} --id <candidate-id> --action <approve|reject|defer|supersede|relink> --actor <operator> --reason <reason> --json`,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return actions;
|
|
479
|
+
}
|
|
480
|
+
function asCount(value) {
|
|
481
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;
|
|
482
|
+
}
|
|
316
483
|
function runStatus(kernel, args) {
|
|
317
484
|
const page = kernel.eventStore.queryEvents(1, 1, {
|
|
318
485
|
projectId: args.projectId ? [args.projectId] : undefined,
|
|
@@ -344,9 +511,15 @@ function runList(kernel, args) {
|
|
|
344
511
|
workspaceId: args.workspaceId ? [args.workspaceId] : undefined,
|
|
345
512
|
threadId: args.threadId ? [args.threadId] : undefined,
|
|
346
513
|
sessionId: args.sessionId ? [args.sessionId] : undefined,
|
|
514
|
+
sinceGlobalSeq: args.sinceGlobalSeq,
|
|
515
|
+
untilGlobalSeq: args.untilGlobalSeq,
|
|
516
|
+
order: args.order,
|
|
347
517
|
});
|
|
348
518
|
return {
|
|
349
519
|
total: page.total,
|
|
520
|
+
sinceGlobalSeq: args.sinceGlobalSeq,
|
|
521
|
+
untilGlobalSeq: args.untilGlobalSeq,
|
|
522
|
+
order: args.order || 'desc',
|
|
350
523
|
events: page.records.map(eventToJson),
|
|
351
524
|
};
|
|
352
525
|
}
|
|
@@ -607,12 +780,24 @@ function runCandidates(kernel, args) {
|
|
|
607
780
|
};
|
|
608
781
|
}
|
|
609
782
|
function printHuman(command, payload) {
|
|
610
|
-
if (command === 'status') {
|
|
783
|
+
if (command === 'status' || command === 'plan') {
|
|
784
|
+
if (command === 'plan') {
|
|
785
|
+
console.log(`healthy: ${payload.healthy}`);
|
|
786
|
+
console.log(`queueSummary: ${JSON.stringify(payload.queueSummary)}`);
|
|
787
|
+
console.log(`nextActions: ${JSON.stringify(payload.nextActions)}`);
|
|
788
|
+
console.log(`blocking: ${JSON.stringify(payload.blocking)}`);
|
|
789
|
+
console.log(`nonBlocking: ${JSON.stringify(payload.nonBlocking)}`);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
611
792
|
console.log(`rawEvents: ${payload.rawEventCount}`);
|
|
612
793
|
console.log(`vectors: ${payload.vectorCount}`);
|
|
613
794
|
console.log(`dreamBacklog: ${JSON.stringify(payload.dreamBacklog)}`);
|
|
614
795
|
console.log(`episodeDream: ${JSON.stringify(payload.episodeDream)}`);
|
|
615
796
|
console.log(`dreamCandidateQueue: ${JSON.stringify(payload.dreamCandidateQueue)}`);
|
|
797
|
+
if (payload.queueSummary)
|
|
798
|
+
console.log(`queueSummary: ${JSON.stringify(payload.queueSummary)}`);
|
|
799
|
+
if (payload.nextActions)
|
|
800
|
+
console.log(`nextActions: ${JSON.stringify(payload.nextActions)}`);
|
|
616
801
|
return;
|
|
617
802
|
}
|
|
618
803
|
if (command === 'dream') {
|
|
@@ -646,6 +831,18 @@ function printHuman(command, payload) {
|
|
|
646
831
|
return;
|
|
647
832
|
}
|
|
648
833
|
if (command === 'candidates') {
|
|
834
|
+
if (payload.queueSummary)
|
|
835
|
+
console.log(`queueSummary: ${JSON.stringify(payload.queueSummary)}`);
|
|
836
|
+
const groups = payload.groups;
|
|
837
|
+
if (groups) {
|
|
838
|
+
for (const [group, values] of Object.entries(groups)) {
|
|
839
|
+
console.log(`${group}: ${values.length}`);
|
|
840
|
+
for (const candidate of values) {
|
|
841
|
+
console.log(`- ${candidate.candidateId} ${candidate.candidateType} ${candidate.status} confidence=${candidate.confidence}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
649
846
|
const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];
|
|
650
847
|
for (const candidate of candidates) {
|
|
651
848
|
console.log(`- ${candidate.candidateId} ${candidate.candidateType} ${candidate.status} confidence=${candidate.confidence}`);
|
|
@@ -741,7 +938,7 @@ async function main() {
|
|
|
741
938
|
console.log(usage());
|
|
742
939
|
return;
|
|
743
940
|
}
|
|
744
|
-
if (args.command === 'status' || args.command === 'candidates') {
|
|
941
|
+
if (args.command === 'status' || args.command === 'plan' || args.command === 'candidates') {
|
|
745
942
|
runReadOnlyInspection(args);
|
|
746
943
|
return;
|
|
747
944
|
}
|
|
@@ -801,3 +998,6 @@ main().catch((error) => {
|
|
|
801
998
|
console.error(error instanceof Error ? error.message : String(error));
|
|
802
999
|
process.exit(1);
|
|
803
1000
|
});
|
|
1001
|
+
function cliArg(value) {
|
|
1002
|
+
return /^[A-Za-z0-9._:/=@+-]+$/u.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1003
|
+
}
|