Skip to content

fix(agent-harness): state memory read→dedupe→write→update contract to the model (#4116)#4476

Merged
senamakel merged 4 commits into
tinyhumansai:mainfrom
senamakel:fix/GH-4116-memory-protocol-scaffold
Jul 4, 2026
Merged

fix(agent-harness): state memory read→dedupe→write→update contract to the model (#4116)#4476
senamakel merged 4 commits into
tinyhumansai:mainfrom
senamakel:fix/GH-4116-memory-protocol-scaffold

Conversation

@senamakel

@senamakel senamakel commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

  • State the memory read → dedupe → write → update-index contract up front in the memory_store and memory_forget tool descriptions, so duplicate entries are prevented rather than only corrected after a duplicate write lands.
  • Lock it in with a test: memory_store's name_and_schema asserts the description carries the contract, so the instruction can't silently regress.

Problem

Issue #4116: agents skip reading the index for dedupe before writing memory, and skip update_memory_md afterward, so MEMORY.md drifts and duplicate entries accumulate.

The prior rework (#4444) added the enforcement state machine + a per-write after_tool correction. But the issue's core complaint — "MEMORY.md not read for dedupe" — was only ever addressed reactively: the protocol was never actually stated to the model. memory_protocol.rs's doc-comment claims "agents are instructed to follow…", yet there is zero instruction text in the live prompt describing the sequence. So dedupe was only nudged after a duplicate write already happened — never prevented.

Solution

Add the contract to the two durable-write tools' description() — model-visible up front, per-tool, with no user-facing output pollution:

  • memory_store: recall existing memory (e.g. memory_recall) to check for a near-duplicate before storing; call update_memory_md after storing.
  • memory_forget: call update_memory_md after removing an entry.

Combined with the merged #4444 enforcement (which corrects mid-run drift) and the existing run-end tracing::warn!, this closes the issue's "read-index → dedupe → write → update-index" scaffold + post-step-check requirement.

Scope note: an earlier draft of this PR also surfaced the run-end stale-index case as model-visible text. That was dropped — AgentRun::text() reads final_response while the persisted history/conversation read run.messages, so a consistent surfacing needs to mutate both crate-internal structures (flagged by the codex bot as a text/transcript divergence). The run-end case stays a tracing::warn! (the issue's "correct or warn"); a consistent model-visible surfacing is left as a follow-up.

Submission Checklist

  • Tests added or updated — openhuman::memory::tools::store::tests::name_and_schema asserts the description states the memory_recall + update_memory_md contract (guards against silent regression).
  • Diff coverage ≥ 80% — the only executable changed line is the test assertion (covered); the description edits are non-executable string literals. Confirmed green in CI's Rust Core Coverage run.
  • Coverage matrix updated — N/A: behaviour-only change to an existing feature (memory-protocol enforcement).
  • All affected feature IDs listed under ## Related — N/A (no new feature row).
  • No new external network dependencies introduced.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: agent tool-description text only, no release-cut UI surface.
  • Linked issue closed via Closes #NNN in ## Related.

Impact

  • Desktop/CLI agent harness only. No schema, migration, or network change; behaviour is limited to the two memory write tools' advertised descriptions.
  • Agents now see the dedupe/index-sync contract at the moment they choose to write memory.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/GH-4116-memory-protocol-scaffold
  • Commit SHA: see head

Validation Run

  • pnpm --filter openhuman-app format:check — N/A (no frontend changes; pre-existing repo JS format warnings are unrelated)
  • pnpm typecheck — N/A (no TS changes)
  • Focused tests: openhuman::memory::tools::store — passed in CI's Rust Core Coverage run (store::tests::name_and_schema ... ok)
  • Rust fmt/check (if changed): rustfmt --edition 2021 --check clean on all touched files
  • Tauri fmt/check (if changed): N/A

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: the read→dedupe→write→update-index contract is now stated to the model up front in the memory write tools' descriptions.
  • User-visible effect: none directly; agents are steered to recall-before-write and reconcile the index after, reducing duplicate memory entries.

Parity Contract

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this one

Summary by CodeRabbit

  • Documentation

    • Clarified the guidance for memory storage and deletion, including the required steps to keep the memory index in sync after changes.
    • Added clearer usage expectations for the memory workflow so the sequence is easier to follow.
  • Tests

    • Updated checks to ensure the memory tool guidance includes the full read, write, and sync process.

@senamakel senamakel requested a review from a team July 3, 2026 21:18
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c71ac383-a16e-4b13-8da5-24dbca58efc3

📥 Commits

Reviewing files that changed from the base of the PR and between 6ea4d86 and a6e58a7.

📒 Files selected for processing (3)
  • src/openhuman/memory/tools/forget.rs
  • src/openhuman/memory/tools/store.rs
  • tests/config_auth_app_state_connectivity_e2e.rs
📝 Walkthrough

Walkthrough

Updates the description strings of the memory_forget and memory_store tools to explicitly state the memory protocol contract: memory_recall must be used to check for duplicates before storing, and update_memory_md must be called after storing or deleting to keep MEMORY.md synchronized. A test is updated to assert this wording.

Changes

Memory Protocol Tool Description Updates

Layer / File(s) Summary
Tool description contract updates
src/openhuman/memory/tools/forget.rs, src/openhuman/memory/tools/store.rs
description() text for both tools now states the required protocol: dedupe via memory_recall before storing, and update_memory_md after storing or deleting, to keep MEMORY.md in sync; the store tool's test asserts both terms appear in the description.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Possibly related issues

Possibly related PRs

  • tinyhumansai/openhuman#4444: Documents the same memory protocol contract (memory_recall/dedupe before storing, update_memory_md after writes/forget) in tool descriptions, related to enforcement via middleware.

Suggested labels: agent

Poem

A rabbit reads the index first,
before it stores, dedupes the worst,
then updates MEMORY.md with care,
so no stale note is lurking there. 🐇📝

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The changes update tool descriptions and a test, but do not show the required harness enforcement or warning/correction behavior for unreconciled writes. Add the middleware enforcement path for read-before-write and update-after-write checks, plus regression tests for warning or correction when the protocol is skipped.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changes stay focused on the memory protocol contract and its related test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: making the memory read→dedupe→write→update contract explicit to the model.

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

@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug labels Jul 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cae7444e2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openhuman/tinyagents/mod.rs Outdated
"[tinyagents] appending memory-protocol run-end drift note to turn text"
);
text.push_str("\n\n");
text.push_str(&note);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist the memory drift note with the final message

When this drift slot is set, the warning is appended only to the local text. The returned history/conversation are still derived from run.messages, and callers such as the chat/session path persist outcome.conversation into the transcript/history before returning outcome.text, so a normal turn that ends with an unreconciled memory write can show the note in the returned reply while storing the original assistant message. On the next turn the model will not see the stale-index warning and the persisted transcript can disagree with what was returned; fold the note into the final assistant message/conversation before conversion/persistence.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — resolved by removing the run-end surfacing from this PR. AgentRun::text() reads final_response while history/conversation derive from run.messages, so appending only to the local text diverged from the persisted transcript exactly as you noted. A consistent model-visible surfacing needs to mutate both final_response and the final assistant message in messages; I've scoped this PR down to the up-front prompt-scaffold (the genuinely-missing half of #4116) and left the run-end model-visible surfacing as a follow-up. The run-end case keeps its tracing::warn! for now.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
src/openhuman/memory/tools/forget.rs (1)

27-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a description-contract test for memory_forget, mirroring store.rs.

Updated the name_and_schema test to validate that tool.description() includes both memory_recall and update_memory_md, with a failure message describing the required read→dedupe→write→update contract. forget.rs's own tests weren't updated with an equivalent assertion that the description mentions update_memory_md, leaving the new contract wording unguarded against future regressions here.

✅ Suggested test addition
     #[test]
     fn name_and_schema() {
         let (_tmp, mem) = test_mem();
         let tool = MemoryForgetTool::new(mem, test_security());
         assert_eq!(tool.name(), "memory_forget");
+        let desc = tool.description();
+        assert!(
+            desc.contains("update_memory_md"),
+            "memory_forget description must instruct calling update_memory_md: {desc}"
+        );
     }
🤖 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 `@src/openhuman/memory/tools/forget.rs` around lines 27 - 31, Add a
description-contract test for `MemoryForget` in the same style as `store.rs` so
the tool’s `description()` is explicitly validated. Update the `memory_forget`
tests to assert that `MemoryForget::description` includes `update_memory_md`
(and the full contract wording if applicable), using the existing
`name_and_schema`/description test pattern to guard against regressions in
`MemoryForget`.
🤖 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.

Nitpick comments:
In `@src/openhuman/memory/tools/forget.rs`:
- Around line 27-31: Add a description-contract test for `MemoryForget` in the
same style as `store.rs` so the tool’s `description()` is explicitly validated.
Update the `memory_forget` tests to assert that `MemoryForget::description`
includes `update_memory_md` (and the full contract wording if applicable), using
the existing `name_and_schema`/description test pattern to guard against
regressions in `MemoryForget`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c47f477c-eecc-4397-a09f-f118262219a0

📥 Commits

Reviewing files that changed from the base of the PR and between 6faeeaa and cae7444.

📒 Files selected for processing (4)
  • src/openhuman/memory/tools/forget.rs
  • src/openhuman/memory/tools/store.rs
  • src/openhuman/tinyagents/middleware.rs
  • src/openhuman/tinyagents/mod.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 3, 2026
…t to the model (tinyhumansai#4116)

The memory-protocol enforcement middleware (tinyhumansai#4444) corrects violations
*after* they happen (a duplicate write, a drifted index), but the
read→dedupe→write→update-index contract was never actually stated to the
model in the live prompt — `memory_protocol.rs`'s "agents are instructed
to follow…" doc was aspirational; no prompt text described the sequence.
So dedupe was only ever nudged *after* a duplicate write landed, never
prevented.

State it up front in the two durable-write tools' descriptions:
- `memory_store`: recall existing memory (e.g. `memory_recall`) to check
  for a near-duplicate before storing; call `update_memory_md` after.
- `memory_forget`: call `update_memory_md` after removing an entry.

Test: `memory_store`'s `name_and_schema` asserts the description carries
the contract, so the up-front instruction can't silently regress.

The run-end stale-index case remains a `tracing::warn!` (the issue's
"correct or warn"); surfacing it model-visibly needs consistent
`final_response` + `messages` mutation and is left as follow-up.

Claude-Session: https://claude.ai/code/session_01KcmdqJVpjmnH31HqTHRLwG
@senamakel senamakel force-pushed the fix/GH-4116-memory-protocol-scaffold branch from cae7444 to 6ea4d86 Compare July 3, 2026 22:38
@senamakel senamakel changed the title fix(agent-harness): state memory protocol up front + surface run-end index drift (#4116) fix(agent-harness): state memory read→dedupe→write→update contract to the model (#4116) Jul 3, 2026
@coderabbitai coderabbitai Bot removed the bug label Jul 3, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ea4d86cb1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openhuman/memory/tools/store.rs Outdated
Comment on lines +33 to +35
Memory protocol: before storing, recall existing memory (e.g. `memory_recall`) to check for a \
near-duplicate so you don't create one; after storing, call `update_memory_md` to keep the \
MEMORY.md index in sync with the store."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid advertising hidden memory protocol tools

When memory_store is exposed through a named tool scope, this unconditional protocol can point the model at tools that are not actually sent to the provider: visible_tool_specs_for_policy keeps only names in the allowlist (src/openhuman/agent/harness/session/builder/mod.rs:61-63), and current named agents include memory_store without memory_recall and/or update_memory_md (profile_memory_agent lines 21-23, trigger_reactor lines 39-42, tinyplace_agent lines 52-54, archivist line 23). In those runs the model cannot satisfy the new read/update instruction, so memory writes can still leave MEMORY.md stale or lead to attempts to use unavailable tools; please add the companion tools to those scopes or make the description conditional on visibility.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — fixed in 7cf5d5f. The wording is now conditional ("if you have a memory-recall tool…", "if update_memory_md is available…") so scopes that expose memory_store without the companion tools (archivist, profile_memory_agent, trigger_reactor, tinyplace_agent) aren't pointed at tools outside their allowlist. I kept it in the description rather than editing those scopes' tool sets, since giving them recall/update tools they were deliberately not granted is a bigger behavioural change.

Unrelated CI unblock: tinyhumansai#4446 (Privacy Mode) registered
`config_get_privacy_mode` / `config_set_privacy_mode` unconditionally
(src/openhuman/config/schemas/controllers.rs) but did not update the
`worker_a_controller_schemas_are_fully_exposed` golden in
tests/config_auth_app_state_connectivity_e2e.rs, so that test fails on
main for every PR that doesn't carry the fix. Add the two methods at their
sorted positions (same fix as tinyhumansai#4475) so this PR's Rust E2E / coverage
jobs go green.

Claude-Session: https://claude.ai/code/session_01KcmdqJVpjmnH31HqTHRLwG
@senamakel

Copy link
Copy Markdown
Member Author

Note on CI: the last red on Rust E2E (mock backend) / Rust Core Coverage was the unrelated, pre-existing worker_a_controller_schemas_are_fully_exposed failure — #4446 (Privacy Mode) registered config_get_privacy_mode/config_set_privacy_mode unconditionally but didn't update that golden list. Added those two lines at their sorted positions (same fix #4475 carries) in a separate test(ci): commit so this PR's E2E/coverage go green. Not part of the memory-protocol change.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01ef1c6419

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/openhuman/memory/tools/forget.rs Outdated
Comment on lines +29 to +30
Memory protocol: after removing an entry, call `update_memory_md` to keep the MEMORY.md \
index in sync with the store."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expose update_memory_md with memory_forget

In the profile-memory flow I checked, profile_memory_agent exposes memory_forget but not update_memory_md (src/openhuman/agent_registry/agents/profile_memory_agent/agent.toml:21-23), and the harness only sends tools whose names are in the named allowlist (visible_tool_specs_for_policy in src/openhuman/agent/harness/session/builder/mod.rs:54-63). This newly added guidance therefore tells the model to call a tool it cannot see after a normal forget request, leaving the index-sync step impossible or wasting a turn on unknown-tool recovery; either include the companion tool in that scope or make the description conditional on visibility.

Useful? React with 👍 / 👎.

senamakel added 2 commits July 3, 2026 22:59
…tinyhumansai#4116 review)

Codex review (P2): some named agent scopes (archivist, profile_memory_agent,
trigger_reactor, tinyplace_agent) advertise `memory_store` without
`memory_recall` / `update_memory_md`, so an unconditional "recall first, then
call update_memory_md" instruction pointed those agents at tools not in their
scope — the very unavailable-tool problem tinyhumansai#4118 is about. Phrase the protocol
conditionally ("if you have a memory-recall tool", "if `update_memory_md` is
available") so it degrades to a no-op when the companion tools aren't present.

Claude-Session: https://claude.ai/code/session_01KcmdqJVpjmnH31HqTHRLwG
@senamakel senamakel merged commit 0304d14 into tinyhumansai:main Jul 4, 2026
13 of 15 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

[Harness] Memory protocol not followed — MEMORY.md not read for dedupe, update_memory_md skipped

1 participant