@agentmemory/agentmemory 0.8.9 → 0.8.10
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 +22 -0
- package/dist/cli.mjs +4 -4
- package/dist/hooks/pre-tool-use.mjs +2 -0
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +2 -1
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/index.mjs +59 -6
- package/dist/index.mjs.map +1 -1
- package/dist/{src-DJpwR1mt.mjs → src-65nK6f5B.mjs} +57 -7
- package/dist/src-65nK6f5B.mjs.map +1 -0
- package/dist/{standalone-DpxhaZLn.mjs → standalone-CdWMLSak.mjs} +2 -2
- package/dist/{standalone-DpxhaZLn.mjs.map → standalone-CdWMLSak.mjs.map} +1 -1
- package/dist/standalone.mjs +1 -1
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-CfbSegvn.mjs → tools-registry-DXnvw9ZI.mjs} +6 -3
- package/dist/tools-registry-DXnvw9ZI.mjs.map +1 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/pre-tool-use.mjs +2 -0
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +2 -1
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/dist/src-DJpwR1mt.mjs.map +0 -1
- package/dist/tools-registry-CfbSegvn.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
Persistent memory for Claude Code, Cursor, Gemini CLI, OpenCode, and any MCP client.
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://gist.github.com/rohitg00/2067ab416f7bbe447c1977edaaa681e2"><img src="https://img.shields.io/badge/Viral%20GitHub%20Gist-669%20stars%20%2F%2091%20forks-FF6B35?style=for-the-badge&logo=github&logoColor=white&labelColor=1a1a1a" alt="Design doc: 686 stars / 94 forks on the gist" /></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<strong>The gist extends Karpathy's LLM Wiki pattern with confidence scoring, lifecycle, knowledge graphs, and hybrid search.<br/> agentmemory is the implementation.</strong>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
10
18
|
<p align="center">
|
|
11
19
|
<a href="https://www.npmjs.com/package/@agentmemory/agentmemory"><img src="https://img.shields.io/npm/v/@agentmemory/agentmemory?color=CB3837&label=npm&style=for-the-badge&logo=npm" alt="npm version" /></a>
|
|
12
20
|
<a href="https://github.com/rohitg00/agentmemory/actions"><img src="https://img.shields.io/github/actions/workflow/status/rohitg00/agentmemory/ci.yml?label=tests&style=for-the-badge&logo=github" alt="CI" /></a>
|
|
@@ -723,6 +731,20 @@ Create `~/.agentmemory/.env`:
|
|
|
723
731
|
# LLM provider to compress the
|
|
724
732
|
# observation — expect significant
|
|
725
733
|
# token spend on active sessions.
|
|
734
|
+
# AGENTMEMORY_INJECT_CONTEXT=false # OFF by default (#143). When on:
|
|
735
|
+
# - SessionStart may inject ~1-2K
|
|
736
|
+
# chars of project context into
|
|
737
|
+
# the first turn of each session
|
|
738
|
+
# (this is what actually reaches
|
|
739
|
+
# the model — Claude Code treats
|
|
740
|
+
# SessionStart stdout as context)
|
|
741
|
+
# - PreToolUse fires /agentmemory/enrich
|
|
742
|
+
# on every file-touching tool call
|
|
743
|
+
# (resource cleanup, not a token
|
|
744
|
+
# fix — PreToolUse stdout is debug
|
|
745
|
+
# log only per Claude Code docs)
|
|
746
|
+
# Observations are still captured via
|
|
747
|
+
# PostToolUse regardless of this flag.
|
|
726
748
|
# GRAPH_EXTRACTION_ENABLED=false
|
|
727
749
|
# CONSOLIDATION_ENABLED=true
|
|
728
750
|
# LESSON_DECAY_ENABLED=true
|
package/dist/cli.mjs
CHANGED
|
@@ -284,12 +284,12 @@ async function main() {
|
|
|
284
284
|
p.intro("agentmemory");
|
|
285
285
|
if (skipEngine) {
|
|
286
286
|
p.log.info("Skipping engine check (--no-engine)");
|
|
287
|
-
await import("./src-
|
|
287
|
+
await import("./src-65nK6f5B.mjs");
|
|
288
288
|
return;
|
|
289
289
|
}
|
|
290
290
|
if (await isEngineRunning()) {
|
|
291
291
|
p.log.success("iii-engine is running");
|
|
292
|
-
await import("./src-
|
|
292
|
+
await import("./src-65nK6f5B.mjs");
|
|
293
293
|
return;
|
|
294
294
|
}
|
|
295
295
|
if (!await startEngine()) {
|
|
@@ -333,7 +333,7 @@ async function main() {
|
|
|
333
333
|
process.exit(1);
|
|
334
334
|
}
|
|
335
335
|
s.stop("iii-engine is ready");
|
|
336
|
-
await import("./src-
|
|
336
|
+
await import("./src-65nK6f5B.mjs");
|
|
337
337
|
}
|
|
338
338
|
async function runStatus() {
|
|
339
339
|
const port = getRestPort();
|
|
@@ -557,7 +557,7 @@ async function runDemo() {
|
|
|
557
557
|
p.log.success("agentmemory is working. Point your agent at it and get back to coding.");
|
|
558
558
|
}
|
|
559
559
|
async function runMcp() {
|
|
560
|
-
await import("./standalone-
|
|
560
|
+
await import("./standalone-CdWMLSak.mjs");
|
|
561
561
|
}
|
|
562
562
|
({
|
|
563
563
|
status: runStatus,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
//#region src/hooks/pre-tool-use.ts
|
|
3
|
+
const INJECT_CONTEXT = process.env["AGENTMEMORY_INJECT_CONTEXT"] === "true";
|
|
3
4
|
const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
|
|
4
5
|
const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
|
|
5
6
|
function authHeaders() {
|
|
@@ -8,6 +9,7 @@ function authHeaders() {
|
|
|
8
9
|
return h;
|
|
9
10
|
}
|
|
10
11
|
async function main() {
|
|
12
|
+
if (!INJECT_CONTEXT) return;
|
|
11
13
|
let input = "";
|
|
12
14
|
for await (const chunk of process.stdin) input += chunk;
|
|
13
15
|
let data;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pre-tool-use.mjs","names":[],"sources":["../../src/hooks/pre-tool-use.ts"],"sourcesContent":["#!/usr/bin/env node\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n const toolName = data.tool_name as string;\n if (!toolName) return;\n\n const fileTools = [\"Edit\", \"Write\", \"Read\", \"Glob\", \"Grep\"];\n if (!fileTools.includes(toolName)) return;\n\n const toolInput = (data.tool_input || {}) as Record<string, unknown>;\n const files: string[] = [];\n const fileKeys =\n toolName === \"Grep\"\n ? [\"path\", \"file\"]\n : [\"file_path\", \"path\", \"file\", \"pattern\"];\n for (const key of fileKeys) {\n const val = toolInput[key];\n if (typeof val === \"string\" && val.length > 0) files.push(val);\n }\n if (files.length === 0) return;\n\n const terms: string[] = [];\n if (toolName === \"Grep\" || toolName === \"Glob\") {\n const pattern = toolInput[\"pattern\"];\n if (typeof pattern === \"string\" && pattern.length > 0) {\n terms.push(pattern);\n }\n }\n\n const sessionId = (data.session_id as string) || \"unknown\";\n\n try {\n const res = await fetch(`${REST_URL}/agentmemory/enrich`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({ sessionId, files, terms, toolName }),\n signal: AbortSignal.timeout(2000),\n });\n\n if (res.ok) {\n const result = (await res.json()) as { context?: string };\n if (result.context) {\n process.stdout.write(result.context);\n }\n }\n } catch {\n // don't block tool execution\n }\n}\n\nmain();\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"pre-tool-use.mjs","names":[],"sources":["../../src/hooks/pre-tool-use.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// Pre-tool-use enrichment hook.\n//\n// THIS HOOK IS A NO-OP BY DEFAULT AS OF 0.8.10 (#143). Previously it\n// fired /agentmemory/enrich on every Edit/Write/Read/Glob/Grep tool call\n// and wrote up to 4000 chars of context to stdout. Claude Code reads\n// PreToolUse stdout and prepends it to the model's next turn, which meant\n// agentmemory was silently injecting ~1000 tokens into every tool turn\n// via the user's Claude Code session. On Claude Pro that burned entire\n// allocations in a handful of messages (@adrianricardo, #143).\n//\n// Users who explicitly want pre-tool enrichment opt in with:\n// AGENTMEMORY_INJECT_CONTEXT=true in ~/.agentmemory/.env\n// and restart Claude Code. Expect your session input token count to grow\n// proportionally with the number of file-touching tool calls per turn.\nconst INJECT_CONTEXT = process.env[\"AGENTMEMORY_INJECT_CONTEXT\"] === \"true\";\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n // Default off: exit immediately so we don't even open stdin. This keeps\n // Claude Code's tool-call hot path as cheap as possible.\n if (!INJECT_CONTEXT) return;\n\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n const toolName = data.tool_name as string;\n if (!toolName) return;\n\n const fileTools = [\"Edit\", \"Write\", \"Read\", \"Glob\", \"Grep\"];\n if (!fileTools.includes(toolName)) return;\n\n const toolInput = (data.tool_input || {}) as Record<string, unknown>;\n const files: string[] = [];\n const fileKeys =\n toolName === \"Grep\"\n ? [\"path\", \"file\"]\n : [\"file_path\", \"path\", \"file\", \"pattern\"];\n for (const key of fileKeys) {\n const val = toolInput[key];\n if (typeof val === \"string\" && val.length > 0) files.push(val);\n }\n if (files.length === 0) return;\n\n const terms: string[] = [];\n if (toolName === \"Grep\" || toolName === \"Glob\") {\n const pattern = toolInput[\"pattern\"];\n if (typeof pattern === \"string\" && pattern.length > 0) {\n terms.push(pattern);\n }\n }\n\n const sessionId = (data.session_id as string) || \"unknown\";\n\n try {\n const res = await fetch(`${REST_URL}/agentmemory/enrich`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({ sessionId, files, terms, toolName }),\n signal: AbortSignal.timeout(2000),\n });\n\n if (res.ok) {\n const result = (await res.json()) as { context?: string };\n if (result.context) {\n process.stdout.write(result.context);\n }\n }\n } catch {\n // don't block tool execution\n }\n}\n\nmain();\n"],"mappings":";;AAgBA,MAAM,iBAAiB,QAAQ,IAAI,kCAAkC;AAErE,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;AAGpB,KAAI,CAAC,eAAgB;CAErB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;CAGF,MAAM,WAAW,KAAK;AACtB,KAAI,CAAC,SAAU;AAGf,KAAI,CADc;EAAC;EAAQ;EAAS;EAAQ;EAAQ;EAAO,CAC5C,SAAS,SAAS,CAAE;CAEnC,MAAM,YAAa,KAAK,cAAc,EAAE;CACxC,MAAM,QAAkB,EAAE;CAC1B,MAAM,WACJ,aAAa,SACT,CAAC,QAAQ,OAAO,GAChB;EAAC;EAAa;EAAQ;EAAQ;EAAU;AAC9C,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,MAAM,UAAU;AACtB,MAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,EAAG,OAAM,KAAK,IAAI;;AAEhE,KAAI,MAAM,WAAW,EAAG;CAExB,MAAM,QAAkB,EAAE;AAC1B,KAAI,aAAa,UAAU,aAAa,QAAQ;EAC9C,MAAM,UAAU,UAAU;AAC1B,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,EAClD,OAAM,KAAK,QAAQ;;CAIvB,MAAM,YAAa,KAAK,cAAyB;AAEjD,KAAI;EACF,MAAM,MAAM,MAAM,MAAM,GAAG,SAAS,sBAAsB;GACxD,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU;IAAE;IAAW;IAAO;IAAO;IAAU,CAAC;GAC3D,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;AAEF,MAAI,IAAI,IAAI;GACV,MAAM,SAAU,MAAM,IAAI,MAAM;AAChC,OAAI,OAAO,QACT,SAAQ,OAAO,MAAM,OAAO,QAAQ;;SAGlC;;AAKV,MAAM"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
//#region src/hooks/session-start.ts
|
|
3
|
+
const INJECT_CONTEXT = process.env["AGENTMEMORY_INJECT_CONTEXT"] === "true";
|
|
3
4
|
const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
|
|
4
5
|
const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
|
|
5
6
|
function authHeaders() {
|
|
@@ -29,7 +30,7 @@ async function main() {
|
|
|
29
30
|
}),
|
|
30
31
|
signal: AbortSignal.timeout(5e3)
|
|
31
32
|
});
|
|
32
|
-
if (res.ok) {
|
|
33
|
+
if (INJECT_CONTEXT && res.ok) {
|
|
33
34
|
const result = await res.json();
|
|
34
35
|
if (result.context) process.stdout.write(result.context);
|
|
35
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-start.mjs","names":[],"sources":["../../src/hooks/session-start.ts"],"sourcesContent":["#!/usr/bin/env node\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n const sessionId =\n (data.session_id as string) || `ses_${Date.now().toString(36)}`;\n const project = (data.cwd as string) || process.cwd();\n\n try {\n const res = await fetch(`${REST_URL}/agentmemory/session/start`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({ sessionId, project, cwd: project }),\n signal: AbortSignal.timeout(5000),\n });\n\n if (res.ok) {\n const result = (await res.json()) as { context?: string };\n if (result.context) {\n process.stdout.write(result.context);\n }\n }\n } catch {\n // silently fail -- don't block Claude Code startup\n }\n}\n\nmain();\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"session-start.mjs","names":[],"sources":["../../src/hooks/session-start.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// Session-start hook.\n//\n// Always registers the session for observation tracking (so memories\n// captured on PostToolUse get attached to the right session). Only writes\n// project context to stdout — which Claude Code prepends to the very first\n// turn — when AGENTMEMORY_INJECT_CONTEXT=true. Default off as of 0.8.10\n// (#143); see pre-tool-use.ts for the full explanation.\nconst INJECT_CONTEXT = process.env[\"AGENTMEMORY_INJECT_CONTEXT\"] === \"true\";\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n const sessionId =\n (data.session_id as string) || `ses_${Date.now().toString(36)}`;\n const project = (data.cwd as string) || process.cwd();\n\n try {\n const res = await fetch(`${REST_URL}/agentmemory/session/start`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({ sessionId, project, cwd: project }),\n signal: AbortSignal.timeout(5000),\n });\n\n // Only write context to stdout when the user has explicitly opted\n // into injection. Registering the session is cheap and doesn't touch\n // Claude Code's input token window.\n if (INJECT_CONTEXT && res.ok) {\n const result = (await res.json()) as { context?: string };\n if (result.context) {\n process.stdout.write(result.context);\n }\n }\n } catch {\n // silently fail -- don't block Claude Code startup\n }\n}\n\nmain();\n"],"mappings":";;AASA,MAAM,iBAAiB,QAAQ,IAAI,kCAAkC;AAErE,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;CAGF,MAAM,YACH,KAAK,cAAyB,OAAO,KAAK,KAAK,CAAC,SAAS,GAAG;CAC/D,MAAM,UAAW,KAAK,OAAkB,QAAQ,KAAK;AAErD,KAAI;EACF,MAAM,MAAM,MAAM,MAAM,GAAG,SAAS,6BAA6B;GAC/D,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU;IAAE;IAAW;IAAS,KAAK;IAAS,CAAC;GAC1D,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;AAKF,MAAI,kBAAkB,IAAI,IAAI;GAC5B,MAAM,SAAU,MAAM,IAAI,MAAM;AAChC,OAAI,OAAO,QACT,SAAQ,OAAO,MAAM,OAAO,QAAQ;;SAGlC;;AAKV,MAAM"}
|
package/dist/index.mjs
CHANGED
|
@@ -159,6 +159,9 @@ function isConsolidationEnabled() {
|
|
|
159
159
|
function isAutoCompressEnabled() {
|
|
160
160
|
return getMergedEnv()["AGENTMEMORY_AUTO_COMPRESS"] === "true";
|
|
161
161
|
}
|
|
162
|
+
function isContextInjectionEnabled() {
|
|
163
|
+
return getMergedEnv()["AGENTMEMORY_INJECT_CONTEXT"] === "true";
|
|
164
|
+
}
|
|
162
165
|
function getConsolidationDecayDays() {
|
|
163
166
|
return safeParseInt(getMergedEnv()["CONSOLIDATION_DECAY_DAYS"], 30);
|
|
164
167
|
}
|
|
@@ -4127,7 +4130,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
4127
4130
|
|
|
4128
4131
|
//#endregion
|
|
4129
4132
|
//#region src/version.ts
|
|
4130
|
-
const VERSION = "0.8.
|
|
4133
|
+
const VERSION = "0.8.10";
|
|
4131
4134
|
|
|
4132
4135
|
//#endregion
|
|
4133
4136
|
//#region src/functions/export-import.ts
|
|
@@ -4244,7 +4247,8 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
4244
4247
|
"0.8.6",
|
|
4245
4248
|
"0.8.7",
|
|
4246
4249
|
"0.8.8",
|
|
4247
|
-
"0.8.9"
|
|
4250
|
+
"0.8.9",
|
|
4251
|
+
"0.8.10"
|
|
4248
4252
|
]).has(importData.version)) return {
|
|
4249
4253
|
success: false,
|
|
4250
4254
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -10109,6 +10113,9 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10109
10113
|
}
|
|
10110
10114
|
const scores = [];
|
|
10111
10115
|
const computeDecay = (createdAt) => Math.exp(-config.lambda * ((Date.now() - new Date(createdAt).getTime()) / (1e3 * 60 * 60 * 24)));
|
|
10116
|
+
const pendingWrites = [];
|
|
10117
|
+
let episodicScored = 0;
|
|
10118
|
+
let semanticScored = 0;
|
|
10112
10119
|
for (const mem of memories) {
|
|
10113
10120
|
if (!mem.isLatest) continue;
|
|
10114
10121
|
const log = logsById.get(mem.id) ?? emptyAccessLog(mem.id);
|
|
@@ -10118,6 +10125,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10118
10125
|
const score = Math.min(1, salience * temporalDecay + reinforcementBoost);
|
|
10119
10126
|
const entry = {
|
|
10120
10127
|
memoryId: mem.id,
|
|
10128
|
+
source: "episodic",
|
|
10121
10129
|
score,
|
|
10122
10130
|
salience,
|
|
10123
10131
|
temporalDecay,
|
|
@@ -10126,7 +10134,8 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10126
10134
|
accessCount: log.count
|
|
10127
10135
|
};
|
|
10128
10136
|
scores.push(entry);
|
|
10129
|
-
|
|
10137
|
+
pendingWrites.push([mem.id, entry]);
|
|
10138
|
+
episodicScored++;
|
|
10130
10139
|
}
|
|
10131
10140
|
for (const sem of semanticMems) {
|
|
10132
10141
|
const log = logsById.get(sem.id) ?? emptyAccessLog(sem.id);
|
|
@@ -10149,6 +10158,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10149
10158
|
const score = Math.min(1, salience * temporalDecay + reinforcementBoost);
|
|
10150
10159
|
const entry = {
|
|
10151
10160
|
memoryId: sem.id,
|
|
10161
|
+
source: "semantic",
|
|
10152
10162
|
score,
|
|
10153
10163
|
salience,
|
|
10154
10164
|
temporalDecay,
|
|
@@ -10157,8 +10167,10 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10157
10167
|
accessCount: effectiveCount
|
|
10158
10168
|
};
|
|
10159
10169
|
scores.push(entry);
|
|
10160
|
-
|
|
10170
|
+
pendingWrites.push([sem.id, entry]);
|
|
10171
|
+
semanticScored++;
|
|
10161
10172
|
}
|
|
10173
|
+
await Promise.all(pendingWrites.map(([id, entry]) => kv.set(KV.retentionScores, id, entry)));
|
|
10162
10174
|
scores.sort((a, b) => b.score - a.score);
|
|
10163
10175
|
const tiers = {
|
|
10164
10176
|
hot: scores.filter((s) => s.score >= config.tierThresholds.hot).length,
|
|
@@ -10170,6 +10182,13 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10170
10182
|
total: scores.length,
|
|
10171
10183
|
...tiers
|
|
10172
10184
|
});
|
|
10185
|
+
if (scores.length > 0) await recordAudit(kv, "retention_score", "mem::retention-score", [], {
|
|
10186
|
+
total: scores.length,
|
|
10187
|
+
episodic: episodicScored,
|
|
10188
|
+
semantic: semanticScored,
|
|
10189
|
+
tiers,
|
|
10190
|
+
config
|
|
10191
|
+
});
|
|
10173
10192
|
return {
|
|
10174
10193
|
success: true,
|
|
10175
10194
|
total: scores.length,
|
|
@@ -10195,21 +10214,53 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
10195
10214
|
}))
|
|
10196
10215
|
};
|
|
10197
10216
|
let evicted = 0;
|
|
10217
|
+
let evictedEpisodic = 0;
|
|
10218
|
+
let evictedSemantic = 0;
|
|
10219
|
+
const evictedIds = [];
|
|
10198
10220
|
for (const candidate of candidates) try {
|
|
10199
|
-
|
|
10221
|
+
let scope;
|
|
10222
|
+
let resolvedSource;
|
|
10223
|
+
if (candidate.source === "semantic") {
|
|
10224
|
+
scope = KV.semantic;
|
|
10225
|
+
resolvedSource = "semantic";
|
|
10226
|
+
} else if (candidate.source === "episodic") {
|
|
10227
|
+
scope = KV.memories;
|
|
10228
|
+
resolvedSource = "episodic";
|
|
10229
|
+
} else if (await kv.get(KV.memories, candidate.memoryId) !== null) {
|
|
10230
|
+
scope = KV.memories;
|
|
10231
|
+
resolvedSource = "episodic";
|
|
10232
|
+
} else {
|
|
10233
|
+
scope = KV.semantic;
|
|
10234
|
+
resolvedSource = "semantic";
|
|
10235
|
+
}
|
|
10236
|
+
await kv.delete(scope, candidate.memoryId);
|
|
10200
10237
|
await kv.delete(KV.retentionScores, candidate.memoryId);
|
|
10201
10238
|
await deleteAccessLog(kv, candidate.memoryId);
|
|
10202
10239
|
evicted++;
|
|
10240
|
+
evictedIds.push(candidate.memoryId);
|
|
10241
|
+
if (resolvedSource === "semantic") evictedSemantic++;
|
|
10242
|
+
else evictedEpisodic++;
|
|
10203
10243
|
} catch {
|
|
10204
10244
|
continue;
|
|
10205
10245
|
}
|
|
10246
|
+
if (evicted > 0) await recordAudit(kv, "delete", "mem::retention-evict", evictedIds, {
|
|
10247
|
+
threshold,
|
|
10248
|
+
evicted,
|
|
10249
|
+
evictedEpisodic,
|
|
10250
|
+
evictedSemantic,
|
|
10251
|
+
reason: "retention score below threshold"
|
|
10252
|
+
});
|
|
10206
10253
|
ctx.logger.info("Retention-based eviction complete", {
|
|
10207
10254
|
evicted,
|
|
10255
|
+
evictedEpisodic,
|
|
10256
|
+
evictedSemantic,
|
|
10208
10257
|
threshold
|
|
10209
10258
|
});
|
|
10210
10259
|
return {
|
|
10211
10260
|
success: true,
|
|
10212
|
-
evicted
|
|
10261
|
+
evicted,
|
|
10262
|
+
evictedEpisodic,
|
|
10263
|
+
evictedSemantic
|
|
10213
10264
|
};
|
|
10214
10265
|
});
|
|
10215
10266
|
}
|
|
@@ -15082,6 +15133,8 @@ async function main() {
|
|
|
15082
15133
|
console.log(`[agentmemory] Consolidation pipeline: registered (CONSOLIDATION_ENABLED=${isConsolidationEnabled() ? "true" : "false"})`);
|
|
15083
15134
|
if (isAutoCompressEnabled()) console.log(`[agentmemory] WARNING: AGENTMEMORY_AUTO_COMPRESS=true — every PostToolUse observation will be sent to your LLM provider for compression. This spends API tokens proportional to your session tool-use frequency (see #138). Set AGENTMEMORY_AUTO_COMPRESS=false to disable.`);
|
|
15084
15135
|
else console.log(`[agentmemory] Auto-compress: OFF (default, #138) — observations indexed via zero-LLM synthetic compression. Set AGENTMEMORY_AUTO_COMPRESS=true to opt-in to LLM-powered summaries (uses your API key).`);
|
|
15136
|
+
if (isContextInjectionEnabled()) console.log(`[agentmemory] WARNING: AGENTMEMORY_INJECT_CONTEXT=true — the PreToolUse and SessionStart hooks will inject up to ~4000 chars of memory context into every tool turn. On Claude Pro this burns session tokens proportional to your tool-call frequency (see #143). Set AGENTMEMORY_INJECT_CONTEXT=false to disable.`);
|
|
15137
|
+
else console.log(`[agentmemory] Context injection: OFF (default, #143) — hooks capture observations but do not inject context into Claude Code's conversation. Set AGENTMEMORY_INJECT_CONTEXT=true to opt-in (warning: expect your Claude Pro allocation to drain faster).`);
|
|
15085
15138
|
const teamConfig = loadTeamConfig();
|
|
15086
15139
|
if (teamConfig) {
|
|
15087
15140
|
registerTeamFunction(sdk, kv, teamConfig);
|