@agentmemory/agentmemory 0.8.6 → 0.8.8

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
@@ -181,15 +181,6 @@ npx @agentmemory/agentmemory
181
181
  </tr>
182
182
  </table>
183
183
 
184
- <p align="center">
185
- <picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/stat-recall.svg"><img src="assets/tags/stat-recall.svg" alt="95.2% retrieval R@5" height="48" /></picture>
186
- <picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/stat-tokens.svg"><img src="assets/tags/stat-tokens.svg" alt="92% fewer tokens" height="48" /></picture>
187
- <picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/stat-tools.svg"><img src="assets/tags/stat-tools.svg" alt="43 MCP tools" height="48" /></picture>
188
- <picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/stat-hooks.svg"><img src="assets/tags/stat-hooks.svg" alt="12 auto hooks" height="48" /></picture>
189
- <picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/stat-deps.svg"><img src="assets/tags/stat-deps.svg" alt="0 external DBs" height="48" /></picture>
190
- <picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/stat-tests.svg"><img src="assets/tags/stat-tests.svg" alt="654 tests passing" height="48" /></picture>
191
- </p>
192
-
193
184
  > Embedding model: `all-MiniLM-L6-v2` (local, free, no API key). Full reports: [`benchmark/LONGMEMEVAL.md`](benchmark/LONGMEMEVAL.md), [`benchmark/QUALITY.md`](benchmark/QUALITY.md), [`benchmark/SCALE.md`](benchmark/SCALE.md). Competitor comparison: [`benchmark/COMPARISON.md`](benchmark/COMPARISON.md) — agentmemory vs mem0, Letta, Khoj, claude-mem, Hippo.
194
185
 
195
186
  ---
@@ -727,6 +718,11 @@ Create `~/.agentmemory/.env`:
727
718
  # III_REST_PORT=3111
728
719
 
729
720
  # Features
721
+ # AGENTMEMORY_AUTO_COMPRESS=false # OFF by default (#138). When on,
722
+ # every PostToolUse hook calls your
723
+ # LLM provider to compress the
724
+ # observation — expect significant
725
+ # token spend on active sessions.
730
726
  # GRAPH_EXTRACTION_ENABLED=false
731
727
  # CONSOLIDATION_ENABLED=true
732
728
  # 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-DjVaT_sI.mjs");
287
+ await import("./src-CneY0pgf.mjs");
288
288
  return;
289
289
  }
290
290
  if (await isEngineRunning()) {
291
291
  p.log.success("iii-engine is running");
292
- await import("./src-DjVaT_sI.mjs");
292
+ await import("./src-CneY0pgf.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-DjVaT_sI.mjs");
336
+ await import("./src-CneY0pgf.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-C7tRDaiu.mjs");
560
+ await import("./standalone-Qmvspmgi.mjs");
561
561
  }
562
562
  ({
563
563
  status: runStatus,
package/dist/index.mjs CHANGED
@@ -156,6 +156,9 @@ function isGraphExtractionEnabled() {
156
156
  function isConsolidationEnabled() {
157
157
  return getMergedEnv()["CONSOLIDATION_ENABLED"] === "true";
158
158
  }
159
+ function isAutoCompressEnabled() {
160
+ return getMergedEnv()["AGENTMEMORY_AUTO_COMPRESS"] === "true";
161
+ }
159
162
  function getConsolidationDecayDays() {
160
163
  return safeParseInt(getMergedEnv()["CONSOLIDATION_DECAY_DAYS"], 30);
161
164
  }
@@ -1997,164 +2000,95 @@ function withKeyedLock(key, fn) {
1997
2000
  }
1998
2001
 
1999
2002
  //#endregion
2000
- //#region src/functions/observe.ts
2001
- function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
2002
- sdk.registerFunction({
2003
- id: "mem::observe",
2004
- description: "Capture and store a tool-use observation"
2005
- }, async (payload) => {
2006
- const ctx = getContext();
2007
- if (!payload?.sessionId || typeof payload.sessionId !== "string" || !payload.hookType || typeof payload.hookType !== "string" || !payload.timestamp || typeof payload.timestamp !== "string") return {
2008
- success: false,
2009
- error: "Invalid payload: sessionId, hookType, and timestamp are required"
2010
- };
2011
- const obsId = generateId("obs");
2012
- let dedupHash;
2013
- if (dedupMap) {
2014
- const d = typeof payload.data === "object" && payload.data !== null ? payload.data : {};
2015
- const toolName = d["tool_name"] || payload.hookType;
2016
- dedupHash = dedupMap.computeHash(payload.sessionId, toolName, d["tool_input"]);
2017
- if (dedupMap.isDuplicate(dedupHash)) return {
2018
- deduplicated: true,
2019
- sessionId: payload.sessionId
2020
- };
2021
- }
2022
- let sanitizedRaw = payload.data;
2023
- try {
2024
- const sanitized = stripPrivateData(JSON.stringify(payload.data));
2025
- sanitizedRaw = JSON.parse(sanitized);
2026
- } catch {
2027
- sanitizedRaw = stripPrivateData(String(payload.data));
2028
- }
2029
- const raw = {
2030
- id: obsId,
2031
- sessionId: payload.sessionId,
2032
- timestamp: payload.timestamp,
2033
- hookType: payload.hookType,
2034
- raw: sanitizedRaw
2035
- };
2036
- if (typeof sanitizedRaw === "object" && sanitizedRaw !== null) {
2037
- const d = sanitizedRaw;
2038
- if (payload.hookType === "post_tool_use" || payload.hookType === "post_tool_failure") {
2039
- raw.toolName = d["tool_name"];
2040
- raw.toolInput = d["tool_input"];
2041
- raw.toolOutput = d["tool_output"] || d["error"];
2042
- }
2043
- if (payload.hookType === "prompt_submit") raw.userPrompt = d["prompt"];
2044
- }
2045
- return withKeyedLock(`obs:${payload.sessionId}`, async () => {
2046
- if (maxObservationsPerSession && maxObservationsPerSession > 0) {
2047
- if ((await kv.list(KV.observations(payload.sessionId))).length >= maxObservationsPerSession) return {
2048
- success: false,
2049
- error: `Session observation limit reached (${maxObservationsPerSession})`
2050
- };
2051
- }
2052
- await kv.set(KV.observations(payload.sessionId), obsId, raw);
2053
- if (dedupMap && dedupHash) dedupMap.record(dedupHash);
2054
- sdk.triggerVoid("stream::set", {
2055
- stream_name: STREAM.name,
2056
- group_id: STREAM.group(payload.sessionId),
2057
- item_id: obsId,
2058
- data: {
2059
- type: "raw",
2060
- observation: raw
2061
- }
2062
- });
2063
- sdk.triggerVoid("stream::set", {
2064
- stream_name: STREAM.name,
2065
- group_id: STREAM.viewerGroup,
2066
- item_id: obsId,
2067
- data: {
2068
- type: "raw",
2069
- observation: raw,
2070
- sessionId: payload.sessionId
2071
- }
2072
- });
2073
- const session = await kv.get(KV.sessions, payload.sessionId);
2074
- if (session) await kv.set(KV.sessions, payload.sessionId, {
2075
- ...session,
2076
- observationCount: (session.observationCount || 0) + 1
2077
- });
2078
- sdk.triggerVoid("mem::compress", {
2079
- observationId: obsId,
2080
- sessionId: payload.sessionId,
2081
- raw
2082
- });
2083
- ctx.logger.info("Observation captured", {
2084
- obsId,
2085
- sessionId: payload.sessionId,
2086
- hook: payload.hookType
2087
- });
2088
- return { observationId: obsId };
2089
- });
2090
- });
2091
- }
2092
-
2093
- //#endregion
2094
- //#region src/prompts/compression.ts
2095
- const COMPRESSION_SYSTEM = `You are a memory compression engine for an AI coding agent. Your job is to extract the essential information from a tool usage observation and compress it into structured data.
2096
-
2097
- Output EXACTLY this XML format with no additional text:
2098
-
2099
- <observation>
2100
- <type>one of: file_read, file_write, file_edit, command_run, search, web_fetch, conversation, error, decision, discovery, subagent, notification, task, other</type>
2101
- <title>Short descriptive title (max 80 chars)</title>
2102
- <subtitle>One-line context (optional)</subtitle>
2103
- <facts>
2104
- <fact>Specific factual detail 1</fact>
2105
- <fact>Specific factual detail 2</fact>
2106
- </facts>
2107
- <narrative>2-3 sentence summary of what happened and why it matters</narrative>
2108
- <concepts>
2109
- <concept>technical concept or pattern</concept>
2110
- </concepts>
2111
- <files>
2112
- <file>path/to/file</file>
2113
- </files>
2114
- <importance>1-10 scale, 10 being critical architectural decision</importance>
2115
- </observation>
2116
-
2117
- Rules:
2118
- - Be concise but preserve ALL technically relevant details
2119
- - File paths must be exact
2120
- - Importance: 1-3 for routine reads, 4-6 for edits/commands, 7-9 for architectural decisions, 10 for breaking changes
2121
- - Concepts should be reusable search terms (e.g., "React hooks", "SQL migration", "auth middleware")
2122
- - Strip any secrets, tokens, or credentials from the output`;
2123
- function buildCompressionPrompt(observation) {
2124
- const parts = [`Timestamp: ${observation.timestamp}`, `Hook: ${observation.hookType}`];
2125
- if (observation.toolName) parts.push(`Tool: ${observation.toolName}`);
2126
- if (observation.toolInput) {
2127
- const input = typeof observation.toolInput === "string" ? observation.toolInput : JSON.stringify(observation.toolInput, null, 2);
2128
- parts.push(`Input:\n${truncate(input, 4e3)}`);
2129
- }
2130
- if (observation.toolOutput) {
2131
- const output = typeof observation.toolOutput === "string" ? observation.toolOutput : JSON.stringify(observation.toolOutput, null, 2);
2132
- parts.push(`Output:\n${truncate(output, 4e3)}`);
2003
+ //#region src/functions/compress-synthetic.ts
2004
+ function inferType(toolName, hookType) {
2005
+ if (hookType === "post_tool_failure") return "error";
2006
+ if (hookType === "prompt_submit") return "conversation";
2007
+ if (hookType === "subagent_stop" || hookType === "task_completed") return "subagent";
2008
+ if (hookType === "notification") return "notification";
2009
+ if (!toolName) return "other";
2010
+ const n = toolName.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
2011
+ const hasWord = (word) => new RegExp(`(^|_)${word}(_|$)`).test(n) || n === word || n.endsWith(word) || n.startsWith(word);
2012
+ if ([
2013
+ "fetch",
2014
+ "http",
2015
+ "web"
2016
+ ].some(hasWord)) return "web_fetch";
2017
+ if ([
2018
+ "grep",
2019
+ "search",
2020
+ "glob",
2021
+ "find"
2022
+ ].some(hasWord)) return "search";
2023
+ if ([
2024
+ "bash",
2025
+ "shell",
2026
+ "exec",
2027
+ "run"
2028
+ ].some(hasWord)) return "command_run";
2029
+ if ([
2030
+ "edit",
2031
+ "update",
2032
+ "patch",
2033
+ "replace"
2034
+ ].some(hasWord)) return "file_edit";
2035
+ if (["write", "create"].some(hasWord)) return "file_write";
2036
+ if (["read", "view"].some(hasWord)) return "file_read";
2037
+ if (["task", "agent"].some(hasWord)) return "subagent";
2038
+ return "other";
2039
+ }
2040
+ function extractFiles$1(input) {
2041
+ if (!input || typeof input !== "object") return [];
2042
+ const o = input;
2043
+ const out = /* @__PURE__ */ new Set();
2044
+ for (const key of [
2045
+ "file_path",
2046
+ "filepath",
2047
+ "path",
2048
+ "filePath",
2049
+ "file",
2050
+ "pattern"
2051
+ ]) {
2052
+ const v = o[key];
2053
+ if (typeof v === "string" && v.length > 0 && v.length < 512) out.add(v);
2054
+ }
2055
+ return [...out];
2056
+ }
2057
+ function stringifyForNarrative(v) {
2058
+ if (v == null) return "";
2059
+ if (typeof v === "string") return v;
2060
+ try {
2061
+ return JSON.stringify(v);
2062
+ } catch {
2063
+ return String(v);
2133
2064
  }
2134
- if (observation.userPrompt) parts.push(`User prompt:\n${truncate(observation.userPrompt, 2e3)}`);
2135
- return parts.join("\n\n");
2136
2065
  }
2137
- function truncate(s, max) {
2138
- return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
2139
- }
2140
-
2141
- //#endregion
2142
- //#region src/prompts/xml.ts
2143
- const VALID_TAG = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
2144
- function getXmlTag(xml, tag) {
2145
- if (!VALID_TAG.test(tag)) return "";
2146
- const match = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
2147
- return match ? match[1].trim() : "";
2066
+ function truncate$1(s, n) {
2067
+ return s.length > n ? s.slice(0, n - 1) + "" : s;
2148
2068
  }
2149
- function getXmlChildren(xml, parentTag, childTag) {
2150
- if (!VALID_TAG.test(parentTag) || !VALID_TAG.test(childTag)) return [];
2151
- const parentMatch = xml.match(new RegExp(`<${parentTag}>([\\s\\S]*?)</${parentTag}>`));
2152
- if (!parentMatch) return [];
2153
- const items = [];
2154
- const re = new RegExp(`<${childTag}>([\\s\\S]*?)</${childTag}>`, "g");
2155
- let m;
2156
- while ((m = re.exec(parentMatch[1])) !== null) items.push(m[1].trim());
2157
- return items;
2069
+ function buildSyntheticCompression(raw) {
2070
+ const toolName = raw.toolName ?? raw.hookType;
2071
+ const inputStr = stringifyForNarrative(raw.toolInput);
2072
+ const outputStr = stringifyForNarrative(raw.toolOutput);
2073
+ const narrativeParts = [
2074
+ raw.userPrompt ?? "",
2075
+ inputStr,
2076
+ outputStr
2077
+ ].filter((s) => s.length > 0);
2078
+ return {
2079
+ id: raw.id,
2080
+ sessionId: raw.sessionId,
2081
+ timestamp: raw.timestamp,
2082
+ type: inferType(toolName, raw.hookType),
2083
+ title: truncate$1(toolName || "observation", 80),
2084
+ subtitle: inputStr ? truncate$1(inputStr, 120) : void 0,
2085
+ facts: [],
2086
+ narrative: truncate$1(narrativeParts.join(" | "), 400),
2087
+ concepts: [],
2088
+ files: extractFiles$1(raw.toolInput),
2089
+ importance: 5,
2090
+ confidence: .3
2091
+ };
2158
2092
  }
2159
2093
 
2160
2094
  //#endregion
@@ -2325,6 +2259,192 @@ function registerSearchFunction(sdk, kv) {
2325
2259
  });
2326
2260
  }
2327
2261
 
2262
+ //#endregion
2263
+ //#region src/functions/observe.ts
2264
+ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
2265
+ sdk.registerFunction({
2266
+ id: "mem::observe",
2267
+ description: "Capture and store a tool-use observation"
2268
+ }, async (payload) => {
2269
+ const ctx = getContext();
2270
+ if (!payload?.sessionId || typeof payload.sessionId !== "string" || !payload.hookType || typeof payload.hookType !== "string" || !payload.timestamp || typeof payload.timestamp !== "string") return {
2271
+ success: false,
2272
+ error: "Invalid payload: sessionId, hookType, and timestamp are required"
2273
+ };
2274
+ const obsId = generateId("obs");
2275
+ let dedupHash;
2276
+ if (dedupMap) {
2277
+ const d = typeof payload.data === "object" && payload.data !== null ? payload.data : {};
2278
+ const toolName = d["tool_name"] || payload.hookType;
2279
+ dedupHash = dedupMap.computeHash(payload.sessionId, toolName, d["tool_input"]);
2280
+ if (dedupMap.isDuplicate(dedupHash)) return {
2281
+ deduplicated: true,
2282
+ sessionId: payload.sessionId
2283
+ };
2284
+ }
2285
+ let sanitizedRaw = payload.data;
2286
+ try {
2287
+ const sanitized = stripPrivateData(JSON.stringify(payload.data));
2288
+ sanitizedRaw = JSON.parse(sanitized);
2289
+ } catch {
2290
+ sanitizedRaw = stripPrivateData(String(payload.data));
2291
+ }
2292
+ const raw = {
2293
+ id: obsId,
2294
+ sessionId: payload.sessionId,
2295
+ timestamp: payload.timestamp,
2296
+ hookType: payload.hookType,
2297
+ raw: sanitizedRaw
2298
+ };
2299
+ if (typeof sanitizedRaw === "object" && sanitizedRaw !== null) {
2300
+ const d = sanitizedRaw;
2301
+ if (payload.hookType === "post_tool_use" || payload.hookType === "post_tool_failure") {
2302
+ raw.toolName = d["tool_name"];
2303
+ raw.toolInput = d["tool_input"];
2304
+ raw.toolOutput = d["tool_output"] || d["error"];
2305
+ }
2306
+ if (payload.hookType === "prompt_submit") raw.userPrompt = d["prompt"];
2307
+ }
2308
+ return withKeyedLock(`obs:${payload.sessionId}`, async () => {
2309
+ if (maxObservationsPerSession && maxObservationsPerSession > 0) {
2310
+ if ((await kv.list(KV.observations(payload.sessionId))).length >= maxObservationsPerSession) return {
2311
+ success: false,
2312
+ error: `Session observation limit reached (${maxObservationsPerSession})`
2313
+ };
2314
+ }
2315
+ await kv.set(KV.observations(payload.sessionId), obsId, raw);
2316
+ if (dedupMap && dedupHash) dedupMap.record(dedupHash);
2317
+ sdk.triggerVoid("stream::set", {
2318
+ stream_name: STREAM.name,
2319
+ group_id: STREAM.group(payload.sessionId),
2320
+ item_id: obsId,
2321
+ data: {
2322
+ type: "raw",
2323
+ observation: raw
2324
+ }
2325
+ });
2326
+ sdk.triggerVoid("stream::set", {
2327
+ stream_name: STREAM.name,
2328
+ group_id: STREAM.viewerGroup,
2329
+ item_id: obsId,
2330
+ data: {
2331
+ type: "raw",
2332
+ observation: raw,
2333
+ sessionId: payload.sessionId
2334
+ }
2335
+ });
2336
+ const session = await kv.get(KV.sessions, payload.sessionId);
2337
+ if (session) await kv.set(KV.sessions, payload.sessionId, {
2338
+ ...session,
2339
+ observationCount: (session.observationCount || 0) + 1
2340
+ });
2341
+ if (isAutoCompressEnabled()) sdk.triggerVoid("mem::compress", {
2342
+ observationId: obsId,
2343
+ sessionId: payload.sessionId,
2344
+ raw
2345
+ });
2346
+ else {
2347
+ const synthetic = buildSyntheticCompression(raw);
2348
+ await kv.set(KV.observations(payload.sessionId), obsId, synthetic);
2349
+ getSearchIndex().add(synthetic);
2350
+ sdk.triggerVoid("stream::set", {
2351
+ stream_name: STREAM.name,
2352
+ group_id: STREAM.group(payload.sessionId),
2353
+ item_id: obsId,
2354
+ data: {
2355
+ type: "compressed",
2356
+ observation: synthetic
2357
+ }
2358
+ });
2359
+ sdk.triggerVoid("stream::set", {
2360
+ stream_name: STREAM.name,
2361
+ group_id: STREAM.viewerGroup,
2362
+ item_id: obsId,
2363
+ data: {
2364
+ type: "compressed",
2365
+ observation: synthetic,
2366
+ sessionId: payload.sessionId
2367
+ }
2368
+ });
2369
+ }
2370
+ ctx.logger.info("Observation captured", {
2371
+ obsId,
2372
+ sessionId: payload.sessionId,
2373
+ hook: payload.hookType,
2374
+ compress: isAutoCompressEnabled() ? "llm" : "synthetic"
2375
+ });
2376
+ return { observationId: obsId };
2377
+ });
2378
+ });
2379
+ }
2380
+
2381
+ //#endregion
2382
+ //#region src/prompts/compression.ts
2383
+ const COMPRESSION_SYSTEM = `You are a memory compression engine for an AI coding agent. Your job is to extract the essential information from a tool usage observation and compress it into structured data.
2384
+
2385
+ Output EXACTLY this XML format with no additional text:
2386
+
2387
+ <observation>
2388
+ <type>one of: file_read, file_write, file_edit, command_run, search, web_fetch, conversation, error, decision, discovery, subagent, notification, task, other</type>
2389
+ <title>Short descriptive title (max 80 chars)</title>
2390
+ <subtitle>One-line context (optional)</subtitle>
2391
+ <facts>
2392
+ <fact>Specific factual detail 1</fact>
2393
+ <fact>Specific factual detail 2</fact>
2394
+ </facts>
2395
+ <narrative>2-3 sentence summary of what happened and why it matters</narrative>
2396
+ <concepts>
2397
+ <concept>technical concept or pattern</concept>
2398
+ </concepts>
2399
+ <files>
2400
+ <file>path/to/file</file>
2401
+ </files>
2402
+ <importance>1-10 scale, 10 being critical architectural decision</importance>
2403
+ </observation>
2404
+
2405
+ Rules:
2406
+ - Be concise but preserve ALL technically relevant details
2407
+ - File paths must be exact
2408
+ - Importance: 1-3 for routine reads, 4-6 for edits/commands, 7-9 for architectural decisions, 10 for breaking changes
2409
+ - Concepts should be reusable search terms (e.g., "React hooks", "SQL migration", "auth middleware")
2410
+ - Strip any secrets, tokens, or credentials from the output`;
2411
+ function buildCompressionPrompt(observation) {
2412
+ const parts = [`Timestamp: ${observation.timestamp}`, `Hook: ${observation.hookType}`];
2413
+ if (observation.toolName) parts.push(`Tool: ${observation.toolName}`);
2414
+ if (observation.toolInput) {
2415
+ const input = typeof observation.toolInput === "string" ? observation.toolInput : JSON.stringify(observation.toolInput, null, 2);
2416
+ parts.push(`Input:\n${truncate(input, 4e3)}`);
2417
+ }
2418
+ if (observation.toolOutput) {
2419
+ const output = typeof observation.toolOutput === "string" ? observation.toolOutput : JSON.stringify(observation.toolOutput, null, 2);
2420
+ parts.push(`Output:\n${truncate(output, 4e3)}`);
2421
+ }
2422
+ if (observation.userPrompt) parts.push(`User prompt:\n${truncate(observation.userPrompt, 2e3)}`);
2423
+ return parts.join("\n\n");
2424
+ }
2425
+ function truncate(s, max) {
2426
+ return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
2427
+ }
2428
+
2429
+ //#endregion
2430
+ //#region src/prompts/xml.ts
2431
+ const VALID_TAG = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
2432
+ function getXmlTag(xml, tag) {
2433
+ if (!VALID_TAG.test(tag)) return "";
2434
+ const match = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
2435
+ return match ? match[1].trim() : "";
2436
+ }
2437
+ function getXmlChildren(xml, parentTag, childTag) {
2438
+ if (!VALID_TAG.test(parentTag) || !VALID_TAG.test(childTag)) return [];
2439
+ const parentMatch = xml.match(new RegExp(`<${parentTag}>([\\s\\S]*?)</${parentTag}>`));
2440
+ if (!parentMatch) return [];
2441
+ const items = [];
2442
+ const re = new RegExp(`<${childTag}>([\\s\\S]*?)</${childTag}>`, "g");
2443
+ let m;
2444
+ while ((m = re.exec(parentMatch[1])) !== null) items.push(m[1].trim());
2445
+ return items;
2446
+ }
2447
+
2328
2448
  //#endregion
2329
2449
  //#region src/eval/schemas.ts
2330
2450
  const HookTypeEnum = z.enum([
@@ -4007,7 +4127,7 @@ function registerAutoForgetFunction(sdk, kv) {
4007
4127
 
4008
4128
  //#endregion
4009
4129
  //#region src/version.ts
4010
- const VERSION = "0.8.6";
4130
+ const VERSION = "0.8.8";
4011
4131
 
4012
4132
  //#endregion
4013
4133
  //#region src/functions/export-import.ts
@@ -4121,7 +4241,9 @@ function registerExportImportFunction(sdk, kv) {
4121
4241
  "0.8.3",
4122
4242
  "0.8.4",
4123
4243
  "0.8.5",
4124
- "0.8.6"
4244
+ "0.8.6",
4245
+ "0.8.7",
4246
+ "0.8.8"
4125
4247
  ]).has(importData.version)) return {
4126
4248
  success: false,
4127
4249
  error: `Unsupported export version: ${importData.version}`
@@ -14957,6 +15079,8 @@ async function main() {
14957
15079
  }
14958
15080
  registerConsolidationPipelineFunction(sdk, kv, provider);
14959
15081
  console.log(`[agentmemory] Consolidation pipeline: registered (CONSOLIDATION_ENABLED=${isConsolidationEnabled() ? "true" : "false"})`);
15082
+ 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.`);
15083
+ 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).`);
14960
15084
  const teamConfig = loadTeamConfig();
14961
15085
  if (teamConfig) {
14962
15086
  registerTeamFunction(sdk, kv, teamConfig);