memorix 1.2.9 → 1.3.0
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 +24 -0
- package/README.md +30 -4
- package/README.zh-CN.md +28 -2
- package/assets/star-history-dark.svg +30 -0
- package/assets/star-history-light.svg +30 -0
- package/assets/star-history.json +599 -0
- package/dist/cli/index.js +1945 -579
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1292 -267
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js +97 -21
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +24 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +1294 -269
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +99 -1
- package/dist/types.js.map +1 -1
- package/dist/vector-backfill-runner.js +97 -21
- package/dist/vector-backfill-runner.js.map +1 -1
- package/docs/1.3-MEMORY-ARCHITECTURE.md +236 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +5 -3
- package/docs/API_REFERENCE.md +20 -1
- package/docs/ARCHITECTURE.md +34 -1
- package/docs/DEVELOPMENT.md +10 -3
- package/docs/INTEGRATIONS.md +1 -1
- package/docs/MODULES.md +27 -1
- package/docs/README.md +3 -3
- package/docs/SETUP.md +6 -4
- package/docs/dev-log/progress.txt +22 -0
- package/docs/hooks-architecture.md +1 -1
- package/llms-full.txt +5 -1
- package/llms.txt +2 -0
- package/package.json +7 -2
- package/plugins/antigravity/memorix/rules/memorix.md +2 -0
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/copilot/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/cursor/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/gemini/memorix/GEMINI.md +2 -0
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/plugins/pi/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/server.json +38 -0
- package/src/cli/command-guide.ts +9 -1
- package/src/cli/commands/agent-integrations.ts +12 -6
- package/src/cli/commands/codegraph.ts +1 -0
- package/src/cli/commands/memory.ts +217 -1
- package/src/cli/commands/setup.ts +200 -23
- package/src/codegraph/auto-context.ts +11 -0
- package/src/codegraph/context-pack.ts +3 -0
- package/src/embedding/api-provider.ts +23 -9
- package/src/embedding/provider.ts +21 -10
- package/src/hooks/installers/index.ts +2 -0
- package/src/knowledge/context-assembly.ts +2 -1
- package/src/knowledge/workset.ts +77 -4
- package/src/memory/long-term-store.ts +242 -0
- package/src/memory/long-term-types.ts +136 -0
- package/src/memory/long-term.ts +626 -0
- package/src/memory/owner.ts +75 -0
- package/src/server.ts +112 -30
- package/src/store/sqlite-db.ts +74 -0
- package/src/types.ts +20 -0
package/src/knowledge/workset.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { KnowledgeWorkspaceStore } from './workspace-store.js';
|
|
|
6
6
|
import { loadKnowledgeWorkspace } from './workspace.js';
|
|
7
7
|
import { WorkflowStore } from './workflow-store.js';
|
|
8
8
|
import { selectWorkflows } from './workflows.js';
|
|
9
|
+
import { renderLongTermMemorySummary, selectLongTermMemoriesForTask } from '../memory/long-term.js';
|
|
9
10
|
import type {
|
|
10
11
|
ContextCandidateFreshness,
|
|
11
12
|
ContextCandidateKind,
|
|
@@ -18,6 +19,8 @@ import type { CodeGraphProviderQuality, ExternalCodeGraphOutline } from '../code
|
|
|
18
19
|
import type { KnowledgeClaim, ClaimEvidenceRef } from './types.js';
|
|
19
20
|
import type { KnowledgePageRecord, KnowledgeWorkspace } from './workspace-types.js';
|
|
20
21
|
import type { WorkflowSelection } from './workflow-types.js';
|
|
22
|
+
import type { ObservationReader } from '../types.js';
|
|
23
|
+
import type { LongTermMemory, LongTermMemoryEvidence } from '../memory/long-term-types.js';
|
|
21
24
|
|
|
22
25
|
export type WorksetCautionKind =
|
|
23
26
|
| 'dirty-worktree'
|
|
@@ -77,6 +80,18 @@ export interface WorksetMemorySource {
|
|
|
77
80
|
reason?: string;
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
/** A task-matching curated long-term item. The full record stays behind CLI detail. */
|
|
84
|
+
export interface WorksetLongTermMemory {
|
|
85
|
+
id: string;
|
|
86
|
+
kind: LongTermMemory['kind'];
|
|
87
|
+
scope: LongTermMemory['scope'];
|
|
88
|
+
state: LongTermMemory['state'];
|
|
89
|
+
title: string;
|
|
90
|
+
summary: string;
|
|
91
|
+
evidenceRefs: string[];
|
|
92
|
+
reason: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
/** Prior-work evidence selected only for an explicit or inferred continuation. */
|
|
81
96
|
export interface WorksetContinuation {
|
|
82
97
|
previousSession?: {
|
|
@@ -103,7 +118,7 @@ export interface WorksetContinuation {
|
|
|
103
118
|
}
|
|
104
119
|
|
|
105
120
|
export interface TaskWorkset {
|
|
106
|
-
version: '1.
|
|
121
|
+
version: '1.3';
|
|
107
122
|
task: string;
|
|
108
123
|
lens: string;
|
|
109
124
|
currentFacts: string[];
|
|
@@ -117,6 +132,7 @@ export interface TaskWorkset {
|
|
|
117
132
|
hiddenCautionMemoryCount: number;
|
|
118
133
|
claims: WorksetClaim[];
|
|
119
134
|
pages: WorksetPage[];
|
|
135
|
+
durableMemory: WorksetLongTermMemory[];
|
|
120
136
|
workflows: WorksetWorkflow[];
|
|
121
137
|
cautions: WorksetCaution[];
|
|
122
138
|
verification: string[];
|
|
@@ -164,6 +180,8 @@ export interface BuildTaskWorksetInput {
|
|
|
164
180
|
stale: number;
|
|
165
181
|
};
|
|
166
182
|
runtimeCautions?: WorksetCaution[];
|
|
183
|
+
/** Reader identity used only for team-scoped durable memory filtering. */
|
|
184
|
+
reader?: ObservationReader;
|
|
167
185
|
maxTokens?: number;
|
|
168
186
|
/** Delivery surface controls receipt semantics without changing prompt shape. */
|
|
169
187
|
deliveryTarget?: ContextDeliveryTarget;
|
|
@@ -297,6 +315,7 @@ function receiptOmissionKind(raw: string): ContextCandidateKind | undefined {
|
|
|
297
315
|
if (raw.includes('state')) return 'code-state';
|
|
298
316
|
if (raw.includes('semantic')) return 'semantic-code';
|
|
299
317
|
if (raw.includes('start')) return 'start-here';
|
|
318
|
+
if (raw.includes('durable-memory')) return 'durable-memory';
|
|
300
319
|
if (raw.includes('memory')) return 'memory';
|
|
301
320
|
if (raw.includes('claim')) return 'claim';
|
|
302
321
|
if (raw.includes('knowledge-page')) return 'knowledge-page';
|
|
@@ -593,6 +612,27 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
593
612
|
}
|
|
594
613
|
}
|
|
595
614
|
|
|
615
|
+
if (input.durableMemory.length > 0) {
|
|
616
|
+
appendLine(lines, '', maxTokens, omitted, 'durable-memory-heading');
|
|
617
|
+
appendLine(lines, 'Durable memory', maxTokens, omitted, 'durable-memory-heading');
|
|
618
|
+
for (const memory of input.durableMemory.slice(0, 3)) {
|
|
619
|
+
appendLine(
|
|
620
|
+
lines,
|
|
621
|
+
'- ' + memory.kind + ' (' + memory.scope + ', ' + memory.state + '; ref durable:' + memory.id + '): ' + memory.summary,
|
|
622
|
+
maxTokens,
|
|
623
|
+
omitted,
|
|
624
|
+
'durable-memory',
|
|
625
|
+
selected,
|
|
626
|
+
{
|
|
627
|
+
kind: 'durable-memory',
|
|
628
|
+
id: 'durable:' + memory.id,
|
|
629
|
+
reason: memory.reason,
|
|
630
|
+
trust: 'source-backed',
|
|
631
|
+
},
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
596
636
|
if (input.workflows.length > 0) {
|
|
597
637
|
appendLine(lines, '', maxTokens, omitted, 'workflow-heading');
|
|
598
638
|
appendLine(lines, 'Project workflow', maxTokens, omitted, 'workflow-heading');
|
|
@@ -674,6 +714,35 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
|
|
|
674
714
|
reason: selection.reasons[claim.id] ?? 'source-qualified task match',
|
|
675
715
|
}));
|
|
676
716
|
|
|
717
|
+
let durableMemory: WorksetLongTermMemory[] = [];
|
|
718
|
+
let durableEvidence: LongTermMemoryEvidence[] = [];
|
|
719
|
+
if (task) {
|
|
720
|
+
try {
|
|
721
|
+
const selected = await selectLongTermMemoriesForTask({
|
|
722
|
+
dataDir: input.dataDir,
|
|
723
|
+
projectId: input.projectId,
|
|
724
|
+
task,
|
|
725
|
+
...(input.reader?.agentId ? { agentId: input.reader.agentId } : {}),
|
|
726
|
+
...(input.reader?.isTeamMember ? { isTeamMember: true } : {}),
|
|
727
|
+
limit: 3,
|
|
728
|
+
});
|
|
729
|
+
durableMemory = selected.map(item => ({
|
|
730
|
+
id: item.memory.id,
|
|
731
|
+
kind: item.memory.kind,
|
|
732
|
+
scope: item.memory.scope,
|
|
733
|
+
state: item.memory.state,
|
|
734
|
+
title: item.memory.title,
|
|
735
|
+
summary: renderLongTermMemorySummary(item.memory, 20),
|
|
736
|
+
evidenceRefs: item.evidence.map(evidence => evidence.id),
|
|
737
|
+
reason: item.reason,
|
|
738
|
+
}));
|
|
739
|
+
durableEvidence = selected.flatMap(item => item.evidence);
|
|
740
|
+
} catch {
|
|
741
|
+
// Long-term memory is optional enrichment; a local identity or schema
|
|
742
|
+
// problem must never block current source/code context delivery.
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
677
746
|
let workspace: KnowledgeWorkspace | undefined;
|
|
678
747
|
let pages: WorksetPage[] = [];
|
|
679
748
|
let workflows: WorksetWorkflow[] = [];
|
|
@@ -719,7 +788,10 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
|
|
|
719
788
|
const evidenceIds = unique(selection.claims.flatMap(claim => evidenceIdsForClaim(
|
|
720
789
|
claim,
|
|
721
790
|
evidenceByClaim.get(claim.id) ?? [],
|
|
722
|
-
))
|
|
791
|
+
)).concat(
|
|
792
|
+
durableMemory.map(memory => 'durable:' + memory.id),
|
|
793
|
+
durableEvidence.map(evidence => 'durable-evidence:' + evidence.id),
|
|
794
|
+
));
|
|
723
795
|
const verification = unique([
|
|
724
796
|
...workflows.flatMap(workflow => workflow.verificationGates),
|
|
725
797
|
...input.verificationHints,
|
|
@@ -758,7 +830,7 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
|
|
|
758
830
|
}
|
|
759
831
|
: undefined;
|
|
760
832
|
const base = {
|
|
761
|
-
version: '1.
|
|
833
|
+
version: '1.3' as const,
|
|
762
834
|
task,
|
|
763
835
|
lens: input.lens,
|
|
764
836
|
currentFacts: input.currentFacts?.map(fact => fact.startsWith('Historical note:')
|
|
@@ -773,6 +845,7 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
|
|
|
773
845
|
hiddenCautionMemoryCount: input.hiddenCautionMemoryCount ?? 0,
|
|
774
846
|
claims,
|
|
775
847
|
pages,
|
|
848
|
+
durableMemory,
|
|
776
849
|
workflows,
|
|
777
850
|
cautions: normalizedCautions,
|
|
778
851
|
verification,
|
|
@@ -789,7 +862,7 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
|
|
|
789
862
|
budget: { maxTokens },
|
|
790
863
|
});
|
|
791
864
|
const receipt: ContextReceipt = {
|
|
792
|
-
version: '1.
|
|
865
|
+
version: '1.3',
|
|
793
866
|
target: input.deliveryTarget ?? 'project-context',
|
|
794
867
|
elapsedMs: Math.max(0, Date.now() - startedAt),
|
|
795
868
|
budget: {
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { getDatabase } from '../store/sqlite-db.js';
|
|
3
|
+
import type {
|
|
4
|
+
LongTermMemory,
|
|
5
|
+
LongTermMemoryEvidence,
|
|
6
|
+
LongTermMemoryEvent,
|
|
7
|
+
} from './long-term-types.js';
|
|
8
|
+
|
|
9
|
+
function optionalText(value: unknown): string | undefined {
|
|
10
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function arrayValue(value: unknown): string[] {
|
|
14
|
+
if (typeof value !== 'string' || !value) return [];
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(value);
|
|
17
|
+
return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === 'string') : [];
|
|
18
|
+
} catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function rowToMemory(row: any): LongTermMemory {
|
|
24
|
+
return {
|
|
25
|
+
id: row.id,
|
|
26
|
+
originProjectId: row.originProjectId,
|
|
27
|
+
ownerId: row.ownerId,
|
|
28
|
+
scope: row.scope,
|
|
29
|
+
kind: row.kind,
|
|
30
|
+
state: row.state,
|
|
31
|
+
portability: row.portability,
|
|
32
|
+
title: row.title,
|
|
33
|
+
content: row.content,
|
|
34
|
+
facts: arrayValue(row.factsJson),
|
|
35
|
+
tags: arrayValue(row.tagsJson),
|
|
36
|
+
...(optionalText(row.applicability) ? { applicability: row.applicability } : {}),
|
|
37
|
+
origin: row.origin,
|
|
38
|
+
createdAt: row.createdAt,
|
|
39
|
+
updatedAt: row.updatedAt,
|
|
40
|
+
...(optionalText(row.qualifiedAt) ? { qualifiedAt: row.qualifiedAt } : {}),
|
|
41
|
+
...(optionalText(row.approvedAt) ? { approvedAt: row.approvedAt } : {}),
|
|
42
|
+
...(optionalText(row.archivedAt) ? { archivedAt: row.archivedAt } : {}),
|
|
43
|
+
...(optionalText(row.supersededBy) ? { supersededBy: row.supersededBy } : {}),
|
|
44
|
+
...(optionalText(row.lastValidatedAt) ? { lastValidatedAt: row.lastValidatedAt } : {}),
|
|
45
|
+
accessCount: Number(row.accessCount ?? 0),
|
|
46
|
+
...(optionalText(row.lastAccessedAt) ? { lastAccessedAt: row.lastAccessedAt } : {}),
|
|
47
|
+
} as LongTermMemory;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function rowToEvidence(row: any): LongTermMemoryEvidence {
|
|
51
|
+
return {
|
|
52
|
+
id: row.id,
|
|
53
|
+
memoryId: row.memoryId,
|
|
54
|
+
kind: row.kind,
|
|
55
|
+
referenceId: row.referenceId,
|
|
56
|
+
relation: row.relation,
|
|
57
|
+
...(optionalText(row.locator) ? { locator: row.locator } : {}),
|
|
58
|
+
...(optionalText(row.capturedHash) ? { capturedHash: row.capturedHash } : {}),
|
|
59
|
+
createdAt: row.createdAt,
|
|
60
|
+
} as LongTermMemoryEvidence;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function rowToEvent(row: any): LongTermMemoryEvent {
|
|
64
|
+
return {
|
|
65
|
+
id: row.id,
|
|
66
|
+
memoryId: row.memoryId,
|
|
67
|
+
kind: row.kind,
|
|
68
|
+
...(optionalText(row.fromState) ? { fromState: row.fromState } : {}),
|
|
69
|
+
...(optionalText(row.toState) ? { toState: row.toState } : {}),
|
|
70
|
+
...(optionalText(row.detail) ? { detail: row.detail } : {}),
|
|
71
|
+
createdAt: row.createdAt,
|
|
72
|
+
} as LongTermMemoryEvent;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Persistence only. Lifecycle, privacy, and portability policy live in long-term.ts. */
|
|
76
|
+
export class LongTermMemoryStore {
|
|
77
|
+
private db: any = null;
|
|
78
|
+
|
|
79
|
+
async init(dataDir: string): Promise<void> {
|
|
80
|
+
this.db = getDatabase(dataDir);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
transaction<T>(fn: () => T): T {
|
|
84
|
+
return this.db.transaction(fn)();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get(id: string): LongTermMemory | undefined {
|
|
88
|
+
const row = this.db.prepare('SELECT * FROM long_term_memories WHERE id = ?').get(id);
|
|
89
|
+
return row ? rowToMemory(row) : undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
list(options: { ownerId?: string; originProjectId?: string; limit?: number } = {}): LongTermMemory[] {
|
|
93
|
+
const limit = Math.max(1, Math.min(1_000, Math.floor(options.limit ?? 500)));
|
|
94
|
+
const clauses: string[] = [];
|
|
95
|
+
const params: unknown[] = [];
|
|
96
|
+
if (options.ownerId) {
|
|
97
|
+
clauses.push('ownerId = ?');
|
|
98
|
+
params.push(options.ownerId);
|
|
99
|
+
}
|
|
100
|
+
if (options.originProjectId) {
|
|
101
|
+
clauses.push('originProjectId = ?');
|
|
102
|
+
params.push(options.originProjectId);
|
|
103
|
+
}
|
|
104
|
+
const where = clauses.length ? ' WHERE ' + clauses.join(' AND ') : '';
|
|
105
|
+
const rows = this.db.prepare(
|
|
106
|
+
'SELECT * FROM long_term_memories' + where + ' ORDER BY updatedAt DESC, id LIMIT ?',
|
|
107
|
+
).all(...params, limit);
|
|
108
|
+
return rows.map(rowToMemory);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
insert(memory: LongTermMemory): void {
|
|
112
|
+
this.db.prepare(`
|
|
113
|
+
INSERT INTO long_term_memories (
|
|
114
|
+
id, originProjectId, ownerId, scope, kind, state, portability,
|
|
115
|
+
title, content, factsJson, tagsJson, applicability, origin,
|
|
116
|
+
createdAt, updatedAt, qualifiedAt, approvedAt, archivedAt,
|
|
117
|
+
supersededBy, lastValidatedAt, accessCount, lastAccessedAt
|
|
118
|
+
) VALUES (
|
|
119
|
+
@id, @originProjectId, @ownerId, @scope, @kind, @state, @portability,
|
|
120
|
+
@title, @content, @factsJson, @tagsJson, @applicability, @origin,
|
|
121
|
+
@createdAt, @updatedAt, @qualifiedAt, @approvedAt, @archivedAt,
|
|
122
|
+
@supersededBy, @lastValidatedAt, @accessCount, @lastAccessedAt
|
|
123
|
+
)
|
|
124
|
+
`).run({
|
|
125
|
+
...memory,
|
|
126
|
+
factsJson: JSON.stringify(memory.facts),
|
|
127
|
+
tagsJson: JSON.stringify(memory.tags),
|
|
128
|
+
applicability: memory.applicability ?? null,
|
|
129
|
+
qualifiedAt: memory.qualifiedAt ?? null,
|
|
130
|
+
approvedAt: memory.approvedAt ?? null,
|
|
131
|
+
archivedAt: memory.archivedAt ?? null,
|
|
132
|
+
supersededBy: memory.supersededBy ?? null,
|
|
133
|
+
lastValidatedAt: memory.lastValidatedAt ?? null,
|
|
134
|
+
lastAccessedAt: memory.lastAccessedAt ?? null,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
update(memory: LongTermMemory): void {
|
|
139
|
+
this.db.prepare(`
|
|
140
|
+
UPDATE long_term_memories SET
|
|
141
|
+
scope = @scope,
|
|
142
|
+
kind = @kind,
|
|
143
|
+
state = @state,
|
|
144
|
+
portability = @portability,
|
|
145
|
+
title = @title,
|
|
146
|
+
content = @content,
|
|
147
|
+
factsJson = @factsJson,
|
|
148
|
+
tagsJson = @tagsJson,
|
|
149
|
+
applicability = @applicability,
|
|
150
|
+
updatedAt = @updatedAt,
|
|
151
|
+
qualifiedAt = @qualifiedAt,
|
|
152
|
+
approvedAt = @approvedAt,
|
|
153
|
+
archivedAt = @archivedAt,
|
|
154
|
+
supersededBy = @supersededBy,
|
|
155
|
+
lastValidatedAt = @lastValidatedAt,
|
|
156
|
+
accessCount = @accessCount,
|
|
157
|
+
lastAccessedAt = @lastAccessedAt
|
|
158
|
+
WHERE id = @id
|
|
159
|
+
`).run({
|
|
160
|
+
...memory,
|
|
161
|
+
factsJson: JSON.stringify(memory.facts),
|
|
162
|
+
tagsJson: JSON.stringify(memory.tags),
|
|
163
|
+
applicability: memory.applicability ?? null,
|
|
164
|
+
qualifiedAt: memory.qualifiedAt ?? null,
|
|
165
|
+
approvedAt: memory.approvedAt ?? null,
|
|
166
|
+
archivedAt: memory.archivedAt ?? null,
|
|
167
|
+
supersededBy: memory.supersededBy ?? null,
|
|
168
|
+
lastValidatedAt: memory.lastValidatedAt ?? null,
|
|
169
|
+
lastAccessedAt: memory.lastAccessedAt ?? null,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
insertEvidence(input: Omit<LongTermMemoryEvidence, 'id' | 'createdAt'> & { createdAt?: string }): LongTermMemoryEvidence {
|
|
174
|
+
const evidence: LongTermMemoryEvidence = {
|
|
175
|
+
id: randomUUID(),
|
|
176
|
+
...input,
|
|
177
|
+
createdAt: input.createdAt ?? new Date().toISOString(),
|
|
178
|
+
};
|
|
179
|
+
this.db.prepare(`
|
|
180
|
+
INSERT INTO long_term_memory_evidence (
|
|
181
|
+
id, memoryId, kind, referenceId, relation, locator, capturedHash, createdAt
|
|
182
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
183
|
+
`).run(
|
|
184
|
+
evidence.id,
|
|
185
|
+
evidence.memoryId,
|
|
186
|
+
evidence.kind,
|
|
187
|
+
evidence.referenceId,
|
|
188
|
+
evidence.relation,
|
|
189
|
+
evidence.locator ?? null,
|
|
190
|
+
evidence.capturedHash ?? null,
|
|
191
|
+
evidence.createdAt,
|
|
192
|
+
);
|
|
193
|
+
return evidence;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
listEvidence(memoryId: string): LongTermMemoryEvidence[] {
|
|
197
|
+
return this.db.prepare(`
|
|
198
|
+
SELECT * FROM long_term_memory_evidence WHERE memoryId = ? ORDER BY createdAt ASC, id
|
|
199
|
+
`).all(memoryId).map(rowToEvidence);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
recordEvent(input: Omit<LongTermMemoryEvent, 'id' | 'createdAt'> & { createdAt?: string }): LongTermMemoryEvent {
|
|
203
|
+
const event: LongTermMemoryEvent = {
|
|
204
|
+
id: randomUUID(),
|
|
205
|
+
...input,
|
|
206
|
+
createdAt: input.createdAt ?? new Date().toISOString(),
|
|
207
|
+
};
|
|
208
|
+
this.db.prepare(`
|
|
209
|
+
INSERT INTO long_term_memory_events (id, memoryId, kind, fromState, toState, detail, createdAt)
|
|
210
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
211
|
+
`).run(
|
|
212
|
+
event.id,
|
|
213
|
+
event.memoryId,
|
|
214
|
+
event.kind,
|
|
215
|
+
event.fromState ?? null,
|
|
216
|
+
event.toState ?? null,
|
|
217
|
+
event.detail ?? null,
|
|
218
|
+
event.createdAt,
|
|
219
|
+
);
|
|
220
|
+
return event;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
listEvents(memoryId: string): LongTermMemoryEvent[] {
|
|
224
|
+
return this.db.prepare(`
|
|
225
|
+
SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
|
|
226
|
+
`).all(memoryId).map(rowToEvent);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
markAccess(ids: string[], at = new Date().toISOString()): void {
|
|
230
|
+
const unique = [...new Set(ids.filter(Boolean))];
|
|
231
|
+
if (unique.length === 0) return;
|
|
232
|
+
const statement = this.db.prepare(`
|
|
233
|
+
UPDATE long_term_memories
|
|
234
|
+
SET accessCount = accessCount + 1, lastAccessedAt = ?
|
|
235
|
+
WHERE id = ?
|
|
236
|
+
`);
|
|
237
|
+
this.db.transaction(() => {
|
|
238
|
+
for (const id of unique) statement.run(at, id);
|
|
239
|
+
})();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable cognitive-memory contracts. These types are intentionally separate
|
|
3
|
+
* from Observation provenance and Knowledge Claim truth contracts.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const LONG_TERM_MEMORY_KINDS = ['episodic', 'semantic', 'procedural'] as const;
|
|
7
|
+
export type LongTermMemoryKind = typeof LONG_TERM_MEMORY_KINDS[number];
|
|
8
|
+
|
|
9
|
+
export const LONG_TERM_MEMORY_SCOPES = ['project', 'user', 'team'] as const;
|
|
10
|
+
export type LongTermMemoryScope = typeof LONG_TERM_MEMORY_SCOPES[number];
|
|
11
|
+
|
|
12
|
+
export const LONG_TERM_MEMORY_STATES = [
|
|
13
|
+
'candidate',
|
|
14
|
+
'qualified',
|
|
15
|
+
'approved',
|
|
16
|
+
'archived',
|
|
17
|
+
'superseded',
|
|
18
|
+
] as const;
|
|
19
|
+
export type LongTermMemoryState = typeof LONG_TERM_MEMORY_STATES[number];
|
|
20
|
+
|
|
21
|
+
export const LONG_TERM_MEMORY_PORTABILITIES = ['project-bound', 'portable'] as const;
|
|
22
|
+
export type LongTermMemoryPortability = typeof LONG_TERM_MEMORY_PORTABILITIES[number];
|
|
23
|
+
|
|
24
|
+
export const LONG_TERM_MEMORY_EVIDENCE_KINDS = [
|
|
25
|
+
'observation',
|
|
26
|
+
'claim',
|
|
27
|
+
'workflow',
|
|
28
|
+
'session',
|
|
29
|
+
'checkpoint',
|
|
30
|
+
'git',
|
|
31
|
+
'code',
|
|
32
|
+
'test',
|
|
33
|
+
'run',
|
|
34
|
+
'document',
|
|
35
|
+
'manual',
|
|
36
|
+
'user',
|
|
37
|
+
] as const;
|
|
38
|
+
export type LongTermMemoryEvidenceKind = typeof LONG_TERM_MEMORY_EVIDENCE_KINDS[number];
|
|
39
|
+
|
|
40
|
+
export const LONG_TERM_MEMORY_EVIDENCE_RELATIONS = [
|
|
41
|
+
'supports',
|
|
42
|
+
'verifies',
|
|
43
|
+
'derives',
|
|
44
|
+
'user-confirmed',
|
|
45
|
+
] as const;
|
|
46
|
+
export type LongTermMemoryEvidenceRelation = typeof LONG_TERM_MEMORY_EVIDENCE_RELATIONS[number];
|
|
47
|
+
|
|
48
|
+
export type LongTermMemoryOrigin = 'manual' | 'agent' | 'hook' | 'git' | 'migration';
|
|
49
|
+
|
|
50
|
+
export interface LongTermMemoryEvidence {
|
|
51
|
+
id: string;
|
|
52
|
+
memoryId: string;
|
|
53
|
+
kind: LongTermMemoryEvidenceKind;
|
|
54
|
+
referenceId: string;
|
|
55
|
+
relation: LongTermMemoryEvidenceRelation;
|
|
56
|
+
locator?: string;
|
|
57
|
+
capturedHash?: string;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LongTermMemory {
|
|
62
|
+
id: string;
|
|
63
|
+
/** Project from which this item and its evidence originated. */
|
|
64
|
+
originProjectId: string;
|
|
65
|
+
/** Local stable owner identity. Team and project records retain attribution. */
|
|
66
|
+
ownerId: string;
|
|
67
|
+
scope: LongTermMemoryScope;
|
|
68
|
+
kind: LongTermMemoryKind;
|
|
69
|
+
state: LongTermMemoryState;
|
|
70
|
+
portability: LongTermMemoryPortability;
|
|
71
|
+
title: string;
|
|
72
|
+
content: string;
|
|
73
|
+
facts: string[];
|
|
74
|
+
tags: string[];
|
|
75
|
+
/** Short condition explaining when the procedure/fact applies. */
|
|
76
|
+
applicability?: string;
|
|
77
|
+
origin: LongTermMemoryOrigin;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
updatedAt: string;
|
|
80
|
+
qualifiedAt?: string;
|
|
81
|
+
approvedAt?: string;
|
|
82
|
+
archivedAt?: string;
|
|
83
|
+
supersededBy?: string;
|
|
84
|
+
lastValidatedAt?: string;
|
|
85
|
+
accessCount: number;
|
|
86
|
+
lastAccessedAt?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type LongTermMemoryEventKind = 'created' | 'qualified' | 'approved' | 'archived' | 'superseded';
|
|
90
|
+
|
|
91
|
+
export interface LongTermMemoryEvent {
|
|
92
|
+
id: string;
|
|
93
|
+
memoryId: string;
|
|
94
|
+
kind: LongTermMemoryEventKind;
|
|
95
|
+
fromState?: LongTermMemoryState;
|
|
96
|
+
toState?: LongTermMemoryState;
|
|
97
|
+
detail?: string;
|
|
98
|
+
createdAt: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface LongTermMemoryReader {
|
|
102
|
+
projectId: string;
|
|
103
|
+
ownerId?: string;
|
|
104
|
+
agentId?: string;
|
|
105
|
+
isTeamMember?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface LongTermMemoryEvidenceInput {
|
|
109
|
+
kind: LongTermMemoryEvidenceKind;
|
|
110
|
+
referenceId: string;
|
|
111
|
+
relation: LongTermMemoryEvidenceRelation;
|
|
112
|
+
locator?: string;
|
|
113
|
+
capturedHash?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface CreateLongTermMemoryInput {
|
|
117
|
+
originProjectId: string;
|
|
118
|
+
ownerId: string;
|
|
119
|
+
scope: LongTermMemoryScope;
|
|
120
|
+
kind: LongTermMemoryKind;
|
|
121
|
+
portability?: LongTermMemoryPortability;
|
|
122
|
+
title: string;
|
|
123
|
+
content: string;
|
|
124
|
+
facts?: string[];
|
|
125
|
+
tags?: string[];
|
|
126
|
+
applicability?: string;
|
|
127
|
+
origin?: LongTermMemoryOrigin;
|
|
128
|
+
evidence: LongTermMemoryEvidenceInput[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface LongTermMemorySelection {
|
|
132
|
+
memory: LongTermMemory;
|
|
133
|
+
evidence: LongTermMemoryEvidence[];
|
|
134
|
+
score: number;
|
|
135
|
+
reason: string;
|
|
136
|
+
}
|