Conversation
…ence Add 4 MCP resources (status, project profile, recent sessions, latest memories), 3 MCP prompts (recall_context, session_handoff, detect_patterns), confidence-scored memory relations, and a unified enrich endpoint that aggregates file context, relevant observations, and past bug memories for PreToolUse hook injection. - Add confidence field to MemoryRelation with auto-scoring from co-occurrence, recency, and relation type - Add mem::enrich function aggregating 3 sources with resilient .catch() fallbacks - Add POST /agentmemory/enrich REST endpoint - Switch PreToolUse hook from file-context to enrich endpoint with search term extraction - Add minConfidence filter and confidence-desc sorting to mem::get-related - 27 new tests (171 total), all passing
📝 WalkthroughWalkthroughAdds an enrich capability (mem::enrich) and API (/agentmemory/enrich) that aggregates file context, search observations, and bug memories with truncation; introduces confidence scoring for memory relations (creation, filtering, and ordering); extends MCP with resources and prompts endpoints; bumps package to v0.4.0 and adds tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client/Hook
participant API as api::enrich
participant Enrich as mem::enrich
participant FileCtx as mem::file-context
participant Search as mem::search
participant KV as KV Store
Client->>API: POST /agentmemory/enrich\n(sessionId, files, terms, toolName)
API->>Enrich: trigger(mem::enrich, payload)
par Parallel Context Gathering
Enrich->>FileCtx: request file-specific context
FileCtx-->>Enrich: file context
Enrich->>Search: query search observations\n(files, basenames, terms)
Search-->>Enrich: up to 5 results
Enrich->>KV: fetch bug memories filtered by files
KV-->>Enrich: matching bug memories
end
Enrich->>Enrich: aggregate context\nwrap in tags and truncate to 4000 chars
Enrich-->>API: { context, truncated }
API-->>Client: 200 { context, truncated }
sequenceDiagram
participant Client as Caller
participant GetRel as mem::get-related
participant DB as Relations DB
participant Compute as computeConfidence
Client->>GetRel: request (sessionId, type, minConfidence?)
GetRel->>DB: fetch relations for session/type
DB-->>GetRel: relations list
loop per relation
GetRel->>Compute: computeConfidence(source,target,type)
Compute-->>GetRel: confidence score
alt confidence >= minConfidence
GetRel->>GetRel: include memory with confidence
else
GetRel->>GetRel: exclude
end
end
GetRel->>GetRel: sort results by confidence desc
GetRel-->>Client: results [{memory, hop, confidence}]
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (4)
src/index.ts (1)
60-60: Consider centralizing startup metadata to avoid drift.Line 60 and Lines 176-178 hardcode version/count strings. A shared constant (or generated values) would reduce mismatch risk with future releases.
Also applies to: 176-178
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` at line 60, Centralize the hardcoded startup metadata by introducing a single exported constant (e.g., VERSION or STARTUP_METADATA) and use it wherever the version/count strings are printed instead of repeating literals; update the console.log call that currently prints `[agentmemory] Starting worker v0.4.0...` and the other occurrences that build version/count strings to reference this new constant (search for the current literal string and the identifiers around the other prints) so all startup/version text is produced from the same source of truth.test/mcp-prompts.test.ts (1)
183-205: Add a non-string argument test for prompt inputs.Given these endpoints are HTTP-facing, adding a case like
task_description: 42(expect 400) will harden regression coverage for runtime payload validation.🧪 Suggested test addition
it("returns 400 for missing required arg", async () => { @@ expect(result.status_code).toBe(400); }); + + it("returns 400 for invalid arg type", async () => { + const fn = sdk.getFunction("mcp::prompts::get")!; + const result = (await fn( + makeReq({ + name: "recall_context", + arguments: { task_description: 42 }, + }), + )) as { status_code: number }; + + expect(result.status_code).toBe(400); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/mcp-prompts.test.ts` around lines 183 - 205, Add a new test case alongside the existing ones that calls sdk.getFunction("mcp::prompts::get") via makeReq but supplies a non-string argument (e.g., arguments: { task_description: 42 }) and assert the response status_code is 400; locate the existing tests using sdk.getFunction("mcp::prompts::get") / makeReq in this file and mirror their structure and assertions to validate runtime payload type checking for prompt inputs.test/mcp-resources.test.ts (1)
254-273: Add a malformed percent-encoding URI regression test.You already cover valid
decodeURIComponentbehavior; adding an invalid encoded URI case helps lock expected client-error semantics.🧪 Suggested test addition
it("handles URI with special characters via decodeURIComponent", async () => { @@ expect(data.project).toBe("my app/subdir"); }); + + it("returns 400 for malformed encoded URI", async () => { + const fn = sdk.getFunction("mcp::resources::read")!; + const result = (await fn( + makeReq({ uri: "agentmemory://project/%E0%A4%A/profile" }), + )) as { status_code: number }; + + expect(result.status_code).toBe(400); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/mcp-resources.test.ts` around lines 254 - 273, Add a new regression test next to the existing decodeURIComponent case that calls sdk.getFunction("mcp::resources::read") with a request built via makeReq whose uri contains malformed percent-encoding (e.g. "agentmemory://project/bad%E0encoding" or a trailing "%"), and assert the function returns the expected client error (HTTP 400 or appropriate status_code) and an informative error body; reuse sdk.overrideTrigger setup used in the valid test and ensure the new it(...) block verifies the server rejects malformed percent-encodings rather than throwing or returning 200.src/mcp/server.ts (1)
470-474: NormalizeminConfidenceat the MCP boundary.Clamping/validating once here keeps
mem::get-relatedbehavior consistent for out-of-range or non-finite client input.🔧 Proposed refactor
case "memory_relations": { if (typeof args.memoryId !== "string" || !args.memoryId.trim()) { return { status_code: 400, body: { error: "memoryId is required for memory_relations" }, }; } + const minConfidenceRaw = + args.minConfidence === undefined ? 0 : Number(args.minConfidence); + if (!Number.isFinite(minConfidenceRaw)) { + return { + status_code: 400, + body: { error: "minConfidence must be a number between 0 and 1" }, + }; + } + const minConfidence = Math.max(0, Math.min(1, minConfidenceRaw)); const result = await sdk.trigger("mem::get-related", { memoryId: args.memoryId, maxHops: (args.maxHops as number) || 2, - minConfidence: (args.minConfidence as number) || 0, + minConfidence, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/mcp/server.ts` around lines 470 - 474, Normalize and clamp the minConfidence value before calling sdk.trigger("mem::get-related"): validate that args.minConfidence is a finite number and clamp it into the [0,1] range (falling back to 0 when missing/invalid), and similarly ensure args.maxHops is a finite integer (defaulting to 2) so mem::get-related always receives well-formed parameters; update the invocation site that constructs the payload for sdk.trigger in server.ts (the block using memoryId, maxHops, minConfidence) to perform these checks and normalization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/functions/enrich.ts`:
- Around line 53-63: The bug memory selection uses KV iteration order; after
filtering in the bugMemoriesPromise chain (reference: bugMemoriesPromise,
Memory, KV.memories) sort the resulting memories by recency (e.g., descending by
memory.updatedAt, falling back to memory.createdAt or memory.timestamp) and then
take the top 3 (slice(0,3)); apply the same change to the other identical
selection block around lines 87–91 so both places consistently sort by most
recent before truncating.
- Around line 75-94: The code injects raw observation narratives and bug memory
text into XML-like tags (see the mapping of searchResult.results ->
r.observation?.narrative and the construction of bugs from bugMemories.map(m =>
`- ${m.title}: ${m.content}`)), which can break downstream parsing; add an
escape function (e.g., escapeXml) that replaces &, <, >, " and ' with their XML
entities and apply it to each narrative, m.title and m.content before
joining/embedding, updating the parts.push calls that build
`<agentmemory-relevant-context>` and `<agentmemory-past-errors>` so all inserted
text is escaped.
In `@src/functions/relations.ts`:
- Around line 183-190: The current selection of matchingRelation via find(...)
in the mem::get-related flow is order-dependent and can pick the wrong relation
when multiple visited edges exist; update the logic in the function (look for
variables allRelations, visited, current, minConfidence, and the
matchingRelation/confidence assignment) to compute the highest confidence among
all relations that match the condition ((r.sourceId === current.id &&
visited.has(r.targetId)) || (r.targetId === current.id &&
visited.has(r.sourceId))) instead of using find; set confidence to that max
(default 0.5 if none), then apply the minConfidence check and push the result
using that max confidence value.
- Around line 150-159: The minConfidence parameter in mem::get-related (the
async function that defines maxHops, MAX_VISITED and minConfidence) must be
normalized: ensure data.minConfidence is coerced to a finite number (use Number
or parseFloat), default to 0 for non-finite values, and clamp the value to the
[0,1] range before it is used; update the assignment to the local minConfidence
variable so invalid inputs (NaN, Infinity, negative, >1) cannot produce silent
empty results.
In `@src/mcp/server.ts`:
- Around line 584-588: The handler is using decodeURIComponent on
client-supplied URI segments (e.g., where projectProfileMatch is set and also
the other occurrences around the other match blocks) which can throw on
malformed percent-encoding; wrap each decodeURIComponent call in a local
try/catch and, on a URI decoding error, respond with a 400 Bad Request instead
of letting the error bubble to the outer 500 handler. Locate the
decodeURIComponent usage for projectProfileMatch and the similar decode calls at
the other reported spots (the match blocks around lines noted in the comment)
and convert them to guarded decoding that returns/res.send a 400 with a brief
error message when decoding fails.
- Around line 752-763: The handler currently only checks presence of
promptArgs.task_description (taskDesc) but allows non-string values; update
validation to ensure promptArgs.task_description and promptArgs.session_id are
strings (and if promptArgs.project is provided it must be a string) and return a
400 with an explanatory error when types are invalid; locate the checks around
taskDesc, session_id, and project in the same handler (references: taskDesc,
promptArgs.task_description, promptArgs.session_id, promptArgs.project, and the
sdk.trigger("mem::search", ...) call) and replace the truthy-only guards with
typeof checks (e.g. typeof task_description !== "string") before calling
sdk.trigger or proceeding, so downstream code always receives strings.
In `@src/triggers/api.ts`:
- Around line 269-273: Request validation only checks that req.body.files is a
non-empty array but does not validate element types; update the handler to
reject non-string entries before calling mem::enrich by verifying that every
element of req.body.files (and req.body.terms if present) is typeof "string" and
return a 400 if any element fails validation. Locate the check around the if
block that inspects req.body?.sessionId and req.body?.files, add a predicate
that uses Array.prototype.every on req.body.files and req.body.terms (when
provided) to ensure all items are strings, and ensure mem::enrich is only called
when these validations pass.
---
Nitpick comments:
In `@src/index.ts`:
- Line 60: Centralize the hardcoded startup metadata by introducing a single
exported constant (e.g., VERSION or STARTUP_METADATA) and use it wherever the
version/count strings are printed instead of repeating literals; update the
console.log call that currently prints `[agentmemory] Starting worker v0.4.0...`
and the other occurrences that build version/count strings to reference this new
constant (search for the current literal string and the identifiers around the
other prints) so all startup/version text is produced from the same source of
truth.
In `@src/mcp/server.ts`:
- Around line 470-474: Normalize and clamp the minConfidence value before
calling sdk.trigger("mem::get-related"): validate that args.minConfidence is a
finite number and clamp it into the [0,1] range (falling back to 0 when
missing/invalid), and similarly ensure args.maxHops is a finite integer
(defaulting to 2) so mem::get-related always receives well-formed parameters;
update the invocation site that constructs the payload for sdk.trigger in
server.ts (the block using memoryId, maxHops, minConfidence) to perform these
checks and normalization.
In `@test/mcp-prompts.test.ts`:
- Around line 183-205: Add a new test case alongside the existing ones that
calls sdk.getFunction("mcp::prompts::get") via makeReq but supplies a non-string
argument (e.g., arguments: { task_description: 42 }) and assert the response
status_code is 400; locate the existing tests using
sdk.getFunction("mcp::prompts::get") / makeReq in this file and mirror their
structure and assertions to validate runtime payload type checking for prompt
inputs.
In `@test/mcp-resources.test.ts`:
- Around line 254-273: Add a new regression test next to the existing
decodeURIComponent case that calls sdk.getFunction("mcp::resources::read") with
a request built via makeReq whose uri contains malformed percent-encoding (e.g.
"agentmemory://project/bad%E0encoding" or a trailing "%"), and assert the
function returns the expected client error (HTTP 400 or appropriate status_code)
and an informative error body; reuse sdk.overrideTrigger setup used in the valid
test and ensure the new it(...) block verifies the server rejects malformed
percent-encodings rather than throwing or returning 200.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
package.jsonplugin/scripts/pre-tool-use.mjssrc/functions/enrich.tssrc/functions/relations.tssrc/hooks/pre-tool-use.tssrc/index.tssrc/mcp/server.tssrc/triggers/api.tssrc/types.tstest/confidence.test.tstest/enrich.test.tstest/mcp-prompts.test.tstest/mcp-resources.test.ts
…ence scoring - enrich.ts: Sort bug memories by recency before slicing top 3; add escapeXml() for narratives and bug content injected into XML tags - relations.ts: Use filter+max instead of find() for matchingRelation to pick highest confidence when multiple edges exist; normalize minConfidence with Number.isFinite + clamp to [0,1] - mcp/server.ts: Guard decodeURIComponent with try/catch returning 400 on malformed percent-encoding; use typeof checks for prompt args instead of truthy checks; normalize minConfidence/maxHops at MCP layer - api.ts: Validate files[] and terms[] element types are strings before calling mem::enrich - Tests: Add malformed URI and non-string prompt arg test cases (173 total)
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/functions/enrich.ts (1)
115-120: Consider truncating at safe boundaries to avoid malformed output.The current truncation slices at an arbitrary character position, which could cut mid-tag (e.g.,
<agentmemory-relevant-con) or mid-escape sequence (e.g.,&am). While thetruncatedflag signals this, downstream consumers might still attempt to parse the XML blocks.A safer approach would be to truncate before the last complete XML block or at a newline boundary.
💡 Optional improvement
let context = parts.join("\n\n"); let truncated = false; if (context.length > MAX_CONTEXT_LENGTH) { - context = context.slice(0, MAX_CONTEXT_LENGTH); + // Truncate at last newline within limit to avoid mid-tag cuts + const truncatedRaw = context.slice(0, MAX_CONTEXT_LENGTH); + const lastNewline = truncatedRaw.lastIndexOf("\n"); + context = lastNewline > 0 ? truncatedRaw.slice(0, lastNewline) : truncatedRaw; truncated = true; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/functions/enrich.ts` around lines 115 - 120, The current arbitrary slicing of context when exceeding MAX_CONTEXT_LENGTH (variables: context, parts, truncated) can cut XML or escape sequences mid-token; instead, after taking the initial slice up to MAX_CONTEXT_LENGTH, search backward in that slice for a safe boundary (e.g., the last complete XML closing tag like "</agentmemory-relevant>" or, if not present, the last double-newline or single newline) and truncate there; if no safe boundary is found fall back to the original slice but ensure truncated = true; update the block around context = parts.join(...) to implement this boundary-aware truncation and keep the truncated flag semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/functions/enrich.ts`:
- Around line 115-120: The current arbitrary slicing of context when exceeding
MAX_CONTEXT_LENGTH (variables: context, parts, truncated) can cut XML or escape
sequences mid-token; instead, after taking the initial slice up to
MAX_CONTEXT_LENGTH, search backward in that slice for a safe boundary (e.g., the
last complete XML closing tag like "</agentmemory-relevant>" or, if not present,
the last double-newline or single newline) and truncate there; if no safe
boundary is found fall back to the original slice but ensure truncated = true;
update the block around context = parts.join(...) to implement this
boundary-aware truncation and keep the truncated flag semantics.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/functions/enrich.tssrc/functions/relations.tssrc/mcp/server.tssrc/triggers/api.tstest/mcp-prompts.test.tstest/mcp-resources.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- test/mcp-prompts.test.ts
Summary
Closes 4 feature gaps identified from GitNexus competitive analysis:
agentmemory://status,agentmemory://project/{name}/profile,agentmemory://project/{name}/recent,agentmemory://memories/latestrecall_context(task_description),session_handoff(session_id),detect_patterns(project?)minConfidencefilter + confidence-desc sorting onmem::get-relatedmem::enrichaggregates file context + relevant observations + bug memories into<agentmemory-relevant-context>/<agentmemory-past-errors>XML blocks. PreToolUse hook now calls/agentmemory/enrichwith search term extraction from Grep/Glob patternsFiles changed (13)
src/types.ts—confidence?: numberon MemoryRelationsrc/functions/relations.ts— confidence scoring + minConfidence filtersrc/functions/enrich.ts— new enrichment functionsrc/mcp/server.ts— 4 resources + 3 prompts endpointssrc/triggers/api.ts—POST /agentmemory/enrich+ version bumpsrc/hooks/pre-tool-use.ts— switch to enrich endpointsrc/index.ts— wire up enrich, version bumppackage.json— 0.3.0 → 0.4.0Test plan
npm test— 171 tests passing (144 existing + 27 new)npm run build— clean build, no TS errorscurl GET /agentmemory/mcp/resources→ 4 resources listedcurl POST /agentmemory/mcp/resources/readwithagentmemory://status→ JSON countscurl POST /agentmemory/mcp/prompts/getwith recall_context → messages arraycurl POST /agentmemory/enrich→ enriched context stringSummary by CodeRabbit
New Features
Tests
Chores