diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md
index 20e7bc78e..1282f292a 100644
--- a/packages/coding-agent/CHANGELOG.md
+++ b/packages/coding-agent/CHANGELOG.md
@@ -1,9 +1,11 @@
# Changelog
## [Unreleased]
-
### Added
+- Added `task.maxRecursionDepth` setting to control how many levels deep subagents can spawn their own subagents (0=none, 1=one level, 2=two levels, -1=unlimited)
+- Added support for nested task artifact naming with parent task prefixes (e.g., "0-Auth.1-Subtask") to organize hierarchical task outputs
+- Added `taskDepth` and `parentTaskPrefix` options to `CreateAgentSessionOptions` for tracking subagent recursion depth and organizing nested artifacts
- Added Grafana Pyroscope continuous profiling integration for CPU and heap profiling
- Added `pyroscope.enabled`, `pyroscope.serverAddress`, `pyroscope.appName`, `pyroscope.basicAuthUser`, `pyroscope.basicAuthPassword`, `pyroscope.tenantID`, and `pyroscope.flushIntervalMs` settings for profiling configuration
- Added support for Pyroscope configuration via environment variables (`PYROSCOPE_URL`, `PYROSCOPE_APP_NAME`, `PYROSCOPE_BASIC_AUTH_USER`, `PYROSCOPE_BASIC_AUTH_PASSWORD`, `PYROSCOPE_TENANT_ID`, `PYROSCOPE_FLUSH_INTERVAL_MS`)
@@ -13,6 +15,9 @@
### Changed
+- Changed task tool spawns configuration from "explore" to "*" to allow subagents to spawn any agent type
+- Changed system prompt to enable parallel delegation guidance for all agents (removed coordinator-only restriction)
+- Changed task tool to automatically disable itself when maximum recursion depth is reached, preventing infinite nesting
- Changed task concurrency from hardcoded constant to configurable setting via `task.maxConcurrency`
- Changed concurrency limit calculation to support unlimited concurrency when set to 0
@@ -22,6 +27,7 @@
### Fixed
+- Fixed task ID display formatting to show hierarchical structure for nested tasks (e.g., "0.1 Auth>Subtask" instead of "0-Auth.1-Subtask")
- Improved frontmatter parsing error messages to include source context for better debugging
## [10.6.1] - 2026-02-04
diff --git a/packages/coding-agent/src/config/settings-schema.ts b/packages/coding-agent/src/config/settings-schema.ts
index d1ae1905b..79d176b43 100644
--- a/packages/coding-agent/src/config/settings-schema.ts
+++ b/packages/coding-agent/src/config/settings-schema.ts
@@ -410,6 +410,17 @@ export const SETTINGS_SCHEMA = {
submenu: true,
},
},
+ "task.maxRecursionDepth": {
+ type: "number",
+ default: 2,
+ ui: {
+ tab: "tools",
+ label: "Task max recursion depth",
+ description:
+ "How many levels deep subagents can spawn their own subagents (0=none, 1=one level, 2=two levels, -1=unlimited)",
+ submenu: true,
+ },
+ },
// ─────────────────────────────────────────────────────────────────────────
// Startup settings
diff --git a/packages/coding-agent/src/prompts/system/system-prompt.md b/packages/coding-agent/src/prompts/system/system-prompt.md
index 4f6657a59..9d085e98f 100644
--- a/packages/coding-agent/src/prompts/system/system-prompt.md
+++ b/packages/coding-agent/src/prompts/system/system-prompt.md
@@ -281,7 +281,6 @@ Suppress:
{{appendSystemPrompt}}
{{/if}}
-{{#if isCoordinator}}
{{#has tools "task"}}
When work forks, you fork.
@@ -290,7 +289,7 @@ Notice sequential habit:
- Comfort: one thing at a time
- Illusion: order = correctness
- Assumption: finish A before starting B
-**Triggers requiring Task tool:**
+ **Triggers requiring Task tool:**
- Editing 4+ files with no dependencies between edits
- Investigating 2+ independent subsystems or questions
- Work decomposing into pieces not needing each other's results
@@ -303,7 +302,6 @@ If cannot articulate why B depends on A: parallel.
Don't carry whole problem in one skull. Split load; bring facts back; then cut code.
{{/has}}
-{{/if}}
This is not practice.
diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts
index 72269ad54..cee786723 100644
--- a/packages/coding-agent/src/sdk.ts
+++ b/packages/coding-agent/src/sdk.ts
@@ -183,6 +183,10 @@ export interface CreateAgentSessionOptions {
outputSchema?: unknown;
/** Whether to include the submit_result tool by default */
requireSubmitResultTool?: boolean;
+ /** Task recursion depth (for subagent sessions). Default: 0 */
+ taskDepth?: number;
+ /** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
+ parentTaskPrefix?: string;
/** Session manager. Default: SessionManager.create(cwd) */
sessionManager?: SessionManager;
@@ -728,6 +732,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
eventBus,
outputSchema: options.outputSchema,
requireSubmitResultTool: options.requireSubmitResultTool,
+ taskDepth: options.taskDepth ?? 0,
getSessionFile: () => sessionManager.getSessionFile() ?? null,
getSessionId: () => sessionManager.getSessionId?.() ?? null,
getSessionSpawns: () => options.spawns ?? "*",
@@ -772,7 +777,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
);
toolSession.internalRouter = internalRouter;
toolSession.getArtifactsDir = getArtifactsDir;
- toolSession.agentOutputManager = new AgentOutputManager(getArtifactsDir);
+ toolSession.agentOutputManager = new AgentOutputManager(
+ getArtifactsDir,
+ options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
+ );
debugStartup("sdk:createTools:start");
// Create and wrap tools with meta notice formatting
@@ -996,7 +1004,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
toolNames,
rules: rulebookRules,
skillsSettings: settingsInstance.getGroup("skills") as SkillsSettings,
- isCoordinator: options.hasUI,
});
if (options.systemPrompt === undefined) {
@@ -1013,7 +1020,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
rules: rulebookRules,
skillsSettings: settingsInstance.getGroup("skills") as SkillsSettings,
customPrompt: options.systemPrompt,
- isCoordinator: options.hasUI,
});
}
return options.systemPrompt(defaultPrompt);
diff --git a/packages/coding-agent/src/system-prompt.ts b/packages/coding-agent/src/system-prompt.ts
index d718320c6..a1f51b664 100644
--- a/packages/coding-agent/src/system-prompt.ts
+++ b/packages/coding-agent/src/system-prompt.ts
@@ -444,8 +444,6 @@ export interface BuildSystemPromptOptions {
preloadedSkills?: Skill[];
/** Pre-loaded rulebook rules (rules with descriptions, excluding TTSR and always-apply). */
rules?: Array<{ name: string; description?: string; path: string; globs?: string[] }>;
- /** Whether this is the main coordinator agent (not a subagent). Enables parallel delegation emphasis. */
- isCoordinator?: boolean;
}
/** Build the system prompt with tools, guidelines, and context */
@@ -465,7 +463,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
skills: providedSkills,
preloadedSkills: providedPreloadedSkills,
rules,
- isCoordinator,
} = options;
const resolvedCwd = cwd ?? process.cwd();
const resolvedCustomPrompt = await resolvePromptInput(customPrompt, "system prompt");
@@ -533,7 +530,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
rules: rules ?? [],
dateTime,
cwd: resolvedCwd,
- isCoordinator: isCoordinator ?? false,
});
}
@@ -550,6 +546,5 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
dateTime,
cwd: resolvedCwd,
appendSystemPrompt: resolvedAppendPrompt ?? "",
- isCoordinator: isCoordinator ?? false,
});
}
diff --git a/packages/coding-agent/src/task/agents.ts b/packages/coding-agent/src/task/agents.ts
index 12f3baf04..6cf8d02ad 100644
--- a/packages/coding-agent/src/task/agents.ts
+++ b/packages/coding-agent/src/task/agents.ts
@@ -46,7 +46,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
frontmatter: {
name: "task",
description: "General-purpose subagent with full capabilities for delegated multi-step tasks",
- spawns: "explore",
+ spawns: "*",
model: "default",
},
template: taskMd,
diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts
index 2930f7e0e..e80bdaabd 100644
--- a/packages/coding-agent/src/task/executor.ts
+++ b/packages/coding-agent/src/task/executor.ts
@@ -157,6 +157,8 @@ export interface ExecutorOptions {
modelOverride?: string | string[];
thinkingLevel?: ThinkingLevel;
outputSchema?: unknown;
+ /** Parent task recursion depth (0 = top-level, 1 = first child, etc.) */
+ taskDepth?: number;
enableLsp?: boolean;
signal?: AbortSignal;
onProgress?: (progress: AgentProgress) => void;
@@ -428,17 +430,25 @@ export async function runSubprocess(options: ExecutorOptions): Promise= 0 && childDepth >= maxRecursionDepth;
+
// Add tools if specified
let toolNames: string[] | undefined;
if (agent.tools && agent.tools.length > 0) {
toolNames = agent.tools;
// Auto-include task tool if spawns defined but task not in tools
- if (agent.spawns !== undefined && !toolNames.includes("task")) {
+ if (agent.spawns !== undefined && !toolNames.includes("task") && !atMaxDepth) {
toolNames = [...toolNames, "task"];
}
}
- const settings = options.settings ?? Settings.isolated();
+ if (atMaxDepth && toolNames?.includes("task")) {
+ toolNames = toolNames.filter(name => name !== "task");
+ }
const pythonToolMode = settings.get("python.toolMode") ?? "both";
if (toolNames?.includes("exec")) {
const expanded = toolNames.filter(name => name !== "exec");
@@ -454,7 +464,13 @@ export async function runSubprocess(options: ExecutorOptions): Promise string | null;
+ readonly #parentPrefix: string | undefined;
- constructor(getArtifactsDir: () => string | null) {
+ constructor(getArtifactsDir: () => string | null, options?: { parentPrefix?: string }) {
this.#getArtifactsDir = getArtifactsDir;
+ this.#parentPrefix = options?.parentPrefix;
}
/**
@@ -41,12 +50,15 @@ export class AgentOutputManager {
return; // Directory doesn't exist yet
}
+ const pattern = this.#parentPrefix
+ ? new RegExp(`^${escapeRegExp(this.#parentPrefix)}\\.(\\d+)-.*\\.md$`)
+ : /^(\d+)-.*\.md$/;
+
let maxId = -1;
for (const file of files) {
- // Agent outputs are named: {index}-{id}.md (e.g., "0-AuthProvider.md")
- const match = file.match(/^(\d+)-.*\.md$/);
+ const match = file.match(pattern);
if (match) {
- const id = parseInt(match[1], 10);
+ const id = Number.parseInt(match[1], 10);
if (id > maxId) maxId = id;
}
}
@@ -61,7 +73,8 @@ export class AgentOutputManager {
*/
async allocate(id: string): Promise {
await this.#ensureInitialized();
- return `${this.#nextId++}-${id}`;
+ const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
+ return `${prefix}${this.#nextId++}-${id}`;
}
/**
@@ -72,7 +85,8 @@ export class AgentOutputManager {
*/
async allocateBatch(ids: string[]): Promise {
await this.#ensureInitialized();
- return ids.map(id => `${this.#nextId++}-${id}`);
+ const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
+ return ids.map(id => `${prefix}${this.#nextId++}-${id}`);
}
/**
diff --git a/packages/coding-agent/src/task/render.ts b/packages/coding-agent/src/task/render.ts
index 69f76a5d9..0d4293f6b 100644
--- a/packages/coding-agent/src/task/render.ts
+++ b/packages/coding-agent/src/task/render.ts
@@ -77,6 +77,18 @@ function formatJsonScalar(value: unknown, _theme: Theme): string {
return "";
}
+function formatTaskId(id: string): string {
+ const segments = id.split(".");
+ if (segments.length < 2) return id;
+
+ const parsed = segments.map(segment => segment.match(/^(\d+)-(.+)$/));
+ if (parsed.some(match => !match)) return id;
+
+ const indices = parsed.map(match => match![1]).join(".");
+ const labels = parsed.map(match => match![2]).join(">");
+ return `${indices} ${labels}`;
+}
+
const MISSING_SUBMIT_RESULT_WARNING_PREFIX = "SYSTEM WARNING: Subagent exited without calling submit_result tool";
function extractMissingSubmitResultWarning(output: string): { warning?: string; rest: string } {
@@ -518,7 +530,8 @@ function renderAgentProgress(
// Main status line: id: description [status] · stats · ⟨agent⟩
const description = progress.description?.trim();
- const titlePart = description ? `${theme.bold(progress.id)}: ${description}` : progress.id;
+ const displayId = formatTaskId(progress.id);
+ const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)}`;
// Only show badge for non-running states (spinner already indicates running)
@@ -748,7 +761,8 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
// Main status line: id: description [status] · stats · ⟨agent⟩
const description = result.description?.trim();
- const titlePart = description ? `${theme.bold(result.id)}: ${description}` : result.id;
+ const displayId = formatTaskId(result.id);
+ const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)} ${formatBadge(
statusText,
iconColor,
diff --git a/packages/coding-agent/src/tools/index.ts b/packages/coding-agent/src/tools/index.ts
index 110615510..a9aa27692 100644
--- a/packages/coding-agent/src/tools/index.ts
+++ b/packages/coding-agent/src/tools/index.ts
@@ -127,6 +127,8 @@ export interface ToolSession {
outputSchema?: unknown;
/** Whether to include the submit_result tool by default */
requireSubmitResultTool?: boolean;
+ /** Task recursion depth (0 = top-level, 1 = first child, etc.) */
+ taskDepth?: number;
/** Get session file */
getSessionFile: () => string | null;
/** Get session ID */
@@ -287,6 +289,11 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
if (name === "lsp") return session.settings.get("lsp.enabled");
if (name === "calc") return session.settings.get("calc.enabled");
if (name === "browser") return session.settings.get("browser.enabled");
+ if (name === "task") {
+ const maxDepth = session.settings.get("task.maxRecursionDepth") ?? 2;
+ const currentDepth = session.taskDepth ?? 0;
+ return maxDepth < 0 || currentDepth < maxDepth;
+ }
return true;
};
if (includeSubmitResult && requestedTools && !requestedTools.includes("submit_result")) {