docs(agent): clarify parameter preconditions (#9580)

* docs(agent): clarify parameter preconditions

* test(agent): cover read-only preconditions

* docs(agent): clarify nested background downgrade

* docs(agent): align working-dir background guidance

* docs(agent): clarify teammate worktree execution

---------

Co-authored-by: tao943 <278275162+tao943@users.noreply.github.com>
This commit is contained in:
tao943 2026-08-24 02:27:09 +00:00 committed by GitHub
parent 5cb946b55b
commit 3a9d2d37f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 71 additions and 8 deletions

View file

@ -16,9 +16,9 @@ Use `agent` to launch a specialized subagent to handle complex, multi-step tasks
- `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.
- `fork_tools` (array of strings, optional): Only valid with `subagent_type="fork"`. Restricts execution to exact canonical tool names or MCP server patterns while keeping the fork's current model-visible tool declarations unchanged for prompt-cache sharing. Entries cannot have surrounding whitespace; wildcards are limited to `mcp__*` or a trailing MCP tool-prefix pattern such as `mcp__github__read_*`. Forks never execute `ask_user_question`; omit `fork_tools` to allow every other inherited tool, or use an empty array to reject every tool call.
- `fork_profile` (string, optional): Only valid with `subagent_type="fork"`. Loads a frontmatter-only regular `.qwen/fork-profiles/<name>.md` of at most 64 KiB from the active project root and applies its required `tools` array plus an optional `promptHint` of at most 200 characters. The file cannot resolve outside the project profile directory. `fork_profile` cannot be combined with `fork_tools` or a named teammate, and it is unavailable in safe mode or bare mode.
- `run_in_background` (boolean, optional): Defaults to `true` for top-level regular agents. Set to `false` to wait for a regular agent's result inline. Headless forks always run in the background. 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 run in the foreground and reject explicit or configured background execution.
- `run_in_background` (boolean, optional): Defaults to `true` for top-level regular agents. Set to `false` to wait for a regular agent's result inline. Headless forks always run in the background. Nested agents run in the foreground unless `run_in_background` is explicitly `true`, which is rejected because nested agents cannot receive background completion notifications. Unnamed caller-owned `working_dir` launches run in the foreground: an explicit `run_in_background: true` request is rejected, while a configured background default (`background: true` in a subagent definition) is rejected at the top level and downgraded to the foreground when nested.
- `isolation` (string, optional): Set to `"worktree"` to run an explicitly named, non-fork agent in an isolated git worktree that Qwen Code creates and manages.
- `working_dir` (string, optional): Pin an explicitly named, non-fork agent to an existing registered git worktree inside the current repository. The caller owns the worktree lifecycle, so this mode runs in the foreground. If both `working_dir` and `isolation` are provided, `working_dir` takes precedence.
- `working_dir` (string, optional): Pin an explicitly named, non-fork agent to an existing registered git worktree inside the current repository. Unnamed launches run in the foreground because the caller owns the worktree lifecycle (see `run_in_background`); a named teammate pinned to one runs concurrently and must be shut down before the worktree is removed. If both `working_dir` and `isolation` are provided, `working_dir` takes precedence.
## How to use `agent` with Qwen Code

View file

@ -135,7 +135,7 @@ Use continuation for related follow-up work. Launch a new agent when the task is
For a named regular subagent, `working_dir` pins the agent to an existing git worktree of the current repository. Relative paths resolve from the current directory, and the worktree must already be registered with git as a linked worktree of this repository.
A `working_dir` launch runs in the foreground because Qwen Code does not own that worktree's lifecycle. It cannot be combined with `subagent_type: "fork"` or background execution. If both `working_dir` and `isolation: "worktree"` are supplied, Qwen Code reuses the caller-owned worktree instead of creating another one. Workflow scripts are deliberately stricter: a workflow `agent()` call that receives both `workingDir` and `isolation` is rejected rather than run with `isolation` ignored.
`working_dir` cannot be combined with `subagent_type: "fork"`. An unnamed caller-owned `working_dir` launch runs in the foreground because Qwen Code does not own that worktree's lifecycle: an explicit `run_in_background: true` request is rejected, while a configured background default (`background: true` in a subagent definition) is rejected at the top level and downgraded to the foreground when nested. If both `working_dir` and `isolation: "worktree"` are supplied, Qwen Code reuses the caller-owned worktree instead of creating another one. Workflow scripts are deliberately stricter: a workflow `agent()` call that receives both `workingDir` and `isolation` is rejected rather than run with `isolation` ignored.
## Getting Started

View file

@ -182,7 +182,7 @@ The `agent` tool accepts an optional `isolation: "worktree"` parameter. When set
Two constraints:
- `isolation: "worktree"` requires a non-fork `subagent_type` — forked sub-agents (`subagent_type: "fork"`) reuse the parent's full conversation context, so isolating them would split intent from working tree.
- Agents using `isolation: "worktree"` follow the default background behavior; the cleanup runs when the agent reports completion. Set `run_in_background: false` for an inline result. Caller-owned `working_dir` launches remain foreground by default because their lifecycle is managed externally.
- Agents using `isolation: "worktree"` follow the default background behavior; the cleanup runs when the agent reports completion. Set `run_in_background: false` for an inline result. Unnamed caller-owned `working_dir` launches run in the foreground; explicit background execution is rejected, while configured background execution (`background: true` in a subagent definition) is rejected at the top level and downgraded to a foreground run when nested because their lifecycle is managed externally.
### Automatic Stale Cleanup

View file

@ -399,6 +399,12 @@ describe('AgentTool', () => {
expect(tool.description).toContain(
'foreground regular agent returns its result inline',
);
expect(tool.description).toContain(
'Unnamed caller-owned `working_dir` launches run in the foreground: an explicit `run_in_background: true` request is rejected',
);
expect(tool.description).toContain(
'a configured background default (`background: true` in a subagent definition) is rejected at the top level and downgraded to the foreground for nested launches',
);
});
it('explains how to continue reusable background agents', async () => {
@ -536,6 +542,12 @@ describe('AgentTool', () => {
expect(properties.properties.run_in_background.description).toContain(
'an explicit false is rejected',
);
expect(properties.properties.run_in_background.description).toContain(
'explicit run_in_background: true is rejected',
);
expect(properties.properties.run_in_background.description).toContain(
'a configured background default is rejected at the top level and downgraded to the foreground for nested launches',
);
});
it('declares the optional todo association', () => {
@ -688,11 +700,23 @@ describe('AgentTool', () => {
plan_mode_required?: {
description?: string;
};
read_only?: {
description?: string;
};
};
};
expect(parameters.properties.plan_mode_required?.description).toContain(
'named teammate',
);
expect(parameters.properties.plan_mode_required?.description).toContain(
'Cannot be combined with read_only',
);
expect(parameters.properties.read_only?.description).toContain(
'named teammate in an active team',
);
expect(parameters.properties.read_only?.description).toContain(
'Cannot be combined with plan_mode_required',
);
vi.mocked(config.isAgentTeamEnabled).mockReturnValue(false);
const ordinaryAgentTool = new AgentTool(config);
@ -1396,6 +1420,42 @@ describe('AgentTool', () => {
).toBeNull();
});
it('rejects read_only without a named teammate', () => {
expect(
agentTool.validateToolParams({
...validParams,
read_only: true,
}),
).toMatch(/named teammate/i);
});
it('rejects read_only when no team is active', () => {
vi.mocked(config.getTeamManager).mockReturnValue(null);
expect(
agentTool.validateToolParams({
...validParams,
name: 'reader',
read_only: true,
}),
).toMatch(/active team/i);
});
it('rejects combining read_only with plan_mode_required', () => {
vi.mocked(config.getTeamManager).mockReturnValue({
spawnTeammate: vi.fn(),
} as never);
expect(
agentTool.validateToolParams({
...validParams,
name: 'reader',
read_only: true,
plan_mode_required: true,
}),
).toMatch(/cannot be used together/i);
});
it('accepts redundant isolation for a named worktree teammate', () => {
vi.mocked(config.getTeamManager).mockReturnValue({} as never);

View file

@ -283,7 +283,8 @@ const TEAM_AGENT_PLAN_REQUIRED_PROPERTY = {
description:
'When true, the named teammate starts in plan mode and must call ' +
'exit_plan_mode to request leader approval before executing. Only valid ' +
'with a named teammate in an active team.',
'with a named teammate in an active team. Cannot be combined with ' +
'read_only.',
};
const TEAM_AGENT_READ_ONLY_PROPERTY = {
@ -291,7 +292,9 @@ const TEAM_AGENT_READ_ONLY_PROPERTY = {
description:
'When true, the named teammate can only inspect the checkout and use ' +
'team coordination tools. Shell, file writes, memory, schedules, and ' +
'nested agents are blocked by an execution allowlist.',
'nested agents are blocked by an execution allowlist. Only valid with a ' +
'named teammate in an active team. Cannot be combined with ' +
'plan_mode_required.',
};
/**
@ -759,7 +762,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 unless run_in_background is explicitly true, which is rejected because they cannot receive background completion notifications. Unnamed caller-owned working_dir launches default to foreground. Named teammates are always concurrent and report through team messaging: omit run_in_background when spawning one — an explicit false is rejected; for an inline blocking result, omit "name" and run a regular agent with run_in_background: false. A teammate pinned to a caller-owned worktree must be shut down before that worktree is removed.',
'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. Unnamed caller-owned working_dir launches run in the foreground; explicit run_in_background: true is rejected, while a configured background default is rejected at the top level and downgraded to the foreground for nested launches because the caller owns the worktree lifecycle. A configured default comes from a subagent definition with background: true. Named teammates are always concurrent and report through team messaging: omit run_in_background when spawning one — an explicit false is rejected; for an inline blocking result, omit "name" and run a regular agent with run_in_background: false. A teammate pinned to a caller-owned worktree must be shut down before that worktree is removed.',
},
...(config.isAgentTeamEnabled()
? {
@ -881,7 +884,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; an explicit \`run_in_background: true\` request is rejected because nested agents cannot receive background completion notifications. Unnamed caller-owned \`working_dir\` launches default to foreground and cannot run in the background; named teammates may use one, but must be shut down before it is removed.
- 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. Unnamed caller-owned \`working_dir\` launches run in the foreground: an explicit \`run_in_background: true\` request is rejected, while a configured background default (\`background: true\` in a subagent definition) is rejected at the top level and downgraded to the foreground for nested launches; named teammates may use one, but must be shut down before it is removed.
- 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