Skip to content

chore(release): v0.4.0#22

Merged
raymondj99 merged 1 commit into
mainfrom
chore/release-v0.4.0
May 18, 2026
Merged

chore(release): v0.4.0#22
raymondj99 merged 1 commit into
mainfrom
chore/release-v0.4.0

Conversation

@raymondj99
Copy link
Copy Markdown
Owner

@raymondj99 raymondj99 commented May 18, 2026

Summary

Two new MCP tools that close the post-hoc curator gap, plus a documentation fix.

  • openmemory_add_relation MCP 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 through openmemory_remember, which forced callers to write a dummy observation just to attach a relation.
  • openmemory_promote_observation MCP tool + MemoryStore::set_observation_memory_tier. Move an observation between memory_tier values (episodicsemanticprocedural) without rewriting content. Enables the episodic-on-write, promote-on-consolidation lifecycle the curator policy assumes.
  • openmemory_remember 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.

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

  • 591 workspace tests pass / 0 fail
  • clippy + rustfmt clean
  • CI green on this PR
  • Tag, release workflow builds tarballs, demo VM uses the published v0.4.0 binary against the rhine-meuse demo with hybrid + vector mode, with add_relation and promote_observation smoke-tested through MCP

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes v0.4.0

  • New Features

    • Added openmemory_add_relation tool to attach typed relations between existing entities.
    • Added openmemory_promote_observation tool to move observations between memory tiers (episodic, semantic, procedural).
  • Changed

    • Updated openmemory_remember documentation to clarify append-only write contract and deduplication behavior.

Review Change Stack

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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: String must contain at most 250 character(s) at "tone_instructions"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

This PR introduces version 0.4.0 with two new MCP tools: openmemory_add_relation and openmemory_promote_observation. The store layer is extended with backing methods, tools are implemented with validation and error handling, and all tests are updated to reflect the new tool registrations.

Changes

Release 0.4.0 and new MCP tool suite

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.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 18, 2026

Merging this PR will not alter performance

✅ 19 untouched benchmarks


Comparing chore/release-v0.4.0 (e84d506) with main (4fed3ff)

Open in CodSpeed

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: 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 win

Update 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4fed3ff and e84d506.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • CHANGELOG.md
  • Cargo.toml
  • crates/openmemory-cli/tests/mcp_e2e.rs
  • crates/openmemory-graph/src/store.rs
  • crates/openmemory-mcp/src/tools/memory.rs
  • crates/openmemory-mcp/src/tools/mod.rs

Comment thread CHANGELOG.md
Comment on lines +12 to 44
## [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
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 | ⚡ Quick win

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.

Comment on lines +505 to +513
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()));
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 | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

Comment on lines +654 to +663
/// 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`.
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 | ⚡ Quick win

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.

Comment on lines +670 to +673
/// 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:..."`).
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 | 🟠 Major | ⚡ Quick win

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.

@raymondj99 raymondj99 merged commit 76884c9 into main May 18, 2026
12 of 14 checks passed
@raymondj99 raymondj99 deleted the chore/release-v0.4.0 branch May 18, 2026 05:06
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