From 5f2e3eb8cd21f646365bb007236af7ccaf2b01c7 Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Wed, 8 Jul 2026 04:19:20 +0530 Subject: [PATCH] fix(orchestrator): route review-before-finalize tasks to synchronous delegation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For 'draft a bio, then have a second agent critique it before you finalize', the orchestrator dispatched the critique via fire-and-forget spawn_async_subagent. The turn finalized immediately and the critique ran as a detached autonomous_task ~10 min later — so the parent never honored 'before you finalize' and burned a wasted run. The orchestrator has no synchronous spawn_subagent; its awaited primitives are spawn_parallel_agents (collects results before returning), blocking delegate_* specialists, and spawn_async_subagent + wait_subagent. Neither the async tool description nor the prompt named the result-gating pattern, so the model mis-routed. Add an explicit 'result-gating tasks run synchronously' rule to the orchestrator prompt and to the spawn_async_subagent description, steering review/critique/verify-before-finalize work to an awaited sub-agent. Add a prompt regression test. --- .../tools/spawn_async_subagent.rs | 7 ++++++- .../agents/orchestrator/prompt.md | 11 +++++++++++ .../agents/orchestrator/prompt.rs | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/openhuman/agent_orchestration/tools/spawn_async_subagent.rs b/src/openhuman/agent_orchestration/tools/spawn_async_subagent.rs index 55433fd134..beac656df3 100644 --- a/src/openhuman/agent_orchestration/tools/spawn_async_subagent.rs +++ b/src/openhuman/agent_orchestration/tools/spawn_async_subagent.rs @@ -47,7 +47,12 @@ impl Tool for SpawnAsyncSubagentTool { Use sparingly, only when the user does not need the result in the current \ response, such as best-effort memory archiving, cleanup, or background \ investigation. Do not use for user-visible answers, code changes, external \ - service writes, financial actions, or anything that may need clarification." + service writes, financial actions, or anything that may need clarification. \ + Never use it when the sub-agent's result must gate your final answer (e.g. \ + review/critique/verify/approve X BEFORE finalizing): this returns immediately \ + and the turn finalizes before the result lands. For those, run a synchronous \ + awaited sub-agent instead — a blocking delegate_* specialist or \ + spawn_parallel_agents (which collects results before returning)." } fn parameters_schema(&self) -> serde_json::Value { diff --git a/src/openhuman/agent_registry/agents/orchestrator/prompt.md b/src/openhuman/agent_registry/agents/orchestrator/prompt.md index c92244a2f3..975bb35b4d 100644 --- a/src/openhuman/agent_registry/agents/orchestrator/prompt.md +++ b/src/openhuman/agent_registry/agents/orchestrator/prompt.md @@ -81,6 +81,17 @@ external-service writes, financial/market actions, scheduling, desktop control, task that may need clarification. If the result matters to the current reply, use the matching specialist delegation tool or `spawn_parallel_agents` instead. +**Result-gating tasks run synchronously (hard rule).** If a sub-agent's output must gate +your final answer — "review / critique / verify / approve / proofread X **before** you +finalize (or answer)" — that is **not** background work. Never dispatch it with +`spawn_async_subagent` (fire-and-forget): the turn finalizes before the result lands, so you +silently ignore "before you finalize" **and** waste a detached run that finishes minutes +later unused. Instead run it and get the result **in this same turn**: a blocking `delegate_*` +specialist, or `spawn_parallel_agents` (it collects every worker's result before returning), +or — only if you already spawned async — `wait_subagent` with a generous `timeout_secs` and +fold the result in before you finalize. Reserve `spawn_async_subagent` for work whose result +the current reply genuinely does **not** depend on. + `spawn_async_subagent` returns an `[async_subagent_ref]` block with both `agent_id` and `agentId`, plus concrete control instructions: diff --git a/src/openhuman/agent_registry/agents/orchestrator/prompt.rs b/src/openhuman/agent_registry/agents/orchestrator/prompt.rs index 2313b1dbcb..9e324f9a47 100644 --- a/src/openhuman/agent_registry/agents/orchestrator/prompt.rs +++ b/src/openhuman/agent_registry/agents/orchestrator/prompt.rs @@ -378,6 +378,24 @@ mod tests { assert_eq!(render_installed_skills(&[]), ""); } + #[test] + fn prompt_routes_result_gating_tasks_to_synchronous_delegation() { + // Regression for #4681: a "critique it before you finalize" task was + // dispatched via fire-and-forget `spawn_async_subagent`, so the turn + // finalized before the critique ran. The orchestrator prompt must + // explicitly route result-gating work to a synchronous/awaited path. + assert!( + ARCHETYPE.contains("Result-gating tasks run synchronously"), + "orchestrator prompt must carry the result-gating delegation rule" + ); + // It must steer such tasks to a synchronous/awaited primitive rather + // than fire-and-forget `spawn_async_subagent`. + assert!( + ARCHETYPE.contains("spawn_parallel_agents") && ARCHETYPE.contains("wait_subagent"), + "the rule must name the synchronous/awaited alternatives" + ); + } + #[test] fn render_installed_skills_flattens_and_caps_long_descriptions() { // Third-party skill descriptions are untrusted, potentially huge