@agentmemory/agentmemory 0.8.11 → 0.8.12

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/AGENTS.md CHANGED
@@ -111,8 +111,8 @@ Hook scripts in `src/hooks/` are standalone Node.js scripts (no iii-sdk import).
111
111
 
112
112
  ## Current Stats (v0.8.9)
113
113
 
114
- - 43 MCP tools (8 visible by default, `AGENTMEMORY_TOOLS=all` for all)
115
- - 103 REST endpoints
114
+ - 44 MCP tools (8 visible by default, `AGENTMEMORY_TOOLS=all` for all)
115
+ - 104 REST endpoints
116
116
  - 6 MCP resources, 3 MCP prompts
117
117
  - 12 hooks, 4 skills
118
118
  - 50+ iii functions
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  </p>
9
9
 
10
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>
11
+ <a href="https://gist.github.com/rohitg00/2067ab416f7bbe447c1977edaaa681e2"><img src="https://img.shields.io/badge/Viral%20GitHub%20Gist-719%20stars%20%2F%2097%20forks-FF6B35?style=for-the-badge&logo=github&logoColor=white&labelColor=1a1a1a" alt="Design doc: 719 stars / 97 forks on the gist" /></a>
12
12
  </p>
13
13
 
14
14
  <p align="center">
@@ -25,7 +25,7 @@
25
25
  <p align="center">
26
26
  <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="38" /></picture>
27
27
  <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="38" /></picture>
28
- <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="38" /></picture>
28
+ <picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/stat-tools.svg"><img src="assets/tags/stat-tools.svg" alt="44 MCP tools" height="38" /></picture>
29
29
  <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="38" /></picture>
30
30
  <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="38" /></picture>
31
31
  <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="38" /></picture>
@@ -133,7 +133,7 @@ agentmemory works with any agent that supports hooks, MCP, or REST API. All agen
133
133
  <sub>AgentSDKProvider</sub>
134
134
  </td>
135
135
  <td align="center" width="12.5%">
136
- <img src="https://img.shields.io/badge/109-endpoints-1f6feb?style=flat-square" alt="REST API" width="48" /><br/>
136
+ <img src="https://img.shields.io/badge/104-endpoints-1f6feb?style=flat-square" alt="REST API" width="48" /><br/>
137
137
  <strong>Any agent</strong><br/>
138
138
  <sub>REST API</sub>
139
139
  </td>
@@ -317,7 +317,7 @@ Implementation details live in `src/cli.ts` (see `runUpgrade` around the `src/cl
317
317
  ### Claude Code (one block, paste it)
318
318
 
319
319
  ```
320
- Install agentmemory: run `npx @agentmemory/agentmemory` in a separate terminal to start the memory server. Then run `/plugin marketplace add rohitg00/agentmemory` and `/plugin install agentmemory` — the plugin registers all 12 hooks, 4 skills, AND auto-wires the `@agentmemory/mcp` stdio server via its `.mcp.json`, so you get 43 MCP tools (memory_smart_search, memory_save, memory_sessions, memory_governance_delete, etc.) without any extra config step. Verify with `curl http://localhost:3111/agentmemory/health`. The real-time viewer is at http://localhost:3113.
320
+ Install agentmemory: run `npx @agentmemory/agentmemory` in a separate terminal to start the memory server. Then run `/plugin marketplace add rohitg00/agentmemory` and `/plugin install agentmemory` — the plugin registers all 12 hooks, 4 skills, AND auto-wires the `@agentmemory/mcp` stdio server via its `.mcp.json`, so you get 44 MCP tools (memory_smart_search, memory_save, memory_sessions, memory_governance_delete, etc.) without any extra config step. Verify with `curl http://localhost:3111/agentmemory/health`. The real-time viewer is at http://localhost:3113.
321
321
  ```
322
322
 
323
323
  <details>
@@ -576,9 +576,9 @@ npm install @xenova/transformers
576
576
 
577
577
  <h2 id="mcp-server"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/section-mcp.svg"><img src="assets/tags/section-mcp.svg" alt="MCP Server" height="32" /></picture></h2>
578
578
 
579
- 43 tools, 6 resources, 3 prompts, and 4 skills — the most comprehensive MCP memory toolkit for any agent.
579
+ 44 tools, 6 resources, 3 prompts, and 4 skills — the most comprehensive MCP memory toolkit for any agent.
580
580
 
581
- ### 43 Tools
581
+ ### 44 Tools
582
582
 
583
583
  <details>
584
584
  <summary>Core tools (always available)</summary>
@@ -586,17 +586,21 @@ npm install @xenova/transformers
586
586
  | Tool | Description |
587
587
  |------|-------------|
588
588
  | `memory_recall` | Search past observations |
589
+ | `memory_compress_file` | Compress markdown files while preserving structure |
589
590
  | `memory_save` | Save an insight, decision, or pattern |
591
+ | `memory_patterns` | Detect recurring patterns |
590
592
  | `memory_smart_search` | Hybrid semantic + keyword search |
591
593
  | `memory_file_history` | Past observations about specific files |
592
594
  | `memory_sessions` | List recent sessions |
595
+ | `memory_timeline` | Chronological observations |
593
596
  | `memory_profile` | Project profile (concepts, files, patterns) |
594
597
  | `memory_export` | Export all memory data |
598
+ | `memory_relations` | Query relationship graph |
595
599
 
596
600
  </details>
597
601
 
598
602
  <details>
599
- <summary>Extended tools (43 total — set AGENTMEMORY_TOOLS=all)</summary>
603
+ <summary>Extended tools (44 total — set AGENTMEMORY_TOOLS=all)</summary>
600
604
 
601
605
  | Tool | Description |
602
606
  |------|-------------|
@@ -772,7 +776,7 @@ Create `~/.agentmemory/.env`:
772
776
  # USER_ID=
773
777
  # TEAM_MODE=private
774
778
 
775
- # Tool visibility: "core" (7 tools) or "all" (43 tools)
779
+ # Tool visibility: "core" (8 tools) or "all" (44 tools)
776
780
  # AGENTMEMORY_TOOLS=core
777
781
  ```
778
782
 
@@ -780,7 +784,7 @@ Create `~/.agentmemory/.env`:
780
784
 
781
785
  <h2 id="api"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/section-api.svg"><img src="assets/tags/section-api.svg" alt="API" height="32" /></picture></h2>
782
786
 
783
- 109 endpoints on port `3111`. The REST API binds to `127.0.0.1` by default. Protected endpoints require `Authorization: Bearer <secret>` when `AGENTMEMORY_SECRET` is set, and mesh sync endpoints require `AGENTMEMORY_SECRET` on both peers.
787
+ 104 endpoints on port `3111`. The REST API binds to `127.0.0.1` by default. Protected endpoints require `Authorization: Bearer <secret>` when `AGENTMEMORY_SECRET` is set, and mesh sync endpoints require `AGENTMEMORY_SECRET` on both peers.
784
788
 
785
789
  <details>
786
790
  <summary>Key endpoints</summary>
package/dist/cli.mjs CHANGED
@@ -286,12 +286,12 @@ async function main() {
286
286
  p.intro("agentmemory");
287
287
  if (skipEngine) {
288
288
  p.log.info("Skipping engine check (--no-engine)");
289
- await import("./src-M6V9yZW5.mjs");
289
+ await import("./src-68MXysnV.mjs");
290
290
  return;
291
291
  }
292
292
  if (await isEngineRunning()) {
293
293
  p.log.success("iii-engine is running");
294
- await import("./src-M6V9yZW5.mjs");
294
+ await import("./src-68MXysnV.mjs");
295
295
  return;
296
296
  }
297
297
  if (!await startEngine()) {
@@ -335,7 +335,7 @@ async function main() {
335
335
  process.exit(1);
336
336
  }
337
337
  s.stop("iii-engine is ready");
338
- await import("./src-M6V9yZW5.mjs");
338
+ await import("./src-68MXysnV.mjs");
339
339
  }
340
340
  async function runStatus() {
341
341
  const port = getRestPort();
@@ -643,7 +643,7 @@ async function runUpgrade() {
643
643
  ].join("\n"), "agentmemory upgrade");
644
644
  }
645
645
  async function runMcp() {
646
- await import("./standalone-XB9gPYmo.mjs");
646
+ await import("./standalone-c2xiEQJ9.mjs");
647
647
  }
648
648
  ({
649
649
  status: runStatus,
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { TriggerAction, registerWorker } from "iii-sdk";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import { dirname, join, resolve, sep } from "node:path";
4
+ import { basename, dirname, extname, join, resolve, sep } from "node:path";
5
5
  import { homedir } from "node:os";
6
6
  import Anthropic from "@anthropic-ai/sdk";
7
7
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
@@ -10,7 +10,7 @@ import { execFile } from "node:child_process";
10
10
  import { promisify } from "node:util";
11
11
  import { lookup } from "node:dns/promises";
12
12
  import { isIP } from "node:net";
13
- import { mkdir, writeFile } from "node:fs/promises";
13
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { createServer } from "node:http";
16
16
 
@@ -2267,6 +2267,17 @@ function registerSearchFunction(sdk, kv) {
2267
2267
  }
2268
2268
  const projectFilter = typeof data.project === "string" && data.project.length > 0 ? data.project : void 0;
2269
2269
  const cwdFilter = typeof data.cwd === "string" && data.cwd.length > 0 ? data.cwd : void 0;
2270
+ const format = typeof data.format === "string" ? data.format : "full";
2271
+ if (![
2272
+ "full",
2273
+ "compact",
2274
+ "narrative"
2275
+ ].includes(format)) throw new Error("mem::search: format must be one of 'full', 'compact', or 'narrative'");
2276
+ let tokenBudget;
2277
+ if (data.token_budget !== void 0) {
2278
+ if (!Number.isInteger(data.token_budget) || data.token_budget < 1) throw new Error("mem::search: token_budget must be a positive integer");
2279
+ tokenBudget = data.token_budget;
2280
+ }
2270
2281
  if (idx.size === 0) {
2271
2282
  const count = await rebuildIndex(kv);
2272
2283
  logger.info("Search index rebuilt", { entries: count });
@@ -2303,13 +2314,81 @@ function registerSearchFunction(sdk, kv) {
2303
2314
  });
2304
2315
  }
2305
2316
  recordAccessBatch(kv, enriched.map((r) => r.observation.id));
2317
+ const estimateTokens = (value) => Math.max(1, Math.ceil(JSON.stringify(value).length / 3));
2318
+ const applyTokenBudget = (items) => {
2319
+ if (!tokenBudget) return {
2320
+ items,
2321
+ used: items.reduce((sum, item) => sum + estimateTokens(item), 0),
2322
+ truncated: false
2323
+ };
2324
+ const selected = [];
2325
+ let used = 0;
2326
+ for (const item of items) {
2327
+ const itemTokens = estimateTokens(item);
2328
+ if (used + itemTokens > tokenBudget) return {
2329
+ items: selected,
2330
+ used,
2331
+ truncated: selected.length < items.length
2332
+ };
2333
+ selected.push(item);
2334
+ used += itemTokens;
2335
+ }
2336
+ return {
2337
+ items: selected,
2338
+ used,
2339
+ truncated: false
2340
+ };
2341
+ };
2342
+ if (format === "compact") {
2343
+ const packed = applyTokenBudget(enriched.map((r) => ({
2344
+ obsId: r.observation.id,
2345
+ sessionId: r.sessionId,
2346
+ title: r.observation.title,
2347
+ type: r.observation.type,
2348
+ score: r.score,
2349
+ timestamp: r.observation.timestamp
2350
+ })));
2351
+ return {
2352
+ format,
2353
+ results: packed.items,
2354
+ tokens_used: packed.used,
2355
+ tokens_budget: tokenBudget,
2356
+ truncated: packed.truncated
2357
+ };
2358
+ }
2359
+ if (format === "narrative") {
2360
+ const packed = applyTokenBudget(enriched.map((r) => ({
2361
+ obsId: r.observation.id,
2362
+ sessionId: r.sessionId,
2363
+ title: r.observation.title,
2364
+ narrative: r.observation.narrative,
2365
+ score: r.score,
2366
+ timestamp: r.observation.timestamp
2367
+ })));
2368
+ const text = packed.items.map((r, index) => `${index + 1}. ${r.title}\n${r.narrative}`).join("\n\n");
2369
+ return {
2370
+ format,
2371
+ results: packed.items,
2372
+ text,
2373
+ tokens_used: packed.used,
2374
+ tokens_budget: tokenBudget,
2375
+ truncated: packed.truncated
2376
+ };
2377
+ }
2378
+ const packed = applyTokenBudget(enriched);
2306
2379
  logger.info("Search completed", {
2307
2380
  query,
2308
- results: enriched.length,
2381
+ results: packed.items.length,
2309
2382
  hasProjectFilter: !!projectFilter,
2310
2383
  hasCwdFilter: !!cwdFilter
2311
2384
  });
2312
- return { results: enriched };
2385
+ return {
2386
+ format,
2387
+ results: packed.items,
2388
+ tokens_used: packed.used,
2389
+ tokens_budget: tokenBudget,
2390
+ truncated: packed.truncated
2391
+ };
2313
2392
  });
2314
2393
  }
2315
2394
 
@@ -4398,7 +4477,7 @@ function registerAutoForgetFunction(sdk, kv) {
4398
4477
 
4399
4478
  //#endregion
4400
4479
  //#region src/version.ts
4401
- const VERSION = "0.8.11";
4480
+ const VERSION = "0.8.12";
4402
4481
 
4403
4482
  //#endregion
4404
4483
  //#region src/functions/export-import.ts
@@ -4513,7 +4592,8 @@ function registerExportImportFunction(sdk, kv) {
4513
4592
  "0.8.8",
4514
4593
  "0.8.9",
4515
4594
  "0.8.10",
4516
- "0.8.11"
4595
+ "0.8.11",
4596
+ "0.8.12"
4517
4597
  ]).has(importData.version)) return {
4518
4598
  success: false,
4519
4599
  error: `Unsupported export version: ${importData.version}`
@@ -10932,6 +11012,121 @@ function registerRetentionFunctions(sdk, kv) {
10932
11012
  });
10933
11013
  }
10934
11014
 
11015
+ //#endregion
11016
+ //#region src/functions/compress-file.ts
11017
+ const SENSITIVE_PATH_TERMS = [
11018
+ "secret",
11019
+ "credential",
11020
+ "private_key",
11021
+ ".env",
11022
+ "id_rsa",
11023
+ "token"
11024
+ ];
11025
+ const COMPRESS_FILE_SYSTEM_PROMPT = `You compress markdown while preserving structure.
11026
+ Rules:
11027
+ - Keep all headings exactly as-is.
11028
+ - Keep all URLs exactly as-is.
11029
+ - Keep all fenced code blocks exactly as-is.
11030
+ - Do not remove sections; shorten prose under each section.
11031
+ - Output only markdown, no wrappers or explanations.`;
11032
+ function stripMarkdownFence(text) {
11033
+ const trimmed = text.trim();
11034
+ const match = trimmed.match(/^```(?:markdown|md)?\s*([\s\S]*?)\s*```$/i);
11035
+ return match ? match[1].trim() : trimmed;
11036
+ }
11037
+ function extractUrls(text) {
11038
+ return Array.from(new Set(text.match(/https?:\/\/[^\s)]+/g) || []));
11039
+ }
11040
+ function extractHeadings(text) {
11041
+ return text.split("\n").map((line) => line.trim()).filter((line) => /^#{1,6}\s+/.test(line));
11042
+ }
11043
+ function extractCodeBlocks(text) {
11044
+ return text.match(/```[\s\S]*?```/g) || [];
11045
+ }
11046
+ function validateCompression(original, compressed) {
11047
+ const errors = [];
11048
+ const originalHeadings = extractHeadings(original);
11049
+ const compressedHeadings = extractHeadings(compressed);
11050
+ for (const heading of originalHeadings) if (!compressedHeadings.includes(heading)) errors.push(`missing heading: ${heading}`);
11051
+ const originalUrls = extractUrls(original).sort();
11052
+ const compressedUrls = extractUrls(compressed).sort();
11053
+ if (originalUrls.length !== compressedUrls.length) errors.push("url count changed");
11054
+ else for (let i = 0; i < originalUrls.length; i++) if (originalUrls[i] !== compressedUrls[i]) {
11055
+ errors.push("url set changed");
11056
+ break;
11057
+ }
11058
+ const originalBlocks = extractCodeBlocks(original);
11059
+ const compressedBlocks = extractCodeBlocks(compressed);
11060
+ if (originalBlocks.length !== compressedBlocks.length) errors.push("code block count changed");
11061
+ else for (let i = 0; i < originalBlocks.length; i++) if (originalBlocks[i] !== compressedBlocks[i]) {
11062
+ errors.push("code block content changed");
11063
+ break;
11064
+ }
11065
+ return errors;
11066
+ }
11067
+ function resolveBackupPath(filePath) {
11068
+ const base = basename(filePath, extname(filePath));
11069
+ const name = base.endsWith(".original") ? base : `${base}.original`;
11070
+ return join(dirname(filePath), `${name}.md`);
11071
+ }
11072
+ function registerCompressFileFunction(sdk, kv, provider) {
11073
+ sdk.registerFunction("mem::compress-file", async (data) => {
11074
+ if (!data?.filePath || typeof data.filePath !== "string") return {
11075
+ success: false,
11076
+ error: "filePath is required"
11077
+ };
11078
+ const absolutePath = resolve(data.filePath);
11079
+ const lowerPath = absolutePath.toLowerCase();
11080
+ if (extname(absolutePath).toLowerCase() !== ".md") return {
11081
+ success: false,
11082
+ error: "filePath must point to a .md file"
11083
+ };
11084
+ if (SENSITIVE_PATH_TERMS.some((term) => lowerPath.includes(term))) return {
11085
+ success: false,
11086
+ error: "refusing to process sensitive-looking path"
11087
+ };
11088
+ let original;
11089
+ try {
11090
+ original = await readFile(absolutePath, "utf-8");
11091
+ } catch {
11092
+ return {
11093
+ success: false,
11094
+ error: "failed to read file"
11095
+ };
11096
+ }
11097
+ if (!original.trim()) return {
11098
+ success: true,
11099
+ skipped: true,
11100
+ reason: "file is empty"
11101
+ };
11102
+ const compressed = stripMarkdownFence(await provider.summarize(COMPRESS_FILE_SYSTEM_PROMPT, `Compress this markdown file while preserving structure and code blocks:\n\n${original}`));
11103
+ const validationErrors = validateCompression(original, compressed);
11104
+ if (validationErrors.length > 0) return {
11105
+ success: false,
11106
+ error: "compression validation failed",
11107
+ details: validationErrors
11108
+ };
11109
+ const backupPath = resolveBackupPath(absolutePath);
11110
+ await writeFile(backupPath, original, "utf-8");
11111
+ await writeFile(absolutePath, compressed, "utf-8");
11112
+ try {
11113
+ await recordAudit(kv, "compress", "mem::compress-file", [], {
11114
+ filePath: absolutePath,
11115
+ backupPath,
11116
+ originalChars: original.length,
11117
+ compressedChars: compressed.length
11118
+ });
11119
+ } catch {}
11120
+ return {
11121
+ success: true,
11122
+ filePath: absolutePath,
11123
+ backupPath,
11124
+ originalChars: original.length,
11125
+ compressedChars: compressed.length
11126
+ };
11127
+ });
11128
+ }
11129
+
10935
11130
  //#endregion
10936
11131
  //#region src/health/thresholds.ts
10937
11132
  const DEFAULTS = {
@@ -11312,11 +11507,25 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
11312
11507
  status_code: 400,
11313
11508
  body: { error: "cwd must be a string" }
11314
11509
  };
11510
+ if (body.format !== void 0 && (typeof body.format !== "string" || ![
11511
+ "full",
11512
+ "compact",
11513
+ "narrative"
11514
+ ].includes(body.format.trim().toLowerCase()))) return {
11515
+ status_code: 400,
11516
+ body: { error: "format must be one of: full, compact, narrative" }
11517
+ };
11518
+ if (body.token_budget !== void 0 && (!Number.isInteger(body.token_budget) || body.token_budget < 1)) return {
11519
+ status_code: 400,
11520
+ body: { error: "token_budget must be a positive integer" }
11521
+ };
11315
11522
  const payload = {
11316
11523
  query: body.query.trim(),
11317
11524
  limit: body.limit,
11318
11525
  project: body.project,
11319
- cwd: body.cwd
11526
+ cwd: body.cwd,
11527
+ format: typeof body.format === "string" ? body.format.trim().toLowerCase() : void 0,
11528
+ token_budget: body.token_budget
11320
11529
  };
11321
11530
  return {
11322
11531
  status_code: 200,
@@ -11335,6 +11544,30 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
11335
11544
  middleware_function_ids: ["middleware::api-auth"]
11336
11545
  }
11337
11546
  });
11547
+ sdk.registerFunction("api::compress-file", async (req) => {
11548
+ const authErr = checkAuth(req, secret);
11549
+ if (authErr) return authErr;
11550
+ const filePath = asNonEmptyString$1((req.body ?? {}).filePath);
11551
+ if (!filePath) return {
11552
+ status_code: 400,
11553
+ body: { error: "filePath is required and must be a non-empty string" }
11554
+ };
11555
+ return {
11556
+ status_code: 200,
11557
+ body: await sdk.trigger({
11558
+ function_id: "mem::compress-file",
11559
+ payload: { filePath }
11560
+ })
11561
+ };
11562
+ });
11563
+ sdk.registerTrigger({
11564
+ type: "http",
11565
+ function_id: "api::compress-file",
11566
+ config: {
11567
+ api_path: "/agentmemory/compress-file",
11568
+ http_method: "POST"
11569
+ }
11570
+ });
11338
11571
  sdk.registerFunction("api::session::start", async (req) => {
11339
11572
  const body = req.body ?? {};
11340
11573
  const sessionId = asNonEmptyString$1(body.sessionId);
@@ -13799,11 +14032,31 @@ const CORE_TOOLS = [
13799
14032
  limit: {
13800
14033
  type: "number",
13801
14034
  description: "Max results to return (default 10)"
14035
+ },
14036
+ format: {
14037
+ type: "string",
14038
+ description: "Result format: full, compact, or narrative (default full)"
14039
+ },
14040
+ token_budget: {
14041
+ type: "number",
14042
+ description: "Optional token budget to trim returned results"
13802
14043
  }
13803
14044
  },
13804
14045
  required: ["query"]
13805
14046
  }
13806
14047
  },
14048
+ {
14049
+ name: "memory_compress_file",
14050
+ description: "Compress a markdown file to reduce token usage while preserving headings, URLs, and code blocks. Creates a .original.md backup before writing.",
14051
+ inputSchema: {
14052
+ type: "object",
14053
+ properties: { filePath: {
14054
+ type: "string",
14055
+ description: "Path to the markdown file to compress"
14056
+ } },
14057
+ required: ["filePath"]
14058
+ }
14059
+ },
13807
14060
  {
13808
14061
  name: "memory_save",
13809
14062
  description: "Explicitly save an important insight, decision, or pattern to long-term memory.",
@@ -14758,13 +15011,46 @@ function registerMcpEndpoints(sdk, kv, secret) {
14758
15011
  status_code: 400,
14759
15012
  body: { error: "query is required for memory_recall" }
14760
15013
  };
15014
+ const format = typeof args.format === "string" ? args.format.trim().toLowerCase() : "full";
15015
+ if (![
15016
+ "full",
15017
+ "compact",
15018
+ "narrative"
15019
+ ].includes(format)) return {
15020
+ status_code: 400,
15021
+ body: { error: "format must be one of: full, compact, narrative" }
15022
+ };
15023
+ const tokenBudget = asNumber(args.token_budget);
15024
+ if (args.token_budget !== void 0 && (!Number.isInteger(tokenBudget) || (tokenBudget ?? 0) < 1)) return {
15025
+ status_code: 400,
15026
+ body: { error: "token_budget must be a positive integer" }
15027
+ };
14761
15028
  const result = await sdk.trigger({
14762
15029
  function_id: "mem::search",
14763
15030
  payload: {
14764
15031
  query: args.query,
14765
- limit: typeof args.limit === "number" ? args.limit : 10
15032
+ limit: typeof args.limit === "number" ? args.limit : 10,
15033
+ format,
15034
+ token_budget: tokenBudget
14766
15035
  }
14767
15036
  });
15037
+ return {
15038
+ status_code: 200,
15039
+ body: { content: [{
15040
+ type: "text",
15041
+ text: format === "narrative" && result && typeof result === "object" && "text" in result && typeof result.text === "string" ? result.text : JSON.stringify(result, null, 2)
15042
+ }] }
15043
+ };
15044
+ }
15045
+ case "memory_compress_file": {
15046
+ if (typeof args.filePath !== "string" || !args.filePath.trim()) return {
15047
+ status_code: 400,
15048
+ body: { error: "filePath is required for memory_compress_file" }
15049
+ };
15050
+ const result = await sdk.trigger({
15051
+ function_id: "mem::compress-file",
15052
+ payload: { filePath: args.filePath.trim() }
15053
+ });
14768
15054
  return {
14769
15055
  status_code: 200,
14770
15056
  body: { content: [{
@@ -16472,6 +16758,7 @@ async function main() {
16472
16758
  registerQueryExpansionFunction(sdk, provider);
16473
16759
  registerTemporalGraphFunctions(sdk, kv, provider);
16474
16760
  registerRetentionFunctions(sdk, kv);
16761
+ registerCompressFileFunction(sdk, kv, provider);
16475
16762
  console.log(`[agentmemory] v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`);
16476
16763
  console.log(`[agentmemory] Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`);
16477
16764
  const snapshotConfig = loadSnapshotConfig();
@@ -16511,7 +16798,7 @@ async function main() {
16511
16798
  }
16512
16799
  }
16513
16800
  console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
16514
- console.log(`[agentmemory] Endpoints: 103 REST + 43 MCP tools + 6 MCP resources + 3 MCP prompts`);
16801
+ console.log(`[agentmemory] Endpoints: 104 REST + 44 MCP tools + 6 MCP resources + 3 MCP prompts`);
16515
16802
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
16516
16803
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
16517
16804
  const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);