From 36ce35a058a4236f45047a0639234d0dd2d72b7c Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:59:11 +0530 Subject: [PATCH] Add reasoning effort for spawned agents --- .../src/components/compact-conversation.tsx | 8 +++++++- apps/x/packages/core/src/agents/spawn-agent.test.ts | 13 +++++++++++++ apps/x/packages/core/src/agents/spawn-agent.ts | 13 ++++++++++++- .../core/src/application/assistant/instructions.ts | 6 ++++-- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/x/apps/renderer/src/components/compact-conversation.tsx b/apps/x/apps/renderer/src/components/compact-conversation.tsx index 22e1d8f1..75c7a2b5 100644 --- a/apps/x/apps/renderer/src/components/compact-conversation.tsx +++ b/apps/x/apps/renderer/src/components/compact-conversation.tsx @@ -9,6 +9,7 @@ import { isTurnUsageMessage, getToolDisplayName, getToolErrorText, + REASONING_EFFORT_LABELS, toToolState, normalizeToolOutput, } from '@/lib/chat-conversation' @@ -38,7 +39,7 @@ export function CompactConversation({ items }: { items: ConversationItem[] }) { } if (isTurnUsageMessage(item)) { return ( -
+
+ {item.reasoningEffort && ( + + {REASONING_EFFORT_LABELS[item.reasoningEffort]} + + )}
) } diff --git a/apps/x/packages/core/src/agents/spawn-agent.test.ts b/apps/x/packages/core/src/agents/spawn-agent.test.ts index fc5b91a3..7ac176d8 100644 --- a/apps/x/packages/core/src/agents/spawn-agent.test.ts +++ b/apps/x/packages/core/src/agents/spawn-agent.test.ts @@ -140,6 +140,19 @@ describe("runSpawnedAgent", () => { expect(started[0].maxModelCalls).toBe(5); }); + it("passes reasoning effort through to the child headless turn", async () => { + const { services, started } = fakeServices({}); + await runSpawnedAgent( + { + task: "compare these options carefully", + instructions: "You analyze tradeoffs.", + reasoning_effort: "high", + }, + { parentTurnId: "parent-1", signal, services }, + ); + expect(started[0].reasoningEffort).toBe("high"); + }); + it("rejects agent_id and instructions together", async () => { const { services } = fakeServices({}); const result = await runSpawnedAgent( diff --git a/apps/x/packages/core/src/agents/spawn-agent.ts b/apps/x/packages/core/src/agents/spawn-agent.ts index 72cbf26c..85510a92 100644 --- a/apps/x/packages/core/src/agents/spawn-agent.ts +++ b/apps/x/packages/core/src/agents/spawn-agent.ts @@ -59,12 +59,20 @@ export const SpawnAgentInput = z.object({ .describe( `Model-call budget for the sub-agent (default and cap: ${DEFAULT_MAX_MODEL_CALLS}).`, ), + reasoning_effort: z + .enum(["low", "medium", "high"]) + .optional() + .describe( + "Optional reasoning-effort override for the sub-agent turn. Omit for auto/provider default. Use `low` for routine extraction or summarization, `medium` for multi-step synthesis, and `high` only when the child task truly needs deeper reasoning.", + ), }); export const SPAWN_AGENT_DESCRIPTION = "Launch a sub-agent that works on a task in its own isolated, headless turn and returns its final answer. " + - "Issue several spawn-agent calls in ONE response to run sub-agents in parallel — use this to fan out independent research, analysis, or file work. " + + "Use it deliberately for independent, heavy, or parallelizable work; avoid spawning for quick single-step lookups. " + + "Issue several spawn-agent calls in ONE response only when the subtasks are genuinely independent and worth running in parallel. " + "Provide either `agent_id` (a stored agent) or `instructions` (construct a specialist on the fly, optionally with `name` and `tools`). " + + "Optionally set `reasoning_effort` for the child turn; leave it unset for auto/provider default, and reserve `high` for tasks that clearly need deeper reasoning. " + "The sub-agent cannot ask the user questions and cannot spawn further sub-agents; give it a complete, self-contained task."; export interface SpawnedAgentCallbacks { @@ -170,6 +178,9 @@ export async function runSpawnedAgent( agent, message: input.task, maxModelCalls, + ...(input.reasoning_effort === undefined + ? {} + : { reasoningEffort: input.reasoning_effort }), signal: opts.signal, }); } catch (error) { diff --git a/apps/x/packages/core/src/application/assistant/instructions.ts b/apps/x/packages/core/src/application/assistant/instructions.ts index a2341ac5..d7476d66 100644 --- a/apps/x/packages/core/src/application/assistant/instructions.ts +++ b/apps/x/packages/core/src/application/assistant/instructions.ts @@ -149,9 +149,11 @@ ${codeModeEnabled **Background Tasks (Self-Running Work):** Rowboat runs *background tasks* — persistent instructions fired on a schedule and/or on incoming emails / calendar events; the flagship surface for *anything recurring*. Load the \`background-task\` skill and act without asking on cadence words ("every morning / daily / each Monday…"), "keep a running summary / digest of…", "watch / monitor…", "whenever a relevant email comes in, X…", "track / follow X". Load it and offer after answering when a one-off question is about decaying or recurring info ("catch me up on X", "morning briefing" — heuristic: if you reach for \`web-search\` to answer a recurring question, a bg-task should be refreshing it). -**Sub-Agents (parallel & heavy work):** The \`spawn-agent\` tool runs a sub-agent in its own isolated, headless thread and returns only its final answer — the sub-agent's tool calls, page fetches, and file reads never enter this conversation. Use it to keep this context clean: a sub-agent can read twenty notes or six web pages and hand you back one paragraph. Issue several spawn-agent calls in ONE response to run them in parallel. +**Sub-Agents (parallel & heavy work):** The \`spawn-agent\` tool runs a sub-agent in its own isolated, headless thread and returns only its final answer — the sub-agent's tool calls, page fetches, and file reads never enter this conversation. Use it sparingly and deliberately when the work is independent, long-running, or context-heavy enough to justify a separate turn. A sub-agent can read twenty notes or six web pages and hand you back one paragraph. Issue several spawn-agent calls in ONE response only when the subtasks are genuinely independent and useful to run in parallel. -*Strong signals (spawn without asking):* the request decomposes into independent lookups ("prep me on these 3 attendees", "compare these vendors") — one sub-agent each; the task needs reading MANY files, notes, pages, or a long document but the user wants a summary ("what do we know about Acme", "summarize this 40-page PDF"); open-ended web research where you don't know the sources upfront. **Research-shaped requests ("catch me up on X", "dig into Y", meeting prep) should route through sub-agents by default** — you act as the synthesizer, weaving their findings together with what you know from memory. +*Strong signals (spawn without asking):* the request decomposes into independent lookups ("prep me on these 3 attendees", "compare these vendors") — one sub-agent each; the task needs reading MANY files, notes, pages, or a long document but the user wants a summary ("what do we know about Acme", "summarize this 40-page PDF"); open-ended web research where you don't know the sources upfront. For research-shaped requests ("catch me up on X", "dig into Y", meeting prep), use sub-agents when there are multiple sources or branches to investigate, then act as the synthesizer and weave their findings together with what you know from memory. + +*Reasoning effort:* \`spawn-agent\` accepts \`reasoning_effort\` for the child turn. Usually omit it and let the provider default apply. Use \`low\` for routine extraction, search, or simple summaries; \`medium\` for multi-step comparison or synthesis; \`high\` only for hard analysis, ambiguous tradeoffs, planning, or tasks where a wrong conclusion would be costly. *Do NOT spawn for:* single quick lookups (one file read, one search — just do it); tasks where the user wants to see the intermediate detail, not a distillation; anything needing user input mid-way (sub-agents run headless and cannot ask questions); driving the app UI or the embedded browser (those are shared surfaces you control, not sub-agents). Remember each sub-agent starts with ZERO context — its \`task\` must be fully self-contained (names, dates, constraints, expected output format).