diff --git a/backend/apps/agents/manager/prompt/prompt_context.py b/backend/apps/agents/manager/prompt/prompt_context.py index 32fa794b..7034438f 100644 --- a/backend/apps/agents/manager/prompt/prompt_context.py +++ b/backend/apps/agents/manager/prompt/prompt_context.py @@ -127,6 +127,17 @@ def _build_browser_context(dashboard_id: str | None, selected_browser_ids: list[ "", "You do NOT have direct access to low-level browser tools (click, type, screenshot, etc.). " "Instead, describe what you want accomplished and the browser agent will handle the details.", + "", + "**Same flow for many items? Give ONE agent the whole list, don't split it.** " + "When a task repeats the SAME steps for a list of inputs (read these 10 profiles, " + "look up these 6 names, open each of these links), delegate it to a SINGLE " + "browser agent with the FULL list in one task, e.g. CreateBrowserAgent(\"Look up " + "the first sentence of the Wikipedia article for each of: A, B, C, D. Do the first " + "one normally, then use BrowserRepeatFlow for the rest\"). The browser agent has a " + "BrowserRepeatFlow tool that runs the repeated flow for all the inputs in one shot " + "(no re-analyzing each page), and hands back the data per item. This is far cheaper " + "and faster than spawning one agent per item with BrowserAgents, use parallel " + "BrowserAgents only for genuinely DIFFERENT tasks, not for the same flow repeated.", ] if browser_cards and selected_browser_ids: diff --git a/backend/tests/test_browser_orchestrator_routing.py b/backend/tests/test_browser_orchestrator_routing.py new file mode 100644 index 00000000..f28a6adf --- /dev/null +++ b/backend/tests/test_browser_orchestrator_routing.py @@ -0,0 +1,38 @@ +"""The orchestrator's browser-delegation guidance. + +BrowserRepeatFlow lives in the browser SUB-agent, but the orchestrator decides +HOW to delegate. If it splits a 'same flow, many items' task into N parallel +sub-agents, each gets one item and RepeatFlow is never reachable. This pins the +guidance that routes such tasks to ONE sub-agent with the whole list, so the +batch path is actually usable (not stranded behind the delegation layer). +""" + +import types + +from backend.apps.agents.manager.prompt import prompt_context as pc + + +def _fake_dashboard(monkeypatch): + # _build_browser_context loads the dashboard; give it a minimal one so it + # gets past the load and emits the static delegation guidance. + import backend.apps.dashboards.dashboards as dash + + class _D: + def model_dump(self, mode="json"): + return {"layout": {"browser_cards": {}}} + monkeypatch.setattr(dash, "_load", lambda did: _D(), raising=True) + + +def test_orchestrator_routes_same_flow_batches_to_one_agent(monkeypatch): + _fake_dashboard(monkeypatch) + ctx = pc._build_browser_context("dash-1", selected_browser_ids=[]) + assert ctx is not None + # the key guidance: one agent + the whole list, not one agent per item + assert "Give ONE agent the whole list" in ctx + assert "BrowserRepeatFlow" in ctx + # and it explicitly steers AWAY from the per-item parallel split for same flows + assert "only for genuinely DIFFERENT tasks" in ctx + + +def test_browser_context_is_none_without_a_dashboard(): + assert pc._build_browser_context(None) is None