memorix 1.9.4 → 1.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +75 -1
  2. package/README.md +12 -6
  3. package/README.zh-CN.md +12 -6
  4. package/dist/cli/index.js +2533 -585
  5. package/dist/index.d.ts +15 -8
  6. package/dist/index.js +1201 -284
  7. package/dist/maintenance-runner.js +387 -216
  8. package/dist/media-audio-runner.js +300 -129
  9. package/dist/media-video-runner.js +300 -129
  10. package/dist/memcode-runtime/CHANGELOG.md +75 -1
  11. package/dist/sdk.d.ts +1 -0
  12. package/dist/sdk.js +1882 -998
  13. package/dist/types.d.ts +1 -1
  14. package/dist/vector-backfill-runner.js +300 -129
  15. package/docs/AGENT_OPERATOR_PLAYBOOK.md +47 -3
  16. package/docs/API_REFERENCE.md +39 -1
  17. package/docs/CONFIGURATION.md +58 -1
  18. package/docs/DEVELOPMENT.md +1 -1
  19. package/docs/DOCKER.md +23 -3
  20. package/docs/INTEGRATIONS.md +1 -1
  21. package/docs/KNOWN_ISSUES_AND_ROADMAP.md +61 -1
  22. package/docs/MULTI-DEVICE-SYNC-RESEARCH.md +4 -0
  23. package/docs/PERFORMANCE.md +46 -2
  24. package/docs/README.md +2 -1
  25. package/docs/SETUP.md +56 -1
  26. package/package.json +3 -2
  27. package/plugins/antigravity/memorix/plugin.json +1 -1
  28. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  29. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  30. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  31. package/plugins/copilot/memorix/plugin.json +1 -1
  32. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  33. package/plugins/hermes/memorix/plugin.yaml +1 -1
  34. package/plugins/omp/memorix/package.json +1 -1
  35. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  36. package/plugins/pi/memorix/package.json +1 -1
  37. package/server.json +2 -2
  38. package/src/cli/capability-map.ts +1 -0
  39. package/src/cli/command-guide.ts +15 -3
  40. package/src/cli/commands/agent-integrations.ts +57 -0
  41. package/src/cli/commands/background.ts +11 -2
  42. package/src/cli/commands/context.ts +2 -0
  43. package/src/cli/commands/continuity.ts +155 -0
  44. package/src/cli/commands/init.ts +5 -2
  45. package/src/cli/commands/serve-http.ts +435 -84
  46. package/src/cli/commands/setup.ts +77 -10
  47. package/src/cli/commands/sync-store.ts +47 -12
  48. package/src/cli/commands/sync.ts +26 -0
  49. package/src/cli/index.ts +3 -1
  50. package/src/cli/tui/ConfigureView.tsx +2 -0
  51. package/src/codegraph/auto-context.ts +17 -0
  52. package/src/config/legacy-loader.ts +2 -2
  53. package/src/config/resolved-config.ts +8 -5
  54. package/src/config/yaml-loader.ts +20 -10
  55. package/src/dashboard/server.ts +10 -12
  56. package/src/embedding/api-provider.ts +4 -2
  57. package/src/embedding/fastembed-provider.ts +47 -14
  58. package/src/embedding/transformers-provider.ts +29 -7
  59. package/src/evaluation/memory-quality-gate.ts +90 -0
  60. package/src/hooks/official-skills.ts +2 -0
  61. package/src/integrations/registry.ts +1 -1
  62. package/src/knowledge/context-assembly.ts +1 -0
  63. package/src/knowledge/context-receipt.ts +15 -0
  64. package/src/knowledge/task-continuity.ts +413 -0
  65. package/src/knowledge/workset.ts +77 -0
  66. package/src/llm/provider.ts +2 -1
  67. package/src/memory/graph.ts +10 -8
  68. package/src/memory/session.ts +1 -1
  69. package/src/orchestrate/coordinator.ts +60 -0
  70. package/src/orchestrate/effect-ledger.ts +160 -0
  71. package/src/orchestrate/pipeline-trace.ts +40 -6
  72. package/src/runtime/maintenance-jobs.ts +12 -6
  73. package/src/sdk.ts +24 -0
  74. package/src/server/mcp-binding-store.ts +14 -3
  75. package/src/server/modern-mcp-bridge.ts +153 -27
  76. package/src/server/tool-profile.ts +3 -2
  77. package/src/server.ts +131 -7
  78. package/src/store/file-lock.ts +28 -3
  79. package/src/store/graph-store.ts +178 -46
  80. package/src/store/persistence.ts +14 -9
  81. package/src/store/sqlite-db.ts +150 -7
  82. package/src/store/sqlite-store.ts +32 -27
  83. package/src/sync/engine.ts +9 -7
  84. package/src/sync/namespace.ts +23 -0
  85. package/src/sync/policy.ts +19 -4
  86. package/src/sync/store-port.ts +43 -13
  87. package/src/sync/types.ts +2 -0
  88. package/src/team/team-store.ts +62 -27
  89. package/src/types.ts +1 -1
  90. package/src/workspace/mcp-adapters/grok.ts +32 -0
@@ -0,0 +1,413 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { sanitizeCredentials } from '../memory/secret-filter.js';
3
+ import { getDatabase } from '../store/sqlite-db.js';
4
+
5
+ export const TASK_CONTINUITY_EVENT_KINDS = [
6
+ 'task',
7
+ 'requirement',
8
+ 'decision',
9
+ 'verification',
10
+ 'risk',
11
+ 'outcome',
12
+ ] as const;
13
+
14
+ export type TaskContinuityEventKind = typeof TASK_CONTINUITY_EVENT_KINDS[number];
15
+ export type TaskContinuityVerificationStatus = 'pending' | 'passed' | 'failed' | 'skipped';
16
+ export type TaskContinuityOutcomeStatus = 'completed' | 'blocked' | 'abandoned';
17
+ export type TaskContinuityStatus = 'open' | TaskContinuityOutcomeStatus;
18
+ export type TaskContinuityOutcomeState = 'validated' | 'validated-with-risks' | 'at-risk' | 'in-progress' | 'unverified';
19
+
20
+ export interface TaskContinuityEvent {
21
+ id: string;
22
+ projectId: string;
23
+ taskId: string;
24
+ task: string;
25
+ kind: TaskContinuityEventKind;
26
+ content: string;
27
+ status?: TaskContinuityVerificationStatus | TaskContinuityOutcomeStatus;
28
+ sourceRef?: string;
29
+ evidenceRefs: string[];
30
+ actor?: string;
31
+ at: string;
32
+ eventKey?: string;
33
+ itemKey?: string;
34
+ supersedesId?: string;
35
+ }
36
+
37
+ export interface TaskContinuityVerification {
38
+ id?: string;
39
+ content: string;
40
+ status: TaskContinuityVerificationStatus;
41
+ sourceRef?: string;
42
+ evidenceRefs: string[];
43
+ }
44
+
45
+ export interface TaskContinuityLedger {
46
+ taskId: string;
47
+ projectId: string;
48
+ task: string;
49
+ status: TaskContinuityStatus;
50
+ requirements: string[];
51
+ decisions: string[];
52
+ verification: TaskContinuityVerification[];
53
+ risks: string[];
54
+ outcomes: string[];
55
+ outcome: TaskContinuityOutcomeProjection;
56
+ updatedAt: string;
57
+ events: TaskContinuityEvent[];
58
+ }
59
+
60
+ export interface TaskContinuityOutcomeProjection {
61
+ state: TaskContinuityOutcomeState;
62
+ score: number;
63
+ verification: {
64
+ passed: number;
65
+ failed: number;
66
+ pending: number;
67
+ skipped: number;
68
+ };
69
+ reasons: string[];
70
+ }
71
+
72
+ const MAX_TEXT_LENGTH = 2_000;
73
+ const MAX_TASK_LENGTH = 1_000;
74
+ const MAX_LIST_ITEMS = 8;
75
+ const MAX_EVIDENCE_REFS = 8;
76
+ const MAX_EVENTS_PER_TASK = 200;
77
+
78
+ function clean(value: string, maxLength = MAX_TEXT_LENGTH): string {
79
+ return sanitizeCredentials(value).replace(/\s+/g, ' ').trim().slice(0, maxLength);
80
+ }
81
+
82
+ function cleanList(values: string[] | undefined, maxLength = MAX_TEXT_LENGTH): string[] {
83
+ return [...new Set((values ?? []).map(value => clean(value, maxLength)).filter(Boolean))].slice(0, MAX_LIST_ITEMS);
84
+ }
85
+
86
+ function parseRefs(value: unknown): string[] {
87
+ if (typeof value !== 'string') return [];
88
+ try {
89
+ const parsed = JSON.parse(value);
90
+ return Array.isArray(parsed)
91
+ ? cleanList(parsed.filter(item => typeof item === 'string'), 500).slice(0, MAX_EVIDENCE_REFS)
92
+ : [];
93
+ } catch {
94
+ return [];
95
+ }
96
+ }
97
+
98
+ export function evaluateTaskContinuity(ledger: Pick<
99
+ TaskContinuityLedger,
100
+ 'status' | 'verification' | 'outcomes' | 'risks'
101
+ >): TaskContinuityOutcomeProjection {
102
+ const verification = {
103
+ passed: ledger.verification.filter(item => item.status === 'passed').length,
104
+ failed: ledger.verification.filter(item => item.status === 'failed').length,
105
+ pending: ledger.verification.filter(item => item.status === 'pending').length,
106
+ skipped: ledger.verification.filter(item => item.status === 'skipped').length,
107
+ };
108
+ const reasons: string[] = [];
109
+ if (verification.failed > 0) reasons.push('verification-failed');
110
+ if (verification.pending > 0) reasons.push('verification-pending');
111
+ if (ledger.risks.length > 0) reasons.push('open-risks');
112
+ if (ledger.status === 'blocked') reasons.push('task-blocked');
113
+ if (ledger.status === 'abandoned') reasons.push('task-abandoned');
114
+
115
+ if (ledger.status === 'blocked' || ledger.status === 'abandoned' || verification.failed > 0) {
116
+ return {
117
+ state: 'at-risk',
118
+ score: verification.passed > verification.failed ? 0.55 : 0.2,
119
+ verification,
120
+ reasons,
121
+ };
122
+ }
123
+ if (ledger.status === 'completed') {
124
+ if (verification.passed > 0 && verification.pending === 0 && verification.skipped === 0) {
125
+ return {
126
+ state: ledger.risks.length > 0 ? 'validated-with-risks' : 'validated',
127
+ score: ledger.risks.length > 0 ? 0.8 : 1,
128
+ verification,
129
+ reasons: reasons.length > 0 ? reasons : ['verification-passed'],
130
+ };
131
+ }
132
+ return {
133
+ state: 'unverified',
134
+ score: 0.4,
135
+ verification,
136
+ reasons: reasons.length > 0 ? reasons : ['no-passing-verification'],
137
+ };
138
+ }
139
+ return {
140
+ state: 'in-progress',
141
+ score: verification.passed > 0 && verification.pending === 0 ? 0.75 : 0.5,
142
+ verification,
143
+ reasons: reasons.length > 0 ? reasons : ['task-open'],
144
+ };
145
+ }
146
+
147
+ function rowToEvent(row: any): TaskContinuityEvent {
148
+ const status = row.item_status ? String(row.item_status) : undefined;
149
+ return {
150
+ id: String(row.id),
151
+ projectId: String(row.project_id),
152
+ taskId: String(row.task_id),
153
+ task: clean(String(row.task), MAX_TASK_LENGTH),
154
+ kind: row.kind as TaskContinuityEventKind,
155
+ content: clean(String(row.content)),
156
+ ...(status ? { status: status as TaskContinuityEvent['status'] } : {}),
157
+ ...(row.source_ref ? { sourceRef: clean(String(row.source_ref), 500) } : {}),
158
+ evidenceRefs: parseRefs(row.evidence_json),
159
+ ...(row.actor ? { actor: clean(String(row.actor), 200) } : {}),
160
+ at: String(row.created_at),
161
+ ...(row.event_key ? { eventKey: clean(String(row.event_key), 200) } : {}),
162
+ ...(row.item_key ? { itemKey: clean(String(row.item_key), 120) } : {}),
163
+ ...(row.supersedes_id ? { supersedesId: clean(String(row.supersedes_id), 120) } : {}),
164
+ };
165
+ }
166
+
167
+ export class TaskContinuityStore {
168
+ private db: any = null;
169
+
170
+ async init(dataDir: string): Promise<void> {
171
+ this.db = getDatabase(dataDir);
172
+ }
173
+
174
+ private requireDb(): any {
175
+ if (!this.db) throw new Error('TaskContinuityStore is not initialized.');
176
+ return this.db;
177
+ }
178
+
179
+ private insert(input: {
180
+ projectId: string;
181
+ taskId: string;
182
+ task: string;
183
+ kind: TaskContinuityEventKind;
184
+ content: string;
185
+ status?: string;
186
+ sourceRef?: string;
187
+ evidenceRefs?: string[];
188
+ actor?: string;
189
+ at?: string;
190
+ eventKey?: string;
191
+ itemKey?: string;
192
+ supersedesId?: string;
193
+ }): TaskContinuityEvent {
194
+ const db = this.requireDb();
195
+ const projectId = clean(input.projectId, 500);
196
+ const taskId = clean(input.taskId, 120);
197
+ const eventKey = input.eventKey ? clean(input.eventKey, 200) : undefined;
198
+ if (eventKey) {
199
+ const existing = db.prepare(
200
+ 'SELECT * FROM task_continuity_events WHERE project_id = ? AND task_id = ? AND event_key = ?',
201
+ ).get(projectId, taskId, eventKey);
202
+ if (existing) return rowToEvent(existing);
203
+ }
204
+ const existingCount = db.prepare(
205
+ 'SELECT COUNT(*) AS count FROM task_continuity_events WHERE project_id = ? AND task_id = ?',
206
+ ).get(projectId, taskId) as { count?: number };
207
+ if (Number(existingCount?.count ?? 0) >= MAX_EVENTS_PER_TASK) {
208
+ throw new Error('Task continuity ledger reached its 200-event limit.');
209
+ }
210
+ const event: TaskContinuityEvent = {
211
+ id: randomUUID(),
212
+ projectId,
213
+ taskId,
214
+ task: clean(input.task, MAX_TASK_LENGTH),
215
+ kind: input.kind,
216
+ content: clean(input.content),
217
+ ...(input.status ? { status: input.status as TaskContinuityEvent['status'] } : {}),
218
+ ...(input.sourceRef ? { sourceRef: clean(input.sourceRef, 500) } : {}),
219
+ evidenceRefs: cleanList(input.evidenceRefs, 500).slice(0, MAX_EVIDENCE_REFS),
220
+ ...(input.actor ? { actor: clean(input.actor, 200) } : {}),
221
+ at: input.at ?? new Date().toISOString(),
222
+ ...(eventKey ? { eventKey } : {}),
223
+ ...(input.itemKey ? { itemKey: clean(input.itemKey, 120) } : {}),
224
+ ...(input.supersedesId ? { supersedesId: clean(input.supersedesId, 120) } : {}),
225
+ };
226
+ db.prepare(`
227
+ INSERT INTO task_continuity_events (
228
+ id, project_id, task_id, task, kind, content, item_status,
229
+ source_ref, evidence_json, actor, created_at, event_key, item_key, supersedes_id
230
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
231
+ `).run(
232
+ event.id,
233
+ event.projectId,
234
+ event.taskId,
235
+ event.task,
236
+ event.kind,
237
+ event.content,
238
+ event.status ?? null,
239
+ event.sourceRef ?? null,
240
+ JSON.stringify(event.evidenceRefs),
241
+ event.actor ?? null,
242
+ event.at,
243
+ event.eventKey ?? null,
244
+ event.itemKey ?? null,
245
+ event.supersedesId ?? null,
246
+ );
247
+ return event;
248
+ }
249
+
250
+ start(input: {
251
+ projectId: string;
252
+ task: string;
253
+ taskId?: string;
254
+ requirements?: string[];
255
+ actor?: string;
256
+ idempotencyKey?: string;
257
+ }): { taskId: string; ledger: TaskContinuityLedger } {
258
+ const task = clean(input.task, MAX_TASK_LENGTH);
259
+ if (!task) throw new Error('A non-empty task is required.');
260
+ const idempotencyKey = input.idempotencyKey ? clean(input.idempotencyKey, 200) : undefined;
261
+ const taskId = clean(input.taskId ?? idempotencyKey ?? '', 120) || randomUUID();
262
+ const requirements = cleanList(input.requirements);
263
+ const transaction = this.requireDb().transaction(() => {
264
+ this.insert({
265
+ projectId: input.projectId,
266
+ taskId,
267
+ task,
268
+ kind: 'task',
269
+ content: task,
270
+ status: 'open',
271
+ actor: input.actor,
272
+ ...(idempotencyKey ? { eventKey: `start:${idempotencyKey}` } : {}),
273
+ });
274
+ for (const requirement of requirements) {
275
+ this.insert({
276
+ projectId: input.projectId,
277
+ taskId,
278
+ task,
279
+ kind: 'requirement',
280
+ content: requirement,
281
+ actor: input.actor,
282
+ });
283
+ }
284
+ });
285
+ transaction.immediate();
286
+ return { taskId, ledger: this.get(input.projectId, taskId)! };
287
+ }
288
+
289
+ record(input: {
290
+ projectId: string;
291
+ taskId: string;
292
+ kind: Exclude<TaskContinuityEventKind, 'task' | 'outcome'>;
293
+ content: string;
294
+ verificationStatus?: TaskContinuityVerificationStatus;
295
+ sourceRef?: string;
296
+ evidenceRefs?: string[];
297
+ actor?: string;
298
+ at?: string;
299
+ verificationId?: string;
300
+ idempotencyKey?: string;
301
+ }): { event: TaskContinuityEvent; ledger: TaskContinuityLedger } {
302
+ const projectId = clean(input.projectId, 500);
303
+ const taskId = clean(input.taskId, 120);
304
+ const existing = this.get(projectId, taskId);
305
+ if (!existing) throw new Error('Task continuity ledger not found: ' + taskId);
306
+ const transaction = this.requireDb().transaction(() => this.insert({
307
+ projectId,
308
+ taskId,
309
+ task: existing.task,
310
+ kind: input.kind,
311
+ content: input.content,
312
+ ...(input.kind === 'verification' && input.verificationStatus
313
+ ? { status: input.verificationStatus }
314
+ : {}),
315
+ sourceRef: input.sourceRef,
316
+ evidenceRefs: input.evidenceRefs,
317
+ actor: input.actor,
318
+ at: input.at,
319
+ ...(input.idempotencyKey ? { eventKey: input.idempotencyKey } : {}),
320
+ ...(input.verificationId ? { itemKey: input.verificationId } : {}),
321
+ }));
322
+ const event = transaction.immediate();
323
+ return { event, ledger: this.get(projectId, taskId)! };
324
+ }
325
+
326
+ close(input: {
327
+ projectId: string;
328
+ taskId: string;
329
+ status: TaskContinuityOutcomeStatus;
330
+ content: string;
331
+ sourceRef?: string;
332
+ evidenceRefs?: string[];
333
+ actor?: string;
334
+ at?: string;
335
+ idempotencyKey?: string;
336
+ }): { event: TaskContinuityEvent; ledger: TaskContinuityLedger } {
337
+ const projectId = clean(input.projectId, 500);
338
+ const taskId = clean(input.taskId, 120);
339
+ const existing = this.get(projectId, taskId);
340
+ if (!existing) throw new Error('Task continuity ledger not found: ' + taskId);
341
+ const transaction = this.requireDb().transaction(() => this.insert({
342
+ projectId,
343
+ taskId,
344
+ task: existing.task,
345
+ kind: 'outcome',
346
+ content: input.content,
347
+ status: input.status,
348
+ sourceRef: input.sourceRef,
349
+ evidenceRefs: input.evidenceRefs,
350
+ actor: input.actor,
351
+ at: input.at,
352
+ ...(input.idempotencyKey ? { eventKey: input.idempotencyKey } : {}),
353
+ }));
354
+ const event = transaction.immediate();
355
+ return { event, ledger: this.get(projectId, taskId)! };
356
+ }
357
+
358
+ get(projectId: string, taskId: string): TaskContinuityLedger | undefined {
359
+ const normalizedProjectId = clean(projectId, 500);
360
+ const normalizedTaskId = clean(taskId, 120);
361
+ const rows = this.requireDb().prepare(`
362
+ SELECT * FROM task_continuity_events
363
+ WHERE project_id = ? AND task_id = ?
364
+ ORDER BY created_at ASC, rowid ASC
365
+ `).all(normalizedProjectId, normalizedTaskId);
366
+ if (rows.length === 0) return undefined;
367
+ return aggregate(rows.map(rowToEvent));
368
+ }
369
+
370
+ list(projectId: string, limit = 20): TaskContinuityLedger[] {
371
+ const rows = this.requireDb().prepare(`
372
+ SELECT task_id
373
+ FROM task_continuity_events
374
+ WHERE project_id = ?
375
+ GROUP BY task_id
376
+ ORDER BY MAX(created_at) DESC
377
+ LIMIT ?
378
+ `).all(projectId, Math.min(100, Math.max(1, Math.floor(limit))));
379
+ return rows
380
+ .map((row: any) => this.get(projectId, String(row.task_id)))
381
+ .filter((ledger: TaskContinuityLedger | undefined): ledger is TaskContinuityLedger => Boolean(ledger));
382
+ }
383
+ }
384
+
385
+ function aggregate(events: TaskContinuityEvent[]): TaskContinuityLedger {
386
+ const first = events.find(event => event.kind === 'task') ?? events[0];
387
+ const latestOutcome = [...events].reverse().find(event => event.kind === 'outcome');
388
+ const verificationByKey = new Map<string, TaskContinuityEvent>();
389
+ for (const event of events.filter(event => event.kind === 'verification')) {
390
+ verificationByKey.set(event.itemKey ?? `event:${event.id}`, event);
391
+ }
392
+ const ledger = {
393
+ taskId: first.taskId,
394
+ projectId: first.projectId,
395
+ task: first.task,
396
+ status: (latestOutcome?.status as TaskContinuityStatus | undefined) ?? 'open',
397
+ requirements: events.filter(event => event.kind === 'requirement').map(event => event.content),
398
+ decisions: events.filter(event => event.kind === 'decision').map(event => event.content),
399
+ verification: [...verificationByKey.values()]
400
+ .map(event => ({
401
+ id: event.itemKey ?? event.id,
402
+ content: event.content,
403
+ status: (event.status as TaskContinuityVerificationStatus | undefined) ?? 'pending',
404
+ ...(event.sourceRef ? { sourceRef: event.sourceRef } : {}),
405
+ evidenceRefs: event.evidenceRefs,
406
+ })),
407
+ risks: events.filter(event => event.kind === 'risk').map(event => event.content),
408
+ outcomes: events.filter(event => event.kind === 'outcome').map(event => event.content),
409
+ updatedAt: events[events.length - 1].at,
410
+ events,
411
+ };
412
+ return { ...ledger, outcome: evaluateTaskContinuity(ledger) };
413
+ }
@@ -25,6 +25,7 @@ import type { KnowledgePageRecord, KnowledgeWorkspace } from './workspace-types.
25
25
  import type { WorkflowSelection } from './workflow-types.js';
26
26
  import type { ObservationReader } from '../types.js';
27
27
  import type { LongTermMemory, LongTermMemoryEvidence } from '../memory/long-term-types.js';
28
+ import type { TaskContinuityLedger } from './task-continuity.js';
28
29
 
29
30
  export type WorksetCautionKind =
30
31
  | 'dirty-worktree'
@@ -148,6 +149,7 @@ export interface WorksetCodeEvolution {
148
149
  export interface TaskWorkset {
149
150
  version: '1.3';
150
151
  task: string;
152
+ continuity?: TaskContinuityLedger;
151
153
  lens: string;
152
154
  /** Always-on "who you are and what this workspace is doing" block. */
153
155
  alwaysOn?: {
@@ -193,6 +195,7 @@ export interface BuildTaskWorksetInput {
193
195
  projectId: string;
194
196
  dataDir: string;
195
197
  task?: string;
198
+ continuity?: TaskContinuityLedger;
196
199
  /** Optional target used to select workflows explicitly compatible with this agent. */
197
200
  agent?: AgentTarget;
198
201
  lens: string;
@@ -366,6 +369,7 @@ function freshnessForMemory(status: WorksetMemorySource['status']): ContextCandi
366
369
  }
367
370
 
368
371
  function receiptOmissionKind(raw: string): ContextCandidateKind | undefined {
372
+ if (raw.includes('continuity')) return 'continuity';
369
373
  if (raw.includes('continuation')) return 'continuation';
370
374
  if (raw.includes('task')) return 'task';
371
375
  if (raw.includes('fact')) return 'current-fact';
@@ -518,6 +522,78 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
518
522
  });
519
523
  appendLine(lines, 'Task lens: ' + input.lens, maxTokens, omitted, 'lens');
520
524
 
525
+ if (input.continuity) {
526
+ const continuity = input.continuity;
527
+ appendLine(lines, '', maxTokens, omitted, 'continuity-heading');
528
+ appendLine(lines, 'Task continuity', maxTokens, omitted, 'continuity-heading', selected, {
529
+ kind: 'continuity',
530
+ id: 'continuity:' + continuity.taskId,
531
+ reason: 'explicit task continuity ledger',
532
+ trust: 'source-backed',
533
+ });
534
+ appendLine(
535
+ lines,
536
+ '- Status: ' + continuity.status + ' | ' + short(continuity.task, 30),
537
+ maxTokens,
538
+ omitted,
539
+ 'continuity-task',
540
+ );
541
+ appendLine(
542
+ lines,
543
+ '- Outcome: ' + continuity.outcome.state,
544
+ maxTokens,
545
+ omitted,
546
+ 'continuity-outcome',
547
+ selected,
548
+ {
549
+ kind: 'continuity',
550
+ id: 'continuity:' + continuity.taskId,
551
+ reason: 'deterministic task outcome projection',
552
+ trust: 'derived',
553
+ },
554
+ );
555
+ for (const requirement of continuity.requirements.slice(0, 3)) {
556
+ appendLine(lines, '- Requirement: ' + short(requirement, 24), maxTokens, omitted, 'continuity-requirement', selected, {
557
+ kind: 'continuity',
558
+ id: 'continuity:' + continuity.taskId,
559
+ reason: 'explicit task requirement',
560
+ trust: 'source-backed',
561
+ });
562
+ }
563
+ for (const decision of continuity.decisions.slice(0, 2)) {
564
+ appendLine(lines, '- Decision: ' + short(decision, 24), maxTokens, omitted, 'continuity-decision', selected, {
565
+ kind: 'continuity',
566
+ id: 'continuity:' + continuity.taskId,
567
+ reason: 'explicit task decision',
568
+ trust: 'source-backed',
569
+ });
570
+ }
571
+ for (const verification of continuity.verification.slice(-3)) {
572
+ appendLine(
573
+ lines,
574
+ '- Verify [' + verification.status + ']: ' + short(verification.content, 22),
575
+ maxTokens,
576
+ omitted,
577
+ 'continuity-verification',
578
+ selected,
579
+ {
580
+ kind: 'continuity',
581
+ id: 'continuity:' + continuity.taskId,
582
+ reason: 'task verification state',
583
+ trust: 'source-backed',
584
+ },
585
+ );
586
+ }
587
+ for (const risk of continuity.risks.slice(0, 2)) {
588
+ appendLine(lines, '- Risk: ' + short(risk, 22), maxTokens, omitted, 'continuity-risk', selected, {
589
+ kind: 'continuity',
590
+ id: 'continuity:' + continuity.taskId,
591
+ reason: 'open task risk',
592
+ trust: 'source-backed',
593
+ });
594
+ }
595
+ }
596
+
521
597
  // Always-on block: the small "who you are and what this workspace is
522
598
  // doing" context every brief carries, mirroring the memory-native feel of
523
599
  // a per-session MEMORY.md. Lines are pre-truncated by the caller; the
@@ -1114,6 +1190,7 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
1114
1190
  version: '1.3' as const,
1115
1191
  task,
1116
1192
  lens: input.lens,
1193
+ ...(input.continuity ? { continuity: input.continuity } : {}),
1117
1194
  ...(input.alwaysOn
1118
1195
  && (
1119
1196
  input.alwaysOn.profile.length > 0
@@ -20,7 +20,7 @@ import {
20
20
  } from '../config.js';
21
21
 
22
22
  export interface LLMConfig {
23
- provider: 'openai' | 'anthropic' | 'openrouter' | 'custom';
23
+ provider: 'openai' | 'anthropic' | 'openrouter' | 'atlascloud' | 'custom';
24
24
  apiKey: string;
25
25
  model?: string;
26
26
  baseUrl?: string;
@@ -201,6 +201,7 @@ const PROVIDER_DEFAULTS: Record<string, { baseUrl: string; model: string }> = {
201
201
  openai: { baseUrl: 'https://api.openai.com/v1', model: 'gpt-4.1-nano' },
202
202
  anthropic: { baseUrl: 'https://api.anthropic.com/v1', model: 'claude-3-5-haiku-latest' },
203
203
  openrouter: { baseUrl: 'https://openrouter.ai/api/v1', model: 'openai/gpt-4.1-nano' },
204
+ atlascloud: { baseUrl: 'https://api.atlascloud.ai/v1', model: 'deepseek-ai/deepseek-v3.2' },
204
205
  custom: { baseUrl: 'http://localhost:11434/v1', model: 'llama3' },
205
206
  };
206
207
 
@@ -11,18 +11,21 @@
11
11
  */
12
12
 
13
13
  import type { Entity, Relation, KnowledgeGraph } from '../types.js';
14
- import { initGraphStore, getGraphStore } from '../store/graph-store.js';
14
+ import { initGraphStore, type GraphSqliteStore } from '../store/graph-store.js';
15
15
 
16
16
  export class KnowledgeGraphManager {
17
17
  private entities: Entity[] = [];
18
18
  private relations: Relation[] = [];
19
19
  private projectDir: string;
20
+ private projectId: string;
21
+ private store: GraphSqliteStore | null = null;
20
22
  private initialized = false;
21
23
  /** Index: lowercase entity name → Entity for O(1) lookups */
22
24
  private entityIndex = new Map<string, Entity>();
23
25
 
24
- constructor(projectDir: string) {
26
+ constructor(projectDir: string, projectId = '__default__') {
25
27
  this.projectDir = projectDir;
28
+ this.projectId = projectId;
26
29
  }
27
30
 
28
31
  /** Rebuild the entity name index */
@@ -36,10 +39,9 @@ export class KnowledgeGraphManager {
36
39
  /** Load graph from SQLite on first access */
37
40
  async init(): Promise<void> {
38
41
  if (this.initialized) return;
39
- await initGraphStore(this.projectDir);
40
- const store = getGraphStore();
41
- this.entities = store.loadEntities();
42
- this.relations = store.loadRelations();
42
+ this.store = await initGraphStore(this.projectDir, this.projectId);
43
+ this.entities = this.store.loadEntities();
44
+ this.relations = this.store.loadRelations();
43
45
  this.rebuildIndex();
44
46
  this.initialized = true;
45
47
  }
@@ -56,8 +58,8 @@ export class KnowledgeGraphManager {
56
58
 
57
59
  /** Persist current state to SQLite */
58
60
  private async save(): Promise<void> {
59
- const store = getGraphStore();
60
- store.replaceAll(this.entities, this.relations);
61
+ if (!this.store) throw new Error('[memorix] KnowledgeGraphManager not initialized');
62
+ this.store.replaceAll(this.entities, this.relations);
61
63
  }
62
64
 
63
65
  /** Create new entities (skip duplicates by name) */
@@ -616,7 +616,7 @@ export async function getSessionContext(
616
616
  let graphNeighbors: string[] = [];
617
617
  if (!reader && activeEntities.length > 0) {
618
618
  try {
619
- const graphMgr = new KnowledgeGraphManager(projectDir);
619
+ const graphMgr = new KnowledgeGraphManager(projectDir, projectId);
620
620
  await graphMgr.init();
621
621
  const { relations } = await graphMgr.readGraph();
622
622
  const activeSet = new Set(activeEntities.map((n) => n.toLowerCase()));