diff --git a/INSTALL_FOR_AGENTS.md b/INSTALL_FOR_AGENTS.md
new file mode 100644
index 000000000..b7df56565
--- /dev/null
+++ b/INSTALL_FOR_AGENTS.md
@@ -0,0 +1,163 @@
+# Install agentmemory (agent runbook)
+
+You are a coding agent. Follow this runbook top to bottom to install agentmemory, wire it into yourself, and verify that persistent memory works. Run each command, check the result against the "Expect" note, then move on. If a step fails, jump to Troubleshooting, and if it still fails, stop and report the step, the exact command, and the error output.
+
+## What you are installing
+
+agentmemory is a local memory server for coding agents. It runs on your machine, exposes a REST API on port 3111 and an MCP server, and stores memories on disk under `~/.agentmemory`. It is built on the iii engine, which agentmemory downloads and manages for you as a pinned binary under `~/.agentmemory/bin`. You do not install iii separately.
+
+Default mode needs no API key and no cloud account. Out of the box it runs hybrid retrieval (BM25 keyword search plus local on-device embeddings), so a full install proves real semantic recall with zero credentials. An LLM provider key is optional and only unlocks richer summaries and auto-injection (see "Optional: richer features").
+
+## Prerequisites
+
+- Node.js >= 20 and npm. Check with `node -v`.
+- macOS or Linux for the one-command path. On Windows, use WSL2; native Windows engine setup is manual and `agentmemory connect` is not supported there.
+- Ports 3111 (REST), 3112 (streams), 3113 (viewer), and 49134 (engine) free. Override the whole block with `--port ` or `--instance ` if any are taken.
+
+## Running non-interactively
+
+Several commands prompt on a TTY (for example the first-run "install globally?" question). As an agent you usually want no prompts. Either set `CI=1` in the environment for the commands below, or rely on the fact that agentmemory skips all prompts automatically when stdin/stdout are not a TTY. Prompts are also never-nag: once answered they persist and are not asked again. Re-run onboarding any time with `agentmemory --reset`.
+
+## 1. Install globally
+
+```bash
+npm install -g @agentmemory/agentmemory
+```
+
+If you hit `EACCES` on a system Node install (macOS/Linux), retry with `sudo npm install -g @agentmemory/agentmemory`.
+
+No-install alternative: skip this step and run the server with `npx -y @agentmemory/agentmemory@latest` everywhere this runbook says `agentmemory`. The `@latest` and `-y` flags matter because npx caches per version and a bare `npx @agentmemory/agentmemory` can serve a stale release.
+
+Expect: the install completes without errors.
+
+## 2. Verify the install
+
+```bash
+agentmemory --version
+```
+
+Expect: a version string is printed. If `command not found`, the global bin is not on `PATH`; use the `npx -y @agentmemory/agentmemory@latest` form instead.
+
+## 3. Start the server
+
+The server listens on port 3111 and auto-starts its pinned iii engine on first run (this can take a few seconds the first time while the engine binary is fetched into `~/.agentmemory/bin`). Run it in the background or in a separate terminal so the rest of the runbook can talk to it.
+
+```bash
+agentmemory &
+```
+
+Or in a dedicated terminal, run `agentmemory` in the foreground. To run a second isolated instance, use `agentmemory --instance 1` (relocates the whole port block to 3211/3212/3213/49234).
+
+Wait until it is reachable, then continue:
+
+```bash
+curl -fsS http://localhost:3111/agentmemory/livez
+```
+
+Expect: a `200` response. Retry for up to ~15 seconds on first run while the engine warms up.
+
+## 4. Prove recall in one command
+
+```bash
+agentmemory demo --serve
+```
+
+`--serve` boots the server, seeds three realistic sessions (JWT auth, an N+1 query fix, rate limiting), runs semantic searches against them, prints the results, and tears everything down. No second terminal needed.
+
+Expect: the demo finds the "N+1 query fix" memory when it searches for "database performance optimization", which keyword matching alone cannot do. Open `http://localhost:3113` while it runs to watch the memory build live.
+
+## 5. Wire MCP into the calling agent
+
+Detect which agent is running this runbook, then wire its MCP config:
+
+```bash
+agentmemory connect
+```
+
+`connect` merges agentmemory into that agent's MCP config and preserves any existing servers. Supported agent names:
+
+`claude-code`, `copilot-cli`, `codex`, `cursor`, `gemini-cli`, `opencode`, `cline`, `continue`, `droid`, `hermes`, `openclaw`, `openhuman`, `pi`, `qwen`, `warp`, `zed`, `antigravity`, `kiro`.
+
+If you cannot tell which agent you are, default to `claude-code`. After wiring, restart the agent or run its MCP reload command (for example `/mcp` in Claude Code) so it picks up the server.
+
+Expect: the agent now lists agentmemory's tools. With the server running you should see the full set of 53 tools (for example `memory_save`, `memory_smart_search`, `memory_sessions`). If you see only 7 tools, the MCP shim could not reach a server, see Troubleshooting.
+
+## 6. Install native skills
+
+```bash
+npx skills add rohitg00/agentmemory -y
+```
+
+This installs the native skills so the agent knows when to call the memory tools, not just that they exist. `connect` makes the tools available; skills teach the agent when to use them.
+
+Expect: the skills are installed for the detected agent.
+
+## 7. Verify a save and recall round-trip
+
+Confirm health first:
+
+```bash
+curl -fsS http://localhost:3111/agentmemory/health
+```
+
+Expect: a JSON body with an ok status.
+
+Now write a memory and read it back. If MCP is wired, call the `memory_save` tool followed by `memory_smart_search`. Otherwise use REST directly (note: these are the REST paths, which differ from the MCP tool names):
+
+```bash
+curl -X POST http://localhost:3111/agentmemory/remember \
+ -H "Content-Type: application/json" \
+ -d '{"content":"agentmemory install verification probe","concepts":["install-check"]}'
+
+curl -X POST http://localhost:3111/agentmemory/smart-search \
+ -H "Content-Type: application/json" \
+ -d '{"query":"install verification probe","limit":5}'
+```
+
+Expect: the first call returns `201`, the second returns `200` with results that include the probe memory you just saved.
+
+If `AGENTMEMORY_SECRET` is set in the environment, the REST API requires it. Add `-H "Authorization: Bearer $AGENTMEMORY_SECRET"` to both calls. By default no secret is set and localhost is open.
+
+## Optional: richer features
+
+These are off by default because they spend tokens. Enable them only if the user wants them. Put configuration in `~/.agentmemory/.env` (no `export` prefix), then restart the server.
+
+- `AGENTMEMORY_INJECT_CONTEXT=true` makes the SessionStart and PreToolUse hooks inject past memory into the agent's context automatically. Cost: spends session tokens proportional to tool-call frequency.
+- `AGENTMEMORY_AUTO_COMPRESS=true` sends each observation to your LLM provider for a richer summary. Cost: spends API tokens proportional to tool-use frequency. Requires a provider key.
+- Provider key: set one of `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, and similar, in the same file. Without a key, agentmemory stays in zero-LLM mode and still indexes and recalls via BM25 plus local embeddings.
+
+## Tool surface
+
+The MCP server exposes 53 tools by default (`--tools all`). Use `--tools core` (or `AGENTMEMORY_TOOLS=core`) for a lean 8-tool set on hosts with tight tool limits. The 8 core tools cover save, recall, smart search, sessions, export, audit, and governance delete.
+
+## Lifecycle commands
+
+- `agentmemory status` shows server and engine state.
+- `agentmemory doctor` runs diagnostics and reports what is misconfigured.
+- `agentmemory stop` stops the engine this CLI started (`stop --force` bypasses the Docker guard).
+- `agentmemory upgrade` upgrades agentmemory and the iii runtime, best effort.
+- `agentmemory --reset` wipes onboarding preferences and re-runs the wizard.
+- `agentmemory import-jsonl ` imports prior Claude Code session logs as memories.
+
+## Troubleshooting
+
+- `command not found: agentmemory`: the global bin is not on `PATH`. Use `npx -y @agentmemory/agentmemory@latest`.
+- `EACCES` during global install: retry with `sudo`, or use the npx form.
+- Stale npx version: run `npx -y @agentmemory/agentmemory@latest`, or clear the cache with `rm -rf ~/.npm/_npx` (macOS/Linux).
+- Port already in use: another instance or process holds 3111. Stop it, or relocate with `agentmemory --instance 1`.
+- Server starts but `livez` never returns 200: re-run with `agentmemory --verbose` to see engine stderr.
+- Engine version warning on start: harmless. agentmemory uses its own pinned engine in `~/.agentmemory/bin` regardless of any `iii` on `PATH`. Set `AGENTMEMORY_III_VERSION` only to override deliberately.
+- Only 7 tools visible in the agent: the MCP shim is in local fallback because it could not reach a server. Start `npx @agentmemory/agentmemory` and ensure `AGENTMEMORY_URL` points at it (default `http://localhost:3111`), then reload MCP.
+- Windows: use WSL2 for the path above. Native Windows runs the server but `connect` and the automated engine install are not supported.
+
+## Report success
+
+Report back to the user:
+
+- agentmemory installed, version, and the server running on port 3111
+- which agent was wired via `agentmemory connect`, and the tool count the agent now sees
+- the save and recall round-trip returned the probe memory
+- the viewer is available at `http://localhost:3113`
+- whether any optional features were enabled
+
+If any step failed, report which step, the exact command, and the error output.
diff --git a/README.md b/README.md
index 87e6b3f0d..c5acc3595 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@
-
+
@@ -87,12 +87,19 @@
## Install
+Fastest path if you use a coding agent: hand it this one instruction and it installs, wires, and verifies agentmemory end to end.
+
+> Retrieve and follow the instructions at: https://raw.githubusercontent.com/rohitg00/agentmemory/main/INSTALL_FOR_AGENTS.md
+
+On Windows the fast path is WSL2. Native Windows engine setup is manual (about 10 to 20 minutes) and `agentmemory connect` is currently unsupported there. See the [Windows notes](#windows) below for the step-by-step.
+
```bash
npm install -g @agentmemory/agentmemory # once — bare `agentmemory` on PATH
# If you hit EACCES on macOS/Linux system Node installs, retry with:
# sudo npm install -g @agentmemory/agentmemory
agentmemory # start the memory server on :3111
agentmemory demo # seed sample sessions + prove recall
+agentmemory demo --serve # one command: boot server, run demo, tear down (no second terminal)
agentmemory connect claude-code # wire MCP into your agent (also: copilot-cli, codex, cursor, gemini-cli, ...)
npx skills add rohitg00/agentmemory -y # install 8 native skills so your agent knows when to use the tools
```
@@ -1141,7 +1148,7 @@ Full registry: [workers.iii.dev](https://workers.iii.dev). Every worker there co
| Prometheus / Grafana | iii OTEL + health monitor |
| Custom plugin systems | `iii worker add ` |
-**174 source files · ~37,800 LOC · 1,390+ tests · 258 functions · 44 KV scopes** — all on three primitives. No `agentmemory plugin install`. The plugin system is iii itself.
+**174 source files · ~37,800 LOC · 1,423+ tests · 258 functions · 44 KV scopes** — all on three primitives. No `agentmemory plugin install`. The plugin system is iii itself.
---
@@ -1465,7 +1472,7 @@ Full endpoint list: [`src/triggers/api.ts`](src/triggers/api.ts)
```bash
npm run dev # Hot reload
npm run build # Production build
-npm test # 1,390+ tests
+npm test # 1,423+ tests
npm run test:integration # API tests (requires running services)
```
diff --git a/src/cli.ts b/src/cli.ts
index acac3d971..ae5be0035 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -43,6 +43,10 @@ import { isFirstRun, readPrefs, resetPrefs, writePrefs } from "./cli/preferences
import { runOnboarding } from "./cli/onboarding.js";
import { setBootVerbose } from "./logger.js";
import { VERSION } from "./version.js";
+import { getAllTools, ESSENTIAL_TOOLS } from "./mcp/tools-registry.js";
+
+const ALL_TOOLS_COUNT = getAllTools().length;
+const CORE_TOOLS_COUNT = getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name)).length;
const __dirname = dirname(fileURLToPath(import.meta.url));
const args = process.argv.slice(2);
@@ -136,7 +140,9 @@ Commands:
--dry-run: show what each fix would do, don't execute
remove Cleanly uninstall agentmemory (pidfile, state, .env, binaries).
--force: skip confirmations · --keep-data: keep memory data
- demo Seed sample sessions and show recall in action
+ demo [--serve] Seed sample sessions and show recall in action.
+ --serve boots the server, runs the demo, and stops it
+ in one command (no second terminal).
upgrade Upgrade local deps + iii runtime (best effort)
stop [--force] Stop the running iii-engine started by this CLI.
--force bypasses the Docker-heuristic guard and signals
@@ -152,7 +158,7 @@ Options:
--help, -h Show this help
--verbose, -v Show engine stderr, boot log, and diagnostic info
--reset Wipe ~/.agentmemory/preferences.json and re-run onboarding
- --tools all|core Tool visibility (default: all = 51 tools; core = 8 essentials)
+ --tools all|core Tool visibility (default: all = ${ALL_TOOLS_COUNT} tools; core = ${CORE_TOOLS_COUNT} essentials)
--no-engine Skip auto-starting iii-engine
--port Override REST port (default: 3111). Streams (N+1), viewer
(N+2), and iii engine (N+46023) auto-derive from N so a
@@ -1197,15 +1203,11 @@ async function main() {
if (attachedBin) {
const detected = iiiBinVersion(attachedBin);
if (detected && detected !== IIPINNED_VERSION) {
- p.log.error(
- `Attached iii-engine appears to be v${detected} (from ${attachedBin}) ` +
- `but agentmemory v${VERSION} hard-pins v${IIPINNED_VERSION}. ` +
- `Engine API drift causes runtime failures (e.g. state::list-not-found on v0.13.0+). ` +
- `Stop the running engine (\`agentmemory stop --force\`) and re-run \`agentmemory\` ` +
- `to install the pinned engine into ~/.agentmemory/bin without touching ${attachedBin}. ` +
- `Or set AGENTMEMORY_III_VERSION=${detected} to override at your own risk.`,
+ p.log.warn(
+ `iii on PATH is v${detected} (from ${attachedBin}) but agentmemory v${VERSION} pins v${IIPINNED_VERSION}. ` +
+ `agentmemory will use its own pinned engine in ~/.agentmemory/bin and leaves ${attachedBin} untouched. ` +
+ `If you want agentmemory to track a different engine, set AGENTMEMORY_III_VERSION=${detected}.`,
);
- process.exit(1);
}
}
adoptRunningEngine();
@@ -2100,19 +2102,82 @@ async function runInit() {
p.outro(`Edit ${target} and you're set.`);
}
+async function startServerForDemo(): Promise<() => Promise> {
+ if (await isAgentmemoryReady()) {
+ return async () => {};
+ }
+
+ const startedEngine = !(await isEngineRunning());
+ if (startedEngine) {
+ const ok = await startEngine();
+ if (!ok) {
+ p.log.error("Could not start iii-engine for the demo.");
+ p.note(installInstructions().join("\n"), "Setup required");
+ process.exit(1);
+ }
+ if (!(await waitForEngine(15000))) {
+ p.log.error("iii-engine did not become ready within 15s.");
+ process.exit(1);
+ }
+ }
+
+ await import("./index.js");
+ if (!(await waitForAgentmemoryReady(15000))) {
+ p.log.error("agentmemory worker did not become ready within 15s.");
+ process.exit(1);
+ }
+
+ return async () => {
+ if (!startedEngine) return;
+ const port = getRestPort();
+ const state = readEngineState();
+ if (state?.kind === "docker") {
+ await stopDockerEngine(state.composeFile, port).catch(() => {});
+ return;
+ }
+ const pids = new Set(findEnginePidsByPort(port));
+ const pidfilePid = readEnginePidfile();
+ if (pidfilePid) pids.add(pidfilePid);
+ for (const pid of pids) {
+ await signalAndWait(pid, "SIGTERM", 3000).catch(() => {});
+ }
+ clearEnginePidfile();
+ clearEngineState();
+ clearWorkerPidfile();
+ };
+}
+
async function runDemo() {
const port = getRestPort();
const base = `http://localhost:${port}`;
p.intro("agentmemory demo");
- if (!(await isAgentmemoryReady())) {
+ const serve = args.includes("--serve");
+ let teardown: () => Promise = async () => {};
+
+ if (serve) {
+ teardown = await startServerForDemo();
+ } else if (!(await isAgentmemoryReady())) {
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/*.`,
);
p.log.info("Start it with: npx @agentmemory/agentmemory");
+ p.log.info("Or run a one-command demo with: npx @agentmemory/agentmemory demo --serve");
process.exit(1);
}
+ try {
+ await runDemoBody(base);
+ } finally {
+ await teardown();
+ }
+
+ if (serve) {
+ process.exit(0);
+ }
+}
+
+async function runDemoBody(base: string) {
const demoProject = "/tmp/agentmemory-demo";
const sessions = buildDemoSessions();
diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts
index 2e148a1bf..cd0f7c824 100644
--- a/src/cli/onboarding.ts
+++ b/src/cli/onboarding.ts
@@ -25,7 +25,8 @@ import { homedir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import * as p from "@clack/prompts";
-import { writePrefs } from "./preferences.js";
+import { appendFileSync, readFileSync } from "node:fs";
+import { readPrefs, writePrefs } from "./preferences.js";
import { resolveAdapter, runAdapter } from "./connect/index.js";
import type { ConnectResult } from "./connect/types.js";
@@ -68,6 +69,14 @@ const PROVIDERS: { value: string; label: string; envKey: string | null }[] = [
{ value: "skip", label: "Skip — BM25-only mode (no LLM key)", envKey: null },
];
+const PROVIDER_COST_HINTS: Record = {
+ anthropic: "rough cost: a fast Haiku-class model keeps compress/consolidate at fractions of a cent per session.",
+ openai: "rough cost: a mini-class model keeps compress/consolidate at fractions of a cent per session.",
+ gemini: "rough cost: a Flash-class model keeps compress/consolidate at fractions of a cent per session.",
+ openrouter: "rough cost: pick a small model; spend tracks your chosen model's per-token price.",
+ minimax: "rough cost: scales with the MiniMax model price per token.",
+};
+
export function buildAgentOptions(): { value: string; label: string; hint?: string }[] {
return [
...NATIVE_AGENTS.map((a) => ({
@@ -219,8 +228,17 @@ export async function runOnboarding(): Promise {
const provider = providerPicked === "skip" ? null : providerPicked;
const agents = (agentsPicked as string[]) ?? [];
+ if (provider) {
+ const hint = PROVIDER_COST_HINTS[provider];
+ if (hint) {
+ p.log.info(hint);
+ }
+ }
+
const envPath = await seedEnvFile(provider);
+ await maybePromptContextInjection(envPath);
+
writePrefs({
lastAgent: agents[0] ?? null,
lastAgents: agents,
@@ -253,6 +271,54 @@ export async function runOnboarding(): Promise {
return { agents, provider };
}
+function enableInjectContextInEnv(envPath: string | null): boolean {
+ if (!envPath || !existsSync(envPath)) return false;
+ try {
+ const current = readFileSync(envPath, "utf-8");
+ if (/^\s*AGENTMEMORY_INJECT_CONTEXT\s*=\s*true\b/m.test(current)) {
+ return true;
+ }
+ const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
+ appendFileSync(envPath, `${prefix}AGENTMEMORY_INJECT_CONTEXT=true\n`, { mode: 0o600 });
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+async function maybePromptContextInjection(envPath: string | null): Promise {
+ if (readPrefs().injectContextChosen) return;
+
+ const enable = await p.confirm({
+ message: "Enable automatic context injection so the agent recalls past sessions without being asked? [y/N]",
+ initialValue: false,
+ });
+
+ if (p.isCancel(enable)) {
+ p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset");
+ process.exit(0);
+ }
+
+ p.log.info(
+ "Cost note: injection spends session tokens proportional to tool-call frequency. Default is off.",
+ );
+
+ writePrefs({ injectContextChosen: true });
+
+ if (enable === true) {
+ const wrote = enableInjectContextInEnv(envPath);
+ if (wrote) {
+ p.log.success("Context injection enabled (AGENTMEMORY_INJECT_CONTEXT=true).");
+ } else {
+ p.log.warn(
+ "Could not update ~/.agentmemory/.env. Set AGENTMEMORY_INJECT_CONTEXT=true there to enable it.",
+ );
+ }
+ } else {
+ p.log.info("Context injection left off. Set AGENTMEMORY_INJECT_CONTEXT=true later to enable.");
+ }
+}
+
async function wireSelectedAgents(agents: string[]): Promise {
p.note("Wire selected agents now?", "next step");
const confirmed = await p.confirm({
diff --git a/src/cli/preferences.ts b/src/cli/preferences.ts
index ccebd1a31..63695db62 100644
--- a/src/cli/preferences.ts
+++ b/src/cli/preferences.ts
@@ -57,6 +57,11 @@ export interface Prefs {
// never updated, so we can show "you joined agentmemory N days ago"
// copy in /status later without keeping a separate file.
firstRunAt: string | null;
+ // Set to true once the user has answered the context-injection prompt
+ // (either way). We never re-ask after this so the prompt stays a
+ // one-time choice, matching the skipGlobalInstall / skipConsoleInstall
+ // never-nag pattern.
+ injectContextChosen: boolean;
}
const DEFAULTS: Prefs = {
@@ -69,6 +74,7 @@ const DEFAULTS: Prefs = {
skipGlobalInstall: false,
skipConsoleInstall: false,
firstRunAt: null,
+ injectContextChosen: false,
};
export function prefsDir(): string {
diff --git a/src/mcp/standalone.ts b/src/mcp/standalone.ts
index f4683747f..dd66ecb1d 100644
--- a/src/mcp/standalone.ts
+++ b/src/mcp/standalone.ts
@@ -51,8 +51,9 @@ function announceMode(handle: Handle): void {
`[@agentmemory/mcp] proxying to agentmemory server at ${handle.baseUrl}\n`,
);
} else {
+ const fullToolCount = getAllTools().length;
process.stderr.write(
- `[@agentmemory/mcp] no server reachable at ${displayAgentmemoryUrl()}; falling back to local InMemoryKV\n`,
+ `[@agentmemory/mcp] no server reachable at ${displayAgentmemoryUrl()}; running reduced LOCAL FALLBACK with ${IMPLEMENTED_TOOLS.size} of ${fullToolCount} tools. Start 'npx @agentmemory/agentmemory' (and point AGENTMEMORY_URL at it) to unlock all ${fullToolCount} tools.\n`,
);
}
}
@@ -338,7 +339,7 @@ async function handleProxyGeneric(
handle: ProxyHandle,
): Promise<{ content: Array<{ type: string; text: string }> }> {
// Forward to the server's full MCP surface so non-Claude clients can
- // reach all 51 tools (lessons, sentinels, slots, signals, graph, …)
+ // reach all 53 tools (lessons, sentinels, slots, signals, graph, …)
// instead of being capped at the 7 IMPLEMENTED_TOOLS set baked into
// this shim. The server validates arguments per tool.
const result = (await handle.call("/agentmemory/mcp/call", {
diff --git a/src/mcp/tools-registry.ts b/src/mcp/tools-registry.ts
index c86d899bb..c4df3499c 100644
--- a/src/mcp/tools-registry.ts
+++ b/src/mcp/tools-registry.ts
@@ -925,7 +925,7 @@ export const V010_SLOTS_TOOLS: McpToolDef[] = [
},
];
-const ESSENTIAL_TOOLS = new Set([
+export const ESSENTIAL_TOOLS = new Set([
"memory_save",
"memory_recall",
"memory_consolidate",
@@ -950,9 +950,9 @@ export function getAllTools(): McpToolDef[] {
}
// default switched from "core" (8 essential tools) to "all"
-// (full 51-tool surface). README and plugin manifests have always
-// advertised 51 tools "in proxy mode"; the old default left OpenCode /
-// Claude Code users seeing 8 with no indication the other 43 existed.
+// (full 53-tool surface). README and plugin manifests have always
+// advertised 53 tools "in proxy mode"; the old default left OpenCode /
+// Claude Code users seeing 8 with no indication the other tools existed.
// Users who want the lean essentials can still set AGENTMEMORY_TOOLS=core.
export function getVisibleTools(): McpToolDef[] {
const mode = process.env["AGENTMEMORY_TOOLS"] || "all";
diff --git a/test/tool-count-consistency.test.ts b/test/tool-count-consistency.test.ts
new file mode 100644
index 000000000..19f0e3a5e
--- /dev/null
+++ b/test/tool-count-consistency.test.ts
@@ -0,0 +1,43 @@
+import { describe, it, expect, vi } from "vitest";
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+
+vi.mock("../src/logger.js", () => ({
+ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
+}));
+
+import { getAllTools, ESSENTIAL_TOOLS } from "../src/mcp/tools-registry.js";
+
+const ROOT = join(import.meta.dirname, "..");
+const EXPECTED_TOOL_COUNT = 53;
+
+function readText(relativePath: string): string {
+ return readFileSync(join(ROOT, relativePath), "utf-8");
+}
+
+describe("Tool count consistency", () => {
+ it("registry exposes the expected number of tools", () => {
+ expect(getAllTools().length).toBe(EXPECTED_TOOL_COUNT);
+ });
+
+ it("cli help derives the tool counts from the registry", () => {
+ const cli = readText("src/cli.ts");
+ expect(cli).toContain("const ALL_TOOLS_COUNT = getAllTools().length;");
+ expect(cli).toContain(
+ "(default: all = ${ALL_TOOLS_COUNT} tools; core = ${CORE_TOOLS_COUNT} essentials)",
+ );
+ expect(cli).not.toMatch(/all\s*=\s*51 tools/);
+ });
+
+ it("core tool count derives from the registry", () => {
+ const coreCount = getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name)).length;
+ expect(coreCount).toBe(ESSENTIAL_TOOLS.size);
+ expect(coreCount).toBeGreaterThan(0);
+ });
+
+ it("README advertises the same tool count as the registry", () => {
+ const readme = readText("README.md");
+ expect(readme).toContain(`${EXPECTED_TOOL_COUNT} MCP tools`);
+ expect(readme).not.toContain("51 MCP tools");
+ });
+});