chore(release): v0.4.0#22
Conversation
Two new MCP tools that close the post-hoc curator gap, plus a
documentation fix on the `openmemory_remember` idempotency contract.
* `openmemory_add_relation` attaches a relation between two existing
entities resolved by `(name, type)`. Previously the only way to add
an edge was through `openmemory_remember`, which forced callers to
write a dummy observation just to attach a relation. `supersedes`
/ `clarifies` / `depends_on` curator flows now have a first-class
surface. Missing entities surface as typed `-32004` errors rather
than being silently created.
* `openmemory_promote_observation` moves an observation between
`memory_tier` values (`episodic` <-> `semantic` <-> `procedural`)
without rewriting content. Enables the episodic-on-write,
promote-on-consolidation lifecycle the curator policy assumes.
Unknown or tombstoned ids return `{ modified: false }` rather than
erroring.
* `openmemory_remember`'s tool description now spells out the
append-only contract: observations are NEVER deduplicated at write
time; run `openmemory_consolidate` for periodic dedup, or
`openmemory_recall` the proposed title first to skip the write on a
high-scoring hit. Relations follow the same contract.
Workspace test count: 591 pass / 0 fail. The `MemoryStore::add_relation`
and `MemoryStore::set_observation_memory_tier` methods back the two
new tools; their input validation rejects empty relation types,
self-loop edges, and missing observation ids with typed errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning
|
| Layer / File(s) | Summary |
|---|---|
Version bump and release documentation Cargo.toml, CHANGELOG.md |
Workspace version incremented to 0.4.0 with all internal dependencies updated; changelog documents the two new tools, their request/response contracts, error handling for missing entities, and clarifies openmemory_remember's append-only semantics. |
MemoryStore relation and tier management crates/openmemory-graph/src/store.rs |
Two public methods added to MemoryStore: add_relation validates both entity endpoints exist, inserts with optional weight defaulting to 1.0, and returns the relation id; set_observation_memory_tier updates active observations and returns a boolean indicating whether the update affected any row. Comprehensive tests verify success paths, entity-not-found errors, self-loop rejection, and missing observation handling. |
MCP tool implementations and handlers crates/openmemory-mcp/src/tools/memory.rs |
Two new tool types registered: OpenMemoryAddRelationTool resolves both entity endpoints by name and type, validates relation type, returns typed errors when entities are missing, and invokes the store method; OpenMemoryPromoteObservationTool converts and applies the tier parameter and returns modified status. Module documentation and openmemory_remember description expanded. Tests cover success paths, typed error responses, empty relation type rejection, and unknown observation ID handling. |
Tool registry and e2e test updates crates/openmemory-mcp/src/tools/mod.rs, crates/openmemory-cli/tests/mcp_e2e.rs |
Tool registry count assertion updated from 11 to 13 total tools; instructions and memory-tools coverage tests extended to include the two new tool names; e2e test tool list expanded to verify both new tools are present. |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
🐰 Relations bloom between two nodes,
Observations climb through memory roads,
Two tools for the graph, 0.4 ordained,
Each tested with care, no edge unstained.
The store grows wise, the MCP speaks true!
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title 'chore(release): v0.4.0' accurately reflects the main change—a version bump release that includes new MCP tools, API updates, and documentation changes. |
| Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. |
| 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 docstrings
- Create stacked PR
- Commit on current branch
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
chore/release-v0.4.0
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/openmemory-cli/tests/mcp_e2e.rs (1)
163-164:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate the stale tool-count comment.
The comment says 11 tools, but the test now validates 13.
🤖 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 `@crates/openmemory-cli/tests/mcp_e2e.rs` around lines 163 - 164, The inline comment above the tools list check is stale ("verify all 11 tools present") but the test actually validates 13 tools; update that comment to reflect 13 tools so it matches the assertion in the test (the comment immediately preceding the call to server.send("tools/list", json!({})) in the mcp_e2e.rs test).
🤖 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 `@CHANGELOG.md`:
- Around line 12-44: The CHANGELOG.md lacks footer reference links for the new
[0.4.0] release header so release comparison links won't resolve; add/update the
footer reference links section to include a link for [0.4.0] (pointing to the
correct compare URL or tag) and ensure the Unreleased baseline reference is
updated if needed so headers like [0.4.0] resolve to proper compare URLs; locate
the [0.4.0] header in the changelog and add the matching reference entry at the
bottom of the file alongside existing release reference links.
In `@crates/openmemory-graph/src/store.rs`:
- Around line 505-513: The current call to tx.query_row(...).unwrap_or(0) masks
SQL/query failures as a missing entity; change it to propagate DB errors instead
of treating them as count==0. Replace the unwrap_or(0) on the tx.query_row call
with proper Result handling (use ? or map_err) so SQL errors are converted into
a MemoryError storage/DB variant (or add one if missing) and only return
MemoryError::EntityNotFound(id.to_string()) when the query succeeds and the
count is actually 0; reference tx, query_row, and MemoryError::EntityNotFound
when making the change.
In `@crates/openmemory-mcp/src/tools/memory.rs`:
- Around line 654-663: The from_entity and to_entity string fields on the
relation params (and any duplicate validation blocks around lines 717–733) must
be validated to reject empty names early; add explicit checks for
from_entity.is_empty() and to_entity.is_empty() in the request/param validation
path (where the Relation param is handled) and return a clear invalid-parameter
error instead of allowing the empty string to fall through to not-found
behavior; update any functions that consume these fields (search/resolve/create
relation flows) to perform this check and short-circuit with an appropriate
error result referencing the fields (from_entity, to_entity) and keep existing
behavior for from_entity_type / EntityTypeParam unchanged.
- Around line 670-673: The weight field (pub weight: Option<f32>) must be
validated for finiteness and range [0.0, 1.0] before any write; update the code
paths that persist edges (the write/insert handlers referenced around the second
occurrence at the 744–747 area) to check if weight.is_some() => ensure
f32::is_finite() and 0.0 <= weight <= 1.0 and otherwise return a clear error
(bad request/validation error) rather than accepting or silently coercing the
value; also apply the same validation when deserializing/normalizing input for
the struct that declares weight so invalid values are rejected early.
---
Outside diff comments:
In `@crates/openmemory-cli/tests/mcp_e2e.rs`:
- Around line 163-164: The inline comment above the tools list check is stale
("verify all 11 tools present") but the test actually validates 13 tools; update
that comment to reflect 13 tools so it matches the assertion in the test (the
comment immediately preceding the call to server.send("tools/list", json!({}))
in the mcp_e2e.rs test).
🪄 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 Plus
Run ID: 24897ded-0758-4883-9a70-2a714cfb8317
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
CHANGELOG.mdCargo.tomlcrates/openmemory-cli/tests/mcp_e2e.rscrates/openmemory-graph/src/store.rscrates/openmemory-mcp/src/tools/memory.rscrates/openmemory-mcp/src/tools/mod.rs
| ## [0.4.0] - 2026-05-17 | ||
|
|
||
| ### Added | ||
|
|
||
| - **`openmemory_add_relation` MCP tool.** Attach a relation | ||
| (`supersedes`, `clarifies`, `depends_on`, …) between two existing | ||
| entities resolved by `(name, type)`. Closes the post-hoc curator | ||
| gap where the only way to add an edge was through | ||
| `openmemory_remember`, which forced callers to write a dummy | ||
| observation just to attach a relation. Both entities must already | ||
| exist; missing entities surface as a typed `-32004` error rather | ||
| than being silently created. New `MemoryStore::add_relation` | ||
| method backs the tool. | ||
| - **`openmemory_promote_observation` MCP tool.** Move an observation | ||
| between `memory_tier` values (`episodic` ↔ `semantic` ↔ | ||
| `procedural`) without rewriting its content. Use after a fact has | ||
| survived consolidation, been accessed repeatedly, or earned | ||
| promotion out of short-term storage. Returns `{ modified: bool, | ||
| memory_tier: <new> }`; unknown or tombstoned observation ids | ||
| return `{ modified: false }` rather than erroring. New | ||
| `MemoryStore::set_observation_memory_tier` method backs the tool. | ||
|
|
||
| ### Changed | ||
|
|
||
| - **`openmemory_remember` description now spells out its idempotency | ||
| contract.** Observations are always **appended**, not deduplicated | ||
| at write time. Calling twice with the same `entity` + identical | ||
| `content` creates two parallel observation rows. Use | ||
| `openmemory_consolidate` for periodic dedup, or | ||
| `openmemory_recall` the proposed title and skip the write on a | ||
| high-scoring hit. Relations follow the same append-only contract. | ||
|
|
||
| ## [0.3.3] - 2026-05-17 |
There was a problem hiding this comment.
Add/update changelog reference links for the new release.
The new [0.4.0] entry should be paired with footer reference links; otherwise release headers won’t resolve to compare URLs and Unreleased remains anchored to an older baseline.
Suggested footer update
-[Unreleased]: https://github.com/raymondj99/openmemory/compare/v0.2.1...HEAD
+[Unreleased]: https://github.com/raymondj99/openmemory/compare/v0.4.0...HEAD
+[0.4.0]: https://github.com/raymondj99/openmemory/compare/v0.3.3...v0.4.0
+[0.3.3]: https://github.com/raymondj99/openmemory/compare/v0.3.2...v0.3.3
+[0.3.2]: https://github.com/raymondj99/openmemory/compare/v0.3.1...v0.3.2
+[0.3.1]: https://github.com/raymondj99/openmemory/compare/v0.3.0...v0.3.1
+[0.3.0]: https://github.com/raymondj99/openmemory/compare/v0.2.1...v0.3.0
[0.2.1]: https://github.com/raymondj99/openmemory/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/raymondj99/openmemory/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/raymondj99/openmemory/releases/tag/v0.1.0🤖 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 `@CHANGELOG.md` around lines 12 - 44, The CHANGELOG.md lacks footer reference
links for the new [0.4.0] release header so release comparison links won't
resolve; add/update the footer reference links section to include a link for
[0.4.0] (pointing to the correct compare URL or tag) and ensure the Unreleased
baseline reference is updated if needed so headers like [0.4.0] resolve to
proper compare URLs; locate the [0.4.0] header in the changelog and add the
matching reference entry at the bottom of the file alongside existing release
reference links.
| let exists: i64 = tx | ||
| .query_row( | ||
| "SELECT COUNT(1) FROM entities WHERE id = ?1", | ||
| params![id], | ||
| |row| row.get(0), | ||
| ) | ||
| .unwrap_or(0); | ||
| if exists == 0 { | ||
| return Err(MemoryError::EntityNotFound(id.to_string())); |
There was a problem hiding this comment.
Propagate SQL failures during entity existence checks.
unwrap_or(0) masks database/query errors as “missing entity”, which can hide real storage faults and return the wrong error category.
Proposed fix
- let exists: i64 = tx
+ let exists: i64 = tx
.query_row(
"SELECT COUNT(1) FROM entities WHERE id = ?1",
params![id],
|row| row.get(0),
)
- .unwrap_or(0);
+ ?;📝 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.
| let exists: i64 = tx | |
| .query_row( | |
| "SELECT COUNT(1) FROM entities WHERE id = ?1", | |
| params![id], | |
| |row| row.get(0), | |
| ) | |
| .unwrap_or(0); | |
| if exists == 0 { | |
| return Err(MemoryError::EntityNotFound(id.to_string())); | |
| let exists: i64 = tx | |
| .query_row( | |
| "SELECT COUNT(1) FROM entities WHERE id = ?1", | |
| params![id], | |
| |row| row.get(0), | |
| ) | |
| ?; | |
| if exists == 0 { | |
| return Err(MemoryError::EntityNotFound(id.to_string())); |
🤖 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 `@crates/openmemory-graph/src/store.rs` around lines 505 - 513, The current
call to tx.query_row(...).unwrap_or(0) masks SQL/query failures as a missing
entity; change it to propagate DB errors instead of treating them as count==0.
Replace the unwrap_or(0) on the tx.query_row call with proper Result handling
(use ? or map_err) so SQL errors are converted into a MemoryError storage/DB
variant (or add one if missing) and only return
MemoryError::EntityNotFound(id.to_string()) when the query succeeds and the
count is actually 0; reference tx, query_row, and MemoryError::EntityNotFound
when making the change.
| /// Name of the entity the relation originates from. Must already | ||
| /// exist (use `openmemory_remember` to create it first). | ||
| pub from_entity: String, | ||
| /// Entity type of `from_entity`. Defaults to `concept` when | ||
| /// omitted, matching the resolution policy elsewhere in the API. | ||
| #[serde(default)] | ||
| pub from_entity_type: Option<EntityTypeParam>, | ||
| /// Name of the entity the relation points to. Must already exist. | ||
| pub to_entity: String, | ||
| /// Entity type of `to_entity`. Defaults to `concept`. |
There was a problem hiding this comment.
Reject empty endpoint names as invalid params.
from_entity / to_entity that are empty currently flow into not-found behavior; this should fail fast as input validation.
Proposed fix
let req: AddRelationInput = parse_args(args)?;
+ if req.from_entity.trim().is_empty() {
+ return Err(JsonRpcError::invalid_params(
+ "from_entity must not be empty",
+ ));
+ }
+ if req.to_entity.trim().is_empty() {
+ return Err(JsonRpcError::invalid_params(
+ "to_entity must not be empty",
+ ));
+ }
if req.relation_type.trim().is_empty() {
return Err(JsonRpcError::invalid_params(
"relation_type must not be empty",
));
}Also applies to: 717-733
🤖 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 `@crates/openmemory-mcp/src/tools/memory.rs` around lines 654 - 663, The
from_entity and to_entity string fields on the relation params (and any
duplicate validation blocks around lines 717–733) must be validated to reject
empty names early; add explicit checks for from_entity.is_empty() and
to_entity.is_empty() in the request/param validation path (where the Relation
param is handled) and return a clear invalid-parameter error instead of allowing
the empty string to fall through to not-found behavior; update any functions
that consume these fields (search/resolve/create relation flows) to perform this
check and short-circuit with an appropriate error result referencing the fields
(from_entity, to_entity) and keep existing behavior for from_entity_type /
EntityTypeParam unchanged.
| /// Edge weight in `[0, 1]`. Defaults to `1.0` when omitted. | ||
| #[serde(default)] | ||
| pub weight: Option<f32>, | ||
| /// Source tag for audit/dedup (e.g. `"curator"`, `"omdemos:..."`). |
There was a problem hiding this comment.
Enforce weight bounds and finiteness before write.
The tool description says weight is in [0, 1], but the handler currently accepts out-of-range and non-finite values.
Proposed fix
fn call(server: &OpenMemoryMcpServer, args: Value) -> Result<CallToolResult, JsonRpcError> {
let req: AddRelationInput = parse_args(args)?;
if req.relation_type.trim().is_empty() {
return Err(JsonRpcError::invalid_params(
"relation_type must not be empty",
));
}
+ if let Some(weight) = req.weight {
+ if !weight.is_finite() || !(0.0..=1.0).contains(&weight) {
+ return Err(JsonRpcError::invalid_params(
+ "weight must be a finite number in [0.0, 1.0]",
+ ));
+ }
+ }Also applies to: 744-747
🤖 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 `@crates/openmemory-mcp/src/tools/memory.rs` around lines 670 - 673, The weight
field (pub weight: Option<f32>) must be validated for finiteness and range [0.0,
1.0] before any write; update the code paths that persist edges (the
write/insert handlers referenced around the second occurrence at the 744–747
area) to check if weight.is_some() => ensure f32::is_finite() and 0.0 <= weight
<= 1.0 and otherwise return a clear error (bad request/validation error) rather
than accepting or silently coercing the value; also apply the same validation
when deserializing/normalizing input for the struct that declares weight so
invalid values are rejected early.
Summary
Two new MCP tools that close the post-hoc curator gap, plus a documentation fix.
openmemory_add_relationMCP tool +MemoryStore::add_relation. Attach a relation (supersedes,clarifies,depends_on, …) between two existing entities resolved by(name, type). Closes the gap where the only way to add an edge was throughopenmemory_remember, which forced callers to write a dummy observation just to attach a relation.openmemory_promote_observationMCP tool +MemoryStore::set_observation_memory_tier. Move an observation betweenmemory_tiervalues (episodic↔semantic↔procedural) without rewriting content. Enables the episodic-on-write, promote-on-consolidation lifecycle the curator policy assumes.openmemory_rememberdescription now spells out the append-only contract: observations are NEVER deduplicated at write time; runopenmemory_consolidatefor periodic dedup, oropenmemory_recallthe proposed title first to skip the write on a high-scoring hit.Branch rebased onto current main (includes the v0.3.3 hotfix), so this PR ships cleanly on top of
4fed3ff chore(release): v0.3.3 (#21).Test plan
add_relationandpromote_observationsmoke-tested through MCP🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes v0.4.0
New Features
openmemory_add_relationtool to attach typed relations between existing entities.openmemory_promote_observationtool to move observations between memory tiers (episodic, semantic, procedural).Changed
openmemory_rememberdocumentation to clarify append-only write contract and deduplication behavior.