diff --git a/README.md b/README.md
index 87e6b3f0d..30483919b 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@
-
+
@@ -93,6 +93,7 @@ npm install -g @agentmemory/agentmemory # once — bare `agentmemory` o
# 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 +1142,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 +1466,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/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");
+ });
+});