diff --git a/packages/coding-agent/src/prompts/tools/task.md b/packages/coding-agent/src/prompts/tools/task.md index 4b192a888..72fe69ac5 100644 --- a/packages/coding-agent/src/prompts/tools/task.md +++ b/packages/coding-agent/src/prompts/tools/task.md @@ -7,11 +7,10 @@ Execution blocks your turn: the call only returns once the work is completely fi - **Sequence only when necessary:** The only reason to run A before B is if B strictly requires A's output to function (e.g., a core API contract or schema migration). {{#if ircEnabled}}If the missing piece is small, run them in parallel and have B ask A via `irc`!{{/if}} - **Role matching:** Assign each subagent a specific `role` (e.g. "Security Reviewer", "DB Migrator"). Do not spawn generic workers. - **No overhead:** Each assignment MUST instruct its agent to skip formatters, linters, and project-wide test suites. You will run those once at the end. -- **Do your own thinking:** NEVER assign reasoning, architecture, or design to `quick_task` or `explore`. They are for mechanical lookups only. Keep hard decisions in your own context or use `task`, `plan`, or `oracle`. - **One-pass agents:** Prefer agents that investigate **and** edit in a single pass; only spin a read-only discovery step (e.g. `explore`) when the affected files are genuinely unknown. # Inputs -- `agent`: The base agent type to use (e.g., `task`, `explore`). +- `agent` (optional): The base agent type to use (e.g., `explore`, `reviewer`). Defaults to `task` (the general-purpose worker) — omit it for the default worker instead of passing `agent: "task"`. {{#if batchEnabled}} - `context`: Shared project state, constraints, and contracts. Applies to the entire batch; do not duplicate this background into individual tasks. - `tasks[]`: Array of subagents to spawn. @@ -37,13 +36,7 @@ Subagents start blank. They have no access to your conversation history. {{#if batchEnabled}} - Pass large payloads using `local://` URIs, never inline text. {{else}} -- *Note: The single-spawn shape has no `context` field.* Write shared project state ONCE to a `local://` file (e.g., `local://ctx.md`) and reference that URL in your assignments. Pass large payloads using `local://` URIs, never inline text. -{{/if}} -{{#if ircEnabled}} -- Once spawned, coordinate with live agents via `irc` using their IDs. If task B depends on task A, B SHOULD message A directly. -{{/if}} -{{#if asyncEnabled}} -- If you run out of things to do and are genuinely blocked waiting for a subagent, use `job poll`. Use `job cancel` only for stalled work. +- Write shared project state ONCE to a `local://` file (e.g., `local://ctx.md`) and reference that URL in your assignments. {{/if}} # Format Contracts diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts index 76bf4e034..d0f718f00 100644 --- a/packages/coding-agent/src/task/executor.ts +++ b/packages/coding-agent/src/task/executor.ts @@ -87,7 +87,7 @@ const MCP_CALL_TIMEOUT_MS = 60_000; */ export const SOFT_REQUEST_BUDGET: Record = { explore: 40, - quick_task: 40, + sonic: 40, default: 90, }; diff --git a/packages/coding-agent/src/task/index.ts b/packages/coding-agent/src/task/index.ts index d61208f65..c48c7e876 100644 --- a/packages/coding-agent/src/task/index.ts +++ b/packages/coding-agent/src/task/index.ts @@ -243,8 +243,11 @@ function validateShapeParams(batchEnabled: boolean, params: TaskParams): string } /** - * Validate the spawn parameter contract against the wire shapes. `agent` is - * always required. With `task.batch` the model-facing shape is + * Validate the spawn parameter contract against the wire shapes. `agent` + * defaults to `task` (the schema default; `execute` normalizes the same way for + * direct callers), so the missing-`agent` guard only fires for callers that + * invoke this validator with an unnormalized blank agent. With `task.batch` the + * model-facing shape is * `{ agent, context, tasks[] }` — `tasks` non-empty with per-item assignments * and unique ids, `context` non-empty, no top-level `assignment` alongside. * The flat `{ agent, ...item }` form stays accepted at runtime under either @@ -328,14 +331,17 @@ function spawnParamsFor(params: TaskParams, item: TaskItem): TaskParams { return spawn; } +/** Agent type spawned when a `task` call omits `agent`; mirrors the schema default in `getTaskSchema`. */ +const DEFAULT_TASK_AGENT = "task"; + /** Generic worker agents whose output sharpens with a tailored `role` rather than the bare type. */ -const GENERIC_SPAWN_AGENTS: ReadonlySet = new Set(["task", "quick_task"]); +const GENERIC_SPAWN_AGENTS: ReadonlySet = new Set(["task", "sonic"]); /** * Advisory — never a rejection — nudging the spawner toward tailored * specialists when it spawns generic role-less workers and still holds spawn * capacity (DepthCapacity: it currently has the `task` tool). Fires when a - * generic `task`/`quick_task` spawn carries no `role`, or when one call clones + * generic `task`/`sonic` spawn carries no `role`, or when one call clones * the same agent ≥2× all without roles. Returns undefined when no nudge applies. */ export function buildSpecializationAdvisory( @@ -559,7 +565,14 @@ export class TaskTool implements AgentTool, ): Promise> { - const params = repairTaskParams(rawParams as TaskParams); + const repaired = repairTaskParams(rawParams as TaskParams); + // The schema defaults `agent` to `task` for model calls, but internal + // callers and stale transcripts build params directly and bypass arktype. + // Normalize once here so every downstream path sees the resolved agent. + const params = + typeof repaired.agent === "string" && repaired.agent.trim() !== "" + ? repaired + : { ...repaired, agent: DEFAULT_TASK_AGENT }; const batchEnabled = this.#isBatchEnabled(); const validationError = validateShapeParams(batchEnabled, params) ?? validateSpawnParams(params, batchEnabled); if (validationError) { diff --git a/packages/coding-agent/src/task/types.ts b/packages/coding-agent/src/task/types.ts index adb3b4647..40faf390c 100644 --- a/packages/coding-agent/src/task/types.ts +++ b/packages/coding-agent/src/task/types.ts @@ -111,7 +111,7 @@ export interface TaskItem { } export const taskSchema = type({ - agent: "string", + agent: "string = 'task'", "id?": "string", "description?": "string", "role?": ROLE_INPUT_SCHEMA, @@ -120,7 +120,7 @@ export const taskSchema = type({ "+": "delete", }); const taskSchemaNoIsolation = type({ - agent: "string", + agent: "string = 'task'", "id?": "string", "description?": "string", "role?": ROLE_INPUT_SCHEMA, @@ -128,13 +128,13 @@ const taskSchemaNoIsolation = type({ "+": "delete", }); const taskSchemaBatch = type({ - agent: "string", + agent: "string = 'task'", context: "string", tasks: taskItemSchemaIsolated.array(), "+": "delete", }); const taskSchemaBatchNoIsolation = type({ - agent: "string", + agent: "string = 'task'", context: "string", tasks: taskItemSchema.array(), "+": "delete", @@ -160,7 +160,7 @@ export function getTaskSchema(options: { isolationEnabled: boolean; batchEnabled * transcripts using the flat form keep working under either setting. */ export interface TaskParams { - /** Agent type; required. */ + /** Agent type to spawn; defaults to `"task"` (the general-purpose worker) when omitted. */ agent?: string; /** Stable agent id (flat form); default = generated AdjectiveNoun. */ id?: string;