@jcyamacho/agent-memory 0.0.15 → 0.0.17
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.
- package/README.md +14 -62
- package/dist/index.js +89 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,69 +27,17 @@ Codex CLI:
|
|
|
27
27
|
codex mcp add memory -- npx -y @jcyamacho/agent-memory
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Example MCP server config:
|
|
31
|
-
|
|
32
|
-
```json
|
|
33
|
-
{
|
|
34
|
-
"mcpServers": {
|
|
35
|
-
"memory": {
|
|
36
|
-
"command": "npx",
|
|
37
|
-
"args": [
|
|
38
|
-
"-y",
|
|
39
|
-
"@jcyamacho/agent-memory"
|
|
40
|
-
]
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
With a custom database path:
|
|
47
|
-
|
|
48
|
-
```json
|
|
49
|
-
{
|
|
50
|
-
"mcpServers": {
|
|
51
|
-
"memory": {
|
|
52
|
-
"command": "npx",
|
|
53
|
-
"args": [
|
|
54
|
-
"-y",
|
|
55
|
-
"@jcyamacho/agent-memory"
|
|
56
|
-
],
|
|
57
|
-
"env": {
|
|
58
|
-
"AGENT_MEMORY_DB_PATH": "/absolute/path/to/memory.db"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
With a custom model cache path:
|
|
66
|
-
|
|
67
|
-
```json
|
|
68
|
-
{
|
|
69
|
-
"mcpServers": {
|
|
70
|
-
"memory": {
|
|
71
|
-
"command": "npx",
|
|
72
|
-
"args": [
|
|
73
|
-
"-y",
|
|
74
|
-
"@jcyamacho/agent-memory"
|
|
75
|
-
],
|
|
76
|
-
"env": {
|
|
77
|
-
"AGENT_MEMORY_MODELS_CACHE_PATH": "/absolute/path/to/models"
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
30
|
Optional LLM instructions to reinforce the MCP's built-in guidance:
|
|
85
31
|
|
|
86
32
|
```text
|
|
87
|
-
Use `
|
|
88
|
-
edge cases. Query with 2-5 short anchor-heavy terms or exact
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
33
|
+
Use `memory_recall` at conversation start and before design choices,
|
|
34
|
+
conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact
|
|
35
|
+
phrases, not
|
|
36
|
+
questions or sentences. `memory_recall` is lexical-first; if it misses, retry once
|
|
37
|
+
with overlapping alternate terms. Use `memory_remember` for one durable fact, then
|
|
38
|
+
use `memory_revise` instead of duplicates and `memory_forget` for wrong or obsolete
|
|
39
|
+
memories. Always pass workspace unless the memory is truly global. Git worktree
|
|
40
|
+
paths are canonicalized to the main repo root on save and recall.
|
|
93
41
|
```
|
|
94
42
|
|
|
95
43
|
## What It Stores
|
|
@@ -147,6 +95,10 @@ If you omit `workspace`, recall still uses text relevance, embedding similarity,
|
|
|
147
95
|
and recency. For best results, pass `workspace` whenever you have one. Save
|
|
148
96
|
memories without a workspace only when they apply across all projects.
|
|
149
97
|
|
|
98
|
+
When you save a memory from a git worktree, `agent-memory` stores the main repo
|
|
99
|
+
root as the workspace. `recall` applies the same normalization to incoming
|
|
100
|
+
workspace queries so linked worktrees still match repo-scoped memories exactly.
|
|
101
|
+
|
|
150
102
|
## Database location
|
|
151
103
|
|
|
152
104
|
By default, the SQLite database is created at:
|
|
@@ -187,8 +139,8 @@ Set `AGENT_MEMORY_MODELS_CACHE_PATH` when you want to:
|
|
|
187
139
|
- share the model cache across reinstalls or multiple clients
|
|
188
140
|
- store model downloads somewhere easier to inspect or manage
|
|
189
141
|
|
|
190
|
-
|
|
191
|
-
|
|
142
|
+
Schema changes are migrated automatically, including workspace normalization for
|
|
143
|
+
existing git worktree memories when the original path can still be resolved.
|
|
192
144
|
|
|
193
145
|
## Development
|
|
194
146
|
|
package/dist/index.js
CHANGED
|
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
|
|
|
12464
12464
|
}
|
|
12465
12465
|
}
|
|
12466
12466
|
// package.json
|
|
12467
|
-
var version2 = "0.0.
|
|
12467
|
+
var version2 = "0.0.17";
|
|
12468
12468
|
|
|
12469
12469
|
// src/config.ts
|
|
12470
12470
|
import { homedir } from "node:os";
|
|
@@ -20146,19 +20146,22 @@ var MAX_LIST_LIMIT = 100;
|
|
|
20146
20146
|
class MemoryService {
|
|
20147
20147
|
repository;
|
|
20148
20148
|
embeddingService;
|
|
20149
|
-
|
|
20149
|
+
workspaceResolver;
|
|
20150
|
+
constructor(repository, embeddingService, workspaceResolver) {
|
|
20150
20151
|
this.repository = repository;
|
|
20151
20152
|
this.embeddingService = embeddingService;
|
|
20153
|
+
this.workspaceResolver = workspaceResolver;
|
|
20152
20154
|
}
|
|
20153
20155
|
async create(input) {
|
|
20154
20156
|
const content = input.content.trim();
|
|
20155
20157
|
if (!content) {
|
|
20156
20158
|
throw new ValidationError("Memory content is required.");
|
|
20157
20159
|
}
|
|
20160
|
+
const workspace = await this.workspaceResolver.resolve(input.workspace);
|
|
20158
20161
|
const memory = await this.repository.create({
|
|
20159
20162
|
content,
|
|
20160
20163
|
embedding: await this.embeddingService.createVector(content),
|
|
20161
|
-
workspace
|
|
20164
|
+
workspace
|
|
20162
20165
|
});
|
|
20163
20166
|
return toPublicMemoryRecord(memory);
|
|
20164
20167
|
}
|
|
@@ -20184,7 +20187,7 @@ class MemoryService {
|
|
|
20184
20187
|
return memory ? toPublicMemoryRecord(memory) : undefined;
|
|
20185
20188
|
}
|
|
20186
20189
|
async list(input) {
|
|
20187
|
-
const workspace =
|
|
20190
|
+
const workspace = await this.workspaceResolver.resolve(input.workspace);
|
|
20188
20191
|
const page = await this.repository.list({
|
|
20189
20192
|
workspace,
|
|
20190
20193
|
workspaceIsNull: workspace ? false : Boolean(input.workspaceIsNull),
|
|
@@ -20202,7 +20205,7 @@ class MemoryService {
|
|
|
20202
20205
|
throw new ValidationError("At least one search term is required.");
|
|
20203
20206
|
}
|
|
20204
20207
|
const requestedLimit = normalizeLimit(input.limit);
|
|
20205
|
-
const workspace =
|
|
20208
|
+
const workspace = await this.workspaceResolver.resolve(input.workspace);
|
|
20206
20209
|
const normalizedQuery = {
|
|
20207
20210
|
terms,
|
|
20208
20211
|
limit: requestedLimit * RECALL_CANDIDATE_LIMIT_MULTIPLIER,
|
|
@@ -20259,10 +20262,6 @@ function normalizeListLimit(value) {
|
|
|
20259
20262
|
}
|
|
20260
20263
|
return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
|
|
20261
20264
|
}
|
|
20262
|
-
function normalizeOptionalString(value) {
|
|
20263
|
-
const trimmed = value?.trim();
|
|
20264
|
-
return trimmed ? trimmed : undefined;
|
|
20265
|
-
}
|
|
20266
20265
|
function normalizeTerms(terms) {
|
|
20267
20266
|
const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
|
|
20268
20267
|
return [...new Set(normalizedTerms)];
|
|
@@ -20272,7 +20271,7 @@ function normalizeTerms(terms) {
|
|
|
20272
20271
|
var recallInputSchema = {
|
|
20273
20272
|
terms: array(string2()).min(1).describe("Search terms for lexical memory lookup. Pass 2-5 short anchor-heavy terms or exact phrases as separate entries. Prefer identifiers, commands, file paths, package names, and conventions likely to appear verbatim in the memory. Avoid vague words, full sentences, and repeating the workspace name. If recall misses, retry once with overlapping alternate terms."),
|
|
20274
20273
|
limit: number2().int().min(1).max(MAX_RECALL_LIMIT).optional().describe("Maximum matches to return. Keep this small when you only need the strongest hits."),
|
|
20275
|
-
workspace: string2().optional().describe("Pass the current working directory. This strongly boosts memories from the active project while still allowing global and cross-workspace matches."),
|
|
20274
|
+
workspace: string2().optional().describe("Pass the current working directory. Git worktree paths are normalized to the main repo root for matching. This strongly boosts memories from the active project while still allowing global and cross-workspace matches."),
|
|
20276
20275
|
updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp."),
|
|
20277
20276
|
updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp.")
|
|
20278
20277
|
};
|
|
@@ -20286,7 +20285,7 @@ ${content}
|
|
|
20286
20285
|
}
|
|
20287
20286
|
function registerRecallTool(server, memory) {
|
|
20288
20287
|
server.registerTool("recall", {
|
|
20289
|
-
description: "Retrieve relevant memories for the current task. Use at conversation start and before design choices, conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact phrases, not questions or full sentences. `recall` is lexical-first; semantic reranking only reorders lexical matches. If it misses, retry once with overlapping alternate terms. Pass workspace. Returns `<memories>...</memories>` or a no-match hint.",
|
|
20288
|
+
description: "Retrieve relevant memories for the current task. Use at conversation start and before design choices, conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact phrases, not questions or full sentences. `recall` is lexical-first; semantic reranking only reorders lexical matches. If it misses, retry once with overlapping alternate terms. Pass workspace; git worktree paths are normalized to the main repo root for matching. Returns `<memories>...</memories>` or a no-match hint.",
|
|
20290
20289
|
inputSchema: recallInputSchema
|
|
20291
20290
|
}, async ({ terms, limit, workspace, updated_after, updated_before }) => {
|
|
20292
20291
|
try {
|
|
@@ -20313,7 +20312,7 @@ ${results.map(toMemoryXml).join(`
|
|
|
20313
20312
|
// src/mcp/tools/remember.ts
|
|
20314
20313
|
var rememberInputSchema = {
|
|
20315
20314
|
content: string2().describe("One durable fact to save. Use a single self-contained sentence or short note with concrete nouns, identifiers, commands, file paths, or exact phrases the agent is likely to reuse."),
|
|
20316
|
-
workspace: string2().optional().describe("Pass the current working directory for project-specific memories. Omit only for truly global memories.")
|
|
20315
|
+
workspace: string2().optional().describe("Pass the current working directory for project-specific memories. Git worktree paths are saved as the main repo root automatically. Omit only for truly global memories.")
|
|
20317
20316
|
};
|
|
20318
20317
|
function registerRememberTool(server, memory) {
|
|
20319
20318
|
server.registerTool("remember", {
|
|
@@ -20370,7 +20369,7 @@ var SERVER_INSTRUCTIONS = [
|
|
|
20370
20369
|
"Use `remember` for one durable fact when the user states a preference, corrects you, or a reusable project decision becomes clear.",
|
|
20371
20370
|
"Call `recall` before `remember`; if the fact already exists, use `revise` instead of creating a duplicate.",
|
|
20372
20371
|
"Use `revise` to correct an existing memory and `forget` to remove a wrong or obsolete one.",
|
|
20373
|
-
"Pass workspace for project-scoped calls. Omit
|
|
20372
|
+
"Pass workspace for project-scoped calls. Git worktree paths are canonicalized to the main repo root on save and recall. Omit workspace only for truly global memories."
|
|
20374
20373
|
].join(" ");
|
|
20375
20374
|
function createMcpServer(memory, version3) {
|
|
20376
20375
|
const server = new McpServer({
|
|
@@ -20502,7 +20501,7 @@ function toUint8Array(value) {
|
|
|
20502
20501
|
}
|
|
20503
20502
|
|
|
20504
20503
|
// src/sqlite/migrations/002-add-memory-embedding.ts
|
|
20505
|
-
function createAddMemoryEmbeddingMigration(embeddingService
|
|
20504
|
+
function createAddMemoryEmbeddingMigration(embeddingService) {
|
|
20506
20505
|
return {
|
|
20507
20506
|
version: 2,
|
|
20508
20507
|
async up(database) {
|
|
@@ -20531,13 +20530,33 @@ function createAddMemoryEmbeddingMigration(embeddingService = new EmbeddingServi
|
|
|
20531
20530
|
}
|
|
20532
20531
|
};
|
|
20533
20532
|
}
|
|
20534
|
-
|
|
20533
|
+
|
|
20534
|
+
// src/sqlite/migrations/003-normalize-workspaces.ts
|
|
20535
|
+
function createNormalizeWorkspaceMigration(workspaceResolver) {
|
|
20536
|
+
return {
|
|
20537
|
+
version: 3,
|
|
20538
|
+
async up(database) {
|
|
20539
|
+
const rows = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace").all();
|
|
20540
|
+
const updateStatement = database.prepare("UPDATE memories SET workspace = ? WHERE workspace = ?");
|
|
20541
|
+
for (const row of rows) {
|
|
20542
|
+
const normalizedWorkspace = await workspaceResolver.resolve(row.workspace);
|
|
20543
|
+
if (!normalizedWorkspace || normalizedWorkspace === row.workspace) {
|
|
20544
|
+
continue;
|
|
20545
|
+
}
|
|
20546
|
+
updateStatement.run(normalizedWorkspace, row.workspace);
|
|
20547
|
+
}
|
|
20548
|
+
}
|
|
20549
|
+
};
|
|
20550
|
+
}
|
|
20535
20551
|
|
|
20536
20552
|
// src/sqlite/migrations/index.ts
|
|
20537
|
-
function createMemoryMigrations(
|
|
20538
|
-
return [
|
|
20553
|
+
function createMemoryMigrations(options) {
|
|
20554
|
+
return [
|
|
20555
|
+
createMemorySchemaMigration,
|
|
20556
|
+
createAddMemoryEmbeddingMigration(options.embeddingService),
|
|
20557
|
+
createNormalizeWorkspaceMigration(options.workspaceResolver)
|
|
20558
|
+
];
|
|
20539
20559
|
}
|
|
20540
|
-
var MEMORY_MIGRATIONS = createMemoryMigrations();
|
|
20541
20560
|
|
|
20542
20561
|
// src/sqlite/db.ts
|
|
20543
20562
|
var PRAGMA_STATEMENTS = [
|
|
@@ -20546,12 +20565,12 @@ var PRAGMA_STATEMENTS = [
|
|
|
20546
20565
|
"foreign_keys = ON",
|
|
20547
20566
|
"busy_timeout = 5000"
|
|
20548
20567
|
];
|
|
20549
|
-
async function openMemoryDatabase(databasePath, options
|
|
20568
|
+
async function openMemoryDatabase(databasePath, options) {
|
|
20550
20569
|
let database;
|
|
20551
20570
|
try {
|
|
20552
20571
|
mkdirSync2(dirname(databasePath), { recursive: true });
|
|
20553
20572
|
database = new Database(databasePath);
|
|
20554
|
-
await initializeMemoryDatabase(database, createMemoryMigrations(options
|
|
20573
|
+
await initializeMemoryDatabase(database, createMemoryMigrations(options));
|
|
20555
20574
|
return database;
|
|
20556
20575
|
} catch (error2) {
|
|
20557
20576
|
database?.close();
|
|
@@ -20563,7 +20582,7 @@ async function openMemoryDatabase(databasePath, options = {}) {
|
|
|
20563
20582
|
});
|
|
20564
20583
|
}
|
|
20565
20584
|
}
|
|
20566
|
-
async function initializeMemoryDatabase(database, migrations
|
|
20585
|
+
async function initializeMemoryDatabase(database, migrations) {
|
|
20567
20586
|
try {
|
|
20568
20587
|
applyPragmas(database);
|
|
20569
20588
|
await runSqliteMigrations(database, migrations);
|
|
@@ -24195,12 +24214,59 @@ function startWebServer(memory, options) {
|
|
|
24195
24214
|
return serve({ fetch: app.fetch, port: options.port });
|
|
24196
24215
|
}
|
|
24197
24216
|
|
|
24217
|
+
// src/workspace-resolver.ts
|
|
24218
|
+
import { execFile } from "node:child_process";
|
|
24219
|
+
import { basename, dirname as dirname2 } from "node:path";
|
|
24220
|
+
import { promisify } from "node:util";
|
|
24221
|
+
var execFileAsync = promisify(execFile);
|
|
24222
|
+
function createGitWorkspaceResolver(options = {}) {
|
|
24223
|
+
const getGitCommonDir = options.getGitCommonDir ?? defaultGetGitCommonDir;
|
|
24224
|
+
const cache = new Map;
|
|
24225
|
+
return {
|
|
24226
|
+
async resolve(workspace) {
|
|
24227
|
+
const trimmed = normalizeOptionalString(workspace);
|
|
24228
|
+
if (!trimmed) {
|
|
24229
|
+
return;
|
|
24230
|
+
}
|
|
24231
|
+
const cached2 = cache.get(trimmed);
|
|
24232
|
+
if (cached2) {
|
|
24233
|
+
return cached2;
|
|
24234
|
+
}
|
|
24235
|
+
const pending = resolveWorkspace(trimmed, getGitCommonDir);
|
|
24236
|
+
cache.set(trimmed, pending);
|
|
24237
|
+
return pending;
|
|
24238
|
+
}
|
|
24239
|
+
};
|
|
24240
|
+
}
|
|
24241
|
+
async function resolveWorkspace(workspace, getGitCommonDir) {
|
|
24242
|
+
try {
|
|
24243
|
+
const gitCommonDir = (await getGitCommonDir(workspace)).trim();
|
|
24244
|
+
if (basename(gitCommonDir) !== ".git") {
|
|
24245
|
+
return workspace;
|
|
24246
|
+
}
|
|
24247
|
+
return dirname2(gitCommonDir);
|
|
24248
|
+
} catch {
|
|
24249
|
+
return workspace;
|
|
24250
|
+
}
|
|
24251
|
+
}
|
|
24252
|
+
async function defaultGetGitCommonDir(cwd) {
|
|
24253
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
24254
|
+
cwd
|
|
24255
|
+
});
|
|
24256
|
+
return stdout.trim();
|
|
24257
|
+
}
|
|
24258
|
+
function normalizeOptionalString(value) {
|
|
24259
|
+
const trimmed = value?.trim();
|
|
24260
|
+
return trimmed ? trimmed : undefined;
|
|
24261
|
+
}
|
|
24262
|
+
|
|
24198
24263
|
// src/index.ts
|
|
24199
24264
|
var config2 = resolveConfig();
|
|
24200
24265
|
var embeddingService = new EmbeddingService({ modelsCachePath: config2.modelsCachePath });
|
|
24201
|
-
var
|
|
24266
|
+
var workspaceResolver = createGitWorkspaceResolver();
|
|
24267
|
+
var database = await openMemoryDatabase(config2.databasePath, { embeddingService, workspaceResolver });
|
|
24202
24268
|
var repository2 = new SqliteMemoryRepository(database);
|
|
24203
|
-
var memoryService = new MemoryService(repository2, embeddingService);
|
|
24269
|
+
var memoryService = new MemoryService(repository2, embeddingService, workspaceResolver);
|
|
24204
24270
|
if (config2.uiMode) {
|
|
24205
24271
|
let shutdown = function() {
|
|
24206
24272
|
server.close();
|