@justfortytwo/memory 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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -0
  3. package/dist/contract.d.ts +19 -0
  4. package/dist/contract.js +41 -0
  5. package/dist/contract.js.map +1 -0
  6. package/dist/db.d.ts +11 -0
  7. package/dist/db.js +33 -0
  8. package/dist/db.js.map +1 -0
  9. package/dist/dispatch.d.ts +3 -0
  10. package/dist/dispatch.js +43 -0
  11. package/dist/dispatch.js.map +1 -0
  12. package/dist/embedder.d.ts +18 -0
  13. package/dist/embedder.js +44 -0
  14. package/dist/embedder.js.map +1 -0
  15. package/dist/enrichment.d.ts +58 -0
  16. package/dist/enrichment.js +109 -0
  17. package/dist/enrichment.js.map +1 -0
  18. package/dist/gate-approval-store.d.ts +17 -0
  19. package/dist/gate-approval-store.js +114 -0
  20. package/dist/gate-approval-store.js.map +1 -0
  21. package/dist/index.d.ts +16 -0
  22. package/dist/index.js +72 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/memory.d.ts +74 -0
  25. package/dist/memory.js +179 -0
  26. package/dist/memory.js.map +1 -0
  27. package/dist/migrate.d.ts +2 -0
  28. package/dist/migrate.js +40 -0
  29. package/dist/migrate.js.map +1 -0
  30. package/dist/migrations/001_init.d.ts +3 -0
  31. package/dist/migrations/001_init.js +40 -0
  32. package/dist/migrations/001_init.js.map +1 -0
  33. package/dist/migrations/002_fts.d.ts +3 -0
  34. package/dist/migrations/002_fts.js +26 -0
  35. package/dist/migrations/002_fts.js.map +1 -0
  36. package/dist/migrations/003_approvals.d.ts +3 -0
  37. package/dist/migrations/003_approvals.js +37 -0
  38. package/dist/migrations/003_approvals.js.map +1 -0
  39. package/dist/tools.d.ts +2 -0
  40. package/dist/tools.js +66 -0
  41. package/dist/tools.js.map +1 -0
  42. package/package.json +68 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Enrico Deleo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # @justfortytwo/memory
2
+
3
+ A standalone **semantic-memory MCP server**. It stores text "memories" with
4
+ free-form provenance and recalls them by meaning (vector search), by keyword
5
+ (FTS5), or by structured filter. Backed by SQLite + [`sqlite-vec`], embeddings
6
+ from a local [Ollama] model.
7
+
8
+ It is **persona-agnostic**: no journal/persona/approval coupling, just a generic
9
+ memory store and tool surface. It can be used on its own, or as a Claude Code
10
+ plugin.
11
+
12
+ [`sqlite-vec`]: https://github.com/asg017/sqlite-vec
13
+ [Ollama]: https://ollama.com
14
+
15
+ ## What it stores
16
+
17
+ A memory is `content` plus provenance:
18
+
19
+ | field | meaning |
20
+ |-------|---------|
21
+ | `content` | the text (embedded for recall) |
22
+ | `source` | where it came from — free-form (`owner`, `web`, `tool:foo`) |
23
+ | `observed` | how it was observed — free-form (`stated`, `inferred`, `imported`) |
24
+ | `date` | ISO date the memory pertains to (defaults to today, UTC) |
25
+ | `tags` | free-form tags for filtering |
26
+ | `supersedes` | id of a prior memory this one replaces (history is **kept**) |
27
+
28
+ Recall is hybrid: `recall` (semantic), `lexical` (FTS5 keyword), and `query`
29
+ (structured). `reindex` + `recall_docs` index/search a directory of markdown
30
+ documents separately from the memory store.
31
+
32
+ ## MCP tools
33
+
34
+ The server registers under the id **`fortytwo-memory`**, so a consumer calls the
35
+ tools as `mcp__fortytwo-memory__<tool>`:
36
+
37
+ | tool | description |
38
+ |------|-------------|
39
+ | `store` | store a memory (+ provenance); set `supersedes` to replace one |
40
+ | `query` | structured query (source/observed/tag/time; live rows by default) |
41
+ | `recall` | semantic top-k recall by meaning |
42
+ | `recall_docs` | semantic recall over reindexed markdown |
43
+ | `lexical` | full-text keyword search (FTS5) |
44
+ | `reindex` | self-heal the doc index from a markdown directory |
45
+ | `export_range` | render a date range of memories to markdown |
46
+
47
+ ### Contract version
48
+
49
+ Consumers depend on the **tool surface**, not the internals. The contract is
50
+ versioned:
51
+
52
+ ```ts
53
+ import { MEMORY_TOOL_CONTRACT_VERSION, memoryToolContract } from '@justfortytwo/memory/contract';
54
+ ```
55
+
56
+ - A **major** change to a tool name, its required inputs, or its result shape is
57
+ a **contract break** → bump `MEMORY_TOOL_CONTRACT_VERSION`. Siblings pin a
58
+ caret range on `@justfortytwo/memory`, so a major bump forces an explicit
59
+ opt-in.
60
+ - Additive changes (new optional inputs, new tools) do **not** bump it.
61
+
62
+ `memoryToolContract` is the authoritative human-readable list of tools and their
63
+ guarantees, kept in sync with the wire schema in `src/tools.ts`.
64
+
65
+ ## Embedder
66
+
67
+ The default embedder is **`OllamaEmbedder`**, which calls a local Ollama
68
+ `/api/embeddings` endpoint.
69
+
70
+ ```bash
71
+ OLLAMA_BASE_URL=http://localhost:11434 # default
72
+ EMBED_MODEL=qwen3-embedding:0.6b # default model (1024-dim)
73
+ ```
74
+
75
+ Pull the model once:
76
+
77
+ ```bash
78
+ ollama pull qwen3-embedding:0.6b
79
+ ```
80
+
81
+ If `EMBED_MODEL` is **unset**, the server falls back to a deterministic,
82
+ dependency-free **`FakeEmbedder`** — useful for tests, CI, and first-run smoke
83
+ checks with zero infra. (The vector tables are fixed at 1024-dim; a model with a
84
+ different dimensionality requires a schema change.)
85
+
86
+ ## Standalone usage
87
+
88
+ ```bash
89
+ # build (once); the server runs the built JS, not TS
90
+ npm run build
91
+
92
+ # apply migrations to the DB (DB_PATH or ./memory.db)
93
+ DB_PATH=./memory.db npm run migrate
94
+
95
+ # run the MCP server over stdio
96
+ DB_PATH=./memory.db EMBED_MODEL=qwen3-embedding:0.6b fortytwo-memory
97
+ ```
98
+
99
+ The `bin` is `fortytwo-memory` → `dist/index.js`. You can also `npx -y
100
+ @justfortytwo/memory` once published.
101
+
102
+ ### As a library
103
+
104
+ ```ts
105
+ import { openDb, runMigrations, OllamaEmbedder, store, recall } from '@justfortytwo/memory';
106
+
107
+ const h = openDb('memory.db');
108
+ await runMigrations(h.k);
109
+ const embedder = new OllamaEmbedder();
110
+
111
+ await store(h, embedder, { content: 'the deploy script lives in scripts/deploy.sh', source: 'owner', observed: 'stated' });
112
+ const hits = await recall(h, embedder, 'how do I deploy?', 5);
113
+ ```
114
+
115
+ ## As a Claude Code plugin
116
+
117
+ `.claude-plugin/plugin.json` declares the plugin; `.mcp.json` registers the
118
+ `fortytwo-memory` server. By default it launches via `npx`:
119
+
120
+ ```jsonc
121
+ {
122
+ "mcpServers": {
123
+ "fortytwo-memory": {
124
+ "command": "npx",
125
+ "args": ["-y", "@justfortytwo/memory"],
126
+ "env": {
127
+ "OLLAMA_BASE_URL": "http://localhost:11434",
128
+ "EMBED_MODEL": "qwen3-embedding:0.6b",
129
+ "DB_PATH": "${CLAUDE_PLUGIN_DATA}/memory.db"
130
+ }
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ `${CLAUDE_PLUGIN_DATA}` survives plugin updates, so the DB persists across
137
+ upgrades. When developing from source, build first (`npm run build`) and swap
138
+ the command to `node` with args `["${CLAUDE_PLUGIN_ROOT}/dist/index.js"]`.
139
+ Claude Code does **not** build MCP servers — they run via npm/npx.
140
+
141
+ ## Continuous enrichment
142
+
143
+ `enrich(h, embedder, candidates)` folds a batch of candidate memories into the
144
+ store: it drops low-salience candidates, **dedupes** near-duplicates by meaning,
145
+ and writes the survivors with provenance — honoring an explicit `supersedes` to
146
+ replace a stale belief (history is kept, never a silent overwrite).
147
+ `enrichFromTurn(h, embedder, turn, extractor)` runs an injected `SalienceExtractor`
148
+ and feeds its candidates to `enrich`.
149
+
150
+ The salience extractor itself is model-driven and lives in the sibling
151
+ **`@justfortytwo/salience`** engine (a `SalienceExtractor` with an injected
152
+ `LlmClient`) — memory owns only the dedupe + write, never the model call.
153
+
154
+ ## Peer seams
155
+
156
+ memory depends on two sibling packages **one-directionally** (declared as optional
157
+ peers, no cycle):
158
+
159
+ - **`@justfortytwo/gate`** — memory ships `GateApprovalStore`
160
+ (`src/gate-approval-store.ts`), a durable SQLite-backed implementation of
161
+ gate's `ApprovalStore` + `AuditLogger` interfaces. Pass it to gate's
162
+ `decide(..., { store, audit })` to back the safety gate's one-shot approvals
163
+ with memory's db instead of the gate's standalone JSONL store.
164
+ - **`@justfortytwo/salience`** — the model-driven salience extractor injected
165
+ into `enrichFromTurn` (see above).
166
+
167
+ ## Development
168
+
169
+ ```bash
170
+ npm run build # tsc
171
+ npm test # vitest run
172
+ npm run test:watch # vitest
173
+ ```
174
+
175
+ Set `RUN_OLLAMA_TESTS=1` to run the opt-in live-Ollama embedder test.
176
+
177
+ ## License
178
+
179
+ MIT © 2026 Enrico Deleo
180
+
181
+ ---
182
+
183
+ Created and maintained by [**Enrico Deleo**](https://enricodeleo.com).
@@ -0,0 +1,19 @@
1
+ /** Bump on any breaking change to the tool surface below. */
2
+ export declare const MEMORY_TOOL_CONTRACT_VERSION = 1;
3
+ /** The MCP server id under which these tools are registered. */
4
+ export declare const MEMORY_SERVER_ID = "fortytwo-memory";
5
+ /** Fully-qualified MCP tool names a consumer invokes. */
6
+ export declare const MEMORY_MCP_TOOLS: readonly ["mcp__fortytwo-memory__store", "mcp__fortytwo-memory__query", "mcp__fortytwo-memory__recall", "mcp__fortytwo-memory__recall_docs", "mcp__fortytwo-memory__lexical", "mcp__fortytwo-memory__reindex", "mcp__fortytwo-memory__export_range"];
7
+ export type MemoryMcpTool = (typeof MEMORY_MCP_TOOLS)[number];
8
+ /** Bare tool names (server-local, without the mcp__<server>__ prefix). */
9
+ export interface MemoryToolSpec {
10
+ /** Bare tool name as declared in the MCP ListTools response. */
11
+ name: string;
12
+ /** One-line human description of the tool's contract. */
13
+ summary: string;
14
+ }
15
+ /**
16
+ * Documented contract for each tool. Kept in sync with tools.ts (the wire
17
+ * schema). This is the authoritative human-readable list siblings code against.
18
+ */
19
+ export declare const memoryToolContract: readonly MemoryToolSpec[];
@@ -0,0 +1,41 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Cross-package contract for @justfortytwo/memory.
3
+ //
4
+ // Siblings depend on this server through a STABLE tool surface, not its
5
+ // internals. The contract version is the coordination point:
6
+ // - A MAJOR bump (breaking change to a tool name, its required inputs, or its
7
+ // result shape) is a CONTRACT BREAK. Siblings pin a caret range on
8
+ // @justfortytwo/memory; a major bump forces them to opt in.
9
+ // - Additive changes (new optional inputs, new tools) do NOT bump the version.
10
+ //
11
+ // The MCP tools are namespaced by the registered server id "fortytwo-memory"
12
+ // (see .mcp.json), so a consumer calls them as `mcp__fortytwo-memory__<tool>`.
13
+ // ---------------------------------------------------------------------------
14
+ /** Bump on any breaking change to the tool surface below. */
15
+ export const MEMORY_TOOL_CONTRACT_VERSION = 1;
16
+ /** The MCP server id under which these tools are registered. */
17
+ export const MEMORY_SERVER_ID = 'fortytwo-memory';
18
+ /** Fully-qualified MCP tool names a consumer invokes. */
19
+ export const MEMORY_MCP_TOOLS = [
20
+ 'mcp__fortytwo-memory__store',
21
+ 'mcp__fortytwo-memory__query',
22
+ 'mcp__fortytwo-memory__recall',
23
+ 'mcp__fortytwo-memory__recall_docs',
24
+ 'mcp__fortytwo-memory__lexical',
25
+ 'mcp__fortytwo-memory__reindex',
26
+ 'mcp__fortytwo-memory__export_range',
27
+ ];
28
+ /**
29
+ * Documented contract for each tool. Kept in sync with tools.ts (the wire
30
+ * schema). This is the authoritative human-readable list siblings code against.
31
+ */
32
+ export const memoryToolContract = [
33
+ { name: 'store', summary: 'Store a memory (content + free-form provenance) and embed it for recall. Supports SUPERSEDE.' },
34
+ { name: 'query', summary: 'Structured query over the memory store (source/observed/tag/time, live-only by default).' },
35
+ { name: 'recall', summary: 'Semantic top-k recall over the memory store by meaning.' },
36
+ { name: 'recall_docs', summary: 'Semantic top-k recall over reindexed markdown documents (the doc_vec index).' },
37
+ { name: 'lexical', summary: 'Full-text keyword search over the memory store (FTS5).' },
38
+ { name: 'reindex', summary: 'Self-heal the doc recall index from a directory of markdown files.' },
39
+ { name: 'export_range', summary: 'Render a date range of memories to markdown (for debugging/export).' },
40
+ ];
41
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mDAAmD;AACnD,EAAE;AACF,wEAAwE;AACxE,6DAA6D;AAC7D,gFAAgF;AAChF,uEAAuE;AACvE,gEAAgE;AAChE,iFAAiF;AACjF,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,8EAA8E;AAE9E,6DAA6D;AAC7D,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC;AAE9C,gEAAgE;AAChE,MAAM,CAAC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAElD,yDAAyD;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,6BAA6B;IAC7B,6BAA6B;IAC7B,8BAA8B;IAC9B,mCAAmC;IACnC,+BAA+B;IAC/B,+BAA+B;IAC/B,oCAAoC;CAC5B,CAAC;AAYX;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA8B;IAC3D,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,8FAA8F,EAAE;IAC1H,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,0FAA0F,EAAE;IACtH,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,yDAAyD,EAAE;IACtF,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,8EAA8E,EAAE;IAChH,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,wDAAwD,EAAE;IACtF,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,oEAAoE,EAAE;IAClG,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,qEAAqE,EAAE;CAChG,CAAC"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import Database from 'better-sqlite3';
2
+ import { type Knex } from 'knex';
3
+ /** Embedding dimensionality. qwen3-embedding:0.6b emits 1024-dim vectors. */
4
+ export declare const EMBED_DIM = 1024;
5
+ export interface DbHandles {
6
+ /** Raw handle: sqlite-vec + FTS5 ops, and atomic relational+vector writes. */
7
+ raw: Database.Database;
8
+ /** Knex handle: migrations + portable relational reads/writes. */
9
+ k: Knex;
10
+ }
11
+ export declare function openDb(dbPath: string): DbHandles;
package/dist/db.js ADDED
@@ -0,0 +1,33 @@
1
+ import Database from 'better-sqlite3';
2
+ import knexPkg from 'knex';
3
+ import * as sqliteVec from 'sqlite-vec';
4
+ // knex ships as CommonJS; under NodeNext ESM (`node dist/index.js`) a named
5
+ // import `{ knex }` throws "Named export 'knex' not found". Default-import the
6
+ // namespace and destructure — identical binding, ESM-safe. (vitest interops the
7
+ // named form fine, which is why db.test passes but the raw-node server did not.)
8
+ const { knex } = knexPkg;
9
+ /** Embedding dimensionality. qwen3-embedding:0.6b emits 1024-dim vectors. */
10
+ export const EMBED_DIM = 1024;
11
+ export function openDb(dbPath) {
12
+ const raw = new Database(dbPath);
13
+ raw.pragma('journal_mode = WAL');
14
+ raw.pragma('busy_timeout = 5000'); // wait up to 5s on writer contention
15
+ sqliteVec.load(raw); // registers the vec0 module + scalar helpers
16
+ // vec0 tables live on the raw handle: sqlite-vec is loaded here, NOT on Knex's
17
+ // own connection, so Knex migrations cannot create vec0 tables. (FTS5, which is
18
+ // compiled into SQLite, and the relational schema CAN be Knex migrations.)
19
+ //
20
+ // `memory_vec` indexes the generic memory store; `doc_vec` indexes reindexed
21
+ // markdown documents. (Generic rename of the original assistant's `journal_vec`.)
22
+ const ddl = (sql) => raw.exec(sql);
23
+ ddl(`CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(embedding float[${EMBED_DIM}])`);
24
+ ddl(`CREATE VIRTUAL TABLE IF NOT EXISTS doc_vec USING vec0(embedding float[${EMBED_DIM}])`);
25
+ const k = knex({
26
+ client: 'better-sqlite3',
27
+ connection: { filename: dbPath },
28
+ useNullAsDefault: true,
29
+ pool: { min: 1, max: 1 },
30
+ });
31
+ return { raw, k };
32
+ }
33
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,OAAsB,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,SAAS,MAAM,YAAY,CAAC;AAExC,4EAA4E;AAC5E,+EAA+E;AAC/E,gFAAgF;AAChF,iFAAiF;AACjF,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;AAEzB,6EAA6E;AAC7E,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC;AAS9B,MAAM,UAAU,MAAM,CAAC,MAAc;IACnC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACjC,GAAG,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,qCAAqC;IACxE,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,6CAA6C;IAElE,+EAA+E;IAC/E,gFAAgF;IAChF,2EAA2E;IAC3E,EAAE;IACF,6EAA6E;IAC7E,kFAAkF;IAClF,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3C,GAAG,CAAC,4EAA4E,SAAS,IAAI,CAAC,CAAC;IAC/F,GAAG,CAAC,yEAAyE,SAAS,IAAI,CAAC,CAAC;IAE5F,MAAM,CAAC,GAAG,IAAI,CAAC;QACb,MAAM,EAAE,gBAAgB;QACxB,UAAU,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;QAChC,gBAAgB,EAAE,IAAI;QACtB,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;KACzB,CAAC,CAAC;IAEH,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AACpB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { DbHandles } from './db.js';
2
+ import type { Embedder } from './embedder.js';
3
+ export declare function callTool(h: DbHandles, embedder: Embedder, name: string, args: Record<string, unknown>): Promise<unknown>;
@@ -0,0 +1,43 @@
1
+ import { store, query, recall, recallDocs, lexical, reindex, exportRange } from './memory.js';
2
+ // Route a bare MCP tool name + arguments to the memory store and return the RAW
3
+ // result. The index.ts CallTool handler wraps this in the MCP content envelope;
4
+ // keeping the routing here (free of any MCP transport) makes the tool surface
5
+ // unit-testable. Throws on an unknown tool.
6
+ export async function callTool(h, embedder, name, args) {
7
+ const a = args;
8
+ switch (name) {
9
+ case 'store':
10
+ return store(h, embedder, {
11
+ content: String(a.content),
12
+ source: a.source,
13
+ observed: a.observed,
14
+ date: a.date,
15
+ tags: Array.isArray(a.tags) ? a.tags : undefined,
16
+ meta: a.meta,
17
+ supersedes: a.supersedes,
18
+ });
19
+ case 'query':
20
+ return query(h, {
21
+ source: a.source,
22
+ observed: a.observed,
23
+ tag: a.tag,
24
+ since: a.since,
25
+ until: a.until,
26
+ liveOnly: a.live_only,
27
+ limit: a.limit,
28
+ });
29
+ case 'recall':
30
+ return recall(h, embedder, String(a.text), a.k ?? 5);
31
+ case 'recall_docs':
32
+ return recallDocs(h, embedder, String(a.text), a.k ?? 5);
33
+ case 'lexical':
34
+ return lexical(h, String(a.text), a.k ?? 50);
35
+ case 'reindex':
36
+ return reindex(h, embedder, String(a.root));
37
+ case 'export_range':
38
+ return exportRange(h, String(a.since), String(a.until));
39
+ default:
40
+ throw new Error(`Unknown tool: ${name}`);
41
+ }
42
+ }
43
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE9F,gFAAgF;AAChF,gFAAgF;AAChF,8EAA8E;AAC9E,4CAA4C;AAC5C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,CAAY,EACZ,QAAkB,EAClB,IAAY,EACZ,IAA6B;IAE7B,MAAM,CAAC,GAAG,IAAI,CAAC;IACf,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,OAAO;YACV,OAAO,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE;gBACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;gBAC1B,MAAM,EAAE,CAAC,CAAC,MAA4B;gBACtC,QAAQ,EAAE,CAAC,CAAC,QAA8B;gBAC1C,IAAI,EAAE,CAAC,CAAC,IAA0B;gBAClC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,IAAiB,CAAC,CAAC,CAAC,SAAS;gBAC9D,IAAI,EAAE,CAAC,CAAC,IAA2C;gBACnD,UAAU,EAAE,CAAC,CAAC,UAAgC;aAC/C,CAAC,CAAC;QACL,KAAK,OAAO;YACV,OAAO,KAAK,CAAC,CAAC,EAAE;gBACd,MAAM,EAAE,CAAC,CAAC,MAA4B;gBACtC,QAAQ,EAAE,CAAC,CAAC,QAA8B;gBAC1C,GAAG,EAAE,CAAC,CAAC,GAAyB;gBAChC,KAAK,EAAE,CAAC,CAAC,KAA2B;gBACpC,KAAK,EAAE,CAAC,CAAC,KAA2B;gBACpC,QAAQ,EAAE,CAAC,CAAC,SAAgC;gBAC5C,KAAK,EAAE,CAAC,CAAC,KAA2B;aACrC,CAAC,CAAC;QACL,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAG,CAAC,CAAC,CAAY,IAAI,CAAC,CAAC,CAAC;QACnE,KAAK,aAAa;YAChB,OAAO,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAG,CAAC,CAAC,CAAY,IAAI,CAAC,CAAC,CAAC;QACvE,KAAK,SAAS;YACZ,OAAO,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAG,CAAC,CAAC,CAAY,IAAI,EAAE,CAAC,CAAC;QAC3D,KAAK,SAAS;YACZ,OAAO,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,KAAK,cAAc;YACjB,OAAO,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1D;YACE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type Vec = Float32Array;
2
+ export interface Embedder {
3
+ embed(text: string): Promise<Vec>;
4
+ }
5
+ export declare function vecToBuffer(v: Vec): Buffer;
6
+ /** Deterministic, dependency-free embedder for hermetic unit tests. */
7
+ export declare class FakeEmbedder implements Embedder {
8
+ private dim;
9
+ constructor(dim?: number);
10
+ embed(text: string): Promise<Vec>;
11
+ }
12
+ /** Calls a local Ollama /api/embeddings endpoint. */
13
+ export declare class OllamaEmbedder implements Embedder {
14
+ private model;
15
+ private baseUrl;
16
+ constructor(model?: string, baseUrl?: string);
17
+ embed(text: string): Promise<Vec>;
18
+ }
@@ -0,0 +1,44 @@
1
+ export function vecToBuffer(v) {
2
+ return Buffer.from(v.buffer, v.byteOffset, v.byteLength);
3
+ }
4
+ /** Deterministic, dependency-free embedder for hermetic unit tests. */
5
+ export class FakeEmbedder {
6
+ dim;
7
+ constructor(dim = 1024) {
8
+ this.dim = dim;
9
+ }
10
+ async embed(text) {
11
+ const v = new Float32Array(this.dim);
12
+ let h = 2166136261 >>> 0;
13
+ for (let i = 0; i < text.length; i++) {
14
+ h ^= text.charCodeAt(i);
15
+ h = Math.imul(h, 16777619);
16
+ }
17
+ for (let i = 0; i < this.dim; i++) {
18
+ h = Math.imul(h ^ (h >>> 13), 16777619);
19
+ v[i] = (h >>> 0) % 1000 / 1000;
20
+ }
21
+ return v;
22
+ }
23
+ }
24
+ /** Calls a local Ollama /api/embeddings endpoint. */
25
+ export class OllamaEmbedder {
26
+ model;
27
+ baseUrl;
28
+ constructor(model = 'qwen3-embedding:0.6b', baseUrl = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434') {
29
+ this.model = model;
30
+ this.baseUrl = baseUrl;
31
+ }
32
+ async embed(text) {
33
+ const res = await fetch(`${this.baseUrl}/api/embeddings`, {
34
+ method: 'POST',
35
+ headers: { 'content-type': 'application/json' },
36
+ body: JSON.stringify({ model: this.model, prompt: text }),
37
+ });
38
+ if (!res.ok)
39
+ throw new Error(`Ollama embeddings failed: ${res.status} ${await res.text()}`);
40
+ const json = (await res.json());
41
+ return Float32Array.from(json.embedding);
42
+ }
43
+ }
44
+ //# sourceMappingURL=embedder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedder.js","sourceRoot":"","sources":["../src/embedder.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,WAAW,CAAC,CAAM;IAChC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;AAC3D,CAAC;AAED,uEAAuE;AACvE,MAAM,OAAO,YAAY;IACH;IAApB,YAAoB,MAAM,IAAI;QAAV,QAAG,GAAH,GAAG,CAAO;IAAG,CAAC;IAClC,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC;QACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACxB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC7B,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;QACjC,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;CACF;AAED,qDAAqD;AACrD,MAAM,OAAO,cAAc;IAEf;IACA;IAFV,YACU,QAAQ,sBAAsB,EAC9B,UAAU,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,wBAAwB;QADjE,UAAK,GAAL,KAAK,CAAyB;QAC9B,YAAO,GAAP,OAAO,CAA0D;IACxE,CAAC;IACJ,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,iBAAiB,EAAE;YACxD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;SAC1D,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5F,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;QAC3D,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC3C,CAAC;CACF"}
@@ -0,0 +1,58 @@
1
+ import type { DbHandles } from './db.js';
2
+ import type { Embedder } from './embedder.js';
3
+ interface SalienceExtractor {
4
+ extractSalient(turn: {
5
+ text: string;
6
+ source?: string;
7
+ observed?: string;
8
+ date?: string;
9
+ meta?: Record<string, unknown>;
10
+ }): Promise<EnrichmentCandidate[]>;
11
+ }
12
+ /** Below this vector distance two memories are treated as near-duplicates. */
13
+ export declare const DEDUPE_DISTANCE = 0.15;
14
+ /** Drop candidates whose salience is below this score. */
15
+ export declare const SALIENCE_THRESHOLD = 0.5;
16
+ /** A distilled candidate memory produced by an (external) salience extractor. */
17
+ export interface EnrichmentCandidate {
18
+ content: string;
19
+ salience: number;
20
+ source?: string;
21
+ observed?: string;
22
+ date?: string;
23
+ tags?: string[];
24
+ meta?: Record<string, unknown>;
25
+ /**
26
+ * If set, this candidate supersedes the given memory id — an upstream
27
+ * contradiction/update judgment (e.g. from the salience salience step).
28
+ * enrich() honors it: the new row is written and the old row is flagged
29
+ * superseded (history kept, never overwritten), even past the dedupe check.
30
+ */
31
+ supersedes?: number | null;
32
+ }
33
+ export interface EnrichmentResult {
34
+ written: number[];
35
+ superseded: number[];
36
+ skipped: number;
37
+ }
38
+ /**
39
+ * Fold a batch of candidate memories into the store: dedupe, supersede stale
40
+ * beliefs (keeping history), and write the survivors with provenance.
41
+ *
42
+ * NOTE: candidates are produced UPSTREAM. This function owns dedupe + write
43
+ * only — it does not call any model.
44
+ */
45
+ export declare function enrich(h: DbHandles, embedder: Embedder, candidates: EnrichmentCandidate[]): Promise<EnrichmentResult>;
46
+ /**
47
+ * Post-turn entry point: extract salient candidates from a turn, then enrich.
48
+ *
49
+ * The extraction step is delegated to the sibling @justfortytwo/salience
50
+ * engine via an INJECTED SalienceExtractor — memory owns dedupe + write, never the
51
+ * model client. The host builds the extractor (salience's createSalienceExtractor
52
+ * with its own LlmClient) and passes it in here.
53
+ */
54
+ export declare function enrichFromTurn(h: DbHandles, embedder: Embedder, turn: {
55
+ text: string;
56
+ source?: string;
57
+ }, extractor: SalienceExtractor): Promise<EnrichmentResult>;
58
+ export {};
@@ -0,0 +1,109 @@
1
+ import { recall, store } from './memory.js';
2
+ // ===========================================================================
3
+ // Continuous enrichment — STUB.
4
+ //
5
+ // Goal: after each conversational turn, distil durable knowledge from the turn
6
+ // and fold it into the memory store WITHOUT ever silently destroying a prior
7
+ // belief. This is the write-side counterpart to recall: recall reads, enrichment
8
+ // curates what is worth remembering.
9
+ //
10
+ // PIPELINE (post-turn):
11
+ //
12
+ // 1. SALIENCE EXTRACTION
13
+ // Take the turn (user + assistant text, tool results) and extract a small
14
+ // set of candidate memories — atomic, self-contained statements worth
15
+ // keeping ("the owner's child is named X", "the deploy script lives at Y").
16
+ // Each candidate carries a salience score; below a threshold we drop it so
17
+ // the store does not fill with noise.
18
+ // The extractor is model-driven, and the LLM call is NOT owned by this
19
+ // package (a memory server must not embed a model client). The salience
20
+ // step lives in the sibling `@justfortytwo/salience` engine, which
21
+ // defines a `SalienceExtractor` (injected `LlmClient`) and returns scored
22
+ // candidates. We inject that extractor and pass its candidates IN to
23
+ // enrich(); this file owns only dedupe + write.
24
+ //
25
+ // 2. DEDUPE / SUPERSEDE (recency wins, history is kept, NEVER overwrite)
26
+ // For each candidate, semantically recall the nearest existing memories.
27
+ // - near-duplicate (distance below DEDUPE_DISTANCE) and same meaning:
28
+ // skip the write — we already know this.
29
+ // - contradiction / update of an existing belief:
30
+ // write the new memory and SUPERSEDE the old row (memory.store with
31
+ // `supersedes`). The old row is retained and flagged superseded_by,
32
+ // so the history of what we believed and when is fully auditable.
33
+ // Recency wins for live recall; nothing is deleted.
34
+ // TODO(impl): "same meaning" vs "contradiction" needs more than vector
35
+ // distance (two close vectors can be opposite facts). This likely needs
36
+ // the same sibling salience step as (1) to judge the relation.
37
+ //
38
+ // 3. WRITE (tagged provenance)
39
+ // Persist surviving candidates via memory.store, tagged with:
40
+ // - source: where the knowledge came from (e.g. the channel/actor)
41
+ // - observed: how we came to believe it (e.g. "stated" vs "inferred")
42
+ // - date: when it was observed
43
+ // Inferred memories MUST be marked observed:"inferred" so downstream
44
+ // consumers can weight stated facts over guesses.
45
+ //
46
+ // The contract: enrichment is ADDITIVE and AUDITABLE. A wrong inference can be
47
+ // superseded later; it is never silently erased.
48
+ // ===========================================================================
49
+ /** Below this vector distance two memories are treated as near-duplicates. */
50
+ export const DEDUPE_DISTANCE = 0.15;
51
+ /** Drop candidates whose salience is below this score. */
52
+ export const SALIENCE_THRESHOLD = 0.5;
53
+ /**
54
+ * Fold a batch of candidate memories into the store: dedupe, supersede stale
55
+ * beliefs (keeping history), and write the survivors with provenance.
56
+ *
57
+ * NOTE: candidates are produced UPSTREAM. This function owns dedupe + write
58
+ * only — it does not call any model.
59
+ */
60
+ export async function enrich(h, embedder, candidates) {
61
+ const written = [];
62
+ const superseded = [];
63
+ let skipped = 0;
64
+ for (const c of candidates) {
65
+ if (c.salience < SALIENCE_THRESHOLD) {
66
+ skipped++;
67
+ continue;
68
+ }
69
+ // Dedupe by meaning — unless the candidate explicitly supersedes a prior row,
70
+ // in which case the update is intentional even if it reads similar. Note:
71
+ // recall is live-only, so we dedupe against LIVE memories, not superseded
72
+ // history (a candidate matching a dead row is intentionally re-written).
73
+ if (c.supersedes == null) {
74
+ const near = await recall(h, embedder, c.content, 1);
75
+ if (near.length > 0 && near[0].distance < DEDUPE_DISTANCE) {
76
+ skipped++;
77
+ continue;
78
+ }
79
+ }
80
+ const id = await store(h, embedder, {
81
+ content: c.content,
82
+ source: c.source,
83
+ observed: c.observed,
84
+ date: c.date,
85
+ tags: c.tags,
86
+ meta: c.meta,
87
+ supersedes: c.supersedes ?? null,
88
+ });
89
+ written.push(id);
90
+ if (c.supersedes != null)
91
+ superseded.push(c.supersedes);
92
+ }
93
+ return { written, superseded, skipped };
94
+ }
95
+ /**
96
+ * Post-turn entry point: extract salient candidates from a turn, then enrich.
97
+ *
98
+ * The extraction step is delegated to the sibling @justfortytwo/salience
99
+ * engine via an INJECTED SalienceExtractor — memory owns dedupe + write, never the
100
+ * model client. The host builds the extractor (salience's createSalienceExtractor
101
+ * with its own LlmClient) and passes it in here.
102
+ */
103
+ export async function enrichFromTurn(h, embedder, turn, extractor) {
104
+ // memory owns dedupe + write; the salience extraction (the model call) is the
105
+ // injected @justfortytwo/salience engine's. We only wire the two together.
106
+ const candidates = await extractor.extractSalient(turn);
107
+ return enrich(h, embedder, candidates);
108
+ }
109
+ //# sourceMappingURL=enrichment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrichment.js","sourceRoot":"","sources":["../src/enrichment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ApprovalStore, AuditLogger, AuditEntry, PendingApproval, AddPendingInput } from '@justfortytwo/gate';
2
+ import type { DbHandles } from './db.js';
3
+ /**
4
+ * SQLite-backed ApprovalStore + AuditLogger for the gate. Pass an instance
5
+ * to gate's `decide(manifest, ctx, { store, audit })` so staged one-shots and the
6
+ * audit trail live in memory's durable db instead of the gate's JSONL default.
7
+ */
8
+ export declare class GateApprovalStore implements ApprovalStore, AuditLogger {
9
+ private readonly h;
10
+ constructor(h: DbHandles);
11
+ addPending(input: AddPendingInput): Promise<string>;
12
+ getByToolUseId(toolUseId: string): Promise<PendingApproval | undefined>;
13
+ markExecutedByToolUseId(toolUseId: string): Promise<boolean>;
14
+ setDecisionByToolUseId(toolUseId: string, status: 'approved' | 'denied', by?: string): Promise<boolean>;
15
+ list(): Promise<PendingApproval[]>;
16
+ log(entry: AuditEntry): Promise<void>;
17
+ }