From 6ea4d86cb1561d0fc0d37d1b2b86340b323ab553 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 3 Jul 2026 21:09:48 +0000 Subject: [PATCH 1/3] =?UTF-8?q?fix(agent-harness):=20state=20the=20memory?= =?UTF-8?q?=20read=E2=86=92dedupe=E2=86=92write=E2=86=92update=20contract?= =?UTF-8?q?=20to=20the=20model=20(#4116)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The memory-protocol enforcement middleware (#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 --- src/openhuman/memory/tools/forget.rs | 4 +++- src/openhuman/memory/tools/store.rs | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/openhuman/memory/tools/forget.rs b/src/openhuman/memory/tools/forget.rs index 59cf612d43..31394baced 100644 --- a/src/openhuman/memory/tools/forget.rs +++ b/src/openhuman/memory/tools/forget.rs @@ -25,7 +25,9 @@ impl Tool for MemoryForgetTool { } fn description(&self) -> &str { - "Remove a memory by namespace and key. Returns whether the memory was found and removed." + "Remove a memory by namespace and key. Returns whether the memory was found and removed. \ + Memory protocol: after removing an entry, call `update_memory_md` to keep the MEMORY.md \ + index in sync with the store." } fn parameters_schema(&self) -> serde_json::Value { diff --git a/src/openhuman/memory/tools/store.rs b/src/openhuman/memory/tools/store.rs index 7b84630c7d..1f6b8d9ec8 100644 --- a/src/openhuman/memory/tools/store.rs +++ b/src/openhuman/memory/tools/store.rs @@ -29,7 +29,10 @@ impl Tool for MemoryStoreTool { "Store a general fact or note in a namespace (e.g. global, background, autocomplete, skill-{id}). \ Do NOT use this for user preferences — for any preference (how the user wants you to behave, \ their tastes, settings, standing instructions) call `save_preference` instead, which routes it \ - to the preference store the assistant actually reads. Requires an explicit namespace." + to the preference store the assistant actually reads. Requires an explicit namespace. \ + 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." } fn parameters_schema(&self) -> serde_json::Value { @@ -145,6 +148,13 @@ mod tests { let schema = tool.parameters_schema(); assert!(schema["properties"]["key"].is_object()); assert!(schema["properties"]["content"].is_object()); + // The memory protocol (#4116) must be stated up front so the model recalls + // for dedupe before writing and reconciles the index after. + let desc = tool.description(); + assert!( + desc.contains("memory_recall") && desc.contains("update_memory_md"), + "memory_store description must state the read→dedupe→write→update contract: {desc}" + ); } #[tokio::test] From 01ef1c6419ba8aeaaba3ff62d7fd801d447ce210 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 3 Jul 2026 22:55:46 +0000 Subject: [PATCH 2/3] test(ci): add privacy_mode config methods to the schema-catalog golden Unrelated CI unblock: #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 #4475) so this PR's Rust E2E / coverage jobs go green. Claude-Session: https://claude.ai/code/session_01KcmdqJVpjmnH31HqTHRLwG --- tests/config_auth_app_state_connectivity_e2e.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/config_auth_app_state_connectivity_e2e.rs b/tests/config_auth_app_state_connectivity_e2e.rs index 266d46584c..802855f2fe 100644 --- a/tests/config_auth_app_state_connectivity_e2e.rs +++ b/tests/config_auth_app_state_connectivity_e2e.rs @@ -2802,6 +2802,7 @@ async fn worker_a_controller_schemas_are_fully_exposed() { "openhuman.config_get_meet_settings", "openhuman.config_get_memory_sync_settings", "openhuman.config_get_onboarding_completed", + "openhuman.config_get_privacy_mode", "openhuman.config_get_runtime_flags", "openhuman.config_get_sandbox_settings", "openhuman.config_get_search_settings", @@ -2811,6 +2812,7 @@ async fn worker_a_controller_schemas_are_fully_exposed() { "openhuman.config_resolve_api_url", "openhuman.config_set_browser_allow_all", "openhuman.config_set_onboarding_completed", + "openhuman.config_set_privacy_mode", "openhuman.config_set_super_context_enabled", "openhuman.config_update_activity_level_settings", "openhuman.config_update_agent_paths", From 7cf5d5f3b06460e38ba975c36652ac2455e79bda Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 3 Jul 2026 22:59:25 +0000 Subject: [PATCH 3/3] fix(agent-harness): gate memory-protocol wording on tool availability (#4116 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #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 --- src/openhuman/memory/tools/forget.rs | 4 ++-- src/openhuman/memory/tools/store.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/openhuman/memory/tools/forget.rs b/src/openhuman/memory/tools/forget.rs index 31394baced..e3a64e7619 100644 --- a/src/openhuman/memory/tools/forget.rs +++ b/src/openhuman/memory/tools/forget.rs @@ -26,8 +26,8 @@ impl Tool for MemoryForgetTool { fn description(&self) -> &str { "Remove a memory by namespace and key. Returns whether the memory was found and removed. \ - Memory protocol: after removing an entry, call `update_memory_md` to keep the MEMORY.md \ - index in sync with the store." + Memory protocol: if `update_memory_md` is available, call it after removing an entry to keep \ + the MEMORY.md index in sync with the store." } fn parameters_schema(&self) -> serde_json::Value { diff --git a/src/openhuman/memory/tools/store.rs b/src/openhuman/memory/tools/store.rs index 1f6b8d9ec8..deda5b58fd 100644 --- a/src/openhuman/memory/tools/store.rs +++ b/src/openhuman/memory/tools/store.rs @@ -30,9 +30,10 @@ impl Tool for MemoryStoreTool { Do NOT use this for user preferences — for any preference (how the user wants you to behave, \ their tastes, settings, standing instructions) call `save_preference` instead, which routes it \ to the preference store the assistant actually reads. Requires an explicit namespace. \ - 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." + Memory protocol (only with tools you actually have available): if you have a memory-recall \ + tool (e.g. `memory_recall`), check for a near-duplicate before storing so you don't create \ + one; and if `update_memory_md` is available, call it after storing to keep the MEMORY.md \ + index in sync with the store." } fn parameters_schema(&self) -> serde_json::Value {