Summary
The event::session::stopped handler sends the session's entire compressed observation set to mem::graph-extract on every session stop, with no change detection. Sessions that stop repeatedly without new activity (heartbeat-style loops) re-extract the identical set every time.
Root cause
src/triggers/events.ts (session-stopped handler):
if (isGraphExtractionEnabled()) {
const observations = await kv.list<CompressedObservation>(
KV.observations(data.sessionId),
);
const compressed = observations.filter((o) => o.title);
if (compressed.length > 0) {
sdk.trigger({
function_id: "mem::graph-extract",
payload: { observations: compressed }, // full set, every stop
action: TriggerAction.Void(),
});
}
}
There is no fingerprint/dedup — every stop re-processes the full history even when zero observations were added since the last extraction.
Observed impact (self-hosted, v0.9.27)
A session capped at 500 observations that stopped every 30 minutes produced a ~213 KB (~50K input tokens) graph-extraction LLM request per cycle, around the clock, with the input identical each time (extraction yields mostly already-known nodes/edges).
This is the same shape as the double/redundant summarize issue (#1062) — full re-processing on session stop with no change detection — just in a different function.
Fix
Included in #1061 (shares the computeInputFingerprint helper introduced there): fingerprint the compressed set, store it per session (KV.graphExtractState), and trigger mem::graph-extract only when the fingerprint changed.
Summary
The
event::session::stoppedhandler sends the session's entire compressed observation set tomem::graph-extracton every session stop, with no change detection. Sessions that stop repeatedly without new activity (heartbeat-style loops) re-extract the identical set every time.Root cause
src/triggers/events.ts(session-stopped handler):There is no fingerprint/dedup — every stop re-processes the full history even when zero observations were added since the last extraction.
Observed impact (self-hosted, v0.9.27)
A session capped at 500 observations that stopped every 30 minutes produced a ~213 KB (~50K input tokens) graph-extraction LLM request per cycle, around the clock, with the input identical each time (extraction yields mostly already-known nodes/edges).
This is the same shape as the double/redundant summarize issue (#1062) — full re-processing on session stop with no change detection — just in a different function.
Fix
Included in #1061 (shares the
computeInputFingerprinthelper introduced there): fingerprint the compressed set, store it per session (KV.graphExtractState), and triggermem::graph-extractonly when the fingerprint changed.