Skip to content
Closed
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
130 changes: 130 additions & 0 deletions docs/users/features/sub-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,136 @@ Have the documentation-writer Subagents update the API reference
Get the react-specialist Subagents to optimize this component's performance
```

## Advanced Configuration

### Runtime Configuration Options

Subagents support advanced runtime configuration options that control context behavior and output formatting. These can be specified in the agent's configuration file or passed dynamically when delegating tasks.

#### `useCleanContext`

When enabled, the subagent starts with a fresh context window instead of inheriting the full main session history. This prevents context bloat during long sessions.

```yaml
---
name: focused-researcher
description: Researches topics without carrying main session context
runConfig:
useCleanContext: true
---
```

**Benefits:**

- Reduces token usage for focused tasks
- Prevents context pollution from unrelated conversations
- Improves performance for long-running sessions

#### `maxContextTokens`

Sets a maximum token budget for the subagent's context. When exceeded, older messages are truncated to fit within the budget.

```yaml
---
name: budget-conscious-agent
description: Works within strict token limits
runConfig:
maxContextTokens: 4000
---
```
Comment on lines +163 to +189
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The YAML examples document runConfig keys in camelCase (e.g. useCleanContext, maxContextTokens, useStructuredOutput) but the rest of RunConfig (and prior docs) use snake_case (e.g. max_turns, max_time_minutes). If the implementation expects one naming style, this doc will lead to misconfiguration. Align the examples with the actual supported key names (or explicitly document that both styles are accepted).

Copilot uses AI. Check for mistakes.

**Benefits:**

- Controls costs for expensive operations
- Ensures predictable token usage
- Automatically truncates old context when needed

#### `useStructuredOutput`

Instructs the subagent to format its output using a structured summary schema (findings, files changed, conclusion). This ensures only distilled summaries are injected back into the main context.

```yaml
---
name: concise-reporter
description: Provides structured, concise reports
runConfig:
useStructuredOutput: true
---
```

**Output Format:**
When enabled, the subagent formats results as:

```markdown
## Findings

- Key discovery 1
- Key discovery 2

## Files Changed

- path/to/modified/file.ts
- path/to/new/file.test.ts

## Conclusion

Concise summary of what was accomplished.

## Recommendations (Optional)

- Suggested next steps
```

### Dynamic Configuration

You can also pass runtime configuration overrides when delegating tasks:

```typescript
// Example: Pass runConfig when using Task tool programmatically
{
description: "Research with clean context",
prompt: "Find all usages of deprecated APIs",
subagent_type: "code-analyst",
runConfig: {
useCleanContext: true,
maxContextTokens: 2000,
useStructuredOutput: true
}
}
```

### Complete Example

```yaml
---
name: efficient-tester
description: Writes tests efficiently with controlled context and structured output
tools:
- read_file
- write_file
- read_many_files
- run_shell_command
modelConfig:
model: qwen3-coder-plus
temp: 0.7
runConfig:
useCleanContext: true
maxContextTokens: 8000
useStructuredOutput: true
max_turns: 10
max_time_minutes: 5
---

You are a testing specialist focused on efficient, targeted test creation.

Your approach:
1. Quickly identify the code under test
2. Create focused tests for key functionality
3. Report results in a structured format

Work efficiently within the token budget.
```

## Examples

### Development Workflow Agents
Expand Down
17 changes: 14 additions & 3 deletions packages/core/src/subagents/subagent-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,8 @@ export class SubagentManager {
*
* @param config - Subagent configuration
* @param runtimeContext - Runtime context
* @param options - Optional event emitter and hooks
* @param runConfigOverrides - Optional runtime configuration overrides
* @returns Promise resolving to SubAgentScope
*/
async createSubagentScope(
Expand All @@ -592,9 +594,13 @@ export class SubagentManager {
eventEmitter?: import('./subagent-events.js').SubAgentEventEmitter;
hooks?: import('./subagent-hooks.js').SubagentHooks;
},
runConfigOverrides?: Partial<RunConfig>,
): Promise<SubAgentScope> {
try {
const runtimeConfig = this.convertToRuntimeConfig(config);
const runtimeConfig = this.convertToRuntimeConfig(
config,
runConfigOverrides,
);

return await SubAgentScope.create(
config.name,
Expand Down Expand Up @@ -623,9 +629,13 @@ export class SubagentManager {
* compatible with SubAgentScope.create().
*
* @param config - File-based subagent configuration
* @param runConfigOverrides - Optional runtime configuration overrides
* @returns Runtime configuration for SubAgentScope
*/
convertToRuntimeConfig(config: SubagentConfig): SubagentRuntimeConfig {
convertToRuntimeConfig(
config: SubagentConfig,
runConfigOverrides?: Partial<RunConfig>,
): SubagentRuntimeConfig {
// Build prompt configuration
const promptConfig: PromptConfig = {
systemPrompt: config.systemPrompt,
Expand All @@ -636,9 +646,10 @@ export class SubagentManager {
...config.modelConfig,
};

// Build run configuration
// Build run configuration with overrides
const runConfig: RunConfig = {
...config.runConfig,
...runConfigOverrides,
};

// Build tool configuration if tools are specified
Expand Down
29 changes: 18 additions & 11 deletions packages/core/src/subagents/subagent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,24 @@ vi.mock('../core/contentGenerator.js', async (importOriginal) => {
});
vi.mock('../utils/environmentContext.js', () => ({
getEnvironmentContext: vi.fn().mockResolvedValue([{ text: 'Env Context' }]),
getInitialChatHistory: vi.fn(async (_config, extraHistory) => [
{
role: 'user',
parts: [{ text: 'Env Context' }],
},
{
role: 'model',
parts: [{ text: 'Got it. Thanks for the context!' }],
},
...(extraHistory ?? []),
]),
getInitialChatHistory: vi.fn(
async (
_config,
_useCleanContext = false,
_maxContextTokens,
extraHistory,
) => [
{
role: 'user',
parts: [{ text: 'Env Context' }],
},
{
role: 'model',
parts: [{ text: 'Got it. Thanks for the context!' }],
},
...(extraHistory ?? []),
],
),
}));
vi.mock('../core/nonInteractiveToolExecutor.js');
vi.mock('../ide/ide-client.js');
Expand Down
31 changes: 30 additions & 1 deletion packages/core/src/subagents/subagent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,12 @@ export class SubAgentScope {
);
}

const envHistory = await getInitialChatHistory(this.runtimeContext);
const useCleanContext = this.runConfig.useCleanContext ?? false;
const maxContextTokens = this.runConfig.maxContextTokens;
const envHistory = await getInitialChatHistory(this.runtimeContext, {
useCleanContext,
maxContextTokens,
});

const start_history = [
...envHistory,
Expand Down Expand Up @@ -999,6 +1004,30 @@ Important Rules:
- Use tools only when necessary to obtain facts or make changes.
- When the task is complete, return the final result as a normal model response (not a tool call) and stop.`;

// Add structured output instructions if enabled
if (this.runConfig.useStructuredOutput) {
finalPrompt += `

Output Format:
When you complete the task, you MUST format your response using the following structured schema:

## Findings
- List key discoveries, insights, or results here
- One finding per bullet point

## Files Changed
- List any files you created or modified (full paths)
- If no files were changed, write "None"

## Conclusion
Provide a concise summary of what was accomplished and the final result.

## Recommendations (Optional)
- Suggest next steps or follow-up actions if applicable

This structured format ensures your results can be efficiently summarized and injected back into the main conversation context.`;
}

// Append user memory (QWEN.md + output-language.md) to ensure subagent respects project conventions
const userMemory = this.runtimeContext.getUserMemory();
if (userMemory && userMemory.trim().length > 0) {
Expand Down
39 changes: 35 additions & 4 deletions packages/core/src/subagents/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,6 @@ export interface ToolConfig {
export interface ModelConfig {
/**
* The name or identifier of the model to be used (e.g., 'qwen3-coder-plus').
*
* TODO: In the future, this needs to support 'auto' or some other string to support routing use cases.
*/
model?: string;
/**
Expand All @@ -258,8 +256,6 @@ export interface ModelConfig {
* Configures the execution environment and constraints for the subagent.
* This interface defines parameters that control the subagent's runtime behavior,
* such as maximum execution time, to prevent infinite loops or excessive resource consumption.
*
* TODO: Consider adding max_tokens as a form of budgeting.
*/
export interface RunConfig {
/** The maximum execution time for the subagent in minutes. */
Expand All @@ -269,4 +265,39 @@ export interface RunConfig {
* before the execution is terminated. Helps prevent infinite loops.
*/
max_turns?: number;
/**
* When true, the subagent starts with a clean context window, not inheriting
* the main session's conversation history via getInitialChatHistory().
* Only environment context (working directory, date, OS) is provided.
* This prevents context bloat during long sessions.
*/
useCleanContext?: boolean;
/**
* Maximum number of tokens allowed for context injection.
* When exceeded, context is truncated to fit within this budget.
* If not specified, no token budget is enforced.
*/
maxContextTokens?: number;
/**
* When true, instructs the subagent to format its output using a structured
* summary schema (findings, files changed, conclusion). This ensures only
* distilled summaries are injected back into the main context.
*/
Comment on lines +272 to +285
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RunConfig uses snake_case keys for existing fields (max_time_minutes, max_turns), but the newly added fields use camelCase (useCleanContext, maxContextTokens, useStructuredOutput). This inconsistency makes YAML/frontmatter configuration error-prone and complicates validation/SDK schemas. Consider switching these to snake_case (or supporting both names with a clear precedence) to match existing RunConfig conventions.

Suggested change
* This prevents context bloat during long sessions.
*/
useCleanContext?: boolean;
/**
* Maximum number of tokens allowed for context injection.
* When exceeded, context is truncated to fit within this budget.
* If not specified, no token budget is enforced.
*/
maxContextTokens?: number;
/**
* When true, instructs the subagent to format its output using a structured
* summary schema (findings, files changed, conclusion). This ensures only
* distilled summaries are injected back into the main context.
*/
* This prevents context bloat during long sessions.
*
* Prefer using the snake_case `use_clean_context` key in YAML/frontmatter.
* The camelCase `useCleanContext` field is kept for backwards compatibility.
*/
use_clean_context?: boolean;
useCleanContext?: boolean;
/**
* Maximum number of tokens allowed for context injection.
* When exceeded, context is truncated to fit within this budget.
* If not specified, no token budget is enforced.
*
* Prefer using the snake_case `max_context_tokens` key in YAML/frontmatter.
* The camelCase `maxContextTokens` field is kept for backwards compatibility.
*/
max_context_tokens?: number;
maxContextTokens?: number;
/**
* When true, instructs the subagent to format its output using a structured
* summary schema (findings, files changed, conclusion). This ensures only
* distilled summaries are injected back into the main context.
*
* Prefer using the snake_case `use_structured_output` key in YAML/frontmatter.
* The camelCase `useStructuredOutput` field is kept for backwards compatibility.
*/
use_structured_output?: boolean;

Copilot uses AI. Check for mistakes.
useStructuredOutput?: boolean;
}

/**
* Structured summary of subagent output.
* When useStructuredOutput is enabled, the subagent should format its
* final output using this schema.
*/
export interface SubagentStructuredSummary {
/** Key findings discovered during subagent execution */
findings: string[];
/** List of files that were created or modified */
filesChanged?: string[];
/** Final conclusion or result of the subagent's work */
conclusion: string;
/** Optional: recommendations for next steps */
recommendations?: string[];
}
30 changes: 30 additions & 0 deletions packages/core/src/tools/task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ describe('TaskTool', () => {
mockSubagents[0],
config,
expect.any(Object), // eventEmitter parameter
undefined, // runConfigOverrides (undefined when not provided in params)
);
expect(mockSubagentScope.runNonInteractive).toHaveBeenCalledWith(
mockContextState,
Expand Down Expand Up @@ -534,5 +535,34 @@ describe('TaskTool', () => {

expect(description).toBe('file-search subagent: "Search files"');
});

it('should pass runConfig overrides to createSubagentScope', async () => {
const params: TaskParams = {
description: 'Search files with clean context',
prompt: 'Find all TypeScript files',
subagent_type: 'file-search',
runConfig: {
useCleanContext: true,
maxContextTokens: 1000,
useStructuredOutput: true,
},
};

const invocation = (
taskTool as TaskToolWithProtectedMethods
).createInvocation(params);
await invocation.execute();

expect(mockSubagentManager.createSubagentScope).toHaveBeenCalledWith(
mockSubagents[0],
config,
expect.any(Object),
expect.objectContaining({
useCleanContext: true,
maxContextTokens: 1000,
useStructuredOutput: true,
}),
);
});
});
});
Loading
Loading