@kinqs/brainrouter-mcp-server 0.3.5 → 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 (60) hide show
  1. package/.env.example +121 -71
  2. package/dist/__tests__/cognitive-extractor.test.js +112 -0
  3. package/dist/__tests__/crypto.test.js +8 -1
  4. package/dist/__tests__/working-memory.test.js +67 -0
  5. package/dist/index.js +0 -0
  6. package/dist/memory/engine.js +21 -1
  7. package/dist/memory/pipeline/cognitive-extractor.js +19 -1
  8. package/dist/memory/recall.d.ts +3 -1
  9. package/dist/memory/recall.js +48 -3
  10. package/dist/memory/store/relevance-judge.d.ts +51 -0
  11. package/dist/memory/store/relevance-judge.js +196 -0
  12. package/dist/memory/working/canvas.js +11 -0
  13. package/package.json +2 -2
  14. package/dist/memory/config.d.ts +0 -2
  15. package/dist/memory/config.js +0 -3
  16. package/dist/memory/pipeline/l1-contradiction.d.ts +0 -7
  17. package/dist/memory/pipeline/l1-contradiction.js +0 -66
  18. package/dist/memory/pipeline/l1-dedup.d.ts +0 -23
  19. package/dist/memory/pipeline/l1-dedup.js +0 -39
  20. package/dist/memory/pipeline/l1-extractor.d.ts +0 -21
  21. package/dist/memory/pipeline/l1-extractor.js +0 -180
  22. package/dist/memory/pipeline/l2-direction-shift.d.ts +0 -10
  23. package/dist/memory/pipeline/l2-direction-shift.js +0 -27
  24. package/dist/memory/pipeline/l2-scene.d.ts +0 -15
  25. package/dist/memory/pipeline/l2-scene.js +0 -140
  26. package/dist/memory/pipeline/l3-distiller.d.ts +0 -15
  27. package/dist/memory/pipeline/l3-distiller.js +0 -40
  28. package/dist/memory/pipeline/task-queue.d.ts +0 -54
  29. package/dist/memory/pipeline/task-queue.js +0 -117
  30. package/dist/memory/prompts/graph-extraction-batch.d.ts +0 -14
  31. package/dist/memory/prompts/graph-extraction-batch.js +0 -54
  32. package/dist/memory/prompts/l1-contradiction-batch.d.ts +0 -16
  33. package/dist/memory/prompts/l1-contradiction-batch.js +0 -47
  34. package/dist/memory/prompts/l1-contradiction.d.ts +0 -1
  35. package/dist/memory/prompts/l1-contradiction.js +0 -25
  36. package/dist/memory/prompts/l1-extraction.d.ts +0 -10
  37. package/dist/memory/prompts/l1-extraction.js +0 -114
  38. package/dist/memory/prompts/l2-direction-shift.d.ts +0 -5
  39. package/dist/memory/prompts/l2-direction-shift.js +0 -32
  40. package/dist/memory/prompts/l2-scene-cluster.d.ts +0 -2
  41. package/dist/memory/prompts/l2-scene-cluster.js +0 -33
  42. package/dist/memory/prompts/l2-scene.d.ts +0 -7
  43. package/dist/memory/prompts/l2-scene.js +0 -40
  44. package/dist/memory/prompts/l3-persona.d.ts +0 -6
  45. package/dist/memory/prompts/l3-persona.js +0 -60
  46. package/dist/memory/store/types.d.ts +0 -101
  47. package/dist/memory/store/types.js +0 -1
  48. package/dist/memory/types.d.ts +0 -207
  49. package/dist/memory/types.js +0 -7
  50. package/dist/memory/validation.d.ts +0 -441
  51. package/dist/memory/validation.js +0 -129
  52. package/dist/tools/agent_memory_tools.d.ts +0 -485
  53. package/dist/tools/agent_memory_tools.js +0 -793
  54. package/dist/tools/get_doc.d.ts +0 -21
  55. package/dist/tools/get_doc.js +0 -24
  56. package/dist/tools/list_docs.d.ts +0 -15
  57. package/dist/tools/list_docs.js +0 -16
  58. package/dist/tools/update_doc.d.ts +0 -24
  59. package/dist/tools/update_doc.js +0 -35
  60. /package/dist/__tests__/{agent_mode.test.d.ts → cognitive-extractor.test.d.ts} +0 -0
@@ -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;
@@ -1,117 +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 { distillScenes } from "./l2-scene.js";
16
- import { distillPersona } from "./l3-distiller.js";
17
- // ─── Config ───────────────────────────────────────────────────────────────────
18
- /** Flush pending tasks before recall if last capture was more than this many ms ago. */
19
- const IDLE_THRESHOLD_MS = parseInt(process.env.BRAINROUTER_FLUSH_IDLE_MS ?? String(30_000), // default: 30s idle
20
- 10);
21
- /** Is the async pipeline enabled? Default: true. Set to "false" to use old fire-and-forget. */
22
- export function isAsyncPipelineEnabled() {
23
- return process.env.BRAINROUTER_ASYNC_PIPELINE !== "false";
24
- }
25
- // ─── Enqueue ──────────────────────────────────────────────────────────────────
26
- /**
27
- * Enqueue a task in the persistent SQLite queue instead of firing it immediately.
28
- * Safe to call from the capture hot path — synchronous SQLite insert, no I/O wait.
29
- */
30
- export function enqueueTask(store, task) {
31
- const id = `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
32
- store.upsertPendingTask({
33
- id,
34
- userId: task.userId,
35
- taskType: task.taskType,
36
- payload: JSON.stringify(task.payload),
37
- createdAt: new Date().toISOString(),
38
- });
39
- }
40
- // ─── Flush ────────────────────────────────────────────────────────────────────
41
- /** Active flush promises per userId to prevent concurrent flushes */
42
- const flushInProgress = new Map();
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 async function flushPendingTasks(userId, store, synthesisRunner) {
50
- const existing = flushInProgress.get(userId);
51
- if (existing)
52
- return existing;
53
- const flushPromise = _doFlush(userId, store, synthesisRunner);
54
- flushInProgress.set(userId, flushPromise);
55
- try {
56
- return await flushPromise;
57
- }
58
- finally {
59
- flushInProgress.delete(userId);
60
- }
61
- }
62
- async function _doFlush(userId, store, synthesisRunner) {
63
- const start = Date.now();
64
- const tasks = store.getPendingTasks(userId);
65
- if (tasks.length === 0) {
66
- return { userId, tasksProcessed: 0, tasksFailed: 0, durationMs: 0 };
67
- }
68
- let processed = 0;
69
- let failed = 0;
70
- // Process l2_scene and l3_persona in FIFO order.
71
- // Deduplicate consecutive same-type tasks (e.g., 10 queued l2_scene → run once).
72
- let lastTaskType = null;
73
- for (const task of tasks) {
74
- try {
75
- if (task.taskType === lastTaskType) {
76
- // Skip duplicate consecutive task of same type — one distillation run is sufficient
77
- store.deletePendingTask(task.id);
78
- continue;
79
- }
80
- switch (task.taskType) {
81
- case "l2_scene":
82
- await distillScenes({ userId, store, llmRunner: synthesisRunner });
83
- break;
84
- case "l3_persona":
85
- await distillPersona({ userId, store, llmRunner: synthesisRunner });
86
- break;
87
- }
88
- lastTaskType = task.taskType;
89
- processed++;
90
- }
91
- catch (err) {
92
- console.error(`[BrainRouter] Flush: task ${task.taskType} failed:`, err.message);
93
- failed++;
94
- }
95
- finally {
96
- store.deletePendingTask(task.id);
97
- }
98
- }
99
- return {
100
- userId,
101
- tasksProcessed: processed,
102
- tasksFailed: failed,
103
- durationMs: Date.now() - start,
104
- };
105
- }
106
- // ─── Idle Check ───────────────────────────────────────────────────────────────
107
- /**
108
- * Returns true if the user has been idle long enough that pre-recall flushing
109
- * won't noticeably delay the response.
110
- */
111
- export function isUserIdle(store, userId) {
112
- const state = store.getSchedulerState(userId);
113
- if (!state.lastCaptureAt)
114
- return true; // never captured = trivially idle
115
- const idleMs = Date.now() - new Date(state.lastCaptureAt).getTime();
116
- return idleMs >= IDLE_THRESHOLD_MS;
117
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Batch Graph Extraction Prompt
3
- *
4
- * Replaces N individual per-memory graph extraction calls with a single call
5
- * covering all new L1 memories from a capture cycle simultaneously.
6
- *
7
- * Token reduction: N → 1 call per capture cycle.
8
- */
9
- export declare const BATCH_GRAPH_EXTRACTION_SYSTEM_PROMPT = "You are an expert knowledge graph extraction engine.\n\nYou will receive a numbered list of memory statements. Extract ALL key entities (nodes)\nand their relationships (edges) across the entire list.\n\nLIMITS:\n- Extract at most 15 key entities total across all memories.\n- Extract at most 20 key relations total.\n- Focus on the most important technical choices, mandates, roles, and decisions.\n\nEntity Types:\n- \"tool\" / \"technology\" (e.g. pnpm, React, Postgres, Zod)\n- \"project\" (e.g. BrainRouter, Kinqs Radio)\n- \"person\" (e.g. lead engineer, user, junior developers)\n- \"concept\" / \"decision\" (e.g. monorepo migration, dark mode, camelCase policy)\n\nRelation Types (use ONLY these):\n- \"uses\" / \"adopts\"\n- \"owns\" / \"manages\"\n- \"decided\" / \"revised\"\n- \"conflicts_with\" / \"replaces\"\n- \"applies_to\"\n- \"requires\"\n- \"is_a\" / \"is_role\"\n\nFor each entity, also include \"sourceIndex\" matching the memory's index number.\n\nOutput ONLY a valid JSON object (no markdown fences):\n{\n \"entities\": [\n { \"entity\": \"Canonical Name\", \"type\": \"tool|technology|project|person|concept|decision\", \"confidence\": 1.0, \"sourceIndex\": 0 }\n ],\n \"relations\": [\n { \"from\": \"Entity A\", \"to\": \"Entity B\", \"relation\": \"uses\", \"confidence\": 1.0 }\n ]\n}";
10
- export declare function formatBatchGraphExtractionPrompt(memories: Array<{
11
- index: number;
12
- content: string;
13
- recordId: string;
14
- }>): string;
@@ -1,54 +0,0 @@
1
- /**
2
- * Batch Graph Extraction Prompt
3
- *
4
- * Replaces N individual per-memory graph extraction calls with a single call
5
- * covering all new L1 memories from a capture cycle simultaneously.
6
- *
7
- * Token reduction: N → 1 call per capture cycle.
8
- */
9
- export const BATCH_GRAPH_EXTRACTION_SYSTEM_PROMPT = `You are an expert knowledge graph extraction engine.
10
-
11
- You will receive a numbered list of memory statements. Extract ALL key entities (nodes)
12
- and their relationships (edges) across the entire list.
13
-
14
- LIMITS:
15
- - Extract at most 15 key entities total across all memories.
16
- - Extract at most 20 key relations total.
17
- - Focus on the most important technical choices, mandates, roles, and decisions.
18
-
19
- Entity Types:
20
- - "tool" / "technology" (e.g. pnpm, React, Postgres, Zod)
21
- - "project" (e.g. BrainRouter, Kinqs Radio)
22
- - "person" (e.g. lead engineer, user, junior developers)
23
- - "concept" / "decision" (e.g. monorepo migration, dark mode, camelCase policy)
24
-
25
- Relation Types (use ONLY these):
26
- - "uses" / "adopts"
27
- - "owns" / "manages"
28
- - "decided" / "revised"
29
- - "conflicts_with" / "replaces"
30
- - "applies_to"
31
- - "requires"
32
- - "is_a" / "is_role"
33
-
34
- For each entity, also include "sourceIndex" matching the memory's index number.
35
-
36
- Output ONLY a valid JSON object (no markdown fences):
37
- {
38
- "entities": [
39
- { "entity": "Canonical Name", "type": "tool|technology|project|person|concept|decision", "confidence": 1.0, "sourceIndex": 0 }
40
- ],
41
- "relations": [
42
- { "from": "Entity A", "to": "Entity B", "relation": "uses", "confidence": 1.0 }
43
- ]
44
- }`;
45
- export function formatBatchGraphExtractionPrompt(memories) {
46
- const memoryList = memories
47
- .map(m => ` [${m.index}] "${m.content}"`)
48
- .join("\n");
49
- return `Extract entities and relations from ALL of the following memories:
50
-
51
- ${memoryList}
52
-
53
- JSON Output:`;
54
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * Batch Contradiction Detection Prompt
3
- *
4
- * Replaces N×K individual pairwise LLM calls with a single call covering
5
- * all new memories and their top FTS candidates simultaneously.
6
- *
7
- * Token reduction: 25 → 1 call per capture cycle (for 5 new memories × 5 candidates).
8
- */
9
- export declare const BATCH_CONTRADICTION_SYSTEM_PROMPT = "You are the Contradiction & Temporal Update Detector for a hierarchical memory engine.\n\nYou will receive a list of NEWLY EXTRACTED memories and a list of EXISTING memories.\nYour task is to identify ALL pairs that contradict, supersede, or update each other.\n\nRULES:\n1. Only flag pairs with genuine overlap or conflict. Ignore unrelated pairs.\n2. Classify each contradiction into one of two kinds:\n - \"temporal_update\": The new memory deliberately supersedes the old one (e.g. user changed their mind, moved cities, switched tools). The NEW memory wins.\n - \"genuine_conflict\": Logically incompatible absolute claims where neither obviously supersedes the other (e.g. conflicting birth years).\n3. Provide a confidence score (0.0\u20131.0). Only include pairs with confidence > 0.7.\n4. If no contradictions exist, return an empty array.\n\nRespond ONLY with a JSON array (no markdown fences):\n[\n {\n \"newIndex\": number,\n \"existingIndex\": number,\n \"kind\": \"temporal_update\" | \"genuine_conflict\",\n \"reason\": \"Brief explanation\",\n \"confidence\": number\n }\n]";
10
- export declare function formatBatchContradictionPrompt(newMemories: Array<{
11
- index: number;
12
- content: string;
13
- }>, existingMemories: Array<{
14
- index: number;
15
- content: string;
16
- }>): string;
@@ -1,47 +0,0 @@
1
- /**
2
- * Batch Contradiction Detection Prompt
3
- *
4
- * Replaces N×K individual pairwise LLM calls with a single call covering
5
- * all new memories and their top FTS candidates simultaneously.
6
- *
7
- * Token reduction: 25 → 1 call per capture cycle (for 5 new memories × 5 candidates).
8
- */
9
- export const BATCH_CONTRADICTION_SYSTEM_PROMPT = `You are the Contradiction & Temporal Update Detector for a hierarchical memory engine.
10
-
11
- You will receive a list of NEWLY EXTRACTED memories and a list of EXISTING memories.
12
- Your task is to identify ALL pairs that contradict, supersede, or update each other.
13
-
14
- RULES:
15
- 1. Only flag pairs with genuine overlap or conflict. Ignore unrelated pairs.
16
- 2. Classify each contradiction into one of two kinds:
17
- - "temporal_update": The new memory deliberately supersedes the old one (e.g. user changed their mind, moved cities, switched tools). The NEW memory wins.
18
- - "genuine_conflict": Logically incompatible absolute claims where neither obviously supersedes the other (e.g. conflicting birth years).
19
- 3. Provide a confidence score (0.0–1.0). Only include pairs with confidence > 0.7.
20
- 4. If no contradictions exist, return an empty array.
21
-
22
- Respond ONLY with a JSON array (no markdown fences):
23
- [
24
- {
25
- "newIndex": number,
26
- "existingIndex": number,
27
- "kind": "temporal_update" | "genuine_conflict",
28
- "reason": "Brief explanation",
29
- "confidence": number
30
- }
31
- ]`;
32
- export function formatBatchContradictionPrompt(newMemories, existingMemories) {
33
- const newList = newMemories
34
- .map(m => ` [N${m.index}] "${m.content}"`)
35
- .join("\n");
36
- const existingList = existingMemories
37
- .map(m => ` [E${m.index}] "${m.content}"`)
38
- .join("\n");
39
- return `NEWLY EXTRACTED MEMORIES:
40
- ${newList}
41
-
42
- EXISTING MEMORIES (candidates for contradiction):
43
- ${existingList}
44
-
45
- Identify all contradicting pairs between the N (new) and E (existing) sets.
46
- JSON Array Output:`;
47
- }
@@ -1 +0,0 @@
1
- export declare const L1_CONTRADICTION_PROMPT = "\nYou are the Contradiction & Temporal Update Detector for a hierarchical memory engine.\nYour task is to compare a NEWLY EXTRACTED memory with an EXISTING RELEVANT memory and determine if they contradict, supersede, or update each other.\n\nNEW MEMORY:\n\"{{newContent}}\"\n\nEXISTING MEMORY:\n\"{{existingContent}}\"\n\nRULES:\n1. Determine if they overlap or conflict. If there is no overlap/conflict, set \"isContradiction\" to false.\n2. If they conflict or override, classify the relationship into one of two kinds:\n - \"temporal_update\": The new memory is a deliberate update, change of mind, transition, or correction to the old memory (e.g. \"User lives in SF\" vs \"User moved to NYC\", or \"User wants pnpm\" vs \"User changed package manager to regular npm\"). This means the new memory actively supersedes the old one.\n - \"genuine_conflict\": The statements are logically incompatible, absolute claims that cannot both be true, and it is NOT a simple update or transition (e.g. \"User was born in 1990\" vs \"User was born in 1995\", or conflicting rules without a clear indication that one was meant to update the other).\n3. Provide a clear reason and a confidence score.\n\nRespond ONLY with a JSON object:\n{\n \"isContradiction\": boolean,\n \"kind\": \"temporal_update\" | \"genuine_conflict\" | null,\n \"reason\": \"Brief explanation if isContradiction is true, else null\",\n \"confidence\": number (0.0 to 1.0)\n}\n";
@@ -1,25 +0,0 @@
1
- export const L1_CONTRADICTION_PROMPT = `
2
- You are the Contradiction & Temporal Update Detector for a hierarchical memory engine.
3
- Your task is to compare a NEWLY EXTRACTED memory with an EXISTING RELEVANT memory and determine if they contradict, supersede, or update each other.
4
-
5
- NEW MEMORY:
6
- "{{newContent}}"
7
-
8
- EXISTING MEMORY:
9
- "{{existingContent}}"
10
-
11
- RULES:
12
- 1. Determine if they overlap or conflict. If there is no overlap/conflict, set "isContradiction" to false.
13
- 2. If they conflict or override, classify the relationship into one of two kinds:
14
- - "temporal_update": The new memory is a deliberate update, change of mind, transition, or correction to the old memory (e.g. "User lives in SF" vs "User moved to NYC", or "User wants pnpm" vs "User changed package manager to regular npm"). This means the new memory actively supersedes the old one.
15
- - "genuine_conflict": The statements are logically incompatible, absolute claims that cannot both be true, and it is NOT a simple update or transition (e.g. "User was born in 1990" vs "User was born in 1995", or conflicting rules without a clear indication that one was meant to update the other).
16
- 3. Provide a clear reason and a confidence score.
17
-
18
- Respond ONLY with a JSON object:
19
- {
20
- "isContradiction": boolean,
21
- "kind": "temporal_update" | "genuine_conflict" | null,
22
- "reason": "Brief explanation if isContradiction is true, else null",
23
- "confidence": number (0.0 to 1.0)
24
- }
25
- `;
@@ -1,10 +0,0 @@
1
- import type { L0Record } from "@brainrouter/types";
2
- export declare const EXTRACT_MEMORIES_SYSTEM_PROMPT = "You are a Skill-Aware Memory Extraction Expert for a software engineering AI assistant.\n\nYour task is to analyze a conversation and extract durable, self-contained memories.\n\n### Scene Segmentation\nDetermine if the topic has changed since the previous scene.\nIf there are existing scenes, PREFER to reuse the most relevant existing scene name \u2014 only create a new name if the topic is genuinely different from ALL existing scenes.\nIf reusing, use the EXACT existing name (no paraphrasing).\nIf creating new, format: \"AI helping [user role] with [goal activity]\" (unique, max 50 chars).\n\n### Memory Types\n\n1. **persona** \u2014 Stable user traits, preferences, identity\n - Format: \"User [prefers/is/always/never] ...\"\n - Priority: 80-100 (core identity) / 50-70 (preferences) / skip if <50\n\n2. **episodic** \u2014 Objective events, decisions, results with timestamps\n - Format: \"User [did X] at [time] resulting in [Y]\"\n - Priority: 80-100 (major decisions) / 60-79 (significant events) / skip if <60\n\n3. **instruction** \u2014 Long-term rules the user gave the AI\n - Format: \"User requires AI to always/never ...\"\n - Priority: 90-100 (hard rules) / 70-89 (preferences) / skip if <70\n - Instructions NEVER decay.\n\n4. **skill_context** \u2014 Observations about how the user runs THIS SKILL specifically\n - Format: \"When running [skill], user tends to ...\"\n - Only extract if a genuine behavioral pattern is visible, not a one-off.\n\n5. **tool_preference** \u2014 Stable preferences about tools, workflows, or command usage.\n6. **codebase_fact / api_contract / data_model** \u2014 Verified facts about code, public APIs, schemas, or storage models.\n7. **dependency_constraint / environment_constraint** \u2014 Version, runtime, platform, sandbox, or deployment constraints.\n8. **architecture_decision / implementation_decision / design_constraint** \u2014 Durable decisions and constraints that future agents must preserve.\n9. **security_policy / performance_baseline** \u2014 Security rules or measured performance facts. Extract only with direct evidence.\n10. **bug_finding / debug_trace / fix_summary / verification_result / failed_attempt / regression_risk** \u2014 Debugging history, fixes, checks, and risk notes.\n11. **task_state / handover_note / blocked_reason / review_comment / release_note** \u2014 Planning, review, release, and continuation state.\n12. **source_evidence / artifact_reference / file_history / command_knowledge** \u2014 Evidence-backed references to files, artifacts, file evolution, or command behavior.\n\nAllowed type values:\npersona, episodic, instruction, skill_context, tool_preference, codebase_fact, api_contract,\ndata_model, dependency_constraint, environment_constraint, architecture_decision,\nimplementation_decision, design_constraint, security_policy, performance_baseline,\nbug_finding, debug_trace, fix_summary, verification_result, failed_attempt, regression_risk,\ntask_state, handover_note, blocked_reason, review_comment, release_note, source_evidence,\nartifact_reference, file_history, command_knowledge.\n\n### Quality Rules\n- Nothingness > Bad memory. Prefer empty over wrong.\n- Memory must stand alone without the conversation.\n- Merge causally-linked facts into one memory.\n- Do not extract AI outputs \u2014 only user behavior and statements.\n- Filter out: tool calls, one-time requests, casual greetings.\n- Evidence-gated types (api_contract, data_model, security_policy, performance_baseline) require a cited file path, command, test result, or explicit user statement.\n- Use confidence from 0.0 to 1.0. Use lower confidence for model inference, higher confidence for direct source or command output.\n- Classify sourceKind as user_instruction, source_file, command_output, test_result, model_inference, or prior_memory.\n- Extract filePaths, repoPaths, and commands when explicitly mentioned; otherwise use empty arrays.\n\n### Output\nReturn ONLY a valid JSON array matching this format exactly:\n[\n {\n \"scene_name\": \"current or inherited scene name\",\n \"message_ids\": [\"id1\"],\n \"memories\": [\n {\n \"type\": \"one allowed type value\",\n \"content\": \"self-contained memory statement\",\n \"priority\": 85,\n \"skill_tag\": \"the active skill\",\n \"source_message_ids\": [\"id1\"],\n \"confidence\": 0.75,\n \"sourceKind\": \"user_instruction|source_file|command_output|test_result|model_inference|prior_memory\",\n \"verificationStatus\": \"verified|unverified|stale\",\n \"repoPaths\": [],\n \"filePaths\": [],\n \"commands\": [],\n \"metadata\": {}\n }\n ]\n }\n]\n";
3
- export declare function formatExtractionPrompt(params: {
4
- newMessages: L0Record[];
5
- backgroundMessages?: L0Record[];
6
- previousSceneName?: string;
7
- existingSceneNames?: string[];
8
- activeSkill?: string;
9
- skillHints?: string;
10
- }): string;