feat(task): added read-summarize override for subagents

- Parsed `read-summarize` frontmatter into `readSummarize` field.
- Applied `read.summarize.enabled: false` override on isolated subagent settings.
- Disabled summarization for `explore` and `librarian` agents.
This commit is contained in:
can1357
2026-06-05 11:36:13 +02:00
parent 2dab082a68
commit eb8e4f7657
7 changed files with 41 additions and 4 deletions
+2 -1
View File
@@ -24,7 +24,7 @@ It covers runtime behavior as implemented today, including precedence, invalid-d
Task agents normalize into `AgentDefinition` (`src/task/types.ts`):
- `name`, `description`, `systemPrompt` (required for a valid loaded agent)
- optional `tools`, `spawns`, `model`, `thinkingLevel`, `output`, `blocking`
- optional `tools`, `spawns`, `model`, `thinkingLevel`, `output`, `blocking`, `autoloadSkills`, `readSummarize`
- `source`: `"bundled" | "user" | "project"`
- optional `filePath`
@@ -35,6 +35,7 @@ Parsing comes from frontmatter via `parseAgentFields()` (`src/discovery/helpers.
- `spawns` accepts `*`, CSV, or array
- backward-compat behavior: if `spawns` missing but `tools` includes `task`, `spawns` becomes `*`
- `output` is passed through as opaque schema data
- `read-summarize: false` (parsed as `readSummarize`) forces the subagent's `read` tool to return verbatim file content instead of structural summaries — `runSubprocess` applies it as a `read.summarize.enabled: false` override on the subagent's isolated settings (`src/task/executor.ts`). `explore` and `librarian` ship with it disabled. Defaults to enabled when the field is absent.
## Bundled agents
@@ -212,6 +212,7 @@ export interface ParsedAgentFields {
output?: unknown;
thinkingLevel?: ThinkingLevel;
autoloadSkills?: string[];
readSummarize?: boolean;
blocking?: boolean;
}
@@ -265,10 +266,11 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
const thinkingLevel = parseThinkingLevel(rawThinkingLevel);
const model = parseModelList(frontmatter.model);
const blocking = parseBoolean(frontmatter.blocking);
const readSummarize = parseBoolean(frontmatter.readSummarize);
const autoloadSkills = parseArrayOrCSV(frontmatter.autoloadSkills)
?.map(s => s.trim())
.filter(Boolean);
return { name, description, tools, spawns, model, output, thinkingLevel, blocking, autoloadSkills };
return { name, description, tools, spawns, model, output, thinkingLevel, blocking, autoloadSkills, readSummarize };
}
async function globIf(
@@ -4,6 +4,7 @@ description: Fast read-only codebase scout returning compressed context for hand
tools: read, search, find, web_search
model: pi/smol
thinking-level: med
read-summarize: false
output:
properties:
summary:
@@ -4,6 +4,7 @@ description: Researches external libraries and APIs by reading source code. Retu
tools: read, search, find, bash, lsp, web_search, ast_grep
model: pi/smol
thinking-level: minimal
read-summarize: false
output:
properties:
answer:
+9 -2
View File
@@ -531,7 +531,10 @@ function createMCPProxyTools(mcpManager: MCPManager): CustomTool[] {
});
}
function createSubagentSettings(baseSettings: Settings): Settings {
function createSubagentSettings(
baseSettings: Settings,
overrides?: Partial<Record<SettingPath, unknown>>,
): Settings {
const snapshot: Partial<Record<SettingPath, unknown>> = {};
for (const key of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
snapshot[key] = baseSettings.get(key);
@@ -545,6 +548,7 @@ function createSubagentSettings(baseSettings: Settings): Settings {
// the parent task approval is the authorization boundary. Use yolo mode
// to preserve unattended subagent execution. User `tools.approval` policies still apply.
"tools.approvalMode": "yolo",
...overrides,
});
}
@@ -619,7 +623,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
}
const settings = options.settings ?? Settings.isolated();
const subagentSettings = createSubagentSettings(settings);
const subagentSettings = createSubagentSettings(
settings,
agent.readSummarize === false ? { "read.summarize.enabled": false } : undefined,
);
const maxRecursionDepth = settings.get("task.maxRecursionDepth") ?? 2;
const maxRuntimeMs = Math.max(0, Math.trunc(Number(settings.get("task.maxRuntimeMs") ?? 0) || 0));
const parentDepth = options.taskDepth ?? 0;
+2
View File
@@ -174,6 +174,8 @@ export interface AgentDefinition {
output?: unknown;
blocking?: boolean;
autoloadSkills?: string[];
/** When `false`, the agent's `read` tool returns verbatim file content instead of structural summaries. */
readSummarize?: boolean;
source: AgentSource;
filePath?: string;
}
@@ -109,4 +109,27 @@ describe("parseAgentFields", () => {
expect(fields).toBeDefined();
expect(fields?.autoloadSkills).toBeUndefined();
});
test("parses readSummarize from boolean frontmatter", () => {
expect(parseAgentFields({ name: "explore", description: "desc", readSummarize: false })?.readSummarize).toBe(
false,
);
expect(parseAgentFields({ name: "explore", description: "desc", readSummarize: true })?.readSummarize).toBe(
true,
);
});
test("parses readSummarize from string frontmatter", () => {
expect(parseAgentFields({ name: "explore", description: "desc", readSummarize: "false" })?.readSummarize).toBe(
false,
);
});
test("ignores invalid readSummarize values", () => {
expect(parseAgentFields({ name: "explore", description: "desc", readSummarize: "nope" })?.readSummarize).toBeUndefined();
});
test("returns undefined readSummarize when field absent", () => {
expect(parseAgentFields({ name: "explore", description: "desc" })?.readSummarize).toBeUndefined();
});
});