From f546d1ad59bd52b47c1109201dc738a3c95e1e0c Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Thu, 9 Jul 2026 06:20:38 +0530 Subject: [PATCH] feat(x): teach copilot when to spawn sub-agents; fence shared surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spawn-agent tool shipped with mechanics but no judgment layer — the only guidance was the tool description. This adds the ambient decision heuristics and closes a child-profile hole. - Copilot system prompt gains a "Sub-Agents (parallel & heavy work)" section (same strong/anti signal structure as Background Tasks): the organizing principle is context hygiene — a sub-agent's reads and fetches never enter the main conversation, only its distilled answer. Research-shaped requests (catch-me-up, dig-into, meeting prep) now route through sub-agents by default, with copilot as synthesizer. Anti-signals: single quick lookups, journeys-as-answer, anything needing mid-task user input, driving shared surfaces. - Background-task agent gets a matching (shorter) note — children have their own model-call budgets, so a bg-task covers more ground per run by fanning out. - The inline default child profile now also excludes app-navigation and browser-control: both drive shared visible surfaces (the UI the user is watching; the single embedded browser pane), which headless or parallel children would corrupt, not just clutter. Still available via explicit tools selection. Co-Authored-By: Claude Fable 5 --- .../src/application/assistant/instructions.ts | 6 ++++++ .../core/src/background-tasks/agent.ts | 4 ++++ .../bridges/inline-agent-resolver.test.ts | 21 +++++++++++++++++++ .../turns/bridges/inline-agent-resolver.ts | 7 ++++++- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/x/packages/core/src/application/assistant/instructions.ts b/apps/x/packages/core/src/application/assistant/instructions.ts index 9518990c..1b709589 100644 --- a/apps/x/packages/core/src/application/assistant/instructions.ts +++ b/apps/x/packages/core/src/application/assistant/instructions.ts @@ -149,6 +149,12 @@ ${codeModeEnabled *Medium signals (load the skill, answer the one-off, then offer):* one-off questions about decaying info ("what's the weather?", "top HN stories?"), "what's the latest on X / catch me up on X / any updates on X" about a person, company, project, or topic, recurring artifacts ("morning briefing", "weekly review", "Acme deal dashboard"). **Heuristic:** if you reach for \`web-search\` or a news tool to answer a recurring question, the answer is the kind of thing a bg-task would refresh on a schedule. +**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. + +*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. + +*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). + **Rowboat Apps:** When users ask you to build/make/create an *app* or *dashboard* ("build me an app that…", "make a dashboard for…"), load the \`apps\` skill FIRST — it defines the app contract (manifest, dist/, Host API) and the build flow. For ambiguous requests that could be a one-off answer ("show me my open PRs"), the skill's intent gate says to confirm before building. Do not hand-roll app folders without the skill. **Live Notes:** If the user explicitly says "live note" or "live-note", load the \`live-note\` skill. Otherwise, do not propose live notes — prefer the \`background-task\` skill for anything recurring. diff --git a/apps/x/packages/core/src/background-tasks/agent.ts b/apps/x/packages/core/src/background-tasks/agent.ts index d49f2c68..7016516a 100644 --- a/apps/x/packages/core/src/background-tasks/agent.ts +++ b/apps/x/packages/core/src/background-tasks/agent.ts @@ -53,6 +53,10 @@ Only available when the run message contains a **"# Coding task"** block (the ta - Group related items, then call \`launch-code-task\` once per group (\`taskSlug\` is your own slug). It runs full-auto in an isolated worktree and **owns the \`## Code Sessions\` section of \`index.md\`** — never edit those rows yourself. Write a complete, self-contained \`prompt\`: the coding agent has no other context and no human to ask. - If nothing is actionable, launch nothing and say so in your summary. +# Sub-agents + +The \`spawn-agent\` tool runs a sub-agent in its own isolated turn and returns only its final answer — its intermediate reads and fetches never enter your context, and it has its own model-call budget separate from yours. Spawn when your instructions require sweeping many sources (several sites, many notes, a long document) and you only need the conclusions, or when the work splits into independent lookups — issue several spawn-agent calls in ONE message to run them in parallel, then synthesize. Do not spawn for single quick lookups. Each sub-agent starts with zero context: its \`task\` must be fully self-contained. + # Triggers The run message tells you which trigger fired and how to interpret it: diff --git a/apps/x/packages/core/src/turns/bridges/inline-agent-resolver.test.ts b/apps/x/packages/core/src/turns/bridges/inline-agent-resolver.test.ts index ff69be97..0ec7b586 100644 --- a/apps/x/packages/core/src/turns/bridges/inline-agent-resolver.test.ts +++ b/apps/x/packages/core/src/turns/bridges/inline-agent-resolver.test.ts @@ -30,6 +30,16 @@ const fakeBuiltins = { inputSchema: z.object({ command: z.string() }), execute: async () => null, }, + "app-navigation": { + description: "Drive the app UI", + inputSchema: z.object({}), + execute: async () => null, + }, + "browser-control": { + description: "Drive the embedded browser", + inputSchema: z.object({}), + execute: async () => null, + }, } as unknown as typeof BuiltinTools; function makeResolver() { @@ -97,5 +107,16 @@ describe("InlineAgentResolver", () => { // Excluded by policy, not by absence: expect(names).not.toContain("executeCommand"); expect(names).not.toContain("spawn-agent"); + // Shared visible surfaces: a headless child must not drive the UI + // the user is watching or the single embedded browser pane. + expect(names).not.toContain("app-navigation"); + expect(names).not.toContain("browser-control"); + }); + + it("shared-surface tools remain available via explicit selection", async () => { + const resolved = await makeResolver().resolve({ + inline: { name: "a", instructions: "x", tools: ["browser-control"] }, + }); + expect(resolved.tools.map((t) => t.name)).toEqual(["browser-control"]); }); }); diff --git a/apps/x/packages/core/src/turns/bridges/inline-agent-resolver.ts b/apps/x/packages/core/src/turns/bridges/inline-agent-resolver.ts index fc2ae4e9..22ba14f0 100644 --- a/apps/x/packages/core/src/turns/bridges/inline-agent-resolver.ts +++ b/apps/x/packages/core/src/turns/bridges/inline-agent-resolver.ts @@ -15,7 +15,10 @@ import { builtinToolDescriptor } from "./builtin-descriptors.js"; // except the ones that make no sense headlessly or in a child. Mirrors the // background-task agent's exclusions (no interactive approval surface) plus // the task/session launchers — an ephemeral child should do its own work, -// not schedule more. +// not schedule more — and the shared visible surfaces: a headless child +// navigating the UI the user is looking at, or parallel children fighting +// over the one embedded browser pane, is broken behavior, not just noise. +// All remain available via an explicit `tools` selection. const DEFAULT_PROFILE_EXCLUDED = new Set([ "executeCommand", // headless: no interactive approval "code_agent_run", // headless: needs interactive permission UI @@ -24,6 +27,8 @@ const DEFAULT_PROFILE_EXCLUDED = new Set([ "create-background-task", "patch-background-task", "run-live-note-agent", + "app-navigation", // shared surface: drives the UI the user is watching + "browser-control", // shared surface: the single embedded browser pane SPAWN_AGENT_TOOL_NAME, ]);