Skip to content

Add optional external_id metadata passthrough for memory retrieval results#583

Open
XelHaku wants to merge 1 commit into
rohitg00:mainfrom
XelHaku:feature/stable-external-memory-ids
Open

Add optional external_id metadata passthrough for memory retrieval results#583
XelHaku wants to merge 1 commit into
rohitg00:mainfrom
XelHaku:feature/stable-external-memory-ids

Conversation

@XelHaku
Copy link
Copy Markdown

@XelHaku XelHaku commented May 20, 2026

Hi! This PR adds optional external_id and metadata passthrough support for saved memories and retrieval results.

The motivation is to make integrations easier when memories originate from another system or dataset and need stable source identifiers. This helps with import/export workflows, deduplication, observability, debugging, and deterministic evaluation where callers need to know exactly which source memory was retrieved.

The change is additive: existing callers can ignore the new fields, and default behavior should remain unchanged.

Included:

  • optional external_id on memory save/create
  • optional metadata passthrough
  • retrieval/search results include the stored external_id and metadata
  • regression test for duplicate content with distinct external IDs
  • docs/example update

Validation run locally:

  • npm test -- test/remember-external-metadata.test.ts
  • npm test
  • npm run build

Summary by CodeRabbit

  • New Features

    • Added optional external_id and metadata fields when saving memories. These fields are persisted and returned in search results, supporting deduplication, observability, and deterministic evaluation.
  • Documentation

    • Updated documentation with examples of the new optional fields in memory save and retrieval workflows.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

@XelHaku is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR adds optional external_id and metadata fields throughout the memory save-and-search pipeline. Changes include type definitions for the new fields, input validation in HTTP and MCP handlers, persistence in memory objects, output inclusion in search results, schema updates for tool contracts, and comprehensive tests demonstrating end-to-end passthrough.

Changes

External ID and Metadata Passthrough

Layer / File(s) Summary
Type Contracts
src/types.ts
RawObservation, Memory, and CompactSearchResult interfaces each gain optional external_id: string and metadata: Record<string, unknown> fields.
Tool Schema and Request Contracts
src/mcp/tools-registry.ts, src/triggers/api.ts
MCP memory_save tool schema and HTTP remember request type extended to accept optional external_id and metadata fields.
HTTP API Remember Handler
src/triggers/api.ts
HTTP remember handler normalizes request external_id and metadata via helper functions (asMetadataRecord), constructs explicit payload, and triggers mem::remember with validated fields.
MCP Tool Handlers
src/mcp/server.ts, src/mcp/standalone.ts
MCP server and standalone implementations extract external_id and metadata from tool arguments, normalize via helpers, and forward to mem::remember or remote endpoint; standalone handler also persists fields to local KV.
Remember Function Input Handling
src/functions/remember.ts
mem::remember validates optional external_id as string and metadata as plain object via new isMetadataRecord guard, then assigns validated values to created Memory object.
Search Results and Observation Mapping
src/state/memory-utils.ts, src/functions/search.ts
memoryToObservation includes fields in CompressedObservation; mem::search includes external_id and metadata in compact and narrative result formats.
Tests and Documentation
README.md, CHANGELOG.md, test/remember-external-metadata.test.ts
Test suite validates external_id deduplication and metadata passthrough end-to-end; README example demonstrates field usage; CHANGELOG documents new feature.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit's tale of fields so fine,
Where external_id and metadata align—
Save your memories, search them back,
With extra data on the track!
From HTTP to MCP so true,
The whole flow carries them through. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% 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 accurately summarizes the main change: adding optional external_id and metadata passthrough for memory retrieval results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feature/stable-external-memory-ids

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

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

Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (1)
test/remember-external-metadata.test.ts (1)

58-120: ⚡ Quick win

Add compact and narrative format assertions for passthrough fields.

These tests validate passthrough on default/full results, but the PR also changes format: "compact" and format: "narrative" mapping. Add targeted assertions there to prevent silent regressions in the exact changed surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/remember-external-metadata.test.ts` around lines 58 - 120, The tests
"keeps duplicate-content memories distinguishable by external_id in search
results" and "returns saved metadata unchanged in search results" need
additional assertions for the new format mapping; after calling sdk.trigger for
function_id "mem::search" add explicit checks that when payload.format is
"compact" and when payload.format is "narrative" the returned
result.results[*].observation still contains the exact external_id and metadata
passthrough (use the same external_id "locomo-conv-1-turn-3" and metadata
object), and for the duplicate-content case assert that both external_ids
("source-a","source-b") are present in compact and narrative responses as well;
locate these tests and update the search-trigger calls to include payload.format
= "compact" and payload.format = "narrative" and add corresponding expect(...)
assertions for observation.external_id and observation.metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/remember-external-metadata.test.ts`:
- Around line 37-51: Current test defines a local mockSdk (with functions Map,
registerFunction/registerTrigger/trigger) instead of using the repository-wide
iii-sdk mocking pattern; replace this local mock with a vi.mock("iii-sdk") mock
in this test that exposes the same API used across tests (a mocked sdk with
trigger, and a kv object implementing get/set/list) and ensure the mocked
trigger honors registration semantics similar to the existing
registerFunction/registerTrigger/trigger behavior so existing calls relying on
functions Map continue to work (e.g., handlers registered via registerFunction
are invoked by mock trigger and special-case "mem::cascade-update" remains
supported).

---

Nitpick comments:
In `@test/remember-external-metadata.test.ts`:
- Around line 58-120: The tests "keeps duplicate-content memories
distinguishable by external_id in search results" and "returns saved metadata
unchanged in search results" need additional assertions for the new format
mapping; after calling sdk.trigger for function_id "mem::search" add explicit
checks that when payload.format is "compact" and when payload.format is
"narrative" the returned result.results[*].observation still contains the exact
external_id and metadata passthrough (use the same external_id
"locomo-conv-1-turn-3" and metadata object), and for the duplicate-content case
assert that both external_ids ("source-a","source-b") are present in compact and
narrative responses as well; locate these tests and update the search-trigger
calls to include payload.format = "compact" and payload.format = "narrative" and
add corresponding expect(...) assertions for observation.external_id and
observation.metadata.
🪄 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: 5f11b521-2f36-470c-a37d-aaf4c5aa9b63

📥 Commits

Reviewing files that changed from the base of the PR and between c14bdc5 and 9b18a80.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • README.md
  • src/functions/remember.ts
  • src/functions/search.ts
  • src/mcp/server.ts
  • src/mcp/standalone.ts
  • src/mcp/tools-registry.ts
  • src/state/memory-utils.ts
  • src/triggers/api.ts
  • src/types.ts
  • test/remember-external-metadata.test.ts

Comment on lines +37 to +51
function mockSdk() {
const functions = new Map<string, Function>();
return {
registerFunction: (id: string, handler: Function) => {
functions.set(id, handler);
},
registerTrigger: () => {},
trigger: async (input: { function_id: string; payload: unknown }) => {
const fn = functions.get(input.function_id);
if (!fn && input.function_id === "mem::cascade-update") return undefined;
if (!fn) throw new Error(`unknown fn ${input.function_id}`);
return fn(input.payload);
},
};
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Align this test harness with the repository’s iii-sdk mocking contract.

This file builds a custom SDK/KV harness instead of mocking iii-sdk via vi.mock("iii-sdk"), which diverges from the enforced test pattern and makes this suite inconsistent with the rest of the test surface.

As per coding guidelines, test/**/*.test.{ts,tsx}: Mock iii-sdk in tests using vi.mock("iii-sdk") with mock sdk.trigger, kv.get/set/list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/remember-external-metadata.test.ts` around lines 37 - 51, Current test
defines a local mockSdk (with functions Map,
registerFunction/registerTrigger/trigger) instead of using the repository-wide
iii-sdk mocking pattern; replace this local mock with a vi.mock("iii-sdk") mock
in this test that exposes the same API used across tests (a mocked sdk with
trigger, and a kv object implementing get/set/list) and ensure the mocked
trigger honors registration semantics similar to the existing
registerFunction/registerTrigger/trigger behavior so existing calls relying on
functions Map continue to work (e.g., handlers registered via registerFunction
are invoked by mock trigger and special-case "mem::cascade-update" remains
supported).

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.

1 participant