feat(flows): teach the workflow builder to pick specialist agents via agent_ref (B37) (#5120)

This commit is contained in:
Cyrus Gray
2026-07-22 14:46:10 +03:00
committed by GitHub
parent 9b726165e1
commit 41bad05895
3 changed files with 154 additions and 5 deletions
@@ -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("36 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);
@@ -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: 36 nodes for a simple automation.** A schedule-trigger →
source-tool → agent-summarize → destination-tool flow is 4 nodes.
+6 -4
View File
@@ -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 {