@alejandroroman/agent-kit 0.1.3 → 0.2.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.
Files changed (102) hide show
  1. package/dist/_memory/dist/config.d.ts +14 -0
  2. package/dist/_memory/dist/config.js +16 -0
  3. package/dist/_memory/dist/db/client.d.ts +2 -0
  4. package/dist/_memory/dist/db/client.js +15 -0
  5. package/dist/_memory/dist/db/schema.d.ts +14 -0
  6. package/dist/_memory/dist/db/schema.js +51 -0
  7. package/dist/_memory/dist/embeddings/ollama.d.ts +12 -0
  8. package/dist/_memory/dist/embeddings/ollama.js +22 -0
  9. package/dist/_memory/dist/embeddings/provider.d.ts +4 -0
  10. package/dist/_memory/dist/embeddings/provider.js +1 -0
  11. package/dist/_memory/dist/index.d.ts +10 -0
  12. package/dist/_memory/dist/index.js +6 -0
  13. package/dist/_memory/dist/search.d.ts +30 -0
  14. package/dist/_memory/dist/search.js +121 -0
  15. package/dist/_memory/dist/server.d.ts +8 -0
  16. package/dist/_memory/dist/server.js +126 -0
  17. package/dist/_memory/dist/store.d.ts +51 -0
  18. package/dist/_memory/dist/store.js +115 -0
  19. package/dist/_memory/server.js +0 -0
  20. package/dist/agent/loop.js +210 -111
  21. package/dist/api/errors.d.ts +3 -0
  22. package/dist/api/errors.js +37 -0
  23. package/dist/api/events.d.ts +5 -0
  24. package/dist/api/events.js +28 -0
  25. package/dist/api/router.js +10 -0
  26. package/dist/api/traces.d.ts +3 -0
  27. package/dist/api/traces.js +35 -0
  28. package/dist/api/types.d.ts +2 -0
  29. package/dist/bootstrap.d.ts +6 -5
  30. package/dist/bootstrap.js +26 -7
  31. package/dist/cli/chat.js +18 -63
  32. package/dist/cli/claude-md-template.d.ts +5 -0
  33. package/dist/cli/claude-md-template.js +220 -0
  34. package/dist/cli/config-writer.js +3 -0
  35. package/dist/cli/create.js +1 -4
  36. package/dist/cli/env.d.ts +14 -0
  37. package/dist/cli/env.js +68 -0
  38. package/dist/cli/init.js +14 -7
  39. package/dist/cli/list.js +1 -2
  40. package/dist/cli/paths.d.ts +3 -0
  41. package/dist/cli/paths.js +4 -0
  42. package/dist/cli/repl.d.ts +23 -0
  43. package/dist/cli/repl.js +73 -0
  44. package/dist/cli/slack-setup.d.ts +6 -0
  45. package/dist/cli/slack-setup.js +234 -0
  46. package/dist/cli/start.js +96 -96
  47. package/dist/cli/ui.d.ts +2 -2
  48. package/dist/cli/ui.js +5 -5
  49. package/dist/cli/validate.js +1 -4
  50. package/dist/cli/whats-new.d.ts +1 -0
  51. package/dist/cli/whats-new.js +69 -0
  52. package/dist/cli.js +14 -0
  53. package/dist/config/resolve.d.ts +1 -0
  54. package/dist/config/resolve.js +1 -0
  55. package/dist/config/schema.d.ts +2 -0
  56. package/dist/config/schema.js +1 -0
  57. package/dist/config/writer.d.ts +18 -0
  58. package/dist/config/writer.js +85 -0
  59. package/dist/cron/scheduler.d.ts +4 -1
  60. package/dist/cron/scheduler.js +99 -52
  61. package/dist/gateways/slack/client.d.ts +1 -0
  62. package/dist/gateways/slack/client.js +9 -0
  63. package/dist/gateways/slack/handler.js +2 -1
  64. package/dist/gateways/slack/index.js +75 -29
  65. package/dist/gateways/slack/listener.d.ts +8 -1
  66. package/dist/gateways/slack/listener.js +36 -10
  67. package/dist/heartbeat/runner.js +99 -82
  68. package/dist/index.js +4 -209
  69. package/dist/llm/anthropic.d.ts +1 -0
  70. package/dist/llm/anthropic.js +11 -2
  71. package/dist/llm/fallback.js +34 -2
  72. package/dist/llm/openai.d.ts +2 -0
  73. package/dist/llm/openai.js +33 -2
  74. package/dist/llm/types.d.ts +16 -2
  75. package/dist/llm/types.js +9 -0
  76. package/dist/logger.js +8 -0
  77. package/dist/media/sanitize.d.ts +5 -0
  78. package/dist/media/sanitize.js +53 -0
  79. package/dist/multi/spawn.js +29 -10
  80. package/dist/session/compaction.js +3 -1
  81. package/dist/session/prune-images.d.ts +9 -0
  82. package/dist/session/prune-images.js +42 -0
  83. package/dist/skills/activate.d.ts +6 -0
  84. package/dist/skills/activate.js +72 -27
  85. package/dist/skills/index.d.ts +1 -1
  86. package/dist/skills/index.js +1 -1
  87. package/dist/telemetry/db.d.ts +63 -0
  88. package/dist/telemetry/db.js +193 -0
  89. package/dist/telemetry/index.d.ts +17 -0
  90. package/dist/telemetry/index.js +82 -0
  91. package/dist/telemetry/sanitize.d.ts +6 -0
  92. package/dist/telemetry/sanitize.js +48 -0
  93. package/dist/telemetry/sqlite-processor.d.ts +11 -0
  94. package/dist/telemetry/sqlite-processor.js +108 -0
  95. package/dist/telemetry/types.d.ts +30 -0
  96. package/dist/telemetry/types.js +31 -0
  97. package/dist/tools/builtin/index.d.ts +2 -0
  98. package/dist/tools/builtin/index.js +2 -0
  99. package/dist/tools/builtin/self-config.d.ts +4 -0
  100. package/dist/tools/builtin/self-config.js +182 -0
  101. package/dist/tools/registry.js +8 -1
  102. package/package.json +26 -20
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ export declare const MemoryConfigSchema: z.ZodObject<{
3
+ dbPath: z.ZodDefault<z.ZodString>;
4
+ embedding: z.ZodDefault<z.ZodObject<{
5
+ provider: z.ZodDefault<z.ZodEnum<{
6
+ ollama: "ollama";
7
+ voyage: "voyage";
8
+ }>>;
9
+ model: z.ZodDefault<z.ZodString>;
10
+ endpoint: z.ZodDefault<z.ZodString>;
11
+ apiKey: z.ZodOptional<z.ZodString>;
12
+ }, z.core.$strip>>;
13
+ }, z.core.$strip>;
14
+ export type MemoryConfig = z.infer<typeof MemoryConfigSchema>;
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ export const MemoryConfigSchema = z.object({
3
+ dbPath: z.string().default("~/.agent-kit/memory.db"),
4
+ embedding: z
5
+ .object({
6
+ provider: z.enum(["ollama", "voyage"]).default("ollama"),
7
+ model: z.string().default("all-minilm:l6-v2"),
8
+ endpoint: z.string().default("http://localhost:11434"),
9
+ apiKey: z.string().optional(),
10
+ })
11
+ .default({
12
+ provider: "ollama",
13
+ model: "all-minilm:l6-v2",
14
+ endpoint: "http://localhost:11434",
15
+ }),
16
+ });
@@ -0,0 +1,2 @@
1
+ import Database from "better-sqlite3";
2
+ export declare function createDatabase(dbPath: string, dimensions?: number): Database.Database;
@@ -0,0 +1,15 @@
1
+ import Database from "better-sqlite3";
2
+ import * as sqliteVec from "sqlite-vec";
3
+ import { SCHEMA_SQL, FTS_SQL, createVecSQL } from "./schema.js";
4
+ export function createDatabase(dbPath, dimensions = 384) {
5
+ const db = new Database(dbPath);
6
+ db.pragma("journal_mode = WAL");
7
+ db.pragma("foreign_keys = ON");
8
+ // Load sqlite-vec extension
9
+ sqliteVec.load(db);
10
+ // Create tables
11
+ db.exec(SCHEMA_SQL);
12
+ db.exec(FTS_SQL);
13
+ db.exec(createVecSQL(dimensions));
14
+ return db;
15
+ }
@@ -0,0 +1,14 @@
1
+ export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS memories (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL,\n namespace TEXT NOT NULL,\n content TEXT NOT NULL,\n type TEXT NOT NULL CHECK(type IN ('fact', 'preference', 'decision', 'learning', 'observation')),\n metadata JSON,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n deleted_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);\nCREATE INDEX IF NOT EXISTS idx_memories_namespace ON memories(namespace);\nCREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);\nCREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);\nCREATE INDEX IF NOT EXISTS idx_memories_active ON memories(deleted_at) WHERE deleted_at IS NULL;\n";
2
+ export declare const FTS_SQL = "\nCREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(\n content, type, namespace, agent_id,\n content='memories', content_rowid='rowid'\n);\n\nCREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN\n INSERT INTO memories_fts(rowid, content, type, namespace, agent_id)\n VALUES (new.rowid, new.content, new.type, new.namespace, new.agent_id);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN\n INSERT INTO memories_fts(memories_fts, rowid, content, type, namespace, agent_id)\n VALUES ('delete', old.rowid, old.content, old.type, old.namespace, old.agent_id);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN\n INSERT INTO memories_fts(memories_fts, rowid, content, type, namespace, agent_id)\n VALUES ('delete', old.rowid, old.content, old.type, old.namespace, old.agent_id);\n INSERT INTO memories_fts(rowid, content, type, namespace, agent_id)\n VALUES (new.rowid, new.content, new.type, new.namespace, new.agent_id);\nEND;\n";
3
+ export declare function createVecSQL(dimensions: number): string;
4
+ export interface MemoryRow {
5
+ id: string;
6
+ agent_id: string;
7
+ namespace: string;
8
+ content: string;
9
+ type: "fact" | "preference" | "decision" | "learning" | "observation";
10
+ metadata: string | null;
11
+ created_at: string;
12
+ updated_at: string;
13
+ deleted_at: string | null;
14
+ }
@@ -0,0 +1,51 @@
1
+ export const SCHEMA_SQL = `
2
+ CREATE TABLE IF NOT EXISTS memories (
3
+ id TEXT PRIMARY KEY,
4
+ agent_id TEXT NOT NULL,
5
+ namespace TEXT NOT NULL,
6
+ content TEXT NOT NULL,
7
+ type TEXT NOT NULL CHECK(type IN ('fact', 'preference', 'decision', 'learning', 'observation')),
8
+ metadata JSON,
9
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
10
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
11
+ deleted_at TEXT
12
+ );
13
+
14
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
15
+ CREATE INDEX IF NOT EXISTS idx_memories_namespace ON memories(namespace);
16
+ CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
17
+ CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
18
+ CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(deleted_at) WHERE deleted_at IS NULL;
19
+ `;
20
+ // FTS5 and vec0 tables are created separately because they need extensions loaded first
21
+ export const FTS_SQL = `
22
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
23
+ content, type, namespace, agent_id,
24
+ content='memories', content_rowid='rowid'
25
+ );
26
+
27
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
28
+ INSERT INTO memories_fts(rowid, content, type, namespace, agent_id)
29
+ VALUES (new.rowid, new.content, new.type, new.namespace, new.agent_id);
30
+ END;
31
+
32
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
33
+ INSERT INTO memories_fts(memories_fts, rowid, content, type, namespace, agent_id)
34
+ VALUES ('delete', old.rowid, old.content, old.type, old.namespace, old.agent_id);
35
+ END;
36
+
37
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
38
+ INSERT INTO memories_fts(memories_fts, rowid, content, type, namespace, agent_id)
39
+ VALUES ('delete', old.rowid, old.content, old.type, old.namespace, old.agent_id);
40
+ INSERT INTO memories_fts(rowid, content, type, namespace, agent_id)
41
+ VALUES (new.rowid, new.content, new.type, new.namespace, new.agent_id);
42
+ END;
43
+ `;
44
+ export function createVecSQL(dimensions) {
45
+ return `
46
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_vec USING vec0(
47
+ id TEXT PRIMARY KEY,
48
+ embedding float[${dimensions}]
49
+ );
50
+ `;
51
+ }
@@ -0,0 +1,12 @@
1
+ import type { EmbeddingProvider } from "./provider.js";
2
+ export interface OllamaConfig {
3
+ endpoint: string;
4
+ model: string;
5
+ }
6
+ export declare class OllamaEmbeddingProvider implements EmbeddingProvider {
7
+ readonly dimensions = 384;
8
+ private endpoint;
9
+ private model;
10
+ constructor(config: OllamaConfig);
11
+ embed(text: string): Promise<number[]>;
12
+ }
@@ -0,0 +1,22 @@
1
+ export class OllamaEmbeddingProvider {
2
+ dimensions = 384;
3
+ endpoint;
4
+ model;
5
+ constructor(config) {
6
+ this.endpoint = config.endpoint;
7
+ this.model = config.model;
8
+ }
9
+ async embed(text) {
10
+ const response = await fetch(`${this.endpoint}/api/embed`, {
11
+ method: "POST",
12
+ headers: { "Content-Type": "application/json" },
13
+ body: JSON.stringify({ model: this.model, input: text }),
14
+ });
15
+ if (!response.ok) {
16
+ const body = await response.text();
17
+ throw new Error(`Ollama embedding failed (${response.status}): ${body}`);
18
+ }
19
+ const data = await response.json();
20
+ return data.embeddings[0];
21
+ }
22
+ }
@@ -0,0 +1,4 @@
1
+ export interface EmbeddingProvider {
2
+ embed(text: string): Promise<number[]>;
3
+ readonly dimensions: number;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ export { createMemoryServer } from "./server.js";
2
+ export { MemoryStore } from "./store.js";
3
+ export { HybridSearch } from "./search.js";
4
+ export { createDatabase } from "./db/client.js";
5
+ export { OllamaEmbeddingProvider } from "./embeddings/ollama.js";
6
+ export { MemoryConfigSchema } from "./config.js";
7
+ export type { EmbeddingProvider } from "./embeddings/provider.js";
8
+ export type { Memory, StoreInput, UpdateInput, ListOptions } from "./store.js";
9
+ export type { SearchOptions, SearchResult } from "./search.js";
10
+ export type { MemoryConfig } from "./config.js";
@@ -0,0 +1,6 @@
1
+ export { createMemoryServer } from "./server.js";
2
+ export { MemoryStore } from "./store.js";
3
+ export { HybridSearch } from "./search.js";
4
+ export { createDatabase } from "./db/client.js";
5
+ export { OllamaEmbeddingProvider } from "./embeddings/ollama.js";
6
+ export { MemoryConfigSchema } from "./config.js";
@@ -0,0 +1,30 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { EmbeddingProvider } from "./embeddings/provider.js";
3
+ import type { MemoryRow } from "./db/schema.js";
4
+ export interface SearchOptions {
5
+ query: string;
6
+ namespace?: string;
7
+ agent_id?: string;
8
+ type?: MemoryRow["type"];
9
+ limit?: number;
10
+ min_score?: number;
11
+ }
12
+ export interface SearchResult {
13
+ id: string;
14
+ content: string;
15
+ type: string;
16
+ namespace: string;
17
+ agent_id: string;
18
+ score: number;
19
+ created_at: string;
20
+ }
21
+ export declare class HybridSearch {
22
+ private db;
23
+ private embeddings;
24
+ constructor(db: Database.Database, embeddings: EmbeddingProvider);
25
+ search(options: SearchOptions): Promise<SearchResult[]>;
26
+ private semanticSearch;
27
+ private keywordSearch;
28
+ private mergeResults;
29
+ private computeRecency;
30
+ }
@@ -0,0 +1,121 @@
1
+ const SEMANTIC_WEIGHT = 0.6;
2
+ const KEYWORD_WEIGHT = 0.3;
3
+ const RECENCY_WEIGHT = 0.1;
4
+ const RECENCY_HALF_LIFE_DAYS = 30;
5
+ const DEFAULT_MIN_SCORE = 0.1;
6
+ export class HybridSearch {
7
+ db;
8
+ embeddings;
9
+ constructor(db, embeddings) {
10
+ this.db = db;
11
+ this.embeddings = embeddings;
12
+ }
13
+ async search(options) {
14
+ const limit = options.limit ?? 10;
15
+ const minScore = options.min_score ?? DEFAULT_MIN_SCORE;
16
+ const queryEmbedding = await this.embeddings.embed(options.query);
17
+ // 1. Semantic search via sqlite-vec
18
+ const semanticResults = this.semanticSearch(queryEmbedding, 50);
19
+ // 2. Keyword search via FTS5
20
+ const keywordResults = this.keywordSearch(options.query, 50);
21
+ // 3. Merge and score
22
+ const merged = this.mergeResults(semanticResults, keywordResults);
23
+ // 4. Fetch full rows, apply filters, apply recency boost
24
+ const results = [];
25
+ for (const { id, score } of merged) {
26
+ const row = this.db
27
+ .prepare(`SELECT * FROM memories WHERE id = ? AND deleted_at IS NULL`)
28
+ .get(id);
29
+ if (!row)
30
+ continue;
31
+ if (options.namespace && row.namespace !== options.namespace)
32
+ continue;
33
+ if (options.agent_id && row.agent_id !== options.agent_id)
34
+ continue;
35
+ if (options.type && row.type !== options.type)
36
+ continue;
37
+ const recencyBoost = this.computeRecency(row.created_at);
38
+ const finalScore = score + RECENCY_WEIGHT * recencyBoost;
39
+ if (finalScore < minScore)
40
+ continue;
41
+ results.push({
42
+ id: row.id,
43
+ content: row.content,
44
+ type: row.type,
45
+ namespace: row.namespace,
46
+ agent_id: row.agent_id,
47
+ score: finalScore,
48
+ created_at: row.created_at,
49
+ });
50
+ if (results.length >= limit)
51
+ break;
52
+ }
53
+ return results.sort((a, b) => b.score - a.score);
54
+ }
55
+ semanticSearch(embedding, limit) {
56
+ const rows = this.db
57
+ .prepare(`SELECT id, distance FROM memories_vec
58
+ WHERE embedding MATCH ? ORDER BY distance LIMIT ?`)
59
+ .all(new Float32Array(embedding), limit);
60
+ // sqlite-vec returns L2 distance; convert to similarity score [0, 1]
61
+ const maxDist = Math.max(...rows.map((r) => r.distance), 1);
62
+ return rows.map((r) => ({
63
+ id: r.id,
64
+ score: 1 - r.distance / maxDist,
65
+ }));
66
+ }
67
+ keywordSearch(query, limit) {
68
+ // Strip quotes, then wrap each term in double quotes to force literal matching
69
+ // (prevents FTS5 operators like AND, OR, NOT, +, - from being interpreted)
70
+ const terms = query
71
+ .replace(/["]/g, "")
72
+ .split(/\s+/)
73
+ .filter(Boolean)
74
+ .map((term) => `"${term}"`)
75
+ .join(" ");
76
+ if (!terms)
77
+ return [];
78
+ try {
79
+ const rows = this.db
80
+ .prepare(`SELECT m.id, rank FROM memories_fts
81
+ JOIN memories m ON memories_fts.rowid = m.rowid
82
+ WHERE memories_fts MATCH ?
83
+ ORDER BY rank LIMIT ?`)
84
+ .all(terms, limit);
85
+ // FTS5 rank is negative (lower = better); normalize to [0, 1]
86
+ const minRank = Math.min(...rows.map((r) => r.rank), -1);
87
+ return rows.map((r) => ({
88
+ id: r.id,
89
+ score: r.rank / minRank, // both negative, so this gives [0, 1]
90
+ }));
91
+ }
92
+ catch {
93
+ // FTS5 can throw on malformed queries; fall back to empty
94
+ return [];
95
+ }
96
+ }
97
+ mergeResults(semantic, keyword) {
98
+ const scores = new Map();
99
+ for (const { id, score } of semantic) {
100
+ scores.set(id, { semantic: score, keyword: 0 });
101
+ }
102
+ for (const { id, score } of keyword) {
103
+ const existing = scores.get(id) ?? { semantic: 0, keyword: 0 };
104
+ existing.keyword = score;
105
+ scores.set(id, existing);
106
+ }
107
+ const merged = [];
108
+ for (const [id, s] of scores) {
109
+ merged.push({
110
+ id,
111
+ score: SEMANTIC_WEIGHT * s.semantic + KEYWORD_WEIGHT * s.keyword,
112
+ });
113
+ }
114
+ return merged.sort((a, b) => b.score - a.score);
115
+ }
116
+ computeRecency(createdAt) {
117
+ const ageMs = Date.now() - new Date(createdAt).getTime();
118
+ const ageDays = ageMs / (1000 * 60 * 60 * 24);
119
+ return Math.exp((-Math.LN2 * ageDays) / RECENCY_HALF_LIFE_DAYS);
120
+ }
121
+ }
@@ -0,0 +1,8 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { EmbeddingProvider } from "./embeddings/provider.js";
3
+ interface ServerOptions {
4
+ dbPath: string;
5
+ embeddingProvider: EmbeddingProvider;
6
+ }
7
+ export declare function createMemoryServer(options: ServerOptions): McpServer;
8
+ export {};
@@ -0,0 +1,126 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { createDatabase } from "./db/client.js";
5
+ import { MemoryStore } from "./store.js";
6
+ import { HybridSearch } from "./search.js";
7
+ import { OllamaEmbeddingProvider } from "./embeddings/ollama.js";
8
+ import { MemoryConfigSchema } from "./config.js";
9
+ import * as os from "os";
10
+ import * as path from "path";
11
+ import * as fs from "fs";
12
+ export function createMemoryServer(options) {
13
+ const resolvedPath = options.dbPath.startsWith("~")
14
+ ? path.join(os.homedir(), options.dbPath.slice(1))
15
+ : options.dbPath;
16
+ const dir = path.dirname(resolvedPath);
17
+ if (!fs.existsSync(dir))
18
+ fs.mkdirSync(dir, { recursive: true });
19
+ const db = createDatabase(resolvedPath, options.embeddingProvider.dimensions);
20
+ const store = new MemoryStore(db, options.embeddingProvider);
21
+ const search = new HybridSearch(db, options.embeddingProvider);
22
+ const server = new McpServer({
23
+ name: "agent-kit-memory",
24
+ version: "0.1.0",
25
+ });
26
+ const MemoryType = z.enum(["fact", "preference", "decision", "learning", "observation"]);
27
+ server.tool("store_memory", "Store a new memory with automatic embedding for semantic search", {
28
+ content: z.string().describe("The memory content to store"),
29
+ type: MemoryType.describe("Memory type: fact, preference, decision, learning, or observation"),
30
+ namespace: z.string().describe("Scope: e.g. 'global', 'finance', 'dev/agent-kit'"),
31
+ agent_id: z.string().optional().describe("Who is storing this memory. Defaults to 'unknown'"),
32
+ metadata: z
33
+ .record(z.string(), z.unknown())
34
+ .optional()
35
+ .describe("Optional metadata: { project, confidence, tags, ... }"),
36
+ }, async (args) => {
37
+ const result = await store.store({
38
+ content: args.content,
39
+ type: args.type,
40
+ namespace: args.namespace,
41
+ agent_id: args.agent_id ?? "unknown",
42
+ metadata: args.metadata,
43
+ });
44
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
45
+ });
46
+ server.tool("search_memory", "Search memories using hybrid semantic + keyword matching", {
47
+ query: z.string().describe("Search query — natural language or keywords"),
48
+ namespace: z.string().optional().describe("Filter by namespace"),
49
+ agent_id: z.string().optional().describe("Filter by agent"),
50
+ type: MemoryType.optional().describe("Filter by memory type"),
51
+ limit: z.number().optional().describe("Max results (default 10)"),
52
+ min_score: z.number().optional().describe("Minimum relevance score 0-1 (default 0.1). Filters out noise"),
53
+ }, async (args) => {
54
+ const results = await search.search(args);
55
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
56
+ });
57
+ server.tool("get_memory", "Retrieve a specific memory by its ID", {
58
+ id: z.string().describe("Memory ID"),
59
+ }, async (args) => {
60
+ const memory = await store.get(args.id);
61
+ if (!memory) {
62
+ return { content: [{ type: "text", text: "Memory not found" }] };
63
+ }
64
+ return { content: [{ type: "text", text: JSON.stringify(memory, null, 2) }] };
65
+ });
66
+ server.tool("update_memory", "Update an existing memory. Re-embeds if content changes", {
67
+ id: z.string().describe("Memory ID to update"),
68
+ content: z.string().optional().describe("New content"),
69
+ type: MemoryType.optional().describe("New type"),
70
+ metadata: z
71
+ .record(z.string(), z.unknown())
72
+ .optional()
73
+ .describe("New metadata (replaces existing)"),
74
+ }, async (args) => {
75
+ const { id, ...updates } = args;
76
+ const result = await store.update(id, updates);
77
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
78
+ });
79
+ server.tool("forget_memory", "Soft-delete a memory. It will no longer appear in search results", {
80
+ id: z.string().describe("Memory ID to forget"),
81
+ }, async (args) => {
82
+ const result = await store.forget(args.id);
83
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
84
+ });
85
+ server.tool("list_memories", "Browse memories by scope, sorted by most recent first", {
86
+ namespace: z.string().optional().describe("Filter by namespace"),
87
+ agent_id: z.string().optional().describe("Filter by agent"),
88
+ type: MemoryType.optional().describe("Filter by memory type"),
89
+ limit: z.number().optional().describe("Max results (default 50)"),
90
+ offset: z.number().optional().describe("Pagination offset"),
91
+ }, async (args) => {
92
+ const results = await store.list(args);
93
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
94
+ });
95
+ return server;
96
+ }
97
+ // CLI entry point
98
+ async function main() {
99
+ const config = MemoryConfigSchema.parse({
100
+ dbPath: process.env.MEMORY_DB_PATH,
101
+ embedding: {
102
+ provider: process.env.MEMORY_EMBEDDING_PROVIDER,
103
+ model: process.env.MEMORY_EMBEDDING_MODEL,
104
+ endpoint: process.env.MEMORY_EMBEDDING_ENDPOINT,
105
+ apiKey: process.env.MEMORY_EMBEDDING_API_KEY,
106
+ },
107
+ });
108
+ const embeddingProvider = new OllamaEmbeddingProvider({
109
+ endpoint: config.embedding.endpoint,
110
+ model: config.embedding.model,
111
+ });
112
+ const server = createMemoryServer({
113
+ dbPath: config.dbPath,
114
+ embeddingProvider,
115
+ });
116
+ const transport = new StdioServerTransport();
117
+ await server.connect(transport);
118
+ }
119
+ // Run if executed directly
120
+ const isMain = process.argv[1]?.endsWith("server.ts") || process.argv[1]?.endsWith("server.js");
121
+ if (isMain) {
122
+ main().catch((err) => {
123
+ console.error("Memory server failed to start:", err);
124
+ process.exit(1);
125
+ });
126
+ }
@@ -0,0 +1,51 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { EmbeddingProvider } from "./embeddings/provider.js";
3
+ import type { MemoryRow } from "./db/schema.js";
4
+ export interface StoreInput {
5
+ content: string;
6
+ type: MemoryRow["type"];
7
+ namespace: string;
8
+ agent_id: string;
9
+ metadata?: Record<string, unknown>;
10
+ }
11
+ export interface UpdateInput {
12
+ content?: string;
13
+ type?: MemoryRow["type"];
14
+ metadata?: Record<string, unknown>;
15
+ }
16
+ export interface ListOptions {
17
+ namespace?: string;
18
+ agent_id?: string;
19
+ type?: MemoryRow["type"];
20
+ limit?: number;
21
+ offset?: number;
22
+ }
23
+ export interface Memory {
24
+ id: string;
25
+ agent_id: string;
26
+ namespace: string;
27
+ content: string;
28
+ type: MemoryRow["type"];
29
+ metadata: Record<string, unknown> | null;
30
+ created_at: string;
31
+ updated_at: string;
32
+ }
33
+ export declare class MemoryStore {
34
+ private db;
35
+ private embeddings;
36
+ constructor(db: Database.Database, embeddings: EmbeddingProvider);
37
+ store(input: StoreInput): Promise<{
38
+ id: string;
39
+ created_at: string;
40
+ }>;
41
+ get(id: string): Promise<Memory | null>;
42
+ update(id: string, input: UpdateInput): Promise<{
43
+ id: string;
44
+ updated_at: string;
45
+ }>;
46
+ forget(id: string): Promise<{
47
+ success: boolean;
48
+ }>;
49
+ list(options?: ListOptions): Promise<Memory[]>;
50
+ private rowToMemory;
51
+ }
@@ -0,0 +1,115 @@
1
+ import { nanoid } from "nanoid";
2
+ export class MemoryStore {
3
+ db;
4
+ embeddings;
5
+ constructor(db, embeddings) {
6
+ this.db = db;
7
+ this.embeddings = embeddings;
8
+ }
9
+ async store(input) {
10
+ const id = nanoid();
11
+ const now = new Date().toISOString();
12
+ const metadataJson = input.metadata ? JSON.stringify(input.metadata) : null;
13
+ const embedding = await this.embeddings.embed(input.content);
14
+ const insertTxn = this.db.transaction(() => {
15
+ this.db
16
+ .prepare(`INSERT INTO memories (id, agent_id, namespace, content, type, metadata, created_at, updated_at)
17
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
18
+ .run(id, input.agent_id, input.namespace, input.content, input.type, metadataJson, now, now);
19
+ this.db
20
+ .prepare(`INSERT INTO memories_vec (id, embedding) VALUES (?, ?)`)
21
+ .run(id, new Float32Array(embedding));
22
+ });
23
+ insertTxn();
24
+ return { id, created_at: now };
25
+ }
26
+ async get(id) {
27
+ const row = this.db
28
+ .prepare(`SELECT * FROM memories WHERE id = ? AND deleted_at IS NULL`)
29
+ .get(id);
30
+ if (!row)
31
+ return null;
32
+ return this.rowToMemory(row);
33
+ }
34
+ async update(id, input) {
35
+ const now = new Date().toISOString();
36
+ const sets = [`updated_at = ?`];
37
+ const values = [now];
38
+ if (input.content !== undefined) {
39
+ sets.push(`content = ?`);
40
+ values.push(input.content);
41
+ }
42
+ if (input.type !== undefined) {
43
+ sets.push(`type = ?`);
44
+ values.push(input.type);
45
+ }
46
+ if (input.metadata !== undefined) {
47
+ sets.push(`metadata = ?`);
48
+ values.push(JSON.stringify(input.metadata));
49
+ }
50
+ values.push(id);
51
+ if (input.content !== undefined) {
52
+ const embedding = await this.embeddings.embed(input.content);
53
+ const updateTxn = this.db.transaction(() => {
54
+ this.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...values);
55
+ this.db.prepare(`DELETE FROM memories_vec WHERE id = ?`).run(id);
56
+ this.db
57
+ .prepare(`INSERT INTO memories_vec (id, embedding) VALUES (?, ?)`)
58
+ .run(id, new Float32Array(embedding));
59
+ });
60
+ updateTxn();
61
+ }
62
+ else {
63
+ this.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...values);
64
+ }
65
+ return { id, updated_at: now };
66
+ }
67
+ async forget(id) {
68
+ const forgetTxn = this.db.transaction(() => {
69
+ const result = this.db
70
+ .prepare(`UPDATE memories SET deleted_at = datetime('now') WHERE id = ? AND deleted_at IS NULL`)
71
+ .run(id);
72
+ if (result.changes > 0) {
73
+ this.db.prepare(`DELETE FROM memories_vec WHERE id = ?`).run(id);
74
+ }
75
+ return result;
76
+ });
77
+ const result = forgetTxn();
78
+ return { success: result.changes > 0 };
79
+ }
80
+ async list(options = {}) {
81
+ const conditions = [`deleted_at IS NULL`];
82
+ const values = [];
83
+ if (options.namespace) {
84
+ conditions.push(`namespace = ?`);
85
+ values.push(options.namespace);
86
+ }
87
+ if (options.agent_id) {
88
+ conditions.push(`agent_id = ?`);
89
+ values.push(options.agent_id);
90
+ }
91
+ if (options.type) {
92
+ conditions.push(`type = ?`);
93
+ values.push(options.type);
94
+ }
95
+ const limit = options.limit ?? 50;
96
+ const offset = options.offset ?? 0;
97
+ const rows = this.db
98
+ .prepare(`SELECT * FROM memories WHERE ${conditions.join(" AND ")}
99
+ ORDER BY created_at DESC LIMIT ? OFFSET ?`)
100
+ .all(...values, limit, offset);
101
+ return rows.map((row) => this.rowToMemory(row));
102
+ }
103
+ rowToMemory(row) {
104
+ return {
105
+ id: row.id,
106
+ agent_id: row.agent_id,
107
+ namespace: row.namespace,
108
+ content: row.content,
109
+ type: row.type,
110
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
111
+ created_at: row.created_at,
112
+ updated_at: row.updated_at,
113
+ };
114
+ }
115
+ }
File without changes