@kinqs/brainrouter-mcp-server 0.3.4 → 0.3.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 (65) hide show
  1. package/.env.example +121 -71
  2. package/README.md +88 -15
  3. package/dist/__tests__/cognitive-extractor.test.js +112 -0
  4. package/dist/__tests__/crypto.test.js +8 -1
  5. package/dist/__tests__/working-memory.test.js +67 -0
  6. package/dist/env-loader.js +47 -0
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.js +12 -1
  9. package/dist/init.d.ts +1 -0
  10. package/dist/init.js +64 -0
  11. package/dist/memory/engine.js +21 -1
  12. package/dist/memory/pipeline/cognitive-extractor.js +19 -1
  13. package/dist/memory/recall.d.ts +3 -1
  14. package/dist/memory/recall.js +48 -3
  15. package/dist/memory/store/relevance-judge.d.ts +51 -0
  16. package/dist/memory/store/relevance-judge.js +196 -0
  17. package/dist/memory/working/canvas.js +11 -0
  18. package/package.json +2 -2
  19. package/dist/memory/config.d.ts +0 -2
  20. package/dist/memory/config.js +0 -3
  21. package/dist/memory/pipeline/l1-contradiction.d.ts +0 -7
  22. package/dist/memory/pipeline/l1-contradiction.js +0 -66
  23. package/dist/memory/pipeline/l1-dedup.d.ts +0 -23
  24. package/dist/memory/pipeline/l1-dedup.js +0 -39
  25. package/dist/memory/pipeline/l1-extractor.d.ts +0 -21
  26. package/dist/memory/pipeline/l1-extractor.js +0 -180
  27. package/dist/memory/pipeline/l2-direction-shift.d.ts +0 -10
  28. package/dist/memory/pipeline/l2-direction-shift.js +0 -27
  29. package/dist/memory/pipeline/l2-scene.d.ts +0 -15
  30. package/dist/memory/pipeline/l2-scene.js +0 -140
  31. package/dist/memory/pipeline/l3-distiller.d.ts +0 -15
  32. package/dist/memory/pipeline/l3-distiller.js +0 -40
  33. package/dist/memory/pipeline/task-queue.d.ts +0 -54
  34. package/dist/memory/pipeline/task-queue.js +0 -117
  35. package/dist/memory/prompts/graph-extraction-batch.d.ts +0 -14
  36. package/dist/memory/prompts/graph-extraction-batch.js +0 -54
  37. package/dist/memory/prompts/l1-contradiction-batch.d.ts +0 -16
  38. package/dist/memory/prompts/l1-contradiction-batch.js +0 -47
  39. package/dist/memory/prompts/l1-contradiction.d.ts +0 -1
  40. package/dist/memory/prompts/l1-contradiction.js +0 -25
  41. package/dist/memory/prompts/l1-extraction.d.ts +0 -10
  42. package/dist/memory/prompts/l1-extraction.js +0 -114
  43. package/dist/memory/prompts/l2-direction-shift.d.ts +0 -5
  44. package/dist/memory/prompts/l2-direction-shift.js +0 -32
  45. package/dist/memory/prompts/l2-scene-cluster.d.ts +0 -2
  46. package/dist/memory/prompts/l2-scene-cluster.js +0 -33
  47. package/dist/memory/prompts/l2-scene.d.ts +0 -7
  48. package/dist/memory/prompts/l2-scene.js +0 -40
  49. package/dist/memory/prompts/l3-persona.d.ts +0 -6
  50. package/dist/memory/prompts/l3-persona.js +0 -60
  51. package/dist/memory/store/types.d.ts +0 -101
  52. package/dist/memory/types.d.ts +0 -207
  53. package/dist/memory/types.js +0 -7
  54. package/dist/memory/validation.d.ts +0 -441
  55. package/dist/memory/validation.js +0 -129
  56. package/dist/tools/agent_memory_tools.d.ts +0 -485
  57. package/dist/tools/agent_memory_tools.js +0 -793
  58. package/dist/tools/get_doc.d.ts +0 -21
  59. package/dist/tools/get_doc.js +0 -24
  60. package/dist/tools/list_docs.d.ts +0 -15
  61. package/dist/tools/list_docs.js +0 -16
  62. package/dist/tools/update_doc.d.ts +0 -24
  63. package/dist/tools/update_doc.js +0 -35
  64. /package/dist/__tests__/{agent_mode.test.d.ts → cognitive-extractor.test.d.ts} +0 -0
  65. /package/dist/{memory/store/types.js → env-loader.d.ts} +0 -0
@@ -1,23 +0,0 @@
1
- import type { L1Record } from "@brainrouter/types";
2
- import type { IMemoryStore } from "@brainrouter/types";
3
- /**
4
- * Result of the deduplication process
5
- */
6
- export interface DedupResult {
7
- /** Memories that are unique and should be stored */
8
- uniqueRecords: L1Record[];
9
- /** Memories that were identified as exact duplicates and dropped */
10
- droppedCount: number;
11
- }
12
- /**
13
- * Proactively deduplicate extracted memories against the existing memory store
14
- * before they are stored.
15
- *
16
- * Uses exact/near-exact string matching to prevent identical noisy facts
17
- * from accumulating in the L1 store.
18
- */
19
- export declare function deduplicateMemories(params: {
20
- records: L1Record[];
21
- store: IMemoryStore;
22
- userId: string;
23
- }): Promise<DedupResult>;
@@ -1,39 +0,0 @@
1
- /**
2
- * Proactively deduplicate extracted memories against the existing memory store
3
- * before they are stored.
4
- *
5
- * Uses exact/near-exact string matching to prevent identical noisy facts
6
- * from accumulating in the L1 store.
7
- */
8
- export async function deduplicateMemories(params) {
9
- const { records, store, userId } = params;
10
- if (records.length === 0) {
11
- return { uniqueRecords: [], droppedCount: 0 };
12
- }
13
- const uniqueRecords = [];
14
- let droppedCount = 0;
15
- for (const newRecord of records) {
16
- // 1. Keyword search to find potentially identical memories
17
- // We only need top 3 to see if there is an exact match
18
- const candidates = store.searchL1Fts(userId, newRecord.content, 3);
19
- let isDuplicate = false;
20
- for (const candidate of candidates) {
21
- // Direct string comparison (case-insensitive, trimmed)
22
- if (candidate.content.trim().toLowerCase() === newRecord.content.trim().toLowerCase()) {
23
- isDuplicate = true;
24
- break;
25
- }
26
- }
27
- if (isDuplicate) {
28
- console.log(`[BrainRouter] Dropped exact duplicate memory: "${newRecord.content}"`);
29
- droppedCount++;
30
- }
31
- else {
32
- uniqueRecords.push(newRecord);
33
- }
34
- }
35
- return {
36
- uniqueRecords,
37
- droppedCount
38
- };
39
- }
@@ -1,21 +0,0 @@
1
- import type { L0Record, L1Record, LLMRunner } from "@brainrouter/types";
2
- export interface L1ExtractionResult {
3
- success: boolean;
4
- extractedCount: number;
5
- records: L1Record[];
6
- sceneNames: string[];
7
- errorMessage?: string;
8
- }
9
- export declare function extractL1Memories(params: {
10
- messages: L0Record[];
11
- userId: string;
12
- sessionKey: string;
13
- sessionId: string;
14
- llmRunner: LLMRunner;
15
- maxMessagesPerExtraction?: number;
16
- maxBackgroundMessages?: number;
17
- previousSceneName?: string;
18
- existingSceneNames?: string[];
19
- activeSkill?: string;
20
- skillHints?: string;
21
- }): Promise<L1ExtractionResult>;
@@ -1,180 +0,0 @@
1
- import { EXTRACT_MEMORIES_SYSTEM_PROMPT, formatExtractionPrompt } from "../prompts/l1-extraction.js";
2
- import { getMemoryTypeConfig } from "../memory-type-config.js";
3
- import crypto from "node:crypto";
4
- const ALLOWED_MEMORY_TYPES = new Set([
5
- "persona", "episodic", "instruction", "skill_context", "tool_preference",
6
- "codebase_fact", "api_contract", "data_model", "dependency_constraint",
7
- "environment_constraint", "architecture_decision", "implementation_decision",
8
- "design_constraint", "security_policy", "performance_baseline", "bug_finding",
9
- "debug_trace", "fix_summary", "verification_result", "failed_attempt",
10
- "regression_risk", "task_state", "handover_note", "blocked_reason",
11
- "review_comment", "release_note", "source_evidence", "artifact_reference",
12
- "file_history", "command_knowledge",
13
- ]);
14
- const ALLOWED_SOURCE_KINDS = new Set([
15
- "", "user_instruction", "source_file", "command_output", "test_result",
16
- "model_inference", "prior_memory",
17
- ]);
18
- const ALLOWED_VERIFICATION_STATUSES = new Set([
19
- "", "verified", "unverified", "stale",
20
- ]);
21
- // Ensure the message has actual words to extract from, not just symbols or single letters.
22
- function shouldExtractL1(text) {
23
- if (!text)
24
- return false;
25
- const clean = text.trim();
26
- if (clean.length < 3)
27
- return false;
28
- // If it's pure symbols/numbers, ignore
29
- if (/^[^a-zA-Z\u4e00-\u9fa5]+$/.test(clean))
30
- return false;
31
- return true;
32
- }
33
- export async function extractL1Memories(params) {
34
- const { messages, userId, sessionKey, sessionId, llmRunner, maxMessagesPerExtraction = 10, maxBackgroundMessages = 5, previousSceneName, existingSceneNames, activeSkill, skillHints } = params;
35
- if (messages.length === 0) {
36
- return { success: true, extractedCount: 0, records: [], sceneNames: [] };
37
- }
38
- const qualifiedMessages = messages.filter((m) => shouldExtractL1(m.messageText));
39
- if (qualifiedMessages.length === 0) {
40
- return { success: true, extractedCount: 0, records: [], sceneNames: [] };
41
- }
42
- const newMessages = qualifiedMessages.slice(-maxMessagesPerExtraction);
43
- const bgEndIdx = qualifiedMessages.length - newMessages.length;
44
- const backgroundMessages = bgEndIdx > 0
45
- ? qualifiedMessages.slice(Math.max(0, bgEndIdx - maxBackgroundMessages), bgEndIdx)
46
- : [];
47
- const userPrompt = formatExtractionPrompt({
48
- newMessages,
49
- backgroundMessages,
50
- previousSceneName,
51
- existingSceneNames,
52
- activeSkill,
53
- skillHints
54
- });
55
- let rawResult;
56
- try {
57
- rawResult = await llmRunner.run({
58
- prompt: userPrompt,
59
- systemPrompt: EXTRACT_MEMORIES_SYSTEM_PROMPT,
60
- taskId: "l1-extraction",
61
- timeoutMs: 120_000
62
- });
63
- }
64
- catch (err) {
65
- const errorMessage = err instanceof Error ? err.message : String(err);
66
- console.error("[BrainRouter] LLM extraction failed:", err);
67
- return { success: false, extractedCount: 0, records: [], sceneNames: [], errorMessage };
68
- }
69
- const parsedScenes = parseExtractionResult(rawResult);
70
- const records = [];
71
- const sceneNames = [];
72
- const nowStr = new Date().toISOString();
73
- for (const scene of parsedScenes) {
74
- sceneNames.push(scene.scene_name);
75
- for (const mem of scene.memories) {
76
- const config = getMemoryTypeConfig(mem.type);
77
- records.push({
78
- id: `l1_${sessionKey}_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`,
79
- userId,
80
- sessionKey,
81
- sessionId,
82
- content: mem.content,
83
- type: mem.type,
84
- priority: mem.priority,
85
- sceneName: scene.scene_name,
86
- skillTag: mem.skill_tag || activeSkill || "",
87
- halfLifeDays: config.halfLifeDays,
88
- supersededBy: null,
89
- timestampStr: "", // Phase 1: Not strictly tracking time from LLM, just raw extraction
90
- timestampStart: "",
91
- timestampEnd: "",
92
- createdTime: nowStr,
93
- updatedTime: nowStr,
94
- metadata: mem.metadata,
95
- confidence: mem.confidence ?? config.defaultConfidence,
96
- status: "active",
97
- sourceKind: mem.sourceKind,
98
- verificationStatus: mem.verificationStatus,
99
- repoPaths: mem.repoPaths,
100
- filePaths: mem.filePaths,
101
- commands: mem.commands,
102
- // ACE fields — zero on creation, updated by citation tracking
103
- citationCount: 0,
104
- lastCitedAt: null,
105
- neverCitedCount: 0,
106
- archived: false,
107
- });
108
- }
109
- }
110
- return {
111
- success: true,
112
- extractedCount: records.length,
113
- records,
114
- sceneNames
115
- };
116
- }
117
- function parseExtractionResult(raw) {
118
- try {
119
- let cleaned = raw.trim();
120
- if (cleaned.startsWith("\`\`\`")) {
121
- cleaned = cleaned.replace(/^\`\`\`(?:json)?\s*\n?/, "").replace(/\n?\`\`\`\s*$/, "");
122
- }
123
- const match = cleaned.match(/\[[\s\S]*\]/);
124
- if (!match)
125
- return [];
126
- const parsed = JSON.parse(match[0]);
127
- if (!Array.isArray(parsed))
128
- return [];
129
- const scenes = [];
130
- for (const item of parsed) {
131
- if (!item || typeof item !== "object")
132
- continue;
133
- const s = item;
134
- const memories = Array.isArray(s.memories) ? s.memories.map((m) => ({
135
- content: String(m.content || ""),
136
- type: parseMemoryType(m.type),
137
- priority: clampNumber(m.priority, 0, 100, 50),
138
- skill_tag: m.skill_tag ? String(m.skill_tag) : undefined,
139
- confidence: typeof m.confidence === "number" ? clampNumber(m.confidence, 0, 1, 0.65) : undefined,
140
- sourceKind: parseSourceKind(m.sourceKind ?? m.source_kind),
141
- verificationStatus: parseVerificationStatus(m.verificationStatus ?? m.verification_status),
142
- repoPaths: parseStringArray(m.repoPaths ?? m.repo_paths),
143
- filePaths: parseStringArray(m.filePaths ?? m.file_paths),
144
- commands: parseStringArray(m.commands),
145
- metadata: m.metadata && typeof m.metadata === "object" ? m.metadata : {}
146
- })).filter((m) => m.content.length > 0) : [];
147
- scenes.push({
148
- scene_name: String(s.scene_name || "Unknown Scene"),
149
- memories
150
- });
151
- }
152
- return scenes;
153
- }
154
- catch (err) {
155
- console.error("[BrainRouter] Failed to parse extraction result", err);
156
- return [];
157
- }
158
- }
159
- function parseMemoryType(value) {
160
- const candidate = String(value || "");
161
- return ALLOWED_MEMORY_TYPES.has(candidate) ? candidate : "episodic";
162
- }
163
- function parseSourceKind(value) {
164
- const candidate = String(value || "");
165
- return ALLOWED_SOURCE_KINDS.has(candidate) ? candidate : "model_inference";
166
- }
167
- function parseVerificationStatus(value) {
168
- const candidate = String(value || "");
169
- return ALLOWED_VERIFICATION_STATUSES.has(candidate) ? candidate : "unverified";
170
- }
171
- function parseStringArray(value) {
172
- if (!Array.isArray(value))
173
- return [];
174
- return [...new Set(value.map((item) => String(item).trim()).filter(Boolean))];
175
- }
176
- function clampNumber(value, min, max, fallback) {
177
- return typeof value === "number" && Number.isFinite(value)
178
- ? Math.min(max, Math.max(min, value))
179
- : fallback;
180
- }
@@ -1,10 +0,0 @@
1
- import type { L1Record, L2SceneRecord, LLMRunner } from "@brainrouter/types";
2
- export declare function detectDirectionShift(params: {
3
- activeScene: L2SceneRecord;
4
- newL1Records: L1Record[];
5
- llmRunner: LLMRunner;
6
- }): Promise<{
7
- shift: boolean;
8
- confidence: number;
9
- reason: string;
10
- }>;
@@ -1,27 +0,0 @@
1
- import { L2_DIRECTION_SHIFT_SYSTEM_PROMPT, formatL2DirectionShiftPrompt } from "../prompts/l2-direction-shift.js";
2
- export async function detectDirectionShift(params) {
3
- const { activeScene, newL1Records, llmRunner } = params;
4
- try {
5
- const prompt = formatL2DirectionShiftPrompt(activeScene.sceneName, activeScene.summaryMd, newL1Records.map(r => ({ content: r.content, type: r.type })));
6
- const response = await llmRunner.run({
7
- prompt,
8
- systemPrompt: L2_DIRECTION_SHIFT_SYSTEM_PROMPT,
9
- taskId: "l2-direction-shift",
10
- timeoutMs: 30_000,
11
- });
12
- const jsonMatch = response.match(/\{[\s\S]*\}/);
13
- if (!jsonMatch) {
14
- throw new Error("No JSON object found in LLM response");
15
- }
16
- const parsed = JSON.parse(jsonMatch[0]);
17
- return {
18
- shift: Boolean(parsed.shift),
19
- confidence: Number(parsed.confidence) || 0,
20
- reason: String(parsed.reason) || "",
21
- };
22
- }
23
- catch (err) {
24
- console.error(`[BrainRouter] L2 direction shift detection failed:`, err.message);
25
- return { shift: false, confidence: 0, reason: "Error" };
26
- }
27
- }
@@ -1,15 +0,0 @@
1
- import type { IMemoryStore } from "@brainrouter/types";
2
- import type { LLMRunner } from "@brainrouter/types";
3
- /**
4
- * L2 Scene Pipeline
5
- * Groups L1 memories by scene_name and asks the LLM to produce
6
- * a Markdown summary for each scene. Updates heat scores.
7
- */
8
- export declare function distillScenes(params: {
9
- userId: string;
10
- store: IMemoryStore;
11
- llmRunner: LLMRunner;
12
- }): Promise<{
13
- scenesDistilled: number;
14
- sceneNames: string[];
15
- }>;
@@ -1,140 +0,0 @@
1
- import { L2_SCENE_SYSTEM_PROMPT, formatL2ScenePrompt } from "../prompts/l2-scene.js";
2
- import { L2_SCENE_CLUSTER_SYSTEM_PROMPT, formatSceneClusterPrompt } from "../prompts/l2-scene-cluster.js";
3
- import { L2_MAX_SCENES } from "../scheduler.js";
4
- import crypto from "node:crypto";
5
- async function canonicalizeSceneNames(params) {
6
- const { userId, store, llmRunner } = params;
7
- const sceneNames = store.getDistinctSceneNames(userId);
8
- if (sceneNames.length < 2)
9
- return;
10
- try {
11
- const rawCluster = await llmRunner.run({
12
- prompt: formatSceneClusterPrompt(sceneNames),
13
- systemPrompt: L2_SCENE_CLUSTER_SYSTEM_PROMPT,
14
- taskId: "l2-scene-clustering",
15
- timeoutMs: 45_000,
16
- });
17
- const jsonMatch = rawCluster.match(/\[[\s\S]*\]/);
18
- if (!jsonMatch)
19
- return;
20
- const clusters = JSON.parse(jsonMatch[0]);
21
- if (!Array.isArray(clusters))
22
- return;
23
- for (const cluster of clusters) {
24
- const canonical = String(cluster.canonical || "").trim();
25
- const aliases = Array.isArray(cluster.aliases) ? cluster.aliases.map((a) => String(a).trim()) : [];
26
- if (!canonical || aliases.length === 0)
27
- continue;
28
- for (const alias of aliases) {
29
- if (alias === canonical)
30
- continue;
31
- store.renameSceneInL1Records(userId, alias, canonical);
32
- }
33
- }
34
- }
35
- catch (err) {
36
- console.error(`[BrainRouter] Scene canonicalization failed for "${userId}":`, err.message);
37
- }
38
- }
39
- /**
40
- * L2 Scene Pipeline
41
- * Groups L1 memories by scene_name and asks the LLM to produce
42
- * a Markdown summary for each scene. Updates heat scores.
43
- */
44
- export async function distillScenes(params) {
45
- const { userId, store, llmRunner } = params;
46
- // Run scene canonicalization/clustering pass to prevent cold-start fragmentation
47
- await canonicalizeSceneNames({ userId, store, llmRunner });
48
- // Decay all existing heat scores (each distillation cycle = time passing)
49
- store.decayL2HeatScores(userId);
50
- const sceneNames = store.getDistinctSceneNames(userId);
51
- if (sceneNames.length === 0) {
52
- return { scenesDistilled: 0, sceneNames: [] };
53
- }
54
- const now = new Date().toISOString();
55
- const distilled = [];
56
- // Fetch existing L2 scene names up-front so the prompt can avoid near-duplicates
57
- const existingL2SceneNames = store.getTopL2Scenes(userId, 50).map(s => s.sceneName);
58
- for (const sceneName of sceneNames) {
59
- const l1s = store.getL1sByScene(userId, sceneName, 30);
60
- if (l1s.length === 0)
61
- continue;
62
- let summaryMd;
63
- try {
64
- summaryMd = await llmRunner.run({
65
- prompt: formatL2ScenePrompt(sceneName, l1s, existingL2SceneNames.filter(n => n !== sceneName)),
66
- systemPrompt: L2_SCENE_SYSTEM_PROMPT,
67
- taskId: "l2-scene-distillation",
68
- timeoutMs: 60_000,
69
- });
70
- }
71
- catch (err) {
72
- console.error(`[BrainRouter] L2 scene distillation failed for "${sceneName}":`, err.message);
73
- continue;
74
- }
75
- const existing = store.getL2SceneByName(userId, sceneName);
76
- const record = {
77
- id: existing?.id ?? `l2_${crypto.randomBytes(6).toString("hex")}`,
78
- userId,
79
- sceneName,
80
- summaryMd: summaryMd.trim(),
81
- heatScore: existing ? Math.min(100, existing.heatScore + 30) : 100,
82
- lastActiveTime: now,
83
- createdTime: existing?.createdTime ?? now,
84
- updatedTime: now,
85
- };
86
- store.upsertL2Scene(record);
87
- distilled.push(sceneName);
88
- }
89
- // Auto-merge cold scenes if we exceed the max threshold
90
- await mergeScenes({ userId, store, llmRunner });
91
- console.error(`[BrainRouter] L2 distilled ${distilled.length} scene(s) for user "${userId}".`);
92
- return { scenesDistilled: distilled.length, sceneNames: distilled };
93
- }
94
- async function mergeScenes(params) {
95
- const { userId, store, llmRunner } = params;
96
- const sceneCount = store.getL2SceneCount(userId);
97
- if (sceneCount < L2_MAX_SCENES)
98
- return;
99
- const overflow = sceneCount - L2_MAX_SCENES + 1;
100
- const coldScenes = store.getColdL2Scenes(userId, overflow + 3);
101
- // Need at least 2 cold scenes to meaningfully merge
102
- if (coldScenes.length < 2)
103
- return;
104
- // Skip any existing [Archived] scene from the merge input — we'll update it separately
105
- const archiveSceneName = "[Archived]";
106
- const scenesToMerge = coldScenes.filter(s => s.sceneName !== archiveSceneName);
107
- if (scenesToMerge.length < 2)
108
- return;
109
- const sceneSummaries = scenesToMerge.map(s => `## ${s.sceneName}\n${s.summaryMd}`).join("\n\n");
110
- let unifiedSummary;
111
- try {
112
- unifiedSummary = await llmRunner.run({
113
- prompt: `Merge the following old scene summaries into a single concise "Archived Scenes" overview.\n\n${sceneSummaries}\n\nOutput only the Markdown summary. Be concise — no preamble.`,
114
- systemPrompt: L2_SCENE_SYSTEM_PROMPT,
115
- taskId: "l2-scene-merge",
116
- timeoutMs: 60_000,
117
- });
118
- }
119
- catch (err) {
120
- console.error(`[BrainRouter] L2 scene merge failed for "${userId}":`, err.message);
121
- return;
122
- }
123
- const now = new Date().toISOString();
124
- // Look up any pre-existing [Archived] scene
125
- const existingArchive = store.getL2SceneByName(userId, archiveSceneName);
126
- const record = {
127
- id: existingArchive?.id ?? `l2_${crypto.randomBytes(6).toString("hex")}`,
128
- userId,
129
- sceneName: archiveSceneName,
130
- summaryMd: unifiedSummary.trim(),
131
- heatScore: 10,
132
- lastActiveTime: now,
133
- createdTime: existingArchive?.createdTime ?? now,
134
- updatedTime: now,
135
- };
136
- store.upsertL2Scene(record);
137
- const idsToDelete = scenesToMerge.map(s => s.id);
138
- store.deleteL2Scenes(userId, idsToDelete);
139
- console.error(`[BrainRouter] L2 auto-merge: merged ${idsToDelete.length} cold scenes into "${archiveSceneName}".`);
140
- }
@@ -1,15 +0,0 @@
1
- import type { IMemoryStore } from "@brainrouter/types";
2
- import type { LLMRunner } from "@brainrouter/types";
3
- /**
4
- * L3 Persona Distillation Pipeline
5
- * Scans ALL persona + instruction L1 memories across all sessions
6
- * for a user and synthesizes a durable Narrative Profile.
7
- */
8
- export declare function distillPersona(params: {
9
- userId: string;
10
- store: IMemoryStore;
11
- llmRunner: LLMRunner;
12
- }): Promise<{
13
- success: boolean;
14
- personaMd?: string;
15
- }>;
@@ -1,40 +0,0 @@
1
- import { L3_PERSONA_SYSTEM_PROMPT, formatL3PersonaPrompt } from "../prompts/l3-persona.js";
2
- /**
3
- * L3 Persona Distillation Pipeline
4
- * Scans ALL persona + instruction L1 memories across all sessions
5
- * for a user and synthesizes a durable Narrative Profile.
6
- */
7
- export async function distillPersona(params) {
8
- const { userId, store, llmRunner } = params;
9
- // Cross-session: fetch all persona + instruction L1s for this user
10
- const memories = store.getPersonaAndInstructionL1s(userId, 100);
11
- if (memories.length === 0) {
12
- console.error(`[BrainRouter] L3 skipped for "${userId}" — no persona/instruction memories yet.`);
13
- return { success: false };
14
- }
15
- let personaMd;
16
- try {
17
- personaMd = await llmRunner.run({
18
- prompt: formatL3PersonaPrompt(memories),
19
- systemPrompt: L3_PERSONA_SYSTEM_PROMPT,
20
- taskId: "l3-persona-distillation",
21
- timeoutMs: 90_000,
22
- });
23
- }
24
- catch (err) {
25
- console.error(`[BrainRouter] L3 persona distillation failed for "${userId}":`, err.message);
26
- return { success: false };
27
- }
28
- const now = new Date().toISOString();
29
- const existing = store.getL3Persona(userId);
30
- const record = {
31
- userId,
32
- personaMd: personaMd.trim(),
33
- l1CountAtGeneration: memories.length,
34
- createdTime: existing?.createdTime ?? now,
35
- updatedTime: now,
36
- };
37
- store.upsertL3Persona(record);
38
- console.error(`[BrainRouter] L3 persona updated for "${userId}" (${memories.length} L1s).`);
39
- return { success: true, personaMd: personaMd.trim() };
40
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * BrainRouter — Background Task Queue (Optimization C)
3
- *
4
- * Decouples L2 scene distillation and L3 persona distillation from the hot
5
- * path of capture(). These are the most expensive, non-latency-critical steps.
6
- *
7
- * Graph extraction (Opt B) fires immediately as fire-and-forget — it is NOT queued.
8
- * Contradiction detection (Opt A) also fires immediately as fire-and-forget.
9
- *
10
- * Flush triggers:
11
- * 1. Lazy — called by recall() when the user is idle (lastCapture > idleThresholdMs)
12
- * 2. Explicit — engine.flush(userId) called directly
13
- * 3. Disabled — BRAINROUTER_ASYNC_PIPELINE=false keeps old fire-and-forget behaviour
14
- */
15
- import type { SqliteMemoryStore } from "../store/sqlite.js";
16
- import type { LLMRunner } from "../types.js";
17
- export type TaskType = "l2_scene" | "l3_persona";
18
- export interface PendingTask {
19
- id: string;
20
- userId: string;
21
- taskType: TaskType;
22
- /** JSON-encoded payload specific to the task type */
23
- payload: string;
24
- createdAt: string;
25
- }
26
- /** Is the async pipeline enabled? Default: true. Set to "false" to use old fire-and-forget. */
27
- export declare function isAsyncPipelineEnabled(): boolean;
28
- /**
29
- * Enqueue a task in the persistent SQLite queue instead of firing it immediately.
30
- * Safe to call from the capture hot path — synchronous SQLite insert, no I/O wait.
31
- */
32
- export declare function enqueueTask(store: SqliteMemoryStore, task: {
33
- userId: string;
34
- taskType: TaskType;
35
- payload: Record<string, unknown>;
36
- }): void;
37
- export interface FlushResult {
38
- userId: string;
39
- tasksProcessed: number;
40
- tasksFailed: number;
41
- durationMs: number;
42
- }
43
- /**
44
- * Process all pending L2/L3 tasks for a user in FIFO order.
45
- *
46
- * Re-entrant safe: if a flush is already in progress for this userId, the existing
47
- * promise is returned instead of starting a second flush.
48
- */
49
- export declare function flushPendingTasks(userId: string, store: SqliteMemoryStore, synthesisRunner: LLMRunner): Promise<FlushResult>;
50
- /**
51
- * Returns true if the user has been idle long enough that pre-recall flushing
52
- * won't noticeably delay the response.
53
- */
54
- export declare function isUserIdle(store: SqliteMemoryStore, userId: string): boolean;