@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 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-DJpwR1mt.mjs");
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-DJpwR1mt.mjs");
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-DJpwR1mt.mjs");
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-DpxhaZLn.mjs");
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":";;AAEA,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,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
+ {"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":";;AAEA,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;AAEF,MAAI,IAAI,IAAI;GACV,MAAM,SAAU,MAAM,IAAI,MAAM;AAChC,OAAI,OAAO,QACT,SAAQ,OAAO,MAAM,OAAO,QAAQ;;SAGlC;;AAKV,MAAM"}
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.9";
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
- await kv.set(KV.retentionScores, mem.id, entry);
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
- await kv.set(KV.retentionScores, sem.id, entry);
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
- await kv.delete(KV.memories, candidate.memoryId);
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);