Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/developers/tools/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Use `agent` to launch a specialized subagent to handle complex, multi-step tasks
- `prompt` (string, required): The detailed task prompt for the subagent to execute. Should contain comprehensive instructions for autonomous execution.
- `subagent_type` (string, optional): The type of specialized agent to use for this task. Defaults to `general-purpose` if omitted.
- `fork_turns` (string, optional): Only valid with `subagent_type="fork"`. Omit it or use `all` for the full parent conversation, or use a positive integer string such as `"3"` for the most recent three real user turns. Tool responses and pure system reminders do not count as turns.
- `run_in_background` (boolean, optional): Defaults to `true` for top-level one-shot agents. Set to `false` to wait for the result inline. Nested agents run in the foreground. Caller-owned `working_dir` launches default to foreground and reject explicit or configured background execution.
- `run_in_background` (boolean, optional): Defaults to `true` for top-level one-shot agents. Set to `false` to wait for the result inline. Nested agents run in the foreground unless `run_in_background` is explicitly `true`, which is rejected because nested agents cannot receive background completion notifications. Caller-owned `working_dir` launches default to foreground and reject explicit or configured background execution.
- `isolation` (string, optional): Set to `"worktree"` to run the agent in an isolated git worktree.

## How to use `agent` with Qwen Code
Expand Down
30 changes: 20 additions & 10 deletions packages/core/src/tools/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,9 @@ describe('AgentTool', () => {
expect(properties.properties.run_in_background.description).toContain(
'interactive fork',
);
expect(properties.properties.run_in_background.description).toContain(
'Nested agents run in the foreground unless run_in_background is explicitly true',
);
});

it('declares fork_turns for fork agents without a none option', () => {
Expand Down Expand Up @@ -1329,6 +1332,7 @@ describe('AgentTool', () => {
description: 'Fork from a nested sub-agent',
prompt: 'Do work',
subagent_type: 'fork',
run_in_background: true,
});
const result = await runWithAgentContext('sub-1', () =>
invocation.execute(new AbortController().signal),
Expand Down Expand Up @@ -5058,12 +5062,10 @@ describe('AgentTool', () => {
);
});

it('downgrades a background request from a nested sub-agent to an awaited foreground run', async () => {
it('rejects an explicit background request from a nested sub-agent', async () => {
// Background delegation is top-level-only in v1: a nested launcher
// cannot honor the background completion contract (send_message /
// task_stop are excluded from its toolset, and completion
// notifications go to the top-level session). The run must complete
// inline instead of orphaning the child's results.
// cannot honor the completion contract. Do not silently turn an
// explicit background request into an awaited foreground run.
vi.mocked(config.getMaxSubagentDepth).mockReturnValue(5);
const params: AgentParams = {
description: 'Start monitor from a nested sub-agent',
Expand All @@ -5080,11 +5082,19 @@ describe('AgentTool', () => {
);

const llmText = partToString(result.llmContent);
expect(llmText).not.toContain('Background agent launched');
expect(llmText).toContain('Monitor done');
expect(mockRegistry.register).toHaveBeenCalledWith(
expect.objectContaining({ isBackgrounded: false }),
);
expect(llmText).toContain('run_in_background: true');
expect(llmText).toContain('not supported from within a sub-agent');
expect(result.error?.message).toBe(llmText);
expect(result.returnDisplay).toMatchObject({
type: 'task_execution',
status: 'failed',
subagentName: 'monitor',
});
expect(mockSubagentManager.loadSubagent).not.toHaveBeenCalled();
expect(mockSubagentManager.createAgentHeadless).not.toHaveBeenCalled();
expect(mockAgent.execute).not.toHaveBeenCalled();
expect(mockRegistry.register).not.toHaveBeenCalled();
expect(mockRegistry.tryReserveBackgroundSlot).not.toHaveBeenCalled();
});

it('keeps an omitted background flag in the foreground for nested sub-agents', async () => {
Expand Down
18 changes: 14 additions & 4 deletions packages/core/src/tools/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ export class AgentTool extends BaseDeclarativeTool<AgentParams, ToolResult> {
type: 'boolean',
default: true,
description:
'Defaults to true for top-level regular subagents. Set to false to run a regular agent in the foreground and return its result inline. Set to true for an interactive fork to receive its completion notification; headless forks always run in the background. Nested agents run in the foreground. Caller-owned working_dir launches default to foreground and cannot run in the background.',
'Defaults to true for top-level regular subagents. Set to false to run a regular agent in the foreground and return its result inline. Set to true for an interactive fork to receive its completion notification; headless forks always run in the background. Nested agents run in the foreground unless run_in_background is explicitly true, which is rejected because they cannot receive background completion notifications. Caller-owned working_dir launches default to foreground and cannot run in the background.',
},
...(config.isAgentTeamEnabled()
? {
Expand Down Expand Up @@ -911,7 +911,7 @@ Usage notes:
- Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
- If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
- If the user asks for agents "in parallel", group independent launches in a single message with multiple Agent tool use content blocks. Do not parallelize overlapping code changes.
- Top-level regular subagents run in the background by default. Set \`run_in_background: false\` when the current turn must wait for the result before continuing. Nested agent launches run in the foreground and return to their direct parent, so the main agent cannot independently address them as background tasks. Caller-owned \`working_dir\` launches default to foreground and cannot run in the background.
- Top-level regular subagents run in the background by default. Set \`run_in_background: false\` when the current turn must wait for the result before continuing. Nested agent launches run in the foreground and return to their direct parent; an explicit \`run_in_background: true\` request is rejected because nested agents cannot receive background completion notifications. Caller-owned \`working_dir\` launches default to foreground and cannot run in the background.
- You can optionally set \`isolation: "worktree"\` to run the agent in a temporary git worktree, giving it an isolated copy of the repository. The worktree is automatically cleaned up if the agent makes no changes; if changes are made, the worktree path and branch are returned in the result so you can review or merge them.
## When to fork

Expand Down Expand Up @@ -2363,6 +2363,15 @@ class AgentToolInvocation extends BaseToolInvocation<AgentParams, ToolResult> {
'Nested forks are not supported',
);
}
if (this.params.run_in_background === true && !isTopLevelSession()) {
debugLogger.debug(
'[AgentTool] Explicit background request rejected because background agents do not nest.',
);
return this.buildSpawnBlockedResult(
'Error: run_in_background: true is not supported from within a sub-agent. Run this agent in the foreground by omitting run_in_background or setting it to false.',
'Background execution is not supported from a nested sub-agent',
);
}
const isFork = isForkRequested;
if (isFork) {
debugLogger.debug(
Expand Down Expand Up @@ -2447,8 +2456,9 @@ class AgentToolInvocation extends BaseToolInvocation<AgentParams, ToolResult> {
// BackgroundTaskRegistry's single session-level notification callback
// would inject the child's completion into the top-level conversation
// while the launcher (typically finished by then) never hears back.
// Downgrade to an awaited foreground run instead of orphaning the
// child's results.
// Implicit background requests downgrade to an awaited foreground run
// instead of orphaning the child's results. The runtime spawn guard
// above rejects an explicit run_in_background: true request.
const backgroundRequested =
isFork && !this.config.isInteractive()
? true
Expand Down
Loading