Skip to content

fix(graph): auto-fire mem::graph-extract at session end (closes #210)#215

Merged
rohitg00 merged 1 commit intomainfrom
fix/210-auto-fire-graph-extract
Apr 29, 2026
Merged

fix(graph): auto-fire mem::graph-extract at session end (closes #210)#215
rohitg00 merged 1 commit intomainfrom
fix/210-auto-fire-graph-extract

Conversation

@rohitg00
Copy link
Copy Markdown
Owner

@rohitg00 rohitg00 commented Apr 29, 2026

Summary

Closes #210. mem::graph-extract was registered and exposed via REST, but never auto-invoked. Graph KV stayed empty even with GRAPH_EXTRACTION_ENABLED=true unless users manually POSTed to /agentmemory/graph/extract.

Reporter @jco-analyst traced this thoroughly: zero callers in observe.ts, compress.ts, consolidation-pipeline.ts, flow-compress.ts, or registerEventTriggers. README pipeline diagram (-> Index in BM25 + vector + knowledge graph) implied per-PostToolUse extraction that didn't exist.

Fix

Wire mem::graph-extract into event::session::stopped — fire-and-forget, gated on isGraphExtractionEnabled(), mirrors the existing mem::slot-reflect glue.

if (isGraphExtractionEnabled()) {
  try {
    const observations = await kv.list<CompressedObservation>(KV.observations(data.sessionId));
    const compressed = observations.filter((o) => o.title);
    if (compressed.length > 0) {
      sdk.triggerVoid("mem::graph-extract", { observations: compressed });
    }
  } catch (err) {
    logger.warn("graph-extract triggerVoid failed", { ... });
  }
}

Idempotent on re-run via existing merge keys (name+type for nodes, source|target|type for edges) at src/functions/graph.ts:115-149.

README

Pipeline diagram split: BM25 + vector at PostToolUse (unchanged), graph extraction now correctly shown at Stop/SessionEnd phase.

Out of scope

  • mem::temporal-graph-extract has the same orphan status — separate follow-up
  • Missing graph-prune/decay/gc/clear (raised in the issue) — separate follow-up; graph KV will grow unbounded until those land

Test plan

  • Build clean (npm run build)
  • Pre-existing test failures unchanged (mcp-standalone, fs-watcher flake)
  • Manual: set GRAPH_EXTRACTION_ENABLED=true, run a session through Claude Code, end session, curl /agentmemory/graph/stats → expect non-zero totalNodes

Summary by CodeRabbit

  • New Features

    • Graph extraction now automatically triggers at session end when the feature flag is enabled, eliminating the need for manual REST API calls.
  • Documentation

    • Updated pipeline documentation to show graph extraction occurs at session end instead of during tool use, and documented feature flags controlling extraction and reflection behavior.

mem::graph-extract was registered and the REST endpoint at
POST /agentmemory/graph/extract was live, but no internal caller invoked
the function. Setting GRAPH_EXTRACTION_ENABLED=true populated nothing —
the graph KV stayed empty unless users manually POSTed observations to
the extract endpoint.

Wire the function into event::session::stopped (alongside the existing
mem::slot-reflect glue). Fire-and-forget via triggerVoid; the existing
node-merge (name+type) and edge-merge (source|target|type) keys make
re-runs idempotent.

README pipeline diagram updated: graph extraction shown at the
Stop/SessionEnd phase rather than implying it runs per PostToolUse.
The previous wording was misleading — the BM25 + vector indices ARE
written per-PostToolUse, but the graph never was.

Note: mem::temporal-graph-extract has the same orphan status and is
NOT addressed here. Tracking separately.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentmemory Ready Ready Preview, Comment Apr 29, 2026 1:39pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

The changes automatically trigger graph extraction when a session ends. Previously, mem::graph-extract required manual REST API invocation. Now, when GRAPH_EXTRACTION_ENABLED=true, compressed observations are extracted to the knowledge graph upon session completion with idempotent deduplication via existing merge keys.

Changes

Cohort / File(s) Summary
Documentation Updates
CHANGELOG.md, README.md
CHANGELOG documents the new auto-trigger behavior (#210). README's Memory Pipeline diagram is revised: indexing now contains only BM25 and vector (knowledge-graph removed); a new "Stop / SessionEnd" phase conditionally performs graph extraction and slot reflection based on feature flags.
Event Handler Implementation
src/triggers/events.ts
event::session::stopped handler now queries session observations with titles and conditionally invokes mem::graph-extract when isGraphExtractionEnabled() is true. Errors are caught and logged via logger.warn; slot-reflection behavior unchanged.

Sequence Diagram

sequenceDiagram
    participant Session
    participant EventSystem as Event System
    participant Handler as Session Stopped Handler
    participant FeatureFlag as Feature Flag Check
    participant ObsDB as Observation Store
    participant GraphExtract as mem::graph-extract
    participant KVStore as Graph KV

    Session->>EventSystem: Session completes
    EventSystem->>Handler: fire event::session::stopped
    Handler->>FeatureFlag: isGraphExtractionEnabled()?
    alt Flag Enabled
        FeatureFlag-->>Handler: true
        Handler->>ObsDB: Query observations for session
        ObsDB-->>Handler: observations with title
        Handler->>GraphExtract: sdk.triggerVoid("mem::graph-extract", {observations})
        GraphExtract->>KVStore: Extract & merge nodes/edges
        KVStore-->>GraphExtract: Stored (idempotent)
        GraphExtract-->>Handler: Complete
    else Flag Disabled
        FeatureFlag-->>Handler: false
        Handler-->>EventSystem: Skip extraction
    end
    Handler-->>EventSystem: Continue (slot reflection, etc.)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 Hop, hop—the session's done and gone,
Graph extraction at the dawn!
No manual calls, just flag and flow,
Observations merge, the knowledge shall grow. ✨🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(graph): auto-fire mem::graph-extract at session end (closes #210)' accurately and concisely describes the primary change: wiring automatic graph extraction at session end to address issue #210.
Linked Issues check ✅ Passed The PR implements the suggested fix from #210: wires mem::graph-extract to event::session::stopped with isGraphExtractionEnabled() gating, fetches/filters compressed observations, and updates the README pipeline diagram to reflect graph extraction at session end instead of PostToolUse.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: graph extraction wiring, README/pipeline updates, and the noted out-of-scope items (temporal-graph-extract orphan status and graph pruning/gc) remain unaddressed as intended.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/210-auto-fire-graph-extract

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/triggers/events.ts (1)

64-67: Tighten observation filtering before calling mem::graph-extract.

Filtering only by truthy title can pass malformed rows; mem::graph-extract builds prompts from title/narrative/concepts/files/type.

Proposed fix
-        const compressed = observations.filter((o) => o.title);
+        const compressed = observations.filter(
+          (o): o is CompressedObservation =>
+            typeof o.title === "string" &&
+            o.title.trim().length > 0 &&
+            typeof o.narrative === "string" &&
+            Array.isArray(o.concepts),
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/triggers/events.ts` around lines 64 - 67, The current filter only checks
truthy title and can pass malformed observation rows to mem::graph-extract;
update the filtering logic where compressed is built to validate required fields
for each observation (e.g., in the observations.filter call that produces
compressed) by ensuring title and type are non-empty strings, narrative is a
string (if required), concepts is an array (and optionally non-empty), and files
is an array of valid file entries before calling
sdk.triggerVoid("mem::graph-extract", { observations: compressed }); this
prevents malformed prompts by only sending well-shaped observation objects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 514: The README uses the wrong config flag name: change the reference
from SLOT_REFLECT_ENABLED to AGENTMEMORY_REFLECT (or vice‑versa) so the
documentation matches the actual configuration variable; update the single
occurrence "-> Slot reflection (if SLOT_REFLECT_ENABLED=true)" to read "-> Slot
reflection (if AGENTMEMORY_REFLECT=true)" and ensure any nearby config examples
and descriptions consistently use AGENTMEMORY_REFLECT.

In `@src/triggers/events.ts`:
- Around line 59-63: Validate data.sessionId before using it to build the KV
scope: before calling isGraphExtractionEnabled()'s KV read (the
kv.list<CompressedObservation>(KV.observations(data.sessionId)) call) check that
data.sessionId is present, is a non-empty string (and matches any expected
pattern/UUID if applicable), and reject/skip the KV read (or throw a controlled
error) when validation fails; update the input validation where this handler is
registered (sdk.registerFunction) to enforce the same contract and add a
recordAudit() call for invalid/malformed sessionId events so malformed payloads
are recorded and the downstream KV read is never attempted.

---

Nitpick comments:
In `@src/triggers/events.ts`:
- Around line 64-67: The current filter only checks truthy title and can pass
malformed observation rows to mem::graph-extract; update the filtering logic
where compressed is built to validate required fields for each observation
(e.g., in the observations.filter call that produces compressed) by ensuring
title and type are non-empty strings, narrative is a string (if required),
concepts is an array (and optionally non-empty), and files is an array of valid
file entries before calling sdk.triggerVoid("mem::graph-extract", {
observations: compressed }); this prevents malformed prompts by only sending
well-shaped observation objects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b064e7a6-440f-46a7-9af8-f4a82863463b

📥 Commits

Reviewing files that changed from the base of the PR and between 2dfe128 and 9170296.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • README.md
  • src/triggers/events.ts

Comment thread README.md
Stop / SessionEnd hook fires
-> Summarize session
-> Knowledge graph extraction (if GRAPH_EXTRACTION_ENABLED=true)
-> Slot reflection (if SLOT_REFLECT_ENABLED=true)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the correct slot reflection flag name.

Line 514 uses SLOT_REFLECT_ENABLED, but the config section documents AGENTMEMORY_REFLECT. This can cause users to enable the wrong flag.

Proposed fix
-  -> Slot reflection (if SLOT_REFLECT_ENABLED=true)
+  -> Slot reflection (if AGENTMEMORY_REFLECT=true)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-> Slot reflection (if SLOT_REFLECT_ENABLED=true)
-> Slot reflection (if AGENTMEMORY_REFLECT=true)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 514, The README uses the wrong config flag name: change
the reference from SLOT_REFLECT_ENABLED to AGENTMEMORY_REFLECT (or vice‑versa)
so the documentation matches the actual configuration variable; update the
single occurrence "-> Slot reflection (if SLOT_REFLECT_ENABLED=true)" to read
"-> Slot reflection (if AGENTMEMORY_REFLECT=true)" and ensure any nearby config
examples and descriptions consistently use AGENTMEMORY_REFLECT.

Comment thread src/triggers/events.ts
Comment on lines +59 to +63
if (isGraphExtractionEnabled()) {
try {
const observations = await kv.list<CompressedObservation>(
KV.observations(data.sessionId),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate sessionId before KV scope reads.

The new path reads KV.observations(data.sessionId) without a runtime guard. A malformed payload can cause incorrect scope reads and noisy downstream triggers.

Proposed fix
     if (isGraphExtractionEnabled()) {
+      if (typeof data.sessionId !== "string" || data.sessionId.trim().length === 0) {
+        logger.warn("graph-extract skipped: invalid sessionId", {
+          sessionId: String(data.sessionId),
+        });
+        return summary;
+      }
       try {
         const observations = await kv.list<CompressedObservation>(
           KV.observations(data.sessionId),
         );
As per coding guidelines `src/{functions,triggers}/**/*.ts`: "Register functions using `sdk.registerFunction()` with validation of inputs, work via kv.get/kv.set/kv.list, and record audit operations via `recordAudit()`".
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isGraphExtractionEnabled()) {
try {
const observations = await kv.list<CompressedObservation>(
KV.observations(data.sessionId),
);
if (isGraphExtractionEnabled()) {
if (typeof data.sessionId !== "string" || data.sessionId.trim().length === 0) {
logger.warn("graph-extract skipped: invalid sessionId", {
sessionId: String(data.sessionId),
});
return summary;
}
try {
const observations = await kv.list<CompressedObservation>(
KV.observations(data.sessionId),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/triggers/events.ts` around lines 59 - 63, Validate data.sessionId before
using it to build the KV scope: before calling isGraphExtractionEnabled()'s KV
read (the kv.list<CompressedObservation>(KV.observations(data.sessionId)) call)
check that data.sessionId is present, is a non-empty string (and matches any
expected pattern/UUID if applicable), and reject/skip the KV read (or throw a
controlled error) when validation fails; update the input validation where this
handler is registered (sdk.registerFunction) to enforce the same contract and
add a recordAudit() call for invalid/malformed sessionId events so malformed
payloads are recorded and the downstream KV read is never attempted.

@rohitg00 rohitg00 merged commit 3a8ba85 into main Apr 29, 2026
5 checks passed
@rohitg00 rohitg00 deleted the fix/210-auto-fire-graph-extract branch April 29, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v0.9.3: mem::graph-extract is registered but never auto-fired — README pipeline diagram (line 509) is misleading

1 participant