@bip-krishna/agentmind 0.1.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 (169) hide show
  1. package/.agentmind/memory.db +0 -0
  2. package/.context-memory/memory.db +0 -0
  3. package/README.md +176 -0
  4. package/apps/dashboard/src/server.ts +369 -0
  5. package/cli/src/index.ts +163 -0
  6. package/dist/apps/dashboard/src/server.d.ts +6 -0
  7. package/dist/apps/dashboard/src/server.d.ts.map +1 -0
  8. package/dist/apps/dashboard/src/server.js +360 -0
  9. package/dist/apps/dashboard/src/server.js.map +1 -0
  10. package/dist/cli/src/index.d.ts +3 -0
  11. package/dist/cli/src/index.d.ts.map +1 -0
  12. package/dist/cli/src/index.js +162 -0
  13. package/dist/cli/src/index.js.map +1 -0
  14. package/dist/examples/advanced.d.ts +2 -0
  15. package/dist/examples/advanced.d.ts.map +1 -0
  16. package/dist/examples/advanced.js +41 -0
  17. package/dist/examples/advanced.js.map +1 -0
  18. package/dist/examples/basic.d.ts +2 -0
  19. package/dist/examples/basic.d.ts.map +1 -0
  20. package/dist/examples/basic.js +22 -0
  21. package/dist/examples/basic.js.map +1 -0
  22. package/dist/packages/core/src/consolidation.d.ts +27 -0
  23. package/dist/packages/core/src/consolidation.d.ts.map +1 -0
  24. package/dist/packages/core/src/consolidation.js +97 -0
  25. package/dist/packages/core/src/consolidation.js.map +1 -0
  26. package/dist/packages/core/src/events.d.ts +18 -0
  27. package/dist/packages/core/src/events.d.ts.map +1 -0
  28. package/dist/packages/core/src/events.js +13 -0
  29. package/dist/packages/core/src/events.js.map +1 -0
  30. package/dist/packages/core/src/extraction.d.ts +32 -0
  31. package/dist/packages/core/src/extraction.d.ts.map +1 -0
  32. package/dist/packages/core/src/extraction.js +65 -0
  33. package/dist/packages/core/src/extraction.js.map +1 -0
  34. package/dist/packages/core/src/index.d.ts +10 -0
  35. package/dist/packages/core/src/index.d.ts.map +1 -0
  36. package/dist/packages/core/src/index.js +7 -0
  37. package/dist/packages/core/src/index.js.map +1 -0
  38. package/dist/packages/core/src/memory-store.d.ts +36 -0
  39. package/dist/packages/core/src/memory-store.d.ts.map +1 -0
  40. package/dist/packages/core/src/memory-store.js +2 -0
  41. package/dist/packages/core/src/memory-store.js.map +1 -0
  42. package/dist/packages/core/src/memory.d.ts +75 -0
  43. package/dist/packages/core/src/memory.d.ts.map +1 -0
  44. package/dist/packages/core/src/memory.js +402 -0
  45. package/dist/packages/core/src/memory.js.map +1 -0
  46. package/dist/packages/core/src/policy.d.ts +17 -0
  47. package/dist/packages/core/src/policy.d.ts.map +1 -0
  48. package/dist/packages/core/src/policy.js +22 -0
  49. package/dist/packages/core/src/policy.js.map +1 -0
  50. package/dist/packages/core/src/types.d.ts +102 -0
  51. package/dist/packages/core/src/types.d.ts.map +1 -0
  52. package/dist/packages/core/src/types.js +23 -0
  53. package/dist/packages/core/src/types.js.map +1 -0
  54. package/dist/packages/embeddings/src/index.d.ts +4 -0
  55. package/dist/packages/embeddings/src/index.d.ts.map +1 -0
  56. package/dist/packages/embeddings/src/index.js +4 -0
  57. package/dist/packages/embeddings/src/index.js.map +1 -0
  58. package/dist/packages/embeddings/src/local.d.ts +15 -0
  59. package/dist/packages/embeddings/src/local.d.ts.map +1 -0
  60. package/dist/packages/embeddings/src/local.js +45 -0
  61. package/dist/packages/embeddings/src/local.js.map +1 -0
  62. package/dist/packages/embeddings/src/openai.d.ts +17 -0
  63. package/dist/packages/embeddings/src/openai.d.ts.map +1 -0
  64. package/dist/packages/embeddings/src/openai.js +41 -0
  65. package/dist/packages/embeddings/src/openai.js.map +1 -0
  66. package/dist/packages/embeddings/src/provider.d.ts +8 -0
  67. package/dist/packages/embeddings/src/provider.d.ts.map +1 -0
  68. package/dist/packages/embeddings/src/provider.js +12 -0
  69. package/dist/packages/embeddings/src/provider.js.map +1 -0
  70. package/dist/packages/integrations/src/anthropic.d.ts +15 -0
  71. package/dist/packages/integrations/src/anthropic.d.ts.map +1 -0
  72. package/dist/packages/integrations/src/anthropic.js +25 -0
  73. package/dist/packages/integrations/src/anthropic.js.map +1 -0
  74. package/dist/packages/integrations/src/index.d.ts +4 -0
  75. package/dist/packages/integrations/src/index.d.ts.map +1 -0
  76. package/dist/packages/integrations/src/index.js +4 -0
  77. package/dist/packages/integrations/src/index.js.map +1 -0
  78. package/dist/packages/integrations/src/openai.d.ts +27 -0
  79. package/dist/packages/integrations/src/openai.d.ts.map +1 -0
  80. package/dist/packages/integrations/src/openai.js +26 -0
  81. package/dist/packages/integrations/src/openai.js.map +1 -0
  82. package/dist/packages/integrations/src/vercel.d.ts +24 -0
  83. package/dist/packages/integrations/src/vercel.d.ts.map +1 -0
  84. package/dist/packages/integrations/src/vercel.js +20 -0
  85. package/dist/packages/integrations/src/vercel.js.map +1 -0
  86. package/dist/packages/retrieval/src/index.d.ts +3 -0
  87. package/dist/packages/retrieval/src/index.d.ts.map +1 -0
  88. package/dist/packages/retrieval/src/index.js +3 -0
  89. package/dist/packages/retrieval/src/index.js.map +1 -0
  90. package/dist/packages/retrieval/src/ranker.d.ts +21 -0
  91. package/dist/packages/retrieval/src/ranker.d.ts.map +1 -0
  92. package/dist/packages/retrieval/src/ranker.js +54 -0
  93. package/dist/packages/retrieval/src/ranker.js.map +1 -0
  94. package/dist/packages/retrieval/src/token.d.ts +3 -0
  95. package/dist/packages/retrieval/src/token.d.ts.map +1 -0
  96. package/dist/packages/retrieval/src/token.js +7 -0
  97. package/dist/packages/retrieval/src/token.js.map +1 -0
  98. package/dist/packages/storage/src/config.d.ts +14 -0
  99. package/dist/packages/storage/src/config.d.ts.map +1 -0
  100. package/dist/packages/storage/src/config.js +41 -0
  101. package/dist/packages/storage/src/config.js.map +1 -0
  102. package/dist/packages/storage/src/index.d.ts +4 -0
  103. package/dist/packages/storage/src/index.d.ts.map +1 -0
  104. package/dist/packages/storage/src/index.js +4 -0
  105. package/dist/packages/storage/src/index.js.map +1 -0
  106. package/dist/packages/storage/src/memory-store.d.ts +37 -0
  107. package/dist/packages/storage/src/memory-store.d.ts.map +1 -0
  108. package/dist/packages/storage/src/memory-store.js +107 -0
  109. package/dist/packages/storage/src/memory-store.js.map +1 -0
  110. package/dist/packages/storage/src/migrations.d.ts +12 -0
  111. package/dist/packages/storage/src/migrations.d.ts.map +1 -0
  112. package/dist/packages/storage/src/migrations.js +89 -0
  113. package/dist/packages/storage/src/migrations.js.map +1 -0
  114. package/dist/packages/storage/src/postgres-store.d.ts +45 -0
  115. package/dist/packages/storage/src/postgres-store.d.ts.map +1 -0
  116. package/dist/packages/storage/src/postgres-store.js +282 -0
  117. package/dist/packages/storage/src/postgres-store.js.map +1 -0
  118. package/dist/packages/storage/src/sqlite-store.d.ts +36 -0
  119. package/dist/packages/storage/src/sqlite-store.d.ts.map +1 -0
  120. package/dist/packages/storage/src/sqlite-store.js +243 -0
  121. package/dist/packages/storage/src/sqlite-store.js.map +1 -0
  122. package/examples/advanced.ts +46 -0
  123. package/examples/basic.ts +25 -0
  124. package/package.json +52 -0
  125. package/packages/core/package.json +13 -0
  126. package/packages/core/src/consolidation.ts +104 -0
  127. package/packages/core/src/events.ts +25 -0
  128. package/packages/core/src/extraction.ts +81 -0
  129. package/packages/core/src/index.ts +22 -0
  130. package/packages/core/src/memory-store.ts +50 -0
  131. package/packages/core/src/memory.ts +447 -0
  132. package/packages/core/src/policy.ts +36 -0
  133. package/packages/core/src/types.ts +119 -0
  134. package/packages/core/tsconfig.json +6 -0
  135. package/packages/embeddings/package.json +9 -0
  136. package/packages/embeddings/src/index.ts +3 -0
  137. package/packages/embeddings/src/local.ts +47 -0
  138. package/packages/embeddings/src/openai.ts +50 -0
  139. package/packages/embeddings/src/provider.d.ts.map +1 -0
  140. package/packages/embeddings/src/provider.js.map +1 -0
  141. package/packages/embeddings/src/provider.ts +17 -0
  142. package/packages/embeddings/tsconfig.json +6 -0
  143. package/packages/integrations/package.json +7 -0
  144. package/packages/integrations/src/anthropic.ts +34 -0
  145. package/packages/integrations/src/index.ts +3 -0
  146. package/packages/integrations/src/openai.ts +52 -0
  147. package/packages/integrations/src/vercel.ts +26 -0
  148. package/packages/retrieval/package.json +8 -0
  149. package/packages/retrieval/src/index.ts +2 -0
  150. package/packages/retrieval/src/ranker.d.ts.map +1 -0
  151. package/packages/retrieval/src/ranker.js.map +1 -0
  152. package/packages/retrieval/src/ranker.ts +77 -0
  153. package/packages/retrieval/src/token.d.ts.map +1 -0
  154. package/packages/retrieval/src/token.js.map +1 -0
  155. package/packages/retrieval/src/token.ts +5 -0
  156. package/packages/retrieval/tsconfig.json +6 -0
  157. package/packages/storage/package.json +11 -0
  158. package/packages/storage/src/config.ts +44 -0
  159. package/packages/storage/src/index.ts +3 -0
  160. package/packages/storage/src/memory-store.ts +100 -0
  161. package/packages/storage/src/migrations.ts +96 -0
  162. package/packages/storage/src/postgres-store.ts +299 -0
  163. package/packages/storage/src/sqlite-store.ts +298 -0
  164. package/packages/storage/tsconfig.json +6 -0
  165. package/test-mvp.mjs +82 -0
  166. package/tests/consolidation.test.ts +45 -0
  167. package/tests/eval.test.ts +102 -0
  168. package/tsconfig.json +21 -0
  169. package/vitest.config.ts +8 -0
Binary file
Binary file
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # agentmind
2
+
3
+ **Persistent, project-scoped context layer for AI applications.**
4
+
5
+ > A persistent, project-scoped context layer — not a vector DB wrapper.
6
+ > Formerly `context-memory` — now `agentmind`. The `context-memory` CLI/bin remains as an alias.
7
+
8
+ ```ts
9
+ memory.remember()
10
+ memory.search()
11
+ memory.context()
12
+ memory.forget()
13
+ ```
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install agentmind
19
+ # legacy alias still works:
20
+ npm install context-memory
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ```ts
26
+ import { Memory } from "agentmind";
27
+ import { SqliteMemoryStore } from "agentmind/storage";
28
+ import { LocalEmbeddingProvider } from "agentmind/embeddings";
29
+
30
+ const store = new SqliteMemoryStore({ path: "./.agentmind/memory.db" });
31
+ const memory = await Memory.create({
32
+ project: "research-mesh",
33
+ store,
34
+ embeddingProvider: new LocalEmbeddingProvider(), // or OpenAIEmbeddingProvider
35
+ });
36
+
37
+ await memory.remember({
38
+ type: "decision",
39
+ content: "We decided to use PostgreSQL with pgvector.",
40
+ importance: 0.9,
41
+ });
42
+
43
+ const ctx = await memory.context({ query: "What database did we decide to use?" });
44
+ console.log(ctx.text);
45
+ // Relevant project memory (research-mesh):
46
+ // - We decided to use PostgreSQL with pgvector.
47
+ ```
48
+
49
+ ## Architecture
50
+
51
+ ```
52
+ agentmind
53
+
54
+ ┌──────────────┼──────────────┐
55
+ ▼ ▼ ▼
56
+ Memory Engine Embedding Engine Dashboard
57
+ │ │ │
58
+ ▼ ▼ ▼
59
+ Retriever OpenAI/etc. HTTP API (4317)
60
+
61
+
62
+ Store Adapter
63
+
64
+ ┌───┴─────────────────┐
65
+ ▼ ▼
66
+ SQLite (node:sqlite) Postgres + pgvector
67
+ InMemory (tests) (pg Pool, hnsw)
68
+ ```
69
+
70
+ **Store is the abstraction** — `MemoryStore` interface: `SqliteMemoryStore` and `PostgresMemoryStore` interchangeable.
71
+
72
+ ## Features
73
+
74
+ ### Core (Phases 1-4)
75
+ - **Project isolation** — `WHERE project_id=?` enforced before vector search (0 leakage, tested 50 queries)
76
+ - **Manual memory creation** — `type`, `importance`, `confidence`, `metadata`, `sourceId`
77
+ - **Embedding abstraction** — `EmbeddingProvider` (`OpenAIEmbeddingProvider`, `LocalEmbeddingProvider` deterministic hash 256d)
78
+ - **Decoupled embeddings** — `embeddings` table separate, re-embeddable per model
79
+ - **Hybrid retrieval** — `semantic×0.6 + keyword×0.2 + importance×0.1 + recency×0.1` (configurable `HybridRanker`)
80
+ - **Token budgeting** — `maxTokens` enforced greedily
81
+ - **Deduplication** — Jaccard >0.85 updates existing
82
+ - **Conflict resolution** — triggers `switched to/migrated to/now using` + embedding similarity >0.82 supersedes old
83
+
84
+ ### Intelligence (Phase 5)
85
+ - **Extraction** — `HeuristicExtractor` + `LLMExtractor` with `EXTRACTION_PROMPT`
86
+ - **Consolidation** — `Consolidator` clusters via cosine >0.82, creates `summary` memories, archives sources
87
+ - **Lifecycle** — `extracted -> candidate -> validated -> stored -> retrieved -> updated -> archived`
88
+
89
+ ### Context (Phase 6)
90
+ - `memory.context({ query, maxTokens, limit, types }) -> { text, tokenCount, memories, sources, retrieval }`
91
+ - Retrieval logging to `retrieval_events`
92
+
93
+ ### Production (Phase 9)
94
+ - **SQLite** via `node:sqlite` `DatabaseSync` (Node 22+, zero native deps) + `InMemoryStore`
95
+ - **Postgres + pgvector** — `PostgresMemoryStore` (`pg` Pool, `CREATE EXTENSION vector`, `vec vector(1536)`, `hnsw` index, `vec <=> $1` native search)
96
+ - **Migrations** — `packages/storage/src/migrations.ts` versioned, `schema_migrations`
97
+ - **Config** — `resolveStorageConfig()` auto-detects `DATABASE_URL` else `AGENTMIND_DB`/`CTX_MEMORY_DB` else `.agentmind/memory.db`
98
+ - **Security** — DB-level `project_id` filter, no cross-project leakage
99
+ - **Privacy** — `forget(id)` deletes embedding, `forgetProject()`, `export()`, `storeSensitiveData=false`
100
+
101
+ ### Observability & Policy
102
+ ```ts
103
+ memory.on("memory.created", m => ...)
104
+ memory.on("retrieval.completed", e => ...)
105
+
106
+ const memory = await Memory.create({
107
+ project: "x",
108
+ policy: {
109
+ autoExtract: true,
110
+ minimumImportance: 0.65,
111
+ maxMemoriesPerProject: 10000,
112
+ storeSensitiveData: false,
113
+ shouldRemember: (m) => !m.content.includes("greeting"),
114
+ }
115
+ });
116
+ ```
117
+
118
+ ### Integrations (Phase 10)
119
+ ```ts
120
+ import { withMemory, createOpenAICaller } from "agentmind/integrations";
121
+ import OpenAI from "openai";
122
+ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
123
+
124
+ const { response } = await withMemory({
125
+ memory,
126
+ messages: [{ role: "user", content: "What's our DB?" }],
127
+ callLLM: createOpenAICaller(openai, "gpt-4o"),
128
+ });
129
+ ```
130
+ Adapters: `withMemory` (OpenAI), `withAnthropicMemory`, `withMemoryAISDK` (Vercel), `withLangChainMemory`.
131
+
132
+ ### Dashboard `npx agentmind dashboard`
133
+
134
+ ```
135
+ http://localhost:4317
136
+ ├── Overview (total/active/archived/byType)
137
+ ├── Memories (search, filter, detail with embedding preview)
138
+ ├── Context Inspector (query -> vector+keyword -> ranking -> final context)
139
+ ├── Retrieval Logs
140
+ └── Consolidation
141
+ ```
142
+
143
+ **API:** `GET /api/projects`, `POST /api/search`, `POST /api/context`, `POST /api/remember`, `GET /api/retrievals/:id`, etc.
144
+
145
+ ### CLI
146
+ ```bash
147
+ npx agentmind init --project research-mesh
148
+ npx agentmind project list
149
+ npx agentmind memory search --project research-mesh --query "database"
150
+ npx agentmind memory consolidate --project research-mesh
151
+ npx agentmind ingest --project research-mesh --content "We use Postgres"
152
+ npx agentmind export --project research-mesh
153
+ npx agentmind dashboard --port 4317
154
+ # legacy alias still works:
155
+ npx context-memory dashboard
156
+ ```
157
+
158
+ ## Evaluation
159
+
160
+ ```bash
161
+ npm test # vitest — 13 tests
162
+ npm run test:mvp # mvp harness
163
+ ```
164
+
165
+ ## Packaging
166
+
167
+ ```json
168
+ // package.json exports
169
+ ".": "./dist/packages/core/src/index.js",
170
+ "./storage": "./dist/packages/storage/src/index.js",
171
+ "./embeddings": "./dist/packages/embeddings/src/index.js",
172
+ "./integrations": "./dist/packages/integrations/src/index.js",
173
+ "./retrieval": "./dist/packages/retrieval/src/index.js"
174
+ ```
175
+
176
+ Modular: `@agentmind/core`, `@agentmind/storage`, etc. Umbrella `agentmind` installs defaults. Legacy `@context-memory/*` remains as alias via bin.
@@ -0,0 +1,369 @@
1
+ import { createServer, IncomingMessage, ServerResponse } from "node:http";
2
+ import { Memory } from "../../../packages/core/src/memory.js";
3
+ import { SqliteMemoryStore } from "../../../packages/storage/src/sqlite-store.js";
4
+ import { LocalEmbeddingProvider } from "../../../packages/embeddings/src/local.js";
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+
8
+ const PORT = Number(process.env.PORT ?? 4317);
9
+ const DB_PATH = process.env.AGENTMIND_DB ?? process.env.CTX_MEMORY_DB ?? "./.agentmind/memory.db";
10
+
11
+ const store = new SqliteMemoryStore({ path: DB_PATH });
12
+ const embed = new LocalEmbeddingProvider();
13
+
14
+ function json(res: ServerResponse, data: unknown, status = 200) {
15
+ res.writeHead(status, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS", "Access-Control-Allow-Headers": "Content-Type" });
16
+ res.end(JSON.stringify(data, null, 2));
17
+ }
18
+
19
+ function parseBody(req: IncomingMessage): Promise<any> {
20
+ return new Promise((resolve, reject) => {
21
+ let body = "";
22
+ req.on("data", (chunk) => (body += chunk));
23
+ req.on("end", () => {
24
+ try { resolve(body ? JSON.parse(body) : {}); } catch (e) { reject(e); }
25
+ });
26
+ });
27
+ }
28
+
29
+ const server = createServer(async (req, res) => {
30
+ if (req.method === "OPTIONS") {
31
+ res.writeHead(204, { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS", "Access-Control-Allow-Headers": "Content-Type" });
32
+ return res.end();
33
+ }
34
+
35
+ const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
36
+ const pathname = url.pathname;
37
+
38
+ try {
39
+ // GET /api/projects
40
+ if (req.method === "GET" && pathname === "/api/projects") {
41
+ const projects = await store.listProjects();
42
+ return json(res, projects);
43
+ }
44
+
45
+ // GET /api/projects/:id/stats
46
+ if (req.method === "GET" && pathname.match(/^\/api\/projects\/[^/]+\/stats$/)) {
47
+ const projectId = pathname.split("/")[3];
48
+ const memories = await store.listMemories(projectId);
49
+ const byType: Record<string, number> = {};
50
+ for (const m of memories) byType[m.type] = (byType[m.type] ?? 0) + 1;
51
+ return json(res, { projectId, total: memories.length, active: memories.filter(m => !m.archived && m.status==="active").length, archived: memories.filter(m=>m.archived).length, superseded: memories.filter(m=>m.status==="superseded").length, byType });
52
+ }
53
+
54
+ // GET /api/projects/:id/memories
55
+ if (req.method === "GET" && pathname.match(/^\/api\/projects\/[^/]+\/memories$/)) {
56
+ const projectId = pathname.split("/")[3];
57
+ const type = url.searchParams.get("type");
58
+ const importance = url.searchParams.get("minImportance");
59
+ let memories = await store.listMemories(projectId);
60
+ if (type) memories = memories.filter(m => m.type === type);
61
+ if (importance) memories = memories.filter(m => m.importance >= Number(importance));
62
+ if (url.searchParams.get("archived") === "false") memories = memories.filter(m => !m.archived);
63
+ return json(res, memories);
64
+ }
65
+
66
+ // POST /api/projects/:id/consolidate
67
+ if (req.method === "POST" && pathname.match(/^\/api\/projects\/[^/]+\/consolidate$/)) {
68
+ const projectId = pathname.split("/")[3];
69
+ const mem = await Memory.create({ project: projectId, store, embeddingProvider: embed });
70
+ // Try to resolve project id to name; if fails use id
71
+ const results = await mem.consolidate();
72
+ return json(res, results);
73
+ }
74
+
75
+ // GET /api/memories/:id
76
+ if (req.method === "GET" && pathname.match(/^\/api\/memories\/[^/]+$/)) {
77
+ const id = pathname.split("/")[3];
78
+ const mem = await store.getMemory(id);
79
+ if (!mem) return json(res, { error: "not found" }, 404);
80
+ const emb = await store.getEmbedding(id);
81
+ const source = mem.sourceId ? await store.getSource(mem.sourceId) : null;
82
+ return json(res, { ...mem, embedding: emb ? { model: emb.model, dimensions: emb.vector.length, preview: emb.vector.slice(0,5) } : null, source });
83
+ }
84
+
85
+ // PATCH /api/memories/:id
86
+ if (req.method === "PATCH" && pathname.match(/^\/api\/memories\/[^/]+$/)) {
87
+ const id = pathname.split("/")[3];
88
+ const body = await parseBody(req);
89
+ await store.updateMemory(id, body);
90
+ const mem = await store.getMemory(id);
91
+ return json(res, mem);
92
+ }
93
+
94
+ // DELETE /api/memories/:id
95
+ if (req.method === "DELETE" && pathname.match(/^\/api\/memories\/[^/]+$/)) {
96
+ const id = pathname.split("/")[3];
97
+ await store.deleteMemory(id);
98
+ return json(res, { ok: true });
99
+ }
100
+
101
+ // DELETE /api/projects/:id
102
+ if (req.method === "DELETE" && pathname.match(/^\/api\/projects\/[^/]+$/)) {
103
+ const id = pathname.split("/")[3];
104
+ await store.deleteProject(id);
105
+ return json(res, { ok: true });
106
+ }
107
+
108
+ // GET /api/projects/:id/export
109
+ if (req.method === "GET" && pathname.match(/^\/api\/projects\/[^/]+\/export$/)) {
110
+ const id = pathname.split("/")[3];
111
+ const memories = await store.listMemories(id);
112
+ return json(res, { projectId: id, memories });
113
+ }
114
+
115
+ // POST /api/search
116
+ if (req.method === "POST" && pathname === "/api/search") {
117
+ const body = await parseBody(req);
118
+ const { project, query, limit = 5, types } = body;
119
+ if (!project || !query) return json(res, { error: "project and query required" }, 400);
120
+ const mem = await Memory.create({ project, store, embeddingProvider: embed });
121
+ const results = await mem.search(query, { limit, types });
122
+ return json(res, results);
123
+ }
124
+
125
+ // POST /api/context — Context Inspector
126
+ if (req.method === "POST" && pathname === "/api/context") {
127
+ const body = await parseBody(req);
128
+ const { project, query, maxTokens = 6000, limit = 10 } = body;
129
+ if (!project || !query) return json(res, { error: "project and query required" }, 400);
130
+ const mem = await Memory.create({ project, store, embeddingProvider: embed });
131
+ const ctx = await mem.context({ query, maxTokens, limit });
132
+ // Also return retrieval details for inspector
133
+ const searchResults = await mem.search(query, { limit });
134
+ return json(res, { ...ctx, retrieval: searchResults.map(r => ({ id: r.memory.id, content: r.memory.content, score: r.score, semanticScore: r.semanticScore, keywordScore: r.keywordScore, type: r.memory.type, importance: r.memory.importance })) });
135
+ }
136
+
137
+ // POST /api/ingest
138
+ if (req.method === "POST" && pathname === "/api/ingest") {
139
+ const body = await parseBody(req);
140
+ const { project, role = "user", content, type = "conversation" } = body;
141
+ if (!project || !content) return json(res, { error: "project and content required" }, 400);
142
+ const mem = await Memory.create({ project, store, embeddingProvider: embed });
143
+ const result = await mem.ingest({ role, content, sourceType: type });
144
+ return json(res, result);
145
+ }
146
+
147
+ // POST /api/remember
148
+ if (req.method === "POST" && pathname === "/api/remember") {
149
+ const body = await parseBody(req);
150
+ const { project, content, type = "fact", importance = 0.5 } = body;
151
+ if (!project || !content) return json(res, { error: "project and content required" }, 400);
152
+ const mem = await Memory.create({ project, store, embeddingProvider: embed });
153
+ const created = await mem.remember({ content, type, importance });
154
+ return json(res, created);
155
+ }
156
+
157
+ // GET /api/retrievals/:projectId
158
+ if (req.method === "GET" && pathname.match(/^\/api\/retrievals\/[^/]+$/)) {
159
+ const projectId = pathname.split("/")[3];
160
+ const events = await store.listRetrievals(projectId);
161
+ return json(res, events);
162
+ }
163
+
164
+ // Static dashboard
165
+ if (pathname === "/" || pathname === "/index.html") {
166
+ const htmlPath = path.join(import.meta.dirname ?? ".", "dashboard.html");
167
+ if (fs.existsSync(htmlPath)) {
168
+ res.writeHead(200, { "Content-Type": "text/html" });
169
+ return res.end(fs.readFileSync(htmlPath, "utf-8"));
170
+ }
171
+ res.writeHead(200, { "Content-Type": "text/html" });
172
+ return res.end(DASHBOARD_HTML);
173
+ }
174
+
175
+ json(res, { error: "not found", path: pathname }, 404);
176
+ } catch (e: any) {
177
+ json(res, { error: e.message ?? String(e), stack: e.stack }, 500);
178
+ }
179
+ });
180
+
181
+ const DASHBOARD_HTML = `<!doctype html>
182
+ <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
183
+ <title>AgentMind Dashboard</title>
184
+ <script src="https://cdn.tailwindcss.com"></script>
185
+ <style>
186
+ ::-webkit-scrollbar{width:6px;height:6px} ::-webkit-scrollbar-thumb{background:#3f3f46;border-radius:3px}
187
+ </style>
188
+ </head><body class="bg-zinc-950 text-zinc-100 min-h-screen">
189
+ <div class="max-w-7xl mx-auto p-6">
190
+ <header class="flex items-center justify-between mb-6">
191
+ <h1 class="text-2xl font-bold tracking-tight">AgentMind <span class="text-zinc-500 font-normal">localhost</span></h1>
192
+ <div class="flex gap-2">
193
+ <select id="projectSelect" class="bg-zinc-900 border border-zinc-800 rounded px-3 py-1.5 text-sm"></select>
194
+ <button onclick="loadProjects()" class="bg-zinc-800 hover:bg-zinc-700 px-3 py-1.5 rounded text-sm">Refresh</button>
195
+ </div>
196
+ </header>
197
+
198
+ <!-- Overview -->
199
+ <div id="overview" class="grid grid-cols-4 gap-4 mb-6">
200
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4"><div class="text-xs text-zinc-500">Total Memories</div><div id="stat-total" class="text-2xl font-mono">-</div></div>
201
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4"><div class="text-xs text-zinc-500">Active / Archived</div><div id="stat-active" class="text-2xl font-mono">-</div></div>
202
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4"><div class="text-xs text-zinc-500">By Type</div><div id="stat-types" class="text-xs font-mono">-</div></div>
203
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4"><div class="text-xs text-zinc-500">Embedding</div><div class="text-xs font-mono">Local hash 256d<br>pgvector ready</div></div>
204
+ </div>
205
+
206
+ <!-- Tabs -->
207
+ <div class="flex gap-2 mb-4 border-b border-zinc-800">
208
+ <button onclick="showTab('memories')" id="tab-memories" class="px-4 py-2 text-sm font-medium border-b-2 border-white">Memories</button>
209
+ <button onclick="showTab('inspector')" id="tab-inspector" class="px-4 py-2 text-sm font-medium border-b-2 border-transparent text-zinc-400">Context Inspector</button>
210
+ <button onclick="showTab('retrieval')" id="tab-retrieval" class="px-4 py-2 text-sm font-medium border-b-2 border-transparent text-zinc-400">Retrieval Logs</button>
211
+ <button onclick="showTab('consolidate')" id="tab-consolidate" class="px-4 py-2 text-sm font-medium border-b-2 border-transparent text-zinc-400">Consolidation</button>
212
+ </div>
213
+
214
+ <!-- Memories -->
215
+ <div id="view-memories">
216
+ <div class="flex gap-2 mb-3">
217
+ <input id="memSearch" placeholder="Search memories..." class="flex-1 bg-zinc-900 border border-zinc-800 rounded px-3 py-2 text-sm" oninput="filterMemories()">
218
+ <select id="typeFilter" class="bg-zinc-900 border border-zinc-800 rounded px-3 py-2 text-sm" onchange="filterMemories()">
219
+ <option value="">All types</option><option>fact</option><option>decision</option><option>knowledge</option><option>preference</option><option>procedure</option><option>task</option><option>summary</option>
220
+ </select>
221
+ <button onclick="loadMemories()" class="bg-white text-black px-4 py-2 rounded text-sm font-medium">Search</button>
222
+ </div>
223
+ <div id="memoryList" class="grid gap-3"></div>
224
+ </div>
225
+
226
+ <!-- Inspector -->
227
+ <div id="view-inspector" class="hidden">
228
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4 mb-4">
229
+ <div class="text-sm font-medium mb-2">Ask your memory</div>
230
+ <div class="flex gap-2">
231
+ <input id="inspectorQuery" placeholder="What database did we decide to use?" class="flex-1 bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm">
232
+ <button onclick="runInspector()" class="bg-white text-black px-4 py-2 rounded text-sm font-medium">Run</button>
233
+ </div>
234
+ </div>
235
+ <div class="grid grid-cols-2 gap-4">
236
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4">
237
+ <div class="text-xs text-zinc-500 mb-2">RETRIEVED MEMORIES</div>
238
+ <div id="inspectorRetrieved" class="space-y-2 text-sm"></div>
239
+ </div>
240
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4">
241
+ <div class="text-xs text-zinc-500 mb-2">FINAL CONTEXT <span id="inspectorTokens" class="font-mono"></span></div>
242
+ <pre id="inspectorContext" class="text-xs font-mono whitespace-pre-wrap bg-zinc-950 p-3 rounded"></pre>
243
+ </div>
244
+ </div>
245
+ </div>
246
+
247
+ <!-- Retrieval -->
248
+ <div id="view-retrieval" class="hidden">
249
+ <div id="retrievalList" class="space-y-2"></div>
250
+ </div>
251
+
252
+ <!-- Consolidate -->
253
+ <div id="view-consolidate" class="hidden">
254
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4">
255
+ <button onclick="runConsolidate()" class="bg-white text-black px-4 py-2 rounded text-sm font-medium">Run consolidation</button>
256
+ <div id="consolidateResult" class="mt-4 text-sm font-mono"></div>
257
+ </div>
258
+ </div>
259
+
260
+ <!-- Memory detail modal -->
261
+ <dialog id="detailModal" class="bg-zinc-900 text-zinc-100 p-6 rounded-lg max-w-2xl w-full border border-zinc-800">
262
+ <div id="detailContent"></div>
263
+ <button onclick="this.closest('dialog').close()" class="mt-4 bg-zinc-800 px-4 py-2 rounded text-sm">Close</button>
264
+ </dialog>
265
+ </div>
266
+
267
+ <script>
268
+ let currentProject = null;
269
+ let allMemories = [];
270
+
271
+ async function loadProjects(){
272
+ const projects = await fetch('/api/projects').then(r=>r.json());
273
+ const sel = document.getElementById('projectSelect');
274
+ sel.innerHTML = projects.map(p=>\`<option value="\${p.id}">\${p.name}</option>\`).join('');
275
+ if(projects[0]){ currentProject = projects[0].id; sel.value = currentProject; loadOverview(); loadMemories(); loadRetrievals(); }
276
+ sel.onchange = e=>{ currentProject = e.target.value; loadOverview(); loadMemories(); loadRetrievals(); };
277
+ }
278
+ async function loadOverview(){
279
+ if(!currentProject) return;
280
+ const stats = await fetch('/api/projects/'+currentProject+'/stats').then(r=>r.json());
281
+ document.getElementById('stat-total').textContent = stats.total;
282
+ document.getElementById('stat-active').textContent = stats.active + ' / ' + stats.archived;
283
+ document.getElementById('stat-types').innerHTML = Object.entries(stats.byType).map(([k,v])=>\`\${k}: \${v}\`).join('<br>');
284
+ }
285
+ async function loadMemories(){
286
+ if(!currentProject) return;
287
+ const memories = await fetch('/api/projects/'+currentProject+'/memories').then(r=>r.json());
288
+ allMemories = memories;
289
+ renderMemories(memories);
290
+ }
291
+ function renderMemories(memories){
292
+ const typeFilter = document.getElementById('typeFilter').value;
293
+ const q = document.getElementById('memSearch').value.toLowerCase();
294
+ let filtered = memories;
295
+ if(typeFilter) filtered = filtered.filter(m=>m.type===typeFilter);
296
+ if(q) filtered = filtered.filter(m=>m.content.toLowerCase().includes(q));
297
+ document.getElementById('memoryList').innerHTML = filtered.map(m=>\`
298
+ <div class="bg-zinc-900 border border-zinc-800 rounded-lg p-4 hover:border-zinc-700 cursor-pointer" onclick="showDetail('\${m.id}')">
299
+ <div class="flex items-center gap-2 mb-1">
300
+ <span class="text-xs px-2 py-0.5 rounded bg-zinc-800 border border-zinc-700">\${m.type}</span>
301
+ <span class="text-xs \${m.status==='active'?'text-emerald-400':m.status==='superseded'?'text-amber-400':'text-zinc-500'}">\${m.status}</span>
302
+ <span class="text-xs text-zinc-500 ml-auto">\${m.importance.toFixed(2)} · \${m.accessCount} uses</span>
303
+ </div>
304
+ <div class="text-sm">\${m.content}</div>
305
+ <div class="text-xs text-zinc-500 mt-1">\${new Date(m.createdAt).toLocaleDateString()} · \${m.archived?'archived':''}</div>
306
+ </div>\`).join('') || '<div class="text-sm text-zinc-500">No memories</div>';
307
+ }
308
+ function filterMemories(){ renderMemories(allMemories); }
309
+ async function showDetail(id){
310
+ const m = await fetch('/api/memories/'+id).then(r=>r.json());
311
+ document.getElementById('detailContent').innerHTML = \`
312
+ <div class="text-xs text-zinc-500">\${m.type} · \${m.status}</div>
313
+ <div class="text-lg mt-2">\${m.content}</div>
314
+ <div class="grid grid-cols-2 gap-4 mt-4 text-xs font-mono">
315
+ <div>Importance: \${m.importance}<br>Confidence: \${m.confidence}<br>Status: \${m.status}<br>Access: \${m.accessCount}</div>
316
+ <div>Created: \${new Date(m.createdAt).toLocaleString()}<br>Updated: \${new Date(m.updatedAt).toLocaleString()}<br>Project: \${m.projectId.slice(0,8)}</div>
317
+ </div>
318
+ <div class="mt-3 text-xs">\${m.source ? 'Source: '+m.source.content : ''}</div>
319
+ <div class="mt-3 text-xs">Embedding: \${m.embedding ? m.embedding.model + ' ' + m.embedding.dimensions + 'd preview ' + JSON.stringify(m.embedding.preview) : 'none'}</div>
320
+ <button onclick="deleteMemory('\${m.id}')" class="mt-4 bg-red-900 hover:bg-red-800 px-3 py-1.5 rounded text-sm">Delete</button>
321
+ \`;
322
+ document.getElementById('detailModal').showModal();
323
+ }
324
+ async function deleteMemory(id){ await fetch('/api/memories/'+id,{method:'DELETE'}); document.getElementById('detailModal').close(); loadMemories(); loadOverview(); }
325
+ function showTab(name){
326
+ ['memories','inspector','retrieval','consolidate'].forEach(t=>{
327
+ document.getElementById('view-'+t).classList.toggle('hidden', t!==name);
328
+ document.getElementById('tab-'+t).className = t===name ? 'px-4 py-2 text-sm font-medium border-b-2 border-white' : 'px-4 py-2 text-sm font-medium border-b-2 border-transparent text-zinc-400';
329
+ });
330
+ if(name==='retrieval') loadRetrievals();
331
+ }
332
+ async function runInspector(){
333
+ const query = document.getElementById('inspectorQuery').value;
334
+ if(!query) return;
335
+ const res = await fetch('/api/context',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({project: currentProject, query})}).then(r=>r.json());
336
+ document.getElementById('inspectorTokens').textContent = '['+res.tokenCount+' tokens]';
337
+ document.getElementById('inspectorContext').textContent = res.text || '(no relevant memories)';
338
+ document.getElementById('inspectorRetrieved').innerHTML = (res.retrieval||[]).map((r,i)=>\`
339
+ <div class="bg-zinc-950 border border-zinc-800 rounded p-2">
340
+ <div class="flex justify-between text-xs"><span>#\${i+1} \${r.type}</span><span class="font-mono">score \${r.score.toFixed(3)} · sem \${r.semanticScore.toFixed(2)} · kw \${r.keywordScore.toFixed(2)}</span></div>
341
+ <div class="text-sm mt-1">\${r.content}</div>
342
+ </div>\`).join('');
343
+ }
344
+ async function loadRetrievals(){
345
+ if(!currentProject) return;
346
+ const events = await fetch('/api/retrievals/'+currentProject).then(r=>r.json());
347
+ document.getElementById('retrievalList').innerHTML = events.map(e=>\`
348
+ <div class="bg-zinc-900 border border-zinc-800 rounded p-3">
349
+ <div class="text-sm">Query: "\${e.query}"</div>
350
+ <div class="text-xs text-zinc-500">\${new Date(e.createdAt).toLocaleString()} · \${e.retrievedMemoryIds.length} memories · \${e.finalContextTokens} tokens</div>
351
+ <div class="text-xs font-mono mt-1">scores: \${JSON.stringify(e.scores).slice(0,120)}</div>
352
+ </div>\`).join('') || '<div class="text-sm text-zinc-500">No retrievals yet</div>';
353
+ }
354
+ async function runConsolidate(){
355
+ const res = await fetch('/api/projects/'+currentProject+'/consolidate',{method:'POST'}).then(r=>r.json());
356
+ document.getElementById('consolidateResult').textContent = JSON.stringify(res,null,2);
357
+ loadMemories(); loadOverview();
358
+ }
359
+
360
+ loadProjects();
361
+ </script>
362
+ </body></html>`;
363
+
364
+ server.listen(PORT, () => {
365
+ console.log(`AgentMind dashboard at http://localhost:${PORT}`);
366
+ console.log(`DB: ${DB_PATH}`);
367
+ });
368
+
369
+ export { server, store };