@agentmemory/agentmemory 0.9.1 → 0.9.3

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.
Files changed (72) hide show
  1. package/README.md +40 -13
  2. package/dist/cli.mjs +147 -26
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/hooks/notification.mjs +6 -0
  5. package/dist/hooks/notification.mjs.map +1 -1
  6. package/dist/hooks/post-tool-failure.mjs +6 -0
  7. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  8. package/dist/hooks/post-tool-use.mjs +35 -1
  9. package/dist/hooks/post-tool-use.mjs.map +1 -1
  10. package/dist/hooks/pre-compact.mjs +6 -0
  11. package/dist/hooks/pre-compact.mjs.map +1 -1
  12. package/dist/hooks/pre-tool-use.mjs +6 -0
  13. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  14. package/dist/hooks/prompt-submit.mjs +6 -0
  15. package/dist/hooks/prompt-submit.mjs.map +1 -1
  16. package/dist/hooks/session-end.mjs +6 -0
  17. package/dist/hooks/session-end.mjs.map +1 -1
  18. package/dist/hooks/session-start.mjs +6 -0
  19. package/dist/hooks/session-start.mjs.map +1 -1
  20. package/dist/hooks/stop.mjs +6 -0
  21. package/dist/hooks/stop.mjs.map +1 -1
  22. package/dist/hooks/subagent-start.mjs +6 -0
  23. package/dist/hooks/subagent-start.mjs.map +1 -1
  24. package/dist/hooks/subagent-stop.mjs +6 -0
  25. package/dist/hooks/subagent-stop.mjs.map +1 -1
  26. package/dist/hooks/task-completed.mjs +6 -0
  27. package/dist/hooks/task-completed.mjs.map +1 -1
  28. package/dist/image-refs-CESf9ndJ.mjs +3 -0
  29. package/dist/image-store-DGvZMMrI.mjs +3 -0
  30. package/dist/index.mjs +2100 -157
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/{src-Dw_gJcCy.mjs → src-3Snd7D3T.mjs} +2021 -267
  33. package/dist/src-3Snd7D3T.mjs.map +1 -0
  34. package/dist/{standalone-BEWvWM5P.mjs → standalone-BG9uPsDK.mjs} +2 -2
  35. package/dist/{standalone-BEWvWM5P.mjs.map → standalone-BG9uPsDK.mjs.map} +1 -1
  36. package/dist/standalone.mjs +136 -2
  37. package/dist/standalone.mjs.map +1 -1
  38. package/dist/{tools-registry-BvWNlj6u.mjs → tools-registry-m8Ofn9vV.mjs} +166 -12
  39. package/dist/tools-registry-m8Ofn9vV.mjs.map +1 -0
  40. package/dist/viewer/index.html +528 -68
  41. package/package.json +5 -3
  42. package/plugin/.claude-plugin/plugin.json +2 -2
  43. package/plugin/scripts/notification.mjs +6 -0
  44. package/plugin/scripts/notification.mjs.map +1 -1
  45. package/plugin/scripts/post-tool-failure.mjs +6 -0
  46. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  47. package/plugin/scripts/post-tool-use.mjs +35 -1
  48. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  49. package/plugin/scripts/pre-compact.mjs +6 -0
  50. package/plugin/scripts/pre-compact.mjs.map +1 -1
  51. package/plugin/scripts/pre-tool-use.mjs +6 -0
  52. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  53. package/plugin/scripts/prompt-submit.mjs +6 -0
  54. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  55. package/plugin/scripts/session-end.mjs +6 -0
  56. package/plugin/scripts/session-end.mjs.map +1 -1
  57. package/plugin/scripts/session-start.mjs +6 -0
  58. package/plugin/scripts/session-start.mjs.map +1 -1
  59. package/plugin/scripts/stop.mjs +6 -0
  60. package/plugin/scripts/stop.mjs.map +1 -1
  61. package/plugin/scripts/subagent-start.mjs +6 -0
  62. package/plugin/scripts/subagent-start.mjs.map +1 -1
  63. package/plugin/scripts/subagent-stop.mjs +6 -0
  64. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  65. package/plugin/scripts/task-completed.mjs +6 -0
  66. package/plugin/scripts/task-completed.mjs.map +1 -1
  67. package/dist/src-Dw_gJcCy.mjs.map +0 -1
  68. package/dist/tools-registry-BvWNlj6u.mjs.map +0 -1
  69. package/dist/transformers-BX_tgxdO.mjs +0 -38684
  70. package/dist/transformers-BX_tgxdO.mjs.map +0 -1
  71. package/dist/transformers-KMm1i9no.mjs +0 -38683
  72. package/dist/transformers-KMm1i9no.mjs.map +0 -1
package/README.md CHANGED
@@ -25,10 +25,10 @@
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="44 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="51 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
- <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>
31
+ <picture><source media="(prefers-color-scheme: dark)" srcset="assets/tags/light/stat-tests.svg"><img src="assets/tags/stat-tests.svg" alt="827 tests passing" height="38" /></picture>
32
32
  </p>
33
33
 
34
34
  <p align="center">
@@ -334,14 +334,14 @@ Implementation details live in `src/cli.ts` (see `runUpgrade` around the `src/cl
334
334
  ### Claude Code (one block, paste it)
335
335
 
336
336
  ```
337
- 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.
337
+ 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 51 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.
338
338
  ```
339
339
 
340
340
  <details>
341
341
  <summary><b>OpenClaw (paste this prompt)</b></summary>
342
342
 
343
343
  ```
344
- Install agentmemory for OpenClaw. Run `npx @agentmemory/agentmemory` in a separate terminal to start the memory server on localhost:3111. Then add this to my OpenClaw MCP config so agentmemory is available with all 43 memory tools:
344
+ Install agentmemory for OpenClaw. Run `npx @agentmemory/agentmemory` in a separate terminal to start the memory server on localhost:3111. Then add this to my OpenClaw MCP config so agentmemory is available with all 50 memory tools:
345
345
 
346
346
  {
347
347
  "mcpServers": {
@@ -363,7 +363,7 @@ Full guide: [`integrations/openclaw/`](integrations/openclaw/)
363
363
  <summary><b>Hermes Agent (paste this prompt)</b></summary>
364
364
 
365
365
  ```
366
- Install agentmemory for Hermes. Run `npx @agentmemory/agentmemory` in a separate terminal to start the memory server on localhost:3111. Then add this to ~/.hermes/config.yaml so Hermes can use agentmemory as an MCP server with all 43 memory tools:
366
+ Install agentmemory for Hermes. Run `npx @agentmemory/agentmemory` in a separate terminal to start the memory server on localhost:3111. Then add this to ~/.hermes/config.yaml so Hermes can use agentmemory as an MCP server with all 50 memory tools:
367
367
 
368
368
  mcp_servers:
369
369
  agentmemory:
@@ -593,9 +593,9 @@ npm install @xenova/transformers
593
593
 
594
594
  <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>
595
595
 
596
- 44 tools, 6 resources, 3 prompts, and 4 skills — the most comprehensive MCP memory toolkit for any agent.
596
+ 51 tools, 6 resources, 3 prompts, and 4 skills — the most comprehensive MCP memory toolkit for any agent.
597
597
 
598
- ### 44 Tools
598
+ ### 50 Tools
599
599
 
600
600
  <details>
601
601
  <summary>Core tools (always available)</summary>
@@ -617,7 +617,7 @@ npm install @xenova/transformers
617
617
  </details>
618
618
 
619
619
  <details>
620
- <summary>Extended tools (44 total — set AGENTMEMORY_TOOLS=all)</summary>
620
+ <summary>Extended tools (50 total — set AGENTMEMORY_TOOLS=all)</summary>
621
621
 
622
622
  | Tool | Description |
623
623
  |------|-------------|
@@ -779,25 +779,35 @@ agentmemory auto-detects from your environment. No API key needed if you have a
779
779
 
780
780
  | Provider | Config | Notes |
781
781
  |----------|--------|-------|
782
- | **Claude subscription** (default) | No config needed | Uses `@anthropic-ai/claude-agent-sdk` |
782
+ | **No-op (default)** | No config needed | LLM-backed compress/summarize is DISABLED. Synthetic BM25 compression + recall still work. See `AGENTMEMORY_ALLOW_AGENT_SDK` below if you used to rely on the Claude-subscription fallback. |
783
783
  | Anthropic API | `ANTHROPIC_API_KEY` | Per-token billing |
784
784
  | MiniMax | `MINIMAX_API_KEY` | Anthropic-compatible |
785
785
  | Gemini | `GEMINI_API_KEY` | Also enables embeddings |
786
786
  | OpenRouter | `OPENROUTER_API_KEY` | Any model |
787
+ | Claude subscription fallback | `AGENTMEMORY_ALLOW_AGENT_SDK=true` | Opt-in only. Spawns `@anthropic-ai/claude-agent-sdk` sessions — used to cause unbounded Stop-hook recursion (#149 follow-up) so it is no longer the default. |
787
788
 
788
789
  ### Environment Variables
789
790
 
790
791
  Create `~/.agentmemory/.env`:
791
792
 
792
793
  ```env
793
- # LLM provider (pick one, or leave empty for Claude subscription)
794
+ # LLM provider (pick one default is the no-op provider: no LLM calls)
794
795
  # ANTHROPIC_API_KEY=sk-ant-...
796
+ # ANTHROPIC_BASE_URL=... # Optional: Anthropic-compatible proxy / Azure
795
797
  # GEMINI_API_KEY=...
796
798
  # OPENROUTER_API_KEY=...
799
+ # MINIMAX_API_KEY=...
800
+ # Opt-in Claude-subscription fallback (spawns @anthropic-ai/claude-agent-sdk);
801
+ # leave OFF unless you understand the Stop-hook recursion risk (#149 follow-up):
802
+ # AGENTMEMORY_ALLOW_AGENT_SDK=true
797
803
 
798
804
  # Embedding provider (auto-detected, or override)
799
805
  # EMBEDDING_PROVIDER=local
800
806
  # VOYAGE_API_KEY=...
807
+ # OPENAI_API_KEY=sk-...
808
+ # OPENAI_BASE_URL=https://api.openai.com # Override for Azure / vLLM / LM Studio / proxies
809
+ # OPENAI_EMBEDDING_MODEL=text-embedding-3-small
810
+ # OPENAI_EMBEDDING_DIMENSIONS=1536 # Required when the model is not in the known-models table
801
811
 
802
812
  # Search tuning
803
813
  # BM25_WEIGHT=0.4
@@ -816,6 +826,23 @@ Create `~/.agentmemory/.env`:
816
826
  # LLM provider to compress the
817
827
  # observation — expect significant
818
828
  # token spend on active sessions.
829
+ # AGENTMEMORY_SLOTS=false # OFF by default. Editable pinned
830
+ # memory slots — persona,
831
+ # user_preferences, tool_guidelines,
832
+ # project_context, guidance,
833
+ # pending_items, session_patterns,
834
+ # self_notes. Size-limited; agent
835
+ # edits via memory_slot_* tools.
836
+ # Pinned slots addressable for
837
+ # SessionStart injection.
838
+ # AGENTMEMORY_REFLECT=false # OFF by default. Requires SLOTS=on.
839
+ # Stop hook fires mem::slot-reflect:
840
+ # scans recent observations, auto-
841
+ # appends TODOs to pending_items,
842
+ # counts patterns in
843
+ # session_patterns, records touched
844
+ # files in project_context. Fire-
845
+ # and-forget; does not block.
819
846
  # AGENTMEMORY_INJECT_CONTEXT=false # OFF by default (#143). When on:
820
847
  # - SessionStart may inject ~1-2K
821
848
  # chars of project context into
@@ -843,7 +870,7 @@ Create `~/.agentmemory/.env`:
843
870
  # USER_ID=
844
871
  # TEAM_MODE=private
845
872
 
846
- # Tool visibility: "core" (8 tools) or "all" (44 tools)
873
+ # Tool visibility: "core" (8 tools) or "all" (51 tools)
847
874
  # AGENTMEMORY_TOOLS=core
848
875
  ```
849
876
 
@@ -884,7 +911,7 @@ Full endpoint list: [`src/triggers/api.ts`](src/triggers/api.ts)
884
911
 
885
912
  Built on [iii-engine](https://iii.dev)'s three primitives — no Express, no Postgres, no Redis.
886
913
 
887
- **118 source files · ~21,800 LOC · 646 tests · 123 functions · 34 KV scopes**
914
+ **118 source files · ~21,800 LOC · 800 tests · 123 functions · 34 KV scopes**
888
915
 
889
916
  <details>
890
917
  <summary>What iii-engine replaces</summary>
@@ -904,7 +931,7 @@ Built on [iii-engine](https://iii.dev)'s three primitives — no Express, no Pos
904
931
  ```bash
905
932
  npm run dev # Hot reload
906
933
  npm run build # Production build
907
- npm test # 646 tests (~1.7s)
934
+ npm test # 800 tests (~1.7s)
908
935
  npm run test:integration # API tests (requires running services)
909
936
  ```
910
937
 
package/dist/cli.mjs CHANGED
@@ -47,7 +47,12 @@ const KV = {
47
47
  enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
48
48
  latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
49
49
  retentionScores: "mem:retention",
50
- accessLog: "mem:access"
50
+ accessLog: "mem:access",
51
+ imageRefs: "mem:image-refs",
52
+ imageEmbeddings: "mem:image-embeddings",
53
+ slots: "mem:slots",
54
+ globalSlots: "mem:slots:global",
55
+ state: "mem:state"
51
56
  };
52
57
  const STREAM = {
53
58
  name: "mem-live",
@@ -87,7 +92,8 @@ Usage: agentmemory [command] [options]
87
92
 
88
93
  Commands:
89
94
  (default) Start agentmemory worker
90
- status Show connection status, memory count, and health
95
+ status Show connection status, memory count, flags, and health
96
+ doctor Run diagnostic checks (server, flags, graph, providers)
91
97
  demo Seed sample sessions and show recall in action
92
98
  upgrade Upgrade local deps + iii runtime (best effort)
93
99
  mcp Start standalone MCP server (no engine required)
@@ -100,10 +106,15 @@ Options:
100
106
  --no-engine Skip auto-starting iii-engine
101
107
  --port <N> Override REST port (default: 3111)
102
108
 
109
+ Environment:
110
+ AGENTMEMORY_URL Full REST base URL (e.g. http://localhost:3111).
111
+ Honored by status, doctor, and MCP shim commands.
112
+
103
113
  Quick start:
104
114
  npx @agentmemory/agentmemory # start with local iii-engine or Docker
105
- npx @agentmemory/agentmemory status # check health
106
- npx @agentmemory/agentmemory demo # try it in 30 seconds (needs server running)
115
+ npx @agentmemory/agentmemory demo # see semantic recall in 30 seconds
116
+ npx @agentmemory/agentmemory doctor # diagnose config + feature flags
117
+ npx @agentmemory/agentmemory status # health + memory count + flags
107
118
  npx @agentmemory/agentmemory upgrade # upgrade agentmemory + iii runtime
108
119
  npx @agentmemory/agentmemory mcp # standalone MCP server (no engine)
109
120
  npx @agentmemory/mcp # same as above (shim package)
@@ -116,16 +127,44 @@ const portIdx = args.indexOf("--port");
116
127
  if (portIdx !== -1 && args[portIdx + 1]) process.env["III_REST_PORT"] = args[portIdx + 1];
117
128
  const skipEngine = args.includes("--no-engine");
118
129
  function getRestPort() {
130
+ const url = process.env["AGENTMEMORY_URL"];
131
+ if (url) try {
132
+ const parsed = new URL(url).port;
133
+ if (parsed) return parseInt(parsed, 10);
134
+ } catch {}
119
135
  return parseInt(process.env["III_REST_PORT"] || "3111", 10) || 3111;
120
136
  }
137
+ function getBaseUrl() {
138
+ const url = process.env["AGENTMEMORY_URL"];
139
+ if (url) return url.replace(/\/+$/, "");
140
+ return `http://localhost:${getRestPort()}`;
141
+ }
142
+ function getViewerUrl() {
143
+ const envUrl = process.env["AGENTMEMORY_VIEWER_URL"];
144
+ if (envUrl) return envUrl.replace(/\/+$/, "");
145
+ try {
146
+ const u = new URL(getBaseUrl());
147
+ const vPort = (parseInt(u.port || "3111", 10) || 3111) + 2;
148
+ return `${u.protocol}//${u.hostname}:${vPort}`;
149
+ } catch {
150
+ return `http://localhost:${getRestPort() + 2}`;
151
+ }
152
+ }
121
153
  async function isEngineRunning() {
122
154
  try {
123
- await fetch(`http://localhost:${getRestPort()}/`, { signal: AbortSignal.timeout(2e3) });
155
+ await fetch(`${getBaseUrl()}/`, { signal: AbortSignal.timeout(2e3) });
124
156
  return true;
125
157
  } catch {
126
158
  return false;
127
159
  }
128
160
  }
161
+ async function isAgentmemoryReady() {
162
+ try {
163
+ return (await fetch(`${getBaseUrl()}/agentmemory/livez`, { signal: AbortSignal.timeout(2e3) })).ok;
164
+ } catch {
165
+ return false;
166
+ }
167
+ }
129
168
  function findIiiConfig() {
130
169
  const candidates = [
131
170
  join(__dirname, "iii-config.yaml"),
@@ -287,12 +326,12 @@ async function main() {
287
326
  p.intro("agentmemory");
288
327
  if (skipEngine) {
289
328
  p.log.info("Skipping engine check (--no-engine)");
290
- await import("./src-Dw_gJcCy.mjs");
329
+ await import("./src-3Snd7D3T.mjs");
291
330
  return;
292
331
  }
293
332
  if (await isEngineRunning()) {
294
333
  p.log.success("iii-engine is running");
295
- await import("./src-Dw_gJcCy.mjs");
334
+ await import("./src-3Snd7D3T.mjs");
296
335
  return;
297
336
  }
298
337
  if (!await startEngine()) {
@@ -336,30 +375,38 @@ async function main() {
336
375
  process.exit(1);
337
376
  }
338
377
  s.stop("iii-engine is ready");
339
- await import("./src-Dw_gJcCy.mjs");
378
+ await import("./src-3Snd7D3T.mjs");
379
+ }
380
+ async function apiFetch(base, path, timeoutMs = 5e3) {
381
+ try {
382
+ return await (await fetch(`${base}/agentmemory/${path}`, { signal: AbortSignal.timeout(timeoutMs) })).json();
383
+ } catch {
384
+ return null;
385
+ }
340
386
  }
341
387
  async function runStatus() {
342
- const port = getRestPort();
343
- const base = `http://localhost:${port}`;
388
+ getRestPort();
389
+ const base = getBaseUrl();
344
390
  p.intro("agentmemory status");
345
391
  if (!await isEngineRunning()) {
346
- p.log.error(`Not running — no response on port ${port}`);
392
+ p.log.error(`Not running — no response at ${base}`);
347
393
  p.log.info("Start with: npx @agentmemory/agentmemory");
348
394
  process.exit(1);
349
395
  }
350
396
  try {
351
- const [healthRes, sessionsRes, graphRes, memoriesRes] = await Promise.all([
352
- fetch(`${base}/agentmemory/health`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
353
- fetch(`${base}/agentmemory/sessions`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
354
- fetch(`${base}/agentmemory/graph/stats`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null),
355
- fetch(`${base}/agentmemory/export`, { signal: AbortSignal.timeout(5e3) }).then((r) => r.json()).catch(() => null)
397
+ const [healthRes, sessionsRes, graphRes, memoriesRes, flagsRes] = await Promise.all([
398
+ apiFetch(base, "health"),
399
+ apiFetch(base, "sessions"),
400
+ apiFetch(base, "graph/stats"),
401
+ apiFetch(base, "export"),
402
+ apiFetch(base, "config/flags")
356
403
  ]);
357
404
  const h = healthRes?.health;
358
405
  const status = healthRes?.status || "unknown";
359
406
  const version = healthRes?.version || "?";
360
407
  const sessions = Array.isArray(sessionsRes?.sessions) ? sessionsRes.sessions.length : 0;
361
- const nodes = graphRes?.nodes || 0;
362
- const edges = graphRes?.edges || 0;
408
+ const nodes = Number(graphRes?.totalNodes ?? graphRes?.nodes ?? graphRes?.nodeCount ?? 0);
409
+ const edges = Number(graphRes?.totalEdges ?? graphRes?.edges ?? graphRes?.edgeCount ?? 0);
363
410
  const cb = healthRes?.circuitBreaker?.state || "closed";
364
411
  const heapMB = h?.memory ? Math.round(h.memory.heapUsed / 1048576) : 0;
365
412
  const uptime = h?.uptimeSeconds ? Math.round(h.uptimeSeconds) : 0;
@@ -369,7 +416,7 @@ async function runStatus() {
369
416
  const estInjectedTokens = Math.min(obsCount, 50) * 38;
370
417
  const tokensSaved = estFullTokens - estInjectedTokens;
371
418
  const pctSaved = estFullTokens > 0 ? Math.round(tokensSaved / estFullTokens * 100) : 0;
372
- p.log.success(`Connected — v${version} on port ${port}`);
419
+ p.log.success(`Connected — v${version} at ${base}`);
373
420
  const lines = [
374
421
  `Health: ${status === "healthy" ? "✓ healthy" : status}`,
375
422
  `Sessions: ${sessions}`,
@@ -379,7 +426,7 @@ async function runStatus() {
379
426
  `Circuit: ${cb}`,
380
427
  `Heap: ${heapMB} MB`,
381
428
  `Uptime: ${uptime}s`,
382
- `Viewer: http://localhost:${port + 2}`
429
+ `Viewer: ${getViewerUrl()}`
383
430
  ];
384
431
  if (obsCount > 0) {
385
432
  lines.push("");
@@ -387,12 +434,85 @@ async function runStatus() {
387
434
  lines.push(` Full context: ~${estFullTokens.toLocaleString()} tokens`);
388
435
  lines.push(` Injected: ~${estInjectedTokens.toLocaleString()} tokens`);
389
436
  }
437
+ if (flagsRes) {
438
+ const provider = flagsRes.provider === "llm" ? "✓ llm" : "✗ noop (no key)";
439
+ const embed = flagsRes.embeddingProvider === "embeddings" ? "✓ embeddings" : "bm25-only";
440
+ const flagRows = (flagsRes.flags || []).map((f) => ` ${f.enabled ? "✓" : "✗"} ${f.key.padEnd(32)} ${f.label}`);
441
+ lines.push("");
442
+ lines.push(`Provider: ${provider}`);
443
+ lines.push(`Embeddings: ${embed}`);
444
+ lines.push(`Flags:`);
445
+ flagRows.forEach((r) => lines.push(r));
446
+ }
390
447
  p.note(lines.join("\n"), "agentmemory");
391
448
  } catch (err) {
392
449
  p.log.error(err instanceof Error ? err.message : String(err));
393
450
  process.exit(1);
394
451
  }
395
452
  }
453
+ function formatChecks(checks) {
454
+ return checks.map((c) => `${c.ok ? "✓" : "✗"} ${c.name}${c.hint ? `\n ${c.hint}` : ""}`).join("\n");
455
+ }
456
+ async function runDoctor() {
457
+ p.intro("agentmemory doctor");
458
+ const base = getBaseUrl();
459
+ const viewerUrl = getViewerUrl();
460
+ const checks = [];
461
+ const serverUp = await isEngineRunning();
462
+ checks.push({
463
+ name: "Server reachable",
464
+ ok: serverUp,
465
+ hint: serverUp ? void 0 : `Start with: npx @agentmemory/agentmemory (tried ${base})`
466
+ });
467
+ if (!serverUp) {
468
+ p.note(formatChecks(checks), "server unreachable");
469
+ process.exit(1);
470
+ }
471
+ const [health, flags, graph] = await Promise.all([
472
+ apiFetch(base, "health", 3e3),
473
+ apiFetch(base, "config/flags", 3e3),
474
+ apiFetch(base, "graph/stats", 3e3)
475
+ ]);
476
+ const viewerUp = await fetch(viewerUrl, { signal: AbortSignal.timeout(2e3) }).then((r) => r.ok).catch(() => false);
477
+ const hasLlm = flags?.provider === "llm";
478
+ const hasEmbed = flags?.embeddingProvider === "embeddings";
479
+ const graphHas = Number(graph?.totalNodes ?? graph?.nodes ?? graph?.nodeCount ?? 0) > 0;
480
+ checks.push({
481
+ name: "Health status",
482
+ ok: health?.status === "healthy",
483
+ hint: health?.status === "healthy" ? void 0 : `Status: ${health?.status || "unknown"}`
484
+ }, {
485
+ name: "Viewer reachable",
486
+ ok: viewerUp,
487
+ hint: viewerUp ? void 0 : `${viewerUrl} not responding`
488
+ }, {
489
+ name: "LLM provider",
490
+ ok: hasLlm,
491
+ hint: hasLlm ? void 0 : "export ANTHROPIC_API_KEY=sk-ant-... (or GEMINI/OPENROUTER/MINIMAX) then restart"
492
+ }, {
493
+ name: "Embedding provider",
494
+ ok: hasEmbed,
495
+ hint: hasEmbed ? void 0 : "Running BM25-only. Add OPENAI_API_KEY / VOYAGE_API_KEY / COHERE_API_KEY / OLLAMA_HOST for semantic recall"
496
+ });
497
+ for (const f of flags?.flags || []) checks.push({
498
+ name: f.label,
499
+ ok: f.enabled,
500
+ hint: f.enabled ? void 0 : f.enableHow
501
+ });
502
+ checks.push({
503
+ name: "Knowledge graph populated",
504
+ ok: graphHas,
505
+ hint: graphHas ? void 0 : "Graph is empty. Run a session with GRAPH_EXTRACTION_ENABLED=true, or POST /agentmemory/graph/extract"
506
+ });
507
+ const passed = checks.filter((c) => c.ok).length;
508
+ const total = checks.length;
509
+ p.note(formatChecks(checks), `${passed}/${total} checks passing`);
510
+ if (passed === total) p.outro("✓ All checks passed. agentmemory is healthy.");
511
+ else {
512
+ p.outro(`${total - passed} issue(s) — follow hints above to fix.`);
513
+ process.exit(1);
514
+ }
515
+ }
396
516
  function buildDemoSessions() {
397
517
  return [
398
518
  {
@@ -521,9 +641,9 @@ async function runDemo() {
521
641
  const port = getRestPort();
522
642
  const base = `http://localhost:${port}`;
523
643
  p.intro("agentmemory demo");
524
- if (!await isEngineRunning()) {
525
- p.log.error(`Not running no response on port ${port}`);
526
- p.log.info("Start the server first: npx @agentmemory/agentmemory");
644
+ if (!await isAgentmemoryReady()) {
645
+ p.log.error(`agentmemory worker not reachable on port ${port} (livez probe failed). Something may be on the port but it isn't serving /agentmemory/*.`);
646
+ p.log.info("Start it with: npx @agentmemory/agentmemory");
527
647
  process.exit(1);
528
648
  }
529
649
  const demoProject = "/tmp/agentmemory-demo";
@@ -553,7 +673,7 @@ async function runDemo() {
553
673
  `Notice: searching "database performance optimization"`,
554
674
  `found the N+1 query fix — keyword matching can't do that.`,
555
675
  "",
556
- `Viewer: http://localhost:${port + 2}`,
676
+ `Viewer: ${getViewerUrl()}`,
557
677
  `Clean up with: curl -X DELETE "${base}/agentmemory/sessions?project=${demoProject}"`
558
678
  ];
559
679
  p.note(lines.join("\n"), "demo complete");
@@ -645,7 +765,7 @@ async function runUpgrade() {
645
765
  ].join("\n"), "agentmemory upgrade");
646
766
  }
647
767
  async function runMcp() {
648
- await import("./standalone-BEWvWM5P.mjs");
768
+ await import("./standalone-BG9uPsDK.mjs");
649
769
  }
650
770
  async function runImportJsonl() {
651
771
  const pathArg = args.slice(1).filter((a) => !a.startsWith("-"))[0];
@@ -701,7 +821,7 @@ async function runImportJsonl() {
701
821
  process.exit(1);
702
822
  }
703
823
  spinner.stop(`imported ${json.imported ?? 0} file(s), ${json.observations ?? 0} observation(s) across ${json.sessionIds?.length || 0} session(s)`);
704
- if (json.sessionIds && json.sessionIds.length > 0) p.log.info(`View at http://localhost:${port + 2} → Replay tab`);
824
+ if (json.sessionIds && json.sessionIds.length > 0) p.log.info(`View at ${getViewerUrl()} → Replay tab`);
705
825
  } catch (err) {
706
826
  spinner.stop("failed");
707
827
  if (err instanceof Error && err.name === "TimeoutError") p.log.error("import timed out after 2 minutes");
@@ -711,6 +831,7 @@ async function runImportJsonl() {
711
831
  }
712
832
  ({
713
833
  status: runStatus,
834
+ doctor: runDoctor,
714
835
  demo: runDemo,
715
836
  upgrade: runUpgrade,
716
837
  mcp: runMcp,