@jcyamacho/agent-memory 0.0.2 → 0.0.4
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 +36 -28
- package/dist/index.js +100 -147
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,6 +13,18 @@ decisions across sessions.
|
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
16
|
+
Claude CLI:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
claude mcp add --scope user memory -- npx -y @jcyamacho/agent-memory
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Codex CLI:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
codex mcp add memory -- npx -y @jcyamacho/agent-memory
|
|
26
|
+
```
|
|
27
|
+
|
|
16
28
|
Example MCP server config:
|
|
17
29
|
|
|
18
30
|
```json
|
|
@@ -51,22 +63,22 @@ With a custom database path:
|
|
|
51
63
|
Recommended LLM instructions to pair with this MCP:
|
|
52
64
|
|
|
53
65
|
```text
|
|
54
|
-
Use `memory_recall` at
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Use `memory_remember`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
Use `memory_recall` at task start and whenever prior preferences, project facts,
|
|
67
|
+
or decisions may matter.
|
|
68
|
+
|
|
69
|
+
Use `memory_remember` only for durable, reusable context: preferences,
|
|
70
|
+
conventions, decisions, constraints, and stable workflow habits. Store one
|
|
71
|
+
concise, self-contained fact per memory. Include `workspace` when available. Do
|
|
72
|
+
not store secrets or temporary noise.
|
|
73
|
+
|
|
74
|
+
For `memory_recall`, pass `terms` as 2-5 distinctive strings that describe what
|
|
75
|
+
you are looking for. Prefer names, identifiers, package names, file names, and
|
|
76
|
+
short phrases. Each term is matched independently — more terms cast a wider net,
|
|
77
|
+
and results matching multiple terms rank higher. Stemming is applied
|
|
78
|
+
automatically, so exact word forms are not required.
|
|
79
|
+
|
|
80
|
+
Use `workspace` to bias ranking toward the current project. Use `created_*`
|
|
81
|
+
only for exact scoping.
|
|
70
82
|
```
|
|
71
83
|
|
|
72
84
|
## What It Stores
|
|
@@ -76,7 +88,7 @@ This MCP is useful for context that should survive across turns and sessions:
|
|
|
76
88
|
- User preferences like response style, formatting, and workflow habits
|
|
77
89
|
- Project facts like paths, architecture choices, and conventions
|
|
78
90
|
- Important decisions and constraints that should not be rediscovered
|
|
79
|
-
-
|
|
91
|
+
- Project-scoped notes that still matter later
|
|
80
92
|
|
|
81
93
|
## Tools
|
|
82
94
|
|
|
@@ -87,16 +99,12 @@ Save durable context for later recall.
|
|
|
87
99
|
Inputs:
|
|
88
100
|
|
|
89
101
|
- `content` -> fact, preference, decision, or context to store
|
|
90
|
-
- `source` -> client, tool, or agent name
|
|
91
102
|
- `workspace` -> repository or workspace path
|
|
92
|
-
- `session` -> conversation or execution session identifier
|
|
93
103
|
|
|
94
104
|
Output:
|
|
95
105
|
|
|
96
106
|
- `id`
|
|
97
|
-
- `source`
|
|
98
107
|
- `workspace`
|
|
99
|
-
- `session`
|
|
100
108
|
- `created_at`
|
|
101
109
|
|
|
102
110
|
### `recall`
|
|
@@ -105,19 +113,16 @@ Retrieve relevant memories for the current task.
|
|
|
105
113
|
|
|
106
114
|
Inputs:
|
|
107
115
|
|
|
108
|
-
- `
|
|
116
|
+
- `terms` -> 2-5 distinctive terms or short phrases that should appear in the
|
|
117
|
+
memory content; avoid full natural-language questions
|
|
109
118
|
- `limit` -> maximum results to return
|
|
110
|
-
- `
|
|
111
|
-
- `preferred_workspace` -> ranking hint for a workspace
|
|
112
|
-
- `filter_source` -> exact source filter
|
|
113
|
-
- `filter_workspace` -> exact workspace filter
|
|
119
|
+
- `workspace` -> workspace or repo path; biases ranking toward this workspace
|
|
114
120
|
- `created_after` -> ISO 8601 lower bound
|
|
115
121
|
- `created_before` -> ISO 8601 upper bound
|
|
116
122
|
|
|
117
123
|
Output:
|
|
118
124
|
|
|
119
|
-
- `results[]` with `id`, `content`, `score`, `
|
|
120
|
-
and `created_at`
|
|
125
|
+
- `results[]` with `id`, `content`, `score`, `workspace`, and `created_at`
|
|
121
126
|
|
|
122
127
|
## Setup
|
|
123
128
|
|
|
@@ -150,6 +155,9 @@ Set `AGENT_MEMORY_DB_PATH` when you want to:
|
|
|
150
155
|
- share a memory DB across multiple clients
|
|
151
156
|
- store the DB somewhere easier to back up or inspect
|
|
152
157
|
|
|
158
|
+
Beta note: schema changes are not migrated. If you are upgrading from an older
|
|
159
|
+
beta, delete the existing memory DB and let the server create a new one.
|
|
160
|
+
|
|
153
161
|
## Run from source
|
|
154
162
|
|
|
155
163
|
If you are developing locally instead of using the published package:
|
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.4";
|
|
12468
12468
|
|
|
12469
12469
|
// src/config.ts
|
|
12470
12470
|
import { homedir } from "node:os";
|
|
@@ -19881,6 +19881,9 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
19881
19881
|
}
|
|
19882
19882
|
};
|
|
19883
19883
|
|
|
19884
|
+
// src/memory-service.ts
|
|
19885
|
+
import { randomUUID } from "node:crypto";
|
|
19886
|
+
|
|
19884
19887
|
// src/errors.ts
|
|
19885
19888
|
class MemoryError extends Error {
|
|
19886
19889
|
code;
|
|
@@ -19905,6 +19908,63 @@ class PersistenceError extends MemoryError {
|
|
|
19905
19908
|
}
|
|
19906
19909
|
}
|
|
19907
19910
|
|
|
19911
|
+
// src/memory-service.ts
|
|
19912
|
+
var DEFAULT_LIMIT = 15;
|
|
19913
|
+
var MAX_LIMIT = 50;
|
|
19914
|
+
|
|
19915
|
+
class MemoryService {
|
|
19916
|
+
repository;
|
|
19917
|
+
constructor(repository) {
|
|
19918
|
+
this.repository = repository;
|
|
19919
|
+
}
|
|
19920
|
+
async save(input) {
|
|
19921
|
+
const content = input.content.trim();
|
|
19922
|
+
if (!content) {
|
|
19923
|
+
throw new ValidationError("Memory content is required.");
|
|
19924
|
+
}
|
|
19925
|
+
const now = new Date;
|
|
19926
|
+
const memory = {
|
|
19927
|
+
id: randomUUID(),
|
|
19928
|
+
content,
|
|
19929
|
+
workspace: normalizeOptionalString(input.workspace),
|
|
19930
|
+
createdAt: now,
|
|
19931
|
+
updatedAt: now
|
|
19932
|
+
};
|
|
19933
|
+
return this.repository.save(memory);
|
|
19934
|
+
}
|
|
19935
|
+
async search(input) {
|
|
19936
|
+
const terms = normalizeTerms(input.terms);
|
|
19937
|
+
if (terms.length === 0) {
|
|
19938
|
+
throw new ValidationError("At least one search term is required.");
|
|
19939
|
+
}
|
|
19940
|
+
const normalizedQuery = {
|
|
19941
|
+
terms,
|
|
19942
|
+
limit: normalizeLimit(input.limit),
|
|
19943
|
+
workspace: normalizeOptionalString(input.workspace),
|
|
19944
|
+
createdAfter: input.createdAfter,
|
|
19945
|
+
createdBefore: input.createdBefore
|
|
19946
|
+
};
|
|
19947
|
+
return this.repository.search(normalizedQuery);
|
|
19948
|
+
}
|
|
19949
|
+
}
|
|
19950
|
+
var normalizeLimit = (value) => {
|
|
19951
|
+
if (value === undefined) {
|
|
19952
|
+
return DEFAULT_LIMIT;
|
|
19953
|
+
}
|
|
19954
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_LIMIT) {
|
|
19955
|
+
throw new ValidationError(`Limit must be an integer between 1 and ${MAX_LIMIT}.`);
|
|
19956
|
+
}
|
|
19957
|
+
return value;
|
|
19958
|
+
};
|
|
19959
|
+
var normalizeOptionalString = (value) => {
|
|
19960
|
+
const trimmed = value?.trim();
|
|
19961
|
+
return trimmed ? trimmed : undefined;
|
|
19962
|
+
};
|
|
19963
|
+
var normalizeTerms = (terms) => {
|
|
19964
|
+
const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
|
|
19965
|
+
return [...new Set(normalizedTerms)];
|
|
19966
|
+
};
|
|
19967
|
+
|
|
19908
19968
|
// src/tools/shared.ts
|
|
19909
19969
|
var toMcpError = (error2) => {
|
|
19910
19970
|
if (error2 instanceof McpError) {
|
|
@@ -19932,23 +19992,18 @@ var parseOptionalDate = (value, fieldName) => {
|
|
|
19932
19992
|
|
|
19933
19993
|
// src/tools/recall.ts
|
|
19934
19994
|
var recallInputSchema = {
|
|
19935
|
-
|
|
19936
|
-
limit: number2().int().min(1).max(
|
|
19937
|
-
|
|
19938
|
-
preferred_workspace: string2().optional().describe("Preferred workspace or repository path to rank higher when relevant. This does not exclude other workspaces."),
|
|
19939
|
-
filter_source: string2().optional().describe("Only return memories from this exact source."),
|
|
19940
|
-
filter_workspace: string2().optional().describe("Only return memories from this exact workspace or repository path."),
|
|
19995
|
+
terms: array(string2()).min(1).describe("Search terms to match against remembered content. Use distinctive keywords, IDs, names, file names, or short phrases as separate items."),
|
|
19996
|
+
limit: number2().int().min(1).max(MAX_LIMIT).optional().describe("Maximum number of memory results to return. Use a small number when you only need the best matches."),
|
|
19997
|
+
workspace: string2().optional().describe("Workspace or repository path. Memories from this workspace rank higher, but other workspaces are not excluded."),
|
|
19941
19998
|
created_after: string2().optional().describe("Only return memories created at or after this ISO 8601 timestamp."),
|
|
19942
19999
|
created_before: string2().optional().describe("Only return memories created at or before this ISO 8601 timestamp.")
|
|
19943
20000
|
};
|
|
19944
20001
|
var recallOutputSchema = {
|
|
19945
20002
|
results: array(object({
|
|
19946
20003
|
id: string2().describe("Stable identifier for the remembered item."),
|
|
19947
|
-
content: string2().describe("The remembered content that matched the
|
|
20004
|
+
content: string2().describe("The remembered content that matched the search terms."),
|
|
19948
20005
|
score: number2().describe("Relevance score for this result. Higher means a better match."),
|
|
19949
|
-
source: string2().optional().describe("Source associated with the memory, if available."),
|
|
19950
20006
|
workspace: string2().optional().describe("Workspace associated with the memory, if available."),
|
|
19951
|
-
session: string2().optional().describe("Session associated with the memory, if available."),
|
|
19952
20007
|
created_at: string2().describe("ISO 8601 timestamp showing when the memory was created.")
|
|
19953
20008
|
}))
|
|
19954
20009
|
};
|
|
@@ -19957,24 +20012,12 @@ var registerRecallTool = (server, memoryService) => {
|
|
|
19957
20012
|
description: "Retrieve previously remembered context that may help with the current task. Use it for user preferences, project facts, prior decisions, constraints, or earlier conversation details.",
|
|
19958
20013
|
inputSchema: recallInputSchema,
|
|
19959
20014
|
outputSchema: recallOutputSchema
|
|
19960
|
-
}, async ({
|
|
19961
|
-
query,
|
|
19962
|
-
limit,
|
|
19963
|
-
preferred_source,
|
|
19964
|
-
preferred_workspace,
|
|
19965
|
-
filter_source,
|
|
19966
|
-
filter_workspace,
|
|
19967
|
-
created_after,
|
|
19968
|
-
created_before
|
|
19969
|
-
}) => {
|
|
20015
|
+
}, async ({ terms, limit, workspace, created_after, created_before }) => {
|
|
19970
20016
|
try {
|
|
19971
20017
|
const results = await memoryService.search({
|
|
19972
|
-
|
|
20018
|
+
terms,
|
|
19973
20019
|
limit,
|
|
19974
|
-
|
|
19975
|
-
preferredWorkspace: preferred_workspace,
|
|
19976
|
-
filterSource: filter_source,
|
|
19977
|
-
filterWorkspace: filter_workspace,
|
|
20020
|
+
workspace,
|
|
19978
20021
|
createdAfter: parseOptionalDate(created_after, "created_after"),
|
|
19979
20022
|
createdBefore: parseOptionalDate(created_before, "created_before")
|
|
19980
20023
|
});
|
|
@@ -19983,17 +20026,17 @@ var registerRecallTool = (server, memoryService) => {
|
|
|
19983
20026
|
id: result.id,
|
|
19984
20027
|
content: result.content,
|
|
19985
20028
|
score: result.score,
|
|
19986
|
-
source: result.source,
|
|
19987
20029
|
workspace: result.workspace,
|
|
19988
|
-
session: result.session,
|
|
19989
20030
|
created_at: result.createdAt.toISOString()
|
|
19990
20031
|
}))
|
|
19991
20032
|
};
|
|
20033
|
+
const matchCount = structuredContent.results.length;
|
|
20034
|
+
const summary = matchCount === 1 ? "Found 1 matching memory." : `Found ${matchCount} matching memories.`;
|
|
19992
20035
|
return {
|
|
19993
20036
|
content: [
|
|
19994
20037
|
{
|
|
19995
20038
|
type: "text",
|
|
19996
|
-
text:
|
|
20039
|
+
text: summary
|
|
19997
20040
|
}
|
|
19998
20041
|
],
|
|
19999
20042
|
structuredContent
|
|
@@ -20007,15 +20050,11 @@ var registerRecallTool = (server, memoryService) => {
|
|
|
20007
20050
|
// src/tools/remember.ts
|
|
20008
20051
|
var rememberInputSchema = {
|
|
20009
20052
|
content: string2().describe("The exact fact, preference, decision, or context to remember for future retrieval. Use a self-contained sentence or short note."),
|
|
20010
|
-
|
|
20011
|
-
workspace: string2().optional().describe("Repository or workspace path this memory belongs to. Use it to keep memories scoped to a project."),
|
|
20012
|
-
session: string2().optional().describe("Conversation or execution session identifier. Useful for tying memories back to one run or thread.")
|
|
20053
|
+
workspace: string2().optional().describe("Repository or workspace path this memory belongs to. Use it to keep memories scoped to a project.")
|
|
20013
20054
|
};
|
|
20014
20055
|
var rememberOutputSchema = {
|
|
20015
20056
|
id: string2().describe("Stable identifier for the saved memory."),
|
|
20016
|
-
source: string2().optional().describe("Source stored with the memory, if provided."),
|
|
20017
20057
|
workspace: string2().optional().describe("Workspace stored with the memory, if provided."),
|
|
20018
|
-
session: string2().optional().describe("Session stored with the memory, if provided."),
|
|
20019
20058
|
created_at: string2().describe("ISO 8601 timestamp showing when the memory was created.")
|
|
20020
20059
|
};
|
|
20021
20060
|
var registerRememberTool = (server, memoryService) => {
|
|
@@ -20023,26 +20062,22 @@ var registerRememberTool = (server, memoryService) => {
|
|
|
20023
20062
|
description: "Save durable context for later recall. Use this for user preferences, project facts, decisions, constraints, or other information worth remembering across turns and tools.",
|
|
20024
20063
|
inputSchema: rememberInputSchema,
|
|
20025
20064
|
outputSchema: rememberOutputSchema
|
|
20026
|
-
}, async ({ content,
|
|
20065
|
+
}, async ({ content, workspace }) => {
|
|
20027
20066
|
try {
|
|
20028
20067
|
const memory = await memoryService.save({
|
|
20029
20068
|
content,
|
|
20030
|
-
|
|
20031
|
-
workspace,
|
|
20032
|
-
session
|
|
20069
|
+
workspace
|
|
20033
20070
|
});
|
|
20034
20071
|
const structuredContent = {
|
|
20035
20072
|
id: memory.id,
|
|
20036
|
-
source: memory.source,
|
|
20037
20073
|
workspace: memory.workspace,
|
|
20038
|
-
session: memory.session,
|
|
20039
20074
|
created_at: memory.createdAt.toISOString()
|
|
20040
20075
|
};
|
|
20041
20076
|
return {
|
|
20042
20077
|
content: [
|
|
20043
20078
|
{
|
|
20044
20079
|
type: "text",
|
|
20045
|
-
text:
|
|
20080
|
+
text: "Saved memory."
|
|
20046
20081
|
}
|
|
20047
20082
|
],
|
|
20048
20083
|
structuredContent
|
|
@@ -20064,81 +20099,6 @@ var createMcpServer = (memoryService, version3) => {
|
|
|
20064
20099
|
return server;
|
|
20065
20100
|
};
|
|
20066
20101
|
|
|
20067
|
-
// src/memory-service.ts
|
|
20068
|
-
import { randomUUID } from "node:crypto";
|
|
20069
|
-
var DEFAULT_LIMIT = 5;
|
|
20070
|
-
var MAX_LIMIT = 20;
|
|
20071
|
-
var SOURCE_BIAS = 0.15;
|
|
20072
|
-
var WORKSPACE_BIAS = 0.1;
|
|
20073
|
-
|
|
20074
|
-
class MemoryService {
|
|
20075
|
-
repository;
|
|
20076
|
-
constructor(repository) {
|
|
20077
|
-
this.repository = repository;
|
|
20078
|
-
}
|
|
20079
|
-
async save(input) {
|
|
20080
|
-
const content = input.content.trim();
|
|
20081
|
-
if (!content) {
|
|
20082
|
-
throw new ValidationError("Memory content is required.");
|
|
20083
|
-
}
|
|
20084
|
-
const now = new Date;
|
|
20085
|
-
const memory = {
|
|
20086
|
-
id: randomUUID(),
|
|
20087
|
-
content,
|
|
20088
|
-
source: normalizeOptionalString(input.source),
|
|
20089
|
-
workspace: normalizeOptionalString(input.workspace),
|
|
20090
|
-
session: normalizeOptionalString(input.session),
|
|
20091
|
-
createdAt: now,
|
|
20092
|
-
updatedAt: now
|
|
20093
|
-
};
|
|
20094
|
-
return this.repository.save(memory);
|
|
20095
|
-
}
|
|
20096
|
-
async search(input) {
|
|
20097
|
-
const query = input.query.trim();
|
|
20098
|
-
if (!query) {
|
|
20099
|
-
throw new ValidationError("Search query is required.");
|
|
20100
|
-
}
|
|
20101
|
-
const normalizedQuery = {
|
|
20102
|
-
query,
|
|
20103
|
-
limit: normalizeLimit(input.limit),
|
|
20104
|
-
preferredSource: normalizeOptionalString(input.preferredSource),
|
|
20105
|
-
preferredWorkspace: normalizeOptionalString(input.preferredWorkspace),
|
|
20106
|
-
filterSource: normalizeOptionalString(input.filterSource),
|
|
20107
|
-
filterWorkspace: normalizeOptionalString(input.filterWorkspace),
|
|
20108
|
-
createdAfter: input.createdAfter,
|
|
20109
|
-
createdBefore: input.createdBefore
|
|
20110
|
-
};
|
|
20111
|
-
const results = await this.repository.search(normalizedQuery);
|
|
20112
|
-
return results.map((result) => ({
|
|
20113
|
-
...result,
|
|
20114
|
-
score: rankResult(result, normalizedQuery)
|
|
20115
|
-
})).sort((left, right) => right.score - left.score).slice(0, normalizedQuery.limit);
|
|
20116
|
-
}
|
|
20117
|
-
}
|
|
20118
|
-
var normalizeLimit = (value) => {
|
|
20119
|
-
if (value === undefined) {
|
|
20120
|
-
return DEFAULT_LIMIT;
|
|
20121
|
-
}
|
|
20122
|
-
if (!Number.isInteger(value) || value < 1 || value > MAX_LIMIT) {
|
|
20123
|
-
throw new ValidationError(`Limit must be an integer between 1 and ${MAX_LIMIT}.`);
|
|
20124
|
-
}
|
|
20125
|
-
return value;
|
|
20126
|
-
};
|
|
20127
|
-
var normalizeOptionalString = (value) => {
|
|
20128
|
-
const trimmed = value?.trim();
|
|
20129
|
-
return trimmed ? trimmed : undefined;
|
|
20130
|
-
};
|
|
20131
|
-
var rankResult = (result, query) => {
|
|
20132
|
-
let score = result.score;
|
|
20133
|
-
if (query.preferredSource && result.source === query.preferredSource) {
|
|
20134
|
-
score += SOURCE_BIAS;
|
|
20135
|
-
}
|
|
20136
|
-
if (query.preferredWorkspace && result.workspace === query.preferredWorkspace) {
|
|
20137
|
-
score += WORKSPACE_BIAS;
|
|
20138
|
-
}
|
|
20139
|
-
return Number(score.toFixed(6));
|
|
20140
|
-
};
|
|
20141
|
-
|
|
20142
20102
|
// src/sqlite-db.ts
|
|
20143
20103
|
import { mkdirSync } from "node:fs";
|
|
20144
20104
|
import { dirname } from "node:path";
|
|
@@ -20147,22 +20107,19 @@ var MEMORY_SCHEMA = `
|
|
|
20147
20107
|
CREATE TABLE IF NOT EXISTS memories (
|
|
20148
20108
|
id TEXT PRIMARY KEY,
|
|
20149
20109
|
content TEXT NOT NULL,
|
|
20150
|
-
source TEXT,
|
|
20151
20110
|
workspace TEXT,
|
|
20152
|
-
session TEXT,
|
|
20153
20111
|
created_at INTEGER NOT NULL,
|
|
20154
20112
|
updated_at INTEGER NOT NULL
|
|
20155
20113
|
);
|
|
20156
20114
|
|
|
20157
20115
|
CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
|
|
20158
|
-
CREATE INDEX IF NOT EXISTS idx_memories_source ON memories(source);
|
|
20159
20116
|
CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace);
|
|
20160
20117
|
|
|
20161
20118
|
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
20162
20119
|
content,
|
|
20163
20120
|
content = 'memories',
|
|
20164
20121
|
content_rowid = 'rowid',
|
|
20165
|
-
tokenize = 'unicode61'
|
|
20122
|
+
tokenize = 'porter unicode61'
|
|
20166
20123
|
);
|
|
20167
20124
|
|
|
20168
20125
|
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
@@ -20208,9 +20165,7 @@ var initializeMemoryDatabase = (database) => {
|
|
|
20208
20165
|
};
|
|
20209
20166
|
|
|
20210
20167
|
// src/sqlite-repository.ts
|
|
20211
|
-
var
|
|
20212
|
-
var MIN_CANDIDATES = 25;
|
|
20213
|
-
var MAX_CANDIDATES = 100;
|
|
20168
|
+
var WORKSPACE_BIAS = 0.1;
|
|
20214
20169
|
|
|
20215
20170
|
class SqliteMemoryRepository {
|
|
20216
20171
|
database;
|
|
@@ -20221,9 +20176,7 @@ class SqliteMemoryRepository {
|
|
|
20221
20176
|
INSERT INTO memories (
|
|
20222
20177
|
id,
|
|
20223
20178
|
content,
|
|
20224
|
-
source,
|
|
20225
20179
|
workspace,
|
|
20226
|
-
session,
|
|
20227
20180
|
created_at,
|
|
20228
20181
|
updated_at
|
|
20229
20182
|
) VALUES (
|
|
@@ -20231,15 +20184,13 @@ class SqliteMemoryRepository {
|
|
|
20231
20184
|
?,
|
|
20232
20185
|
?,
|
|
20233
20186
|
?,
|
|
20234
|
-
?,
|
|
20235
|
-
?,
|
|
20236
20187
|
?
|
|
20237
20188
|
)
|
|
20238
20189
|
`);
|
|
20239
20190
|
}
|
|
20240
20191
|
async save(memory) {
|
|
20241
20192
|
try {
|
|
20242
|
-
this.insertStatement.run(memory.id, memory.content, memory.
|
|
20193
|
+
this.insertStatement.run(memory.id, memory.content, memory.workspace, memory.createdAt.getTime(), memory.updatedAt.getTime());
|
|
20243
20194
|
return memory;
|
|
20244
20195
|
} catch (error2) {
|
|
20245
20196
|
throw new PersistenceError("Failed to save memory.", { cause: error2 });
|
|
@@ -20247,48 +20198,44 @@ class SqliteMemoryRepository {
|
|
|
20247
20198
|
}
|
|
20248
20199
|
async search(query) {
|
|
20249
20200
|
try {
|
|
20250
|
-
const
|
|
20251
|
-
const
|
|
20252
|
-
|
|
20253
|
-
|
|
20254
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
20257
|
-
|
|
20258
|
-
params.push(query.filterWorkspace);
|
|
20201
|
+
const selectParams = [];
|
|
20202
|
+
const whereParams = [toFtsQuery(query.terms)];
|
|
20203
|
+
let scoreExpr;
|
|
20204
|
+
if (query.workspace) {
|
|
20205
|
+
scoreExpr = "MAX(0, -bm25(memories_fts) + CASE WHEN m.workspace = ? THEN ? ELSE 0.0 END)";
|
|
20206
|
+
selectParams.push(query.workspace, WORKSPACE_BIAS);
|
|
20207
|
+
} else {
|
|
20208
|
+
scoreExpr = "MAX(0, -bm25(memories_fts))";
|
|
20259
20209
|
}
|
|
20210
|
+
const whereClauses = ["memories_fts MATCH ?"];
|
|
20260
20211
|
if (query.createdAfter) {
|
|
20261
20212
|
whereClauses.push("m.created_at >= ?");
|
|
20262
|
-
|
|
20213
|
+
whereParams.push(query.createdAfter.getTime());
|
|
20263
20214
|
}
|
|
20264
20215
|
if (query.createdBefore) {
|
|
20265
20216
|
whereClauses.push("m.created_at <= ?");
|
|
20266
|
-
|
|
20217
|
+
whereParams.push(query.createdBefore.getTime());
|
|
20267
20218
|
}
|
|
20219
|
+
const params = [...selectParams, ...whereParams, query.limit];
|
|
20268
20220
|
const statement = this.database.prepare(`
|
|
20269
20221
|
SELECT
|
|
20270
20222
|
m.id,
|
|
20271
20223
|
m.content,
|
|
20272
|
-
m.source,
|
|
20273
20224
|
m.workspace,
|
|
20274
|
-
m.session,
|
|
20275
20225
|
m.created_at,
|
|
20276
|
-
|
|
20226
|
+
${scoreExpr} AS score
|
|
20277
20227
|
FROM memories_fts
|
|
20278
20228
|
INNER JOIN memories AS m ON m.rowid = memories_fts.rowid
|
|
20279
20229
|
WHERE ${whereClauses.join(" AND ")}
|
|
20280
|
-
ORDER BY
|
|
20230
|
+
ORDER BY score DESC
|
|
20281
20231
|
LIMIT ?
|
|
20282
20232
|
`);
|
|
20283
|
-
params.push(toCandidateLimit(query.limit));
|
|
20284
20233
|
const rows = statement.all(...params);
|
|
20285
20234
|
return rows.map((row) => ({
|
|
20286
20235
|
id: row.id,
|
|
20287
20236
|
content: row.content,
|
|
20288
20237
|
score: row.score,
|
|
20289
|
-
source: row.source ?? undefined,
|
|
20290
20238
|
workspace: row.workspace ?? undefined,
|
|
20291
|
-
session: row.session ?? undefined,
|
|
20292
20239
|
createdAt: new Date(row.created_at)
|
|
20293
20240
|
}));
|
|
20294
20241
|
} catch (error2) {
|
|
@@ -20298,8 +20245,14 @@ class SqliteMemoryRepository {
|
|
|
20298
20245
|
}
|
|
20299
20246
|
}
|
|
20300
20247
|
}
|
|
20301
|
-
var
|
|
20302
|
-
var
|
|
20248
|
+
var toFtsQuery = (terms) => terms.map(toFtsTerm).join(" OR ");
|
|
20249
|
+
var toFtsTerm = (term) => {
|
|
20250
|
+
const escaped = term.replaceAll('"', '""');
|
|
20251
|
+
if (term.includes(" ")) {
|
|
20252
|
+
return `"${escaped}"`;
|
|
20253
|
+
}
|
|
20254
|
+
return `"${escaped}"*`;
|
|
20255
|
+
};
|
|
20303
20256
|
|
|
20304
20257
|
// src/index.ts
|
|
20305
20258
|
var { databasePath } = resolveConfig();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jcyamacho/agent-memory",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agent-memory": "dist/index.js"
|
|
7
7
|
},
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build": "bun build src/index.ts --target=node --external better-sqlite3 --outfile dist/index.js --banner \"#!/usr/bin/env node\"",
|
|
24
24
|
"start": "node dist/index.js",
|
|
25
25
|
"test": "bun test",
|
|
26
|
-
"lint": "biome check --write",
|
|
26
|
+
"lint": "biome check --write && tsc --noEmit",
|
|
27
27
|
"release:patch": "npm version patch && git push origin main --follow-tags",
|
|
28
28
|
"release:minor": "npm version minor && git push origin main --follow-tags",
|
|
29
29
|
"release:major": "npm version major && git push origin main --follow-tags"
|