diff --git a/src/openhuman/flows/agents/workflow_builder/builder_prompt.rs b/src/openhuman/flows/agents/workflow_builder/builder_prompt.rs index 6a744fda9..36fc9fe51 100644 --- a/src/openhuman/flows/agents/workflow_builder/builder_prompt.rs +++ b/src/openhuman/flows/agents/workflow_builder/builder_prompt.rs @@ -639,6 +639,120 @@ mod tests { } } + /// B37 (Gap 1): the standing prompt must actually teach the builder to + /// reach for a specialist `agent_ref` — ground the id via + /// `list_agent_profiles`, understand that `agent_ref` runs a real agent + /// turn with its own tool loop (not just a persona-flavored completion), + /// and see concrete examples of when a plain agent node isn't enough. + #[test] + fn standing_prompt_teaches_specialist_agent_ref_selection() { + const STANDING_PROMPT: &str = include_str!("prompt.md"); + + for rule in [ + "list_agent_profiles", + "Picking a specialist via `agent_ref`", + "code_executor", + "researcher", + ] { + assert!( + STANDING_PROMPT.contains(rule), + "standing prompt must teach specialist selection via `{rule}` — the \ + builder needs to know it can ground a real agent_ref with \ + list_agent_profiles instead of hallucinating one" + ); + } + } + + /// The runtime already gives an `agent_ref` step the selected specialist's + /// full persona/model/tool loop/iteration cap (`run_via_harness` in + /// `tinyflows/caps.rs`) — the prompt must say so, not describe it as a + /// future capability. + #[test] + fn standing_prompt_links_agent_ref_to_the_full_tool_loop() { + const STANDING_PROMPT: &str = include_str!("prompt.md"); + + assert!( + STANDING_PROMPT.contains("specialist") + && (STANDING_PROMPT.contains("tool loop") + || STANDING_PROMPT.contains("full persona")), + "standing prompt must link agent_ref to the specialist's full tool loop \ + (the harness path), not just a persona/model swap" + ); + } + + /// Regression guard: the old `list_agent_profiles` description (and any + /// prompt copy that echoed it) claimed the per-agent tool loop was "a + /// follow-up" and that a step "still gets tools from the node's own + /// inline `tools` list for now". That's false — `run_via_harness` already + /// gives an `agent_ref` step its selected specialist's real tool loop — + /// and the stale wording actively discouraged using `agent_ref` at all. + #[test] + fn standing_prompt_has_no_stale_agent_ref_followup_language() { + const STANDING_PROMPT: &str = include_str!("prompt.md"); + + for banned in [ + "is a follow-up", + "for now", + "still gets tools from the node's own", + ] { + assert!( + !STANDING_PROMPT.contains(banned), + "standing prompt must not carry the stale agent_ref-tool-loop \ + phrasing `{banned}` — the harness path already gives agent_ref \ + its full tool loop" + ); + } + } + + /// `list_agent_profiles`'s own tool description used to discourage + /// `agent_ref` with stale "follow-up"/"for now" wording (issue B37, Gap + /// 1) — pin that it now correctly describes the harness's full tool + /// loop instead. + #[test] + fn list_agent_profiles_tool_description_has_no_stale_followup_language() { + use crate::openhuman::flows::builder_tools::ListAgentProfilesTool; + use crate::openhuman::tools::traits::Tool; + + let description = ListAgentProfilesTool::new().description().to_string(); + + for banned in ["is a follow-up", "for now"] { + assert!( + !description.contains(banned), + "list_agent_profiles description must not carry the stale \ + phrasing `{banned}` — an agent_ref step already gets the \ + selected specialist's full tool loop" + ); + } + assert!( + description.contains("tool loop"), + "list_agent_profiles description must describe agent_ref as running \ + the specialist's full tool loop" + ); + } + + /// Guard against over-fragmentation: the minimal-graph rule (don't chain + /// agents doing the same kind of work) must survive alongside the new + /// specialist guidance (do pick a specialist when the step needs tools + /// the plain agent lacks) — neither should crowd the other out. + #[test] + fn standing_prompt_keeps_minimal_graph_warning_alongside_specialist_guidance() { + const STANDING_PROMPT: &str = include_str!("prompt.md"); + + assert!( + STANDING_PROMPT.contains("minimal viable graph"), + "standing prompt must still warn to prefer the minimal viable graph" + ); + assert!( + STANDING_PROMPT.contains("3–6 nodes") || STANDING_PROMPT.contains("3-6 nodes"), + "standing prompt must still carry the 3-6 node sizing guidance" + ); + assert!( + STANDING_PROMPT.contains("SAME kind of work"), + "standing prompt must still warn against chaining agents doing the \ + same kind of work, even after adding specialist-selection guidance" + ); + } + #[test] fn repair_includes_run_id_error_and_failing_nodes() { let mut r = req(BuildMode::Repair); diff --git a/src/openhuman/flows/agents/workflow_builder/prompt.md b/src/openhuman/flows/agents/workflow_builder/prompt.md index 68350a80e..3dd502810 100644 --- a/src/openhuman/flows/agents/workflow_builder/prompt.md +++ b/src/openhuman/flows/agents/workflow_builder/prompt.md @@ -141,6 +141,11 @@ rather than a general context recall), use `memory_hybrid_search` in its makes the graph savable at all. - `list_flows` / `get_flow` → reuse or clone an existing flow instead of duplicating one. + - `list_agent_profiles` → the real specialist agent ids (`researcher`, + `code_executor`, …) an `agent` node can set as `config.agent_ref`. The + agent analogue of `search_tool_catalog`: never guess/hallucinate an id — + look it up. See "Picking a specialist via `agent_ref`" below for when and + how to use it. - **Missing the integration the workflow needs?** See "Connecting integrations" below — you can help the user link it before you build, rather than dead-ending. @@ -325,6 +330,24 @@ A `WorkflowGraph` is `{ name?, nodes: [...], edges: [...] }`. (recall a preference) or should remember a result/state across runs, wire an `agent` node that uses memory instead of hardcoding context memory already holds. Use sparingly — only when the workflow truly needs it. + + **Picking a specialist via `agent_ref`.** A plain `agent` node (no + `agent_ref`) only has the default LLM plus whatever it's given in + `input_context`/`prompt` — it cannot run code, browse the web, or reach + any domain-specific tool. If a step genuinely needs to DO something — + execute code, search the web, touch a domain the workflow author didn't + already wire as a `tool_call` — set `config.agent_ref` to the specialist + that owns those tools instead of hoping the plain agent can wing it. + Setting `agent_ref` runs that step as a REAL agent turn: the selected + agent's full persona, model, tool loop, and iteration cap, not just a + differently-worded completion. **WHEN**: the step needs code/file + execution, web research, or any tool a specialist owns that the plain + agent doesn't have. **HOW**: call `list_agent_profiles`, pick the `id` + whose `tools`/`description` match the step's need, and set it verbatim on + `config.agent_ref` — never hallucinate an id, exactly like grounding a + `tool_call` slug via `search_tool_catalog`. Examples: "generate an HTML + report from this data" → `code_executor`; "research our competitors" → + `researcher`. 3. **`tool_call`** — an action. Two flavours by `config.slug`: - **Composio app action** — `config.slug` = a real action slug (from `search_tool_catalog`, e.g. `GMAIL_SEND_EMAIL`) + `config.connection_ref` @@ -587,7 +610,17 @@ failure at runtime. Rules of thumb: `agent` nodes when one could handle both tasks in its prompt (e.g. "extract the key fields AND compose a brief" in one node, rather than "extract" → "compose" as two nodes). Chain agents only when they need - genuinely different models, schemas, or `agent_ref` profiles. + genuinely different models, schemas, or `agent_ref` profiles. **Don't + chain multiple agents doing the SAME kind of work** just to spread it + across steps — that's the over-fragmentation this rule warns against. + +- **DO pick a specialist when the step needs tools the plain agent lacks.** + The minimal-graph rule is about node COUNT, not about under-provisioning a + step — a step that needs to run code, search the web, or touch a + specialist's tools literally cannot do that job as a plain `agent` node, + so setting `config.agent_ref` there isn't added complexity, it's the + difference between the step working and silently no-op'ing. See "Picking + a specialist via `agent_ref`" above. - **Target: 3–6 nodes for a simple automation.** A schedule-trigger → source-tool → agent-summarize → destination-tool flow is 4 nodes. diff --git a/src/openhuman/flows/builder_tools.rs b/src/openhuman/flows/builder_tools.rs index 7f23a0638..5f2aa45e4 100644 --- a/src/openhuman/flows/builder_tools.rs +++ b/src/openhuman/flows/builder_tools.rs @@ -2264,10 +2264,12 @@ impl Tool for ListAgentProfilesTool { field (e.g. researcher, code_executor, crypto_agent). Read-only. Returns \ a JSON array of { id, name, description, model, tools, tags }. Use this to \ pick a real agent_ref — a coding step should reference the coding agent, a \ - research step the researcher — instead of guessing an id. Note: an \ - agent_ref applies that agent's persona/model to the step; its private \ - tool loop is a follow-up, so a step still gets tools from the node's own \ - inline `tools` list for now." + research step the researcher — instead of guessing an id. Note: setting \ + agent_ref runs the step as a REAL agent turn (its own `run_single`), with \ + the selected specialist's full persona, model, tool loop, and iteration \ + cap — not just a persona-flavored completion. A plain `agent` node with \ + no agent_ref only gets the default LLM plus its own inline `tools` list; \ + it cannot run code, search the web, or use any specialist's tools." } fn parameters_schema(&self) -> Value {