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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const ID_PATTERN = /^[A-Za-z0-9:_-]{3,160}$/;
|
|
6
|
+
|
|
7
|
+
export interface LocalMemoryOwner {
|
|
8
|
+
id: string;
|
|
9
|
+
source: 'environment' | 'local-file';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface OwnerFile {
|
|
13
|
+
version: 1;
|
|
14
|
+
id: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ownerPath(dataDir: string): string {
|
|
19
|
+
const resolved = path.resolve(dataDir);
|
|
20
|
+
// The normal runtime data directory is ~/.memorix/data, so keep identity
|
|
21
|
+
// beside it. Custom data directories and isolated tests own their identity
|
|
22
|
+
// inside the supplied directory instead of accidentally sharing a temp root.
|
|
23
|
+
const home = path.basename(resolved).toLocaleLowerCase('en-US') === 'data'
|
|
24
|
+
? path.dirname(resolved)
|
|
25
|
+
: resolved;
|
|
26
|
+
return path.join(home, 'memorix-user.json');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizedOwnerId(value: string | undefined): string | undefined {
|
|
30
|
+
const trimmed = value?.trim();
|
|
31
|
+
return trimmed && ID_PATTERN.test(trimmed) ? trimmed : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function readOwnerFile(filePath: string): Promise<LocalMemoryOwner | undefined> {
|
|
35
|
+
try {
|
|
36
|
+
const value = JSON.parse(await fs.readFile(filePath, 'utf8')) as Partial<OwnerFile>;
|
|
37
|
+
const id = typeof value.id === 'string' ? normalizedOwnerId(value.id) : undefined;
|
|
38
|
+
return id ? { id, source: 'local-file' } : undefined;
|
|
39
|
+
} catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a local stable user identity without deriving anything from prompts,
|
|
46
|
+
* host names, or account details. Reads do not create an identity; first write
|
|
47
|
+
* creates one atomically enough for normal local CLI/MCP use.
|
|
48
|
+
*/
|
|
49
|
+
export async function resolveLocalMemoryOwner(
|
|
50
|
+
dataDir: string,
|
|
51
|
+
options: { create?: boolean } = {},
|
|
52
|
+
): Promise<LocalMemoryOwner | undefined> {
|
|
53
|
+
const configured = normalizedOwnerId(process.env.MEMORIX_USER_ID);
|
|
54
|
+
if (configured) return { id: configured, source: 'environment' };
|
|
55
|
+
|
|
56
|
+
const filePath = ownerPath(dataDir);
|
|
57
|
+
const existing = await readOwnerFile(filePath);
|
|
58
|
+
if (existing || !options.create) return existing;
|
|
59
|
+
|
|
60
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
61
|
+
const created: OwnerFile = {
|
|
62
|
+
version: 1,
|
|
63
|
+
id: 'local-' + randomUUID(),
|
|
64
|
+
createdAt: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
try {
|
|
67
|
+
await fs.writeFile(filePath, JSON.stringify(created, null, 2) + '\n', { encoding: 'utf8', flag: 'wx' });
|
|
68
|
+
return { id: created.id, source: 'local-file' };
|
|
69
|
+
} catch (error: any) {
|
|
70
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
71
|
+
const concurrent = await readOwnerFile(filePath);
|
|
72
|
+
if (concurrent) return concurrent;
|
|
73
|
+
throw new Error('Memorix could not establish a valid local memory owner identity.');
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -671,9 +671,10 @@ export async function createMemorixServer(
|
|
|
671
671
|
'Use type to classify: gotcha ([GOTCHA] critical pitfall), decision ([DECISION] architecture choice), ' +
|
|
672
672
|
'problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), ' +
|
|
673
673
|
'discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), ' +
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
674
|
+
'session-request ([SESSION] original goal). ' +
|
|
675
|
+
'Project visibility is the default. Personal or team visibility requires an explicitly joined coordination identity. ' +
|
|
676
|
+
'Set longTerm only when the caller explicitly wants an additional source-backed long-term candidate; it is never injected until an operator records a review through the CLI. ' +
|
|
677
|
+
'For a read-only task, do not store unless the user explicitly asks to save a record.',
|
|
677
678
|
inputSchema: {
|
|
678
679
|
entityName: z.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
|
|
679
680
|
type: z.enum(OBSERVATION_TYPES).describe('Observation type for classification'),
|
|
@@ -697,30 +698,53 @@ export async function createMemorixServer(
|
|
|
697
698
|
visibility: z.enum(['personal', 'project', 'team']).optional().describe(
|
|
698
699
|
'Retrieval scope. Project is the normal shared default; personal/team require memorix_session_start with joinTeam=true.',
|
|
699
700
|
),
|
|
701
|
+
longTerm: z.object({
|
|
702
|
+
kind: z.enum(['episodic', 'semantic', 'procedural']).describe('Cognitive kind for an additional long-term candidate.'),
|
|
703
|
+
scope: z.enum(['project', 'user', 'team']).optional().default('project').describe('Long-term scope. User capture is private source evidence and needs a bound agent identity.'),
|
|
704
|
+
tags: z.array(z.string()).optional().describe('Small set of retrieval tags for the candidate.'),
|
|
705
|
+
applicability: z.string().optional().describe('When this durable fact or procedure applies.'),
|
|
706
|
+
}).optional().describe('Optional explicit request to create a source-backed long-term candidate alongside this observation. Candidates are not automatically injected.'),
|
|
700
707
|
overrideReadOnly: z.boolean().optional().describe(
|
|
701
708
|
'Use only when the user explicitly asks to save memory during a read-only or no-modification task.',
|
|
702
709
|
),
|
|
703
710
|
},
|
|
704
711
|
},
|
|
705
|
-
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility, overrideReadOnly }) => {
|
|
712
|
+
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility, longTerm, overrideReadOnly }) => {
|
|
706
713
|
const unresolved = requireResolvedProject('store memory in the current project');
|
|
707
714
|
if (unresolved) return unresolved;
|
|
708
715
|
const readOnlyBoundary = blockReadOnlyAutopilotWrite(overrideReadOnly);
|
|
709
716
|
if (readOnlyBoundary) return readOnlyBoundary;
|
|
710
|
-
|
|
717
|
+
// Keep visibility undefined for an ordinary upsert. storeObservation then
|
|
718
|
+
// preserves an existing targeted/personal record instead of silently
|
|
719
|
+
// widening it to project scope. A long-term scope is explicit and must
|
|
720
|
+
// still set the source observation's corresponding visibility.
|
|
721
|
+
const requestedVisibility = (
|
|
722
|
+
longTerm?.scope === 'user'
|
|
723
|
+
? 'personal'
|
|
724
|
+
: longTerm?.scope === 'team'
|
|
725
|
+
? 'team'
|
|
726
|
+
: visibility
|
|
727
|
+
) as 'personal' | 'project' | 'team' | undefined;
|
|
728
|
+
const effectiveVisibility = requestedVisibility ?? 'project';
|
|
711
729
|
const reader = getObservationReader();
|
|
712
|
-
if (
|
|
730
|
+
if (effectiveVisibility !== 'project' && !currentAgentId) {
|
|
713
731
|
return {
|
|
714
732
|
content: [{ type: 'text' as const, text: 'Personal or team memory requires memorix_session_start with joinTeam=true so Memorix can bind an owner.' }],
|
|
715
733
|
isError: true as const,
|
|
716
734
|
};
|
|
717
735
|
}
|
|
718
|
-
if (
|
|
736
|
+
if (effectiveVisibility === 'team' && !reader.isTeamMember) {
|
|
719
737
|
return {
|
|
720
738
|
content: [{ type: 'text' as const, text: 'Team memory requires an active coordination membership in the current project.' }],
|
|
721
739
|
isError: true as const,
|
|
722
740
|
};
|
|
723
741
|
}
|
|
742
|
+
if (longTerm?.scope === 'team' && !reader.isTeamMember) {
|
|
743
|
+
return {
|
|
744
|
+
content: [{ type: 'text' as const, text: 'Team long-term memory requires an active coordination membership in the current project.' }],
|
|
745
|
+
isError: true as const,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
724
748
|
try {
|
|
725
749
|
return await withFreshIndex(async () => {
|
|
726
750
|
|
|
@@ -752,7 +776,7 @@ export async function createMemorixServer(
|
|
|
752
776
|
// ── Formation Pipeline (active mode: decides storage) ─────────────
|
|
753
777
|
let formationResult: FormedMemory | null = null;
|
|
754
778
|
let formationNote = '';
|
|
755
|
-
if (useFormation && !topicKey && !progress) {
|
|
779
|
+
if (useFormation && !topicKey && !progress && !longTerm) {
|
|
756
780
|
let currentFormationStage: FormationStage | 'setup' = 'setup';
|
|
757
781
|
const completedFormationStages: Partial<Record<FormationStage, number>> = {};
|
|
758
782
|
const formationStartTime = Date.now();
|
|
@@ -915,7 +939,7 @@ export async function createMemorixServer(
|
|
|
915
939
|
// This keeps memory count low and prevents duplication (Mem0-style).
|
|
916
940
|
let compactAction = '';
|
|
917
941
|
let compactMerged = false;
|
|
918
|
-
if (!useFormation && !topicKey && !progress) {
|
|
942
|
+
if (!useFormation && !topicKey && !progress && !longTerm) {
|
|
919
943
|
try {
|
|
920
944
|
const searchResult = await compactSearch({
|
|
921
945
|
query: `${title} ${narrative.substring(0, 200)}`,
|
|
@@ -1094,10 +1118,33 @@ export async function createMemorixServer(
|
|
|
1094
1118
|
sourceDetail: 'explicit',
|
|
1095
1119
|
valueCategory: formationResult?.evaluation.category,
|
|
1096
1120
|
createdByAgentId: currentAgentId,
|
|
1097
|
-
visibility,
|
|
1121
|
+
visibility: requestedVisibility,
|
|
1098
1122
|
visibilityReader: reader,
|
|
1099
1123
|
});
|
|
1100
1124
|
|
|
1125
|
+
let longTermNote = '';
|
|
1126
|
+
if (longTerm) {
|
|
1127
|
+
try {
|
|
1128
|
+
const { promoteObservationToLongTermMemory } = await import('./memory/long-term.js');
|
|
1129
|
+
const promoted = await promoteObservationToLongTermMemory({
|
|
1130
|
+
dataDir: projectDir,
|
|
1131
|
+
observation: obs,
|
|
1132
|
+
scope: longTerm.scope,
|
|
1133
|
+
kind: longTerm.kind,
|
|
1134
|
+
tags: longTerm.tags,
|
|
1135
|
+
applicability: longTerm.applicability,
|
|
1136
|
+
reader: {
|
|
1137
|
+
projectId: project.id,
|
|
1138
|
+
...(reader.agentId ? { agentId: reader.agentId } : {}),
|
|
1139
|
+
...(reader.isTeamMember ? { isTeamMember: true } : {}),
|
|
1140
|
+
},
|
|
1141
|
+
});
|
|
1142
|
+
longTermNote = `\nLong-term candidate: ${promoted.memory.id} (${promoted.memory.kind}/${promoted.memory.scope}). Review it with the CLI before it is delivered automatically.`;
|
|
1143
|
+
} catch (longTermError) {
|
|
1144
|
+
longTermNote = `\n[WARN] Observation stored, but long-term candidate was not created: ${longTermError instanceof Error ? longTermError.message : 'unknown error'}`;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1101
1148
|
// Add a reference to the entity's observations
|
|
1102
1149
|
await graphManager.addObservations([
|
|
1103
1150
|
{ entityName, contents: [`[#${obs.id}] ${title}`] },
|
|
@@ -1123,7 +1170,7 @@ export async function createMemorixServer(
|
|
|
1123
1170
|
// ── Formation Pipeline (shadow/fallback mode: observe only) ─────
|
|
1124
1171
|
// Fire-and-forget: runs after storage to collect metrics.
|
|
1125
1172
|
// Never blocks the MCP response — purely for A/B comparison data.
|
|
1126
|
-
if (!useFormation && !topicKey && !progress) {
|
|
1173
|
+
if (!useFormation && !topicKey && !progress && !longTerm) {
|
|
1127
1174
|
const shadowFormation = async () => {
|
|
1128
1175
|
let oldCompactDecision: { action: string, targetId?: number, reason?: string, durationMs?: number } | null = null;
|
|
1129
1176
|
try {
|
|
@@ -1215,7 +1262,7 @@ export async function createMemorixServer(
|
|
|
1215
1262
|
content: [
|
|
1216
1263
|
{
|
|
1217
1264
|
type: 'text' as const,
|
|
1218
|
-
text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)\nEntity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ''}${compactAction}${compressionNote}${enrichment}${formationNote}${attributionWarning}`,
|
|
1265
|
+
text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)\nEntity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ''}${compactAction}${compressionNote}${enrichment}${formationNote}${attributionWarning}${longTermNote}`,
|
|
1219
1266
|
},
|
|
1220
1267
|
],
|
|
1221
1268
|
};
|
|
@@ -1667,6 +1714,7 @@ export async function createMemorixServer(
|
|
|
1667
1714
|
...(external.caution
|
|
1668
1715
|
? { runtimeCautions: [{ kind: 'external-codegraph-fallback' as const, message: external.caution }] }
|
|
1669
1716
|
: {}),
|
|
1717
|
+
reader: getObservationReader(),
|
|
1670
1718
|
});
|
|
1671
1719
|
const text = buildContextPackPrompt(pack);
|
|
1672
1720
|
|
|
@@ -2475,11 +2523,11 @@ export async function createMemorixServer(
|
|
|
2475
2523
|
{
|
|
2476
2524
|
title: 'Memory Details',
|
|
2477
2525
|
description:
|
|
2478
|
-
'Fetch full observation
|
|
2526
|
+
'Fetch full observation, mini-skill, or curated durable-memory details — includes source kind (explicit memory / hook trace / git evidence), ' +
|
|
2479
2527
|
'value category, and cross-references (~500-1000 tokens each). ' +
|
|
2480
2528
|
'Do not re-fetch content already covered by a complete memorix_project_context brief unless a specific fact is still missing or the user asks for deeper history; provide purpose when intentionally expanding. ' +
|
|
2481
2529
|
'Always use memorix_search first to find relevant IDs, then fetch only what you need. ' +
|
|
2482
|
-
'Accepts typed refs from search results (e.g. "obs:42", "skill:3") via the typedRefs field, ' +
|
|
2530
|
+
'Accepts typed refs from search results (e.g. "obs:42", "skill:3") and durable refs from a project brief (e.g. "durable:<uuid>") via the typedRefs field, ' +
|
|
2483
2531
|
'or legacy numeric ids / object refs for backward compatibility.',
|
|
2484
2532
|
inputSchema: {
|
|
2485
2533
|
ids: z.array(z.number()).optional().describe('Observation IDs to fetch (legacy, from memorix_search results)'),
|
|
@@ -2489,7 +2537,7 @@ export async function createMemorixServer(
|
|
|
2489
2537
|
projectId: z.string().optional().describe('Project ID for global-search disambiguation'),
|
|
2490
2538
|
}),
|
|
2491
2539
|
).optional().describe('Explicit observation refs. Prefer this for global search results.'),
|
|
2492
|
-
typedRefs: z.array(z.string()).optional().describe('Typed memory refs from search results, e.g. "obs:42", "skill:3", "obs:42@org/proj"'),
|
|
2540
|
+
typedRefs: z.array(z.string()).optional().describe('Typed memory refs from search results or a project brief, e.g. "obs:42", "skill:3", "durable:<uuid>", "obs:42@org/proj"'),
|
|
2493
2541
|
purpose: z.string().optional().describe(
|
|
2494
2542
|
'Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user\'s explicit request.',
|
|
2495
2543
|
),
|
|
@@ -2503,15 +2551,20 @@ export async function createMemorixServer(
|
|
|
2503
2551
|
const safeIds = coerceNumberArray(ids);
|
|
2504
2552
|
const safeRefs = coerceObservationRefs(refs);
|
|
2505
2553
|
const safeTypedRefs = coerceStringArray(typedRefs);
|
|
2554
|
+
const durableIds = safeTypedRefs.flatMap((ref) => {
|
|
2555
|
+
const match = /^durable:([0-9a-f-]{36})$/i.exec(ref.trim());
|
|
2556
|
+
return match ? [match[1]] : [];
|
|
2557
|
+
});
|
|
2558
|
+
const observationTypedRefs = safeTypedRefs.filter(ref => !/^durable:/i.test(ref.trim()));
|
|
2506
2559
|
const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id)
|
|
2507
|
-
||
|
|
2560
|
+
|| observationTypedRefs.some((ref) => ref.includes('@') && !ref.endsWith(`@${project.id}`));
|
|
2508
2561
|
if (!hasCrossProjectRef) {
|
|
2509
2562
|
const boundary = requireExplicitAutopilotExpansion('memorix_detail', purpose);
|
|
2510
2563
|
if (boundary) return boundary;
|
|
2511
2564
|
const requestedObservationIds = [
|
|
2512
2565
|
...safeIds,
|
|
2513
2566
|
...safeRefs.map(ref => ref.id),
|
|
2514
|
-
...
|
|
2567
|
+
...observationTypedRefs.flatMap((ref) => {
|
|
2515
2568
|
const match = /^obs:(\d+)(?:@.+)?$/i.exec(ref.trim());
|
|
2516
2569
|
return match ? [Number(match[1])] : [];
|
|
2517
2570
|
}),
|
|
@@ -2520,18 +2573,47 @@ export async function createMemorixServer(
|
|
|
2520
2573
|
if (duplicate) return duplicate;
|
|
2521
2574
|
}
|
|
2522
2575
|
|
|
2523
|
-
|
|
2524
|
-
let result;
|
|
2576
|
+
const formatted: string[] = [];
|
|
2525
2577
|
try {
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2578
|
+
if (durableIds.length > 0) {
|
|
2579
|
+
const [{ getLongTermMemoryDetail }, { resolveLocalMemoryOwner }] = await Promise.all([
|
|
2580
|
+
import('./memory/long-term.js'),
|
|
2581
|
+
import('./memory/owner.js'),
|
|
2582
|
+
]);
|
|
2583
|
+
const owner = await resolveLocalMemoryOwner(projectDir, { create: false });
|
|
2584
|
+
const observationReader = getObservationReader();
|
|
2585
|
+
const durableReader = {
|
|
2586
|
+
projectId: project.id,
|
|
2587
|
+
...(owner ? { ownerId: owner.id } : {}),
|
|
2588
|
+
...(observationReader.agentId ? { agentId: observationReader.agentId } : {}),
|
|
2589
|
+
...(observationReader.isTeamMember ? { isTeamMember: true } : {}),
|
|
2590
|
+
};
|
|
2591
|
+
const details = await Promise.all(durableIds.slice(0, 5).map(async (id) => {
|
|
2592
|
+
try {
|
|
2593
|
+
return await getLongTermMemoryDetail({ dataDir: projectDir, id, reader: durableReader });
|
|
2594
|
+
} catch (error) {
|
|
2595
|
+
return { error: error instanceof Error ? error.message : String(error), id };
|
|
2596
|
+
}
|
|
2597
|
+
}));
|
|
2598
|
+
formatted.push(JSON.stringify({ durableMemory: details }, null, 2));
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
if (observationTypedRefs.length > 0 || safeRefs.length > 0 || safeIds.length > 0) {
|
|
2602
|
+
const reader = getObservationReader(hasCrossProjectRef ? 'global' : 'project');
|
|
2603
|
+
const result = observationTypedRefs.length > 0
|
|
2604
|
+
? await compactDetail(observationTypedRefs, { reader })
|
|
2605
|
+
: safeRefs.length > 0
|
|
2606
|
+
? await compactDetail(safeRefs, { reader })
|
|
2607
|
+
: await compactDetail(safeIds.map(id => ({ id, projectId: project.id })), { reader });
|
|
2608
|
+
formatted.push(
|
|
2609
|
+
result.documents.length > 0
|
|
2610
|
+
? result.formatted
|
|
2611
|
+
: observationTypedRefs.length > 0
|
|
2612
|
+
? `No memories found for refs: ${observationTypedRefs.join(', ')}`
|
|
2613
|
+
: safeRefs.length > 0
|
|
2614
|
+
? `No memories found for refs: ${safeRefs.map((ref) => `${ref.projectId ?? 'current'}#${ref.id}`).join(', ')}`
|
|
2615
|
+
: `No memories found for IDs: ${safeIds.join(', ')}`,
|
|
2616
|
+
);
|
|
2535
2617
|
}
|
|
2536
2618
|
} catch (err) {
|
|
2537
2619
|
return { content: [{ type: 'text' as const, text: err instanceof Error ? err.message : String(err) }], isError: true };
|
|
@@ -2541,8 +2623,8 @@ export async function createMemorixServer(
|
|
|
2541
2623
|
content: [
|
|
2542
2624
|
{
|
|
2543
2625
|
type: 'text' as const,
|
|
2544
|
-
text:
|
|
2545
|
-
?
|
|
2626
|
+
text: formatted.length > 0
|
|
2627
|
+
? formatted.join('\n\n')
|
|
2546
2628
|
: safeTypedRefs.length > 0
|
|
2547
2629
|
? `No memories found for refs: ${safeTypedRefs.join(', ')}`
|
|
2548
2630
|
: safeRefs.length > 0
|
package/src/store/sqlite-db.ts
CHANGED
|
@@ -497,6 +497,62 @@ const CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE = [
|
|
|
497
497
|
');',
|
|
498
498
|
].join('\n');
|
|
499
499
|
|
|
500
|
+
// ── 1.3 Durable cognitive memory ───────────────────────────────────
|
|
501
|
+
|
|
502
|
+
const CREATE_LONG_TERM_MEMORIES_TABLE = `
|
|
503
|
+
CREATE TABLE IF NOT EXISTS long_term_memories (
|
|
504
|
+
id TEXT PRIMARY KEY,
|
|
505
|
+
originProjectId TEXT NOT NULL,
|
|
506
|
+
ownerId TEXT NOT NULL,
|
|
507
|
+
scope TEXT NOT NULL,
|
|
508
|
+
kind TEXT NOT NULL,
|
|
509
|
+
state TEXT NOT NULL,
|
|
510
|
+
portability TEXT NOT NULL DEFAULT 'project-bound',
|
|
511
|
+
title TEXT NOT NULL,
|
|
512
|
+
content TEXT NOT NULL,
|
|
513
|
+
factsJson TEXT NOT NULL DEFAULT '[]',
|
|
514
|
+
tagsJson TEXT NOT NULL DEFAULT '[]',
|
|
515
|
+
applicability TEXT,
|
|
516
|
+
origin TEXT NOT NULL,
|
|
517
|
+
createdAt TEXT NOT NULL,
|
|
518
|
+
updatedAt TEXT NOT NULL,
|
|
519
|
+
qualifiedAt TEXT,
|
|
520
|
+
approvedAt TEXT,
|
|
521
|
+
archivedAt TEXT,
|
|
522
|
+
supersededBy TEXT,
|
|
523
|
+
lastValidatedAt TEXT,
|
|
524
|
+
accessCount INTEGER NOT NULL DEFAULT 0,
|
|
525
|
+
lastAccessedAt TEXT
|
|
526
|
+
);
|
|
527
|
+
`;
|
|
528
|
+
|
|
529
|
+
const CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE = `
|
|
530
|
+
CREATE TABLE IF NOT EXISTS long_term_memory_evidence (
|
|
531
|
+
id TEXT PRIMARY KEY,
|
|
532
|
+
memoryId TEXT NOT NULL,
|
|
533
|
+
kind TEXT NOT NULL,
|
|
534
|
+
referenceId TEXT NOT NULL,
|
|
535
|
+
relation TEXT NOT NULL,
|
|
536
|
+
locator TEXT,
|
|
537
|
+
capturedHash TEXT,
|
|
538
|
+
createdAt TEXT NOT NULL,
|
|
539
|
+
FOREIGN KEY (memoryId) REFERENCES long_term_memories(id) ON DELETE CASCADE
|
|
540
|
+
);
|
|
541
|
+
`;
|
|
542
|
+
|
|
543
|
+
const CREATE_LONG_TERM_MEMORY_EVENTS_TABLE = `
|
|
544
|
+
CREATE TABLE IF NOT EXISTS long_term_memory_events (
|
|
545
|
+
id TEXT PRIMARY KEY,
|
|
546
|
+
memoryId TEXT NOT NULL,
|
|
547
|
+
kind TEXT NOT NULL,
|
|
548
|
+
fromState TEXT,
|
|
549
|
+
toState TEXT,
|
|
550
|
+
detail TEXT,
|
|
551
|
+
createdAt TEXT NOT NULL,
|
|
552
|
+
FOREIGN KEY (memoryId) REFERENCES long_term_memories(id) ON DELETE CASCADE
|
|
553
|
+
);
|
|
554
|
+
`;
|
|
555
|
+
|
|
500
556
|
// ── Runtime maintenance jobs ───────────────────────────────────────
|
|
501
557
|
|
|
502
558
|
const CREATE_MAINTENANCE_JOBS_TABLE = `
|
|
@@ -599,6 +655,11 @@ CREATE INDEX IF NOT EXISTS idx_knowledge_page_claims_claim ON knowledge_page_cla
|
|
|
599
655
|
CREATE INDEX IF NOT EXISTS idx_knowledge_proposals_workspace_status ON knowledge_proposals(workspaceId, status, createdAt DESC);
|
|
600
656
|
CREATE INDEX IF NOT EXISTS idx_knowledge_workflows_workspace_status ON knowledge_workflows(workspaceId, status, updatedAt DESC);
|
|
601
657
|
CREATE INDEX IF NOT EXISTS idx_knowledge_workflow_runs_project_workflow ON knowledge_workflow_runs(projectId, workflowId, startedAt DESC);
|
|
658
|
+
CREATE INDEX IF NOT EXISTS idx_long_term_memories_owner_state ON long_term_memories(ownerId, state, updatedAt DESC);
|
|
659
|
+
CREATE INDEX IF NOT EXISTS idx_long_term_memories_project_state ON long_term_memories(originProjectId, state, updatedAt DESC);
|
|
660
|
+
CREATE INDEX IF NOT EXISTS idx_long_term_memories_scope_portability ON long_term_memories(scope, portability, state, updatedAt DESC);
|
|
661
|
+
CREATE INDEX IF NOT EXISTS idx_long_term_memory_evidence_memory ON long_term_memory_evidence(memoryId, createdAt);
|
|
662
|
+
CREATE INDEX IF NOT EXISTS idx_long_term_memory_events_memory ON long_term_memory_events(memoryId, createdAt);
|
|
602
663
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_ready ON maintenance_jobs(status, run_after);
|
|
603
664
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_project ON maintenance_jobs(project_id, status, run_after);
|
|
604
665
|
CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_targets(updated_at);
|
|
@@ -705,6 +766,19 @@ const SCHEMA_MIGRATIONS: SchemaMigration[] = [
|
|
|
705
766
|
db.exec('CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key)');
|
|
706
767
|
},
|
|
707
768
|
},
|
|
769
|
+
{
|
|
770
|
+
id: '1.3-long-term-memory',
|
|
771
|
+
apply: (db) => {
|
|
772
|
+
db.exec(CREATE_LONG_TERM_MEMORIES_TABLE);
|
|
773
|
+
db.exec(CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE);
|
|
774
|
+
db.exec(CREATE_LONG_TERM_MEMORY_EVENTS_TABLE);
|
|
775
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_long_term_memories_owner_state ON long_term_memories(ownerId, state, updatedAt DESC)');
|
|
776
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_long_term_memories_project_state ON long_term_memories(originProjectId, state, updatedAt DESC)');
|
|
777
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_long_term_memories_scope_portability ON long_term_memories(scope, portability, state, updatedAt DESC)');
|
|
778
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_long_term_memory_evidence_memory ON long_term_memory_evidence(memoryId, createdAt)');
|
|
779
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_long_term_memory_events_memory ON long_term_memory_events(memoryId, createdAt)');
|
|
780
|
+
},
|
|
781
|
+
},
|
|
708
782
|
];
|
|
709
783
|
|
|
710
784
|
function applySchemaMigrations(db: any): void {
|
package/src/types.ts
CHANGED
|
@@ -648,3 +648,23 @@ export interface MCPConfigAdapter {
|
|
|
648
648
|
/** Get the default config file path for this agent */
|
|
649
649
|
getConfigPath(projectRoot?: string): string;
|
|
650
650
|
}
|
|
651
|
+
|
|
652
|
+
// Durable cognitive memory is intentionally kept in its own module so the
|
|
653
|
+
// legacy Observation contract remains source/provenance focused.
|
|
654
|
+
export type {
|
|
655
|
+
CreateLongTermMemoryInput,
|
|
656
|
+
LongTermMemory,
|
|
657
|
+
LongTermMemoryEvidence,
|
|
658
|
+
LongTermMemoryEvidenceInput,
|
|
659
|
+
LongTermMemoryEvidenceKind,
|
|
660
|
+
LongTermMemoryEvidenceRelation,
|
|
661
|
+
LongTermMemoryEvent,
|
|
662
|
+
LongTermMemoryEventKind,
|
|
663
|
+
LongTermMemoryKind,
|
|
664
|
+
LongTermMemoryOrigin,
|
|
665
|
+
LongTermMemoryPortability,
|
|
666
|
+
LongTermMemoryReader,
|
|
667
|
+
LongTermMemoryScope,
|
|
668
|
+
LongTermMemorySelection,
|
|
669
|
+
LongTermMemoryState,
|
|
670
|
+
} from './memory/long-term-types.js';
|