1. Problem Overview
While agentmemory v0.9.21 is running normally, the following two anomalies are observed:
| Symptom |
Description |
| Status shows Memories/Observations as 0 |
Running agentmemory status in the terminal shows Memories and Observations both as 0, yet the Viewer page (http://localhost:3113) and direct API calls return data correctly. |
| Graph is always empty |
The Graph tab in the Viewer is empty; agentmemory status shows 0 nodes, 0 edges; calling /agentmemory/graph/stats returns all zeros. |
2. Environment
- Version:
0.9.21
- OS: macOS
- Node: v24.13.1 (via nvm)
- Data Dir:
./data/state_store.db (file-based SQLite via iii-engine)
- Actual Data Scale: 26 sessions, 121 observations, 4 memories
3. Root Cause Analysis
3.1 Status Shows 0 — /agentmemory/export API Times Out
The implementation of agentmemory status in dist/cli.mjs is:
const [healthRes, sessionsRes, graphRes, memoriesRes, flagsRes] = await Promise.all([
apiFetch(base, "health"),
apiFetch(base, "sessions"),
apiFetch(base, "graph/stats"),
apiFetch(base, "export"), // ← the culprit
apiFetch(base, "config/flags")
]);
const obsCount = memoriesRes?.observations?.length || 0;
const memCount = memoriesRes?.memories?.length || 0;
Root cause: status relies on /agentmemory/export to obtain memory and observation counts, but this API consistently times out (>5s no response) with the current dataset, causing memoriesRes to be undefined and both counts to fall back to 0.
Verification:
# Times out
$ curl -s http://localhost:3111/agentmemory/export
# (5s+ no response)
# Yet memories/sessions APIs work fine
$ curl -s http://localhost:3111/agentmemory/memories
{"memories": [...]} // returns 4 items
$ curl -s http://localhost:3111/agentmemory/sessions
{"sessions": [...]} // returns 26 items
Additional finding: state_store.db contained 2 corrupted session records without an id field. Removing them did not resolve the export timeout, indicating a deeper issue (likely a performance bottleneck in iii-engine's file-based KV adapter when handling large numbers of concurrent kv.list() calls).
3.2 Graph is Empty — session.stopped Event is Never Published
Automatic extraction is broken:
The graph extraction registration in dist/index.mjs is:
sdk.registerFunction("event::session::stopped", async (data) => {
// ... triggers mem::summarize, mem::slot-reflect ...
if (isGraphExtractionEnabled()) {
const compressed = (await kv.list(KV.observations(data.sessionId)))
.filter((o) => o.title);
if (compressed.length > 0)
sdk.triggerVoid("mem::graph-extract", { observations: compressed });
}
return summary;
});
This event subscribes to the agentmemory.session.stopped topic. However, after searching the entire codebase, no code publishes the agentmemory.session.stopped event:
- The
session-end hook only calls /agentmemory/session/end
api::session::end only updates endedAt / status, publishing no events
- No other hooks / MCP tools publish this event either
Result: All sessions have endedAt but no stoppedAt, so graph extraction is never triggered automatically.
3.3 Viewer "Build Graph" Button Calls a Non-existent API
The Viewer page (dist/viewer/index.html) calls the following when the graph is empty:
var buildResult = await apiPost('graph/build', {});
However, /agentmemory/graph/build has no corresponding endpoint in v0.9.21 (returns HTTP 404).
4. Local Workaround
The following are local workarounds. The cli.mjs modification will be overwritten after upgrading the npm package.
4.1 Fix agentmemory status Display
Modified file: node_modules/@agentmemory/agentmemory/dist/cli.mjs (or the equivalent path in your global installation)
Changes:
Around line ~2208, change:
apiFetch(base, "export"),
To:
apiFetch(base, "memories"),
Around line ~2221, change:
const obsCount = memoriesRes?.observations?.length || 0;
To:
const obsCount = Array.isArray(sessionsRes?.sessions)
? sessionsRes.sessions.reduce((sum, s) => sum + (s.observationCount || 0), 0)
: 0;
Effect: status no longer depends on the timing-out export API; it reads directly from the memories and sessions endpoints, returning correct counts.
4.2 Manually Build Graph — build-graph.mjs
Since automatic extraction is broken, this script reads the underlying observation data files directly and calls /agentmemory/graph/extract to manually extract the graph.
Attention: This script has only been tested locally and may not be universally applicable.
Full source:
#!/usr/bin/env node
/**
* Manually build the agentmemory knowledge graph from existing observations.
* Works around the missing session.stopped event in v0.9.21.
*/
import { readFileSync, readdirSync } from "fs";
import { join } from "path";
const DATA_DIR = "./data/state_store.db";
const BASE = process.env.AGENTMEMORY_URL || "http://localhost:3111";
const SECRET = process.env.AGENTMEMORY_SECRET || "";
const BATCH_SIZE = 15; // observations per batch
const CONCURRENCY = 2; // parallel batches
function headers() {
const h = { "Content-Type": "application/json" };
if (SECRET) h["Authorization"] = `Bearer ${SECRET}`;
return h;
}
async function apiPost(path, body) {
const res = await fetch(`${BASE}/agentmemory/${path}`, {
method: "POST",
headers: headers(),
body: JSON.stringify(body),
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`POST ${path} failed: ${res.status} ${text}`);
}
return res.json();
}
function parseFirstJson(raw) {
let depth = 0;
let inString = false;
let escape = false;
let start = null;
for (let i = 0; i < raw.length; i++) {
const b = raw[i];
if (escape) {
escape = false;
continue;
}
if (b === 0x5c) {
escape = true;
continue;
}
if (b === 0x22 && start !== null) {
inString = !inString;
} else if (!inString) {
if (b === 0x7b) {
if (depth === 0) start = i;
depth++;
} else if (b === 0x7d) {
depth--;
if (depth === 0 && start !== null) {
try {
const str = raw.slice(start, i + 1).toString("utf-8");
return JSON.parse(str);
} catch {
// continue searching
}
start = null;
}
}
}
}
return null;
}
function readObservations() {
const files = readdirSync(DATA_DIR).filter(
(f) => f.startsWith("mem%3Aobs%3A") && f.endsWith(".bin")
);
const observations = [];
for (const file of files) {
try {
const raw = readFileSync(join(DATA_DIR, file));
const obj = parseFirstJson(raw);
if (obj && typeof obj === "object") {
for (const obs of Object.values(obj)) {
if (obs && typeof obs === "object" && obs.title) {
observations.push(obs);
}
}
}
} catch (e) {
console.warn(`Skip ${file}: ${e.message}`);
}
}
return observations;
}
async function processBatch(batch, batchNum) {
console.log(`[Batch ${batchNum}] Sending ${batch.length} observations...`);
try {
const result = await apiPost("graph/extract", { observations: batch });
if (result?.success) {
const nodes = result.nodesAdded || 0;
const edges = result.edgesAdded || 0;
console.log(`[Batch ${batchNum}] -> ${nodes} nodes, ${edges} edges`);
return { nodes, edges };
} else {
console.error(
`[Batch ${batchNum}] -> failed: ${result?.error || "unknown"}`
);
return { nodes: 0, edges: 0 };
}
} catch (e) {
console.error(`[Batch ${batchNum}] -> error: ${e.message}`);
return { nodes: 0, edges: 0 };
}
}
async function runInParallel(batches, concurrency) {
const results = [];
for (let i = 0; i < batches.length; i += concurrency) {
const chunk = batches.slice(i, i + concurrency);
const chunkResults = await Promise.all(
chunk.map((batch, idx) => processBatch(batch, i + idx + 1))
);
results.push(...chunkResults);
}
return results;
}
async function main() {
const observations = readObservations();
console.log(`Found ${observations.length} observations with titles`);
if (observations.length === 0) {
console.log("No observations to extract graph from.");
return;
}
const batches = [];
for (let i = 0; i < observations.length; i += BATCH_SIZE) {
batches.push(observations.slice(i, i + BATCH_SIZE));
}
console.log(
`Split into ${batches.length} batches (size ${BATCH_SIZE}, concurrency ${CONCURRENCY})`
);
const startTime = Date.now();
const results = await runInParallel(batches, CONCURRENCY);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
const totalNodes = results.reduce((s, r) => s + r.nodes, 0);
const totalEdges = results.reduce((s, r) => s + r.edges, 0);
console.log(
`\nDone in ${elapsed}s! Total extracted: ${totalNodes} nodes, ${totalEdges} edges`
);
try {
const stats = await (
await fetch(`${BASE}/agentmemory/graph/stats`, { headers: headers() })
).json();
console.log("Current graph stats:", JSON.stringify(stats, null, 2));
} catch (e) {
console.warn("Could not fetch graph stats:", e.message);
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
Usage:
cd /path/to/your/agentmemory-project
node build-graph.mjs
My local results (174 observations, batch=15, concurrency=2):
Found 174 observations with titles
Split into 12 batches (size 15, concurrency 2)
...
Done in 321.0s! Total extracted: 177 nodes, 104 edges
Current graph stats:
totalNodes: 253
totalEdges: 289
4.3 Clean Up Corrupted Data
Directly parsed data/state_store.db/mem%3Asessions.bin and removed entries without an id field, eliminating 2 corrupted records.
5. Suggested Fixes
-
Fix /agentmemory/export API timeout
- Investigate the performance bottleneck in iii-engine's file-based KV adapter under large-scale concurrent
kv.list() calls; or add pagination / streaming to export.
-
Fix session.stopped event publishing
- Explicitly publish the
agentmemory.session.stopped topic inside api::session::end or the session-end hook; or have iii-engine auto-publish lifecycle events on kv.update state changes.
-
Implement /agentmemory/graph/build API
- This endpoint is called by the Viewer but not implemented on the server side. A batch graph-build endpoint that re-processes existing observations would close the gap.
Reported on: 2026-05-26
1. Problem Overview
While agentmemory v0.9.21 is running normally, the following two anomalies are observed:
agentmemory statusin the terminal showsMemoriesandObservationsboth as0, yet the Viewer page (http://localhost:3113) and direct API calls return data correctly.agentmemory statusshows0 nodes, 0 edges; calling/agentmemory/graph/statsreturns all zeros.2. Environment
0.9.21./data/state_store.db(file-based SQLite via iii-engine)3. Root Cause Analysis
3.1 Status Shows 0 —
/agentmemory/exportAPI Times OutThe implementation of
agentmemory statusindist/cli.mjsis:Root cause:
statusrelies on/agentmemory/exportto obtain memory and observation counts, but this API consistently times out (>5s no response) with the current dataset, causingmemoriesResto beundefinedand both counts to fall back to0.Verification:
Additional finding:
state_store.dbcontained 2 corrupted session records without anidfield. Removing them did not resolve the export timeout, indicating a deeper issue (likely a performance bottleneck in iii-engine's file-based KV adapter when handling large numbers of concurrentkv.list()calls).3.2 Graph is Empty —
session.stoppedEvent is Never PublishedAutomatic extraction is broken:
The graph extraction registration in
dist/index.mjsis:This event subscribes to the
agentmemory.session.stoppedtopic. However, after searching the entire codebase, no code publishes theagentmemory.session.stoppedevent:session-endhook only calls/agentmemory/session/endapi::session::endonly updatesendedAt/status, publishing no eventsResult: All sessions have
endedAtbut nostoppedAt, so graph extraction is never triggered automatically.3.3 Viewer "Build Graph" Button Calls a Non-existent API
The Viewer page (
dist/viewer/index.html) calls the following when the graph is empty:However,
/agentmemory/graph/buildhas no corresponding endpoint in v0.9.21 (returns HTTP 404).4. Local Workaround
4.1 Fix
agentmemory statusDisplayModified file:
node_modules/@agentmemory/agentmemory/dist/cli.mjs(or the equivalent path in your global installation)Changes:
Around line ~2208, change:
To:
Around line ~2221, change:
To:
Effect:
statusno longer depends on the timing-outexportAPI; it reads directly from thememoriesandsessionsendpoints, returning correct counts.4.2 Manually Build Graph —
build-graph.mjsSince automatic extraction is broken, this script reads the underlying observation data files directly and calls
/agentmemory/graph/extractto manually extract the graph.Attention: This script has only been tested locally and may not be universally applicable.
Full source:
Usage:
cd /path/to/your/agentmemory-project node build-graph.mjsMy local results (174 observations, batch=15, concurrency=2):
4.3 Clean Up Corrupted Data
Directly parsed
data/state_store.db/mem%3Asessions.binand removed entries without anidfield, eliminating 2 corrupted records.5. Suggested Fixes
Fix
/agentmemory/exportAPI timeoutkv.list()calls; or add pagination / streaming to export.Fix
session.stoppedevent publishingagentmemory.session.stoppedtopic insideapi::session::endor thesession-endhook; or have iii-engine auto-publish lifecycle events onkv.updatestate changes.Implement
/agentmemory/graph/buildAPIReported on: 2026-05-26