diff --git a/src/openhuman/agent_registry/agents/loader.rs b/src/openhuman/agent_registry/agents/loader.rs index 040bd7bb16..cbba657cab 100644 --- a/src/openhuman/agent_registry/agents/loader.rs +++ b/src/openhuman/agent_registry/agents/loader.rs @@ -751,6 +751,28 @@ mod tests { "orchestrator must have read-only direct tool `{direct}`" ); } + // Direct memory surface (#4762): recall/store are the product's + // core and must be first-class direct tools, not a sub-agent + // spawn — a trivial recall or a single "remember this" must not + // pay a blocking agentic round-trip (over-delegation, #4744) that + // can hang or return a 0-char result with persistence unconfirmed. + // Deep tree walks / reconciliation still delegate to + // `retrieve_memory` / `manage_profile_memory`. + for direct in ["memory_recall", "memory_store", "save_preference"] { + assert!( + tools.iter().any(|t| t == direct), + "orchestrator must have direct memory tool `{direct}` (#4762)" + ); + } + // Memory-protocol close-out (#4116): a direct `memory_store` write + // obliges an `update_memory_md` index reconcile, so the tool that + // performs it must be in scope — otherwise the protocol's guidance + // is unsatisfiable and MEMORY.md (loaded here) drifts from the store. + assert!( + tools.iter().any(|t| t == "update_memory_md"), + "orchestrator must have `update_memory_md` to reconcile MEMORY.md \ + after a direct memory_store (#4762)" + ); } ToolScope::Wildcard => panic!("orchestrator must have named tool allowlist"), } diff --git a/src/openhuman/agent_registry/agents/orchestrator/agent.toml b/src/openhuman/agent_registry/agents/orchestrator/agent.toml index 031c12030d..bfec1e5a1e 100644 --- a/src/openhuman/agent_registry/agents/orchestrator/agent.toml +++ b/src/openhuman/agent_registry/agents/orchestrator/agent.toml @@ -223,6 +223,36 @@ named = [ "web_search_tool", "web_fetch", "http_request", + # Direct memory surface (#4762). Recall/store are the product's core and must + # be first-class DIRECT tools, not a sub-agent spawn: a trivial "what is my Q3 + # OKR" recall or a "remember my Q3 OKR" write should not pay a blocking + # agentic round-trip (over-delegation, #4744) that can hang or — as observed — + # spawn `manage_profile_memory` which did 0 tool calls and returned a 0-char + # result, leaving the write's persistence unconfirmed. `memory_recall` reads + # the same store `memory_store` writes, so store→recall closes the loop inline + # with a real confirmation. Deep tree walks / reconciliation / people-graph / + # persona edits still delegate to `retrieve_memory` (agent_memory) and + # `manage_profile_memory` (profile_memory_agent) below. These are memory-store + # writes, NOT file/shell mutation — the "no write tools" rule (below) is about + # editing files and running commands, which stays out. + "memory_recall", + "memory_store", + "save_preference", + # Memory-protocol close-out (#4116). A successful `memory_store` is a durable + # write, so the memory-protocol middleware appends a "call `update_memory_md` + # to keep the MEMORY.md index in sync" note to the tool result and warns at run + # end if the index was never reconciled. Since the orchestrator now takes the + # direct-store path itself, it must own that closing step too — otherwise the + # guidance is unsatisfiable and MEMORY.md (which this agent loads, + # `omit_memory_md = false`) drifts from the store. `update_memory_md` is locked + # to the MEMORY.md / SKILL.md workspace index files (enum-gated, symlink-hardened), + # NOT a general file-editor: it is the index-sync counterpart of `memory_store`, + # so it belongs in the same memory bucket as the writes above — the "no write + # tools" rule (below) is about editing arbitrary files (`edit` / `file_write` / + # `apply_patch`) and running commands, which stay out. `save_preference` writes + # to the preference store and is deliberately NOT part of the MEMORY.md wiki, so + # it needs no reconcile. + "update_memory_md", # Managed file-storage transfer surface for passing artifacts/attachments # between chat, users, and workers. Storage lifecycle mutators # (`storage_set_visibility`, `storage_delete_file`) stay out of the broad diff --git a/src/openhuman/agent_registry/agents/orchestrator/prompt.md b/src/openhuman/agent_registry/agents/orchestrator/prompt.md index cd83c344eb..020e7f8950 100644 --- a/src/openhuman/agent_registry/agents/orchestrator/prompt.md +++ b/src/openhuman/agent_registry/agents/orchestrator/prompt.md @@ -23,7 +23,8 @@ Follow this sequence for every user message: - **Do this even if remembered context could plausibly answer.** The user wants the live source of truth, not a stale summary. - If the relevant toolkit is **not** in **Connected Integrations**, call `composio_connect { toolkit: "" }` **directly** to raise an **inline connect card** so the user can authorize in one click, then continue the task once it returns `connected: true`. Do **not** refuse based on the Connected Integrations list (that is only what is *already* connected, not what is *connectable*), do **not** make "go to Settings → Connections" your first move, and do **not** silently fall back to memory retrieval (see "Connecting external services" below). 3. **Can I solve this with direct tools?** - - Yes: use direct tools (`retrieve_memory`, `read_workspace_state`, `composio_list_connections`, task tools, etc.). + - Yes: use direct tools (`memory_recall`, `read_workspace_state`, `composio_list_connections`, task tools, etc.). + - **Memory is direct work.** Recalling a fact (`memory_recall`), storing a fact (`memory_store`), or saving a preference (`save_preference`) needs **no sub-agent** — call the direct tool and confirm the result. After a `memory_store` write, follow the memory protocol and call `update_memory_md` (targeting `MEMORY.md`) to keep the index in sync with the store; `save_preference` writes the preference store and needs no such reconcile. Reserve the `retrieve_memory` and `manage_profile_memory` **delegates** for genuinely deep work: multi-hop memory-tree walks, ingest/index into the long-term tree, reconciling overlapping notes, people-graph/alias management, or persona edits. Do **not** spawn a sub-agent for a single recall or a single "remember this". - **Quick lookups are direct work.** Use `web_search_tool` for quick discovery, `web_fetch` for one URL/body read, and `http_request` for basic API/HTTP semantics (methods, headers, JSON endpoints, status/HEAD checks). Reserve `research` for multi-source crawls, comparisons, deep digests, or uncertain evidence gathering. - **Read-only file lookups are direct work.** Reading a file the user names, grepping for a string, or listing a directory (`file_read` / `grep` / `glob` / `list`) needs no sub-agent. Managed storage transfer is also direct when the user needs uploaded/downloaded/listed/linked artifacts. But you cannot use generic write/edit tools: the moment the task requires *changing* a file — even a one-line edit — delegate it to `run_code` (see below). Never promise an edit you cannot make yourself. - No: continue.