From 2e20d871514f6ed9da538cb6897030c9183c9f38 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 26 Feb 2026 20:47:54 +0100 Subject: [PATCH] feat(coding-agent): simplified skill management by removing per-task pinning - Removed per-task skill pinning feature; subagents now inherit session skill set instead of per-task selection. - Removed `preloadedSkills` option from `CreateAgentSessionOptions` interface and related system prompt plumbing. - Removed `skills` field from Task schema; task execution now passes available session skills directly to subagents. - Simplified task execution pipeline by removing skill resolution logic and preloaded skills handling from system prompt templates. --- docs/skills.md | 11 +--- packages/coding-agent/CHANGELOG.md | 14 +++++ .../prompts/system/custom-system-prompt.md | 10 --- .../src/prompts/system/system-prompt.md | 8 --- .../coding-agent/src/prompts/tools/task.md | 1 - packages/coding-agent/src/sdk.ts | 4 -- packages/coding-agent/src/system-prompt.ts | 37 +---------- packages/coding-agent/src/task/executor.ts | 2 - packages/coding-agent/src/task/index.ts | 63 +++---------------- packages/coding-agent/src/task/template.ts | 6 +- packages/coding-agent/src/task/types.ts | 5 -- .../test/system-prompt-templates.test.ts | 1 - .../test/tools/task-template.test.ts | 10 --- 13 files changed, 29 insertions(+), 143 deletions(-) diff --git a/docs/skills.md b/docs/skills.md index 994630fd2..961f66ba8 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -122,19 +122,12 @@ For providers other than codex/claude/native (for example `agents`, `claude-plug System prompt construction (`src/system-prompt.ts`) uses discovered skills as follows: -- if `read` tool is available **and** no explicit preloaded skills are supplied: +- if `read` tool is available: - include discovered skills list in prompt - otherwise: - omit discovered list -- if preloaded skills are provided (for example from Task tool skill pinning): - - inline full preloaded skill contents in `` -### Task tool skill pinning - -When a Task call specifies `skills`, runtime resolves names against session skills: - -- unknown names cause an immediate error with available skill names -- resolved skills are passed as preloaded skills to subagents +Task tool subagents receive the session's discovered/provided skills list via normal session creation; there is no per-task skill pinning override. ### Interactive `/skill:` commands diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f59457513..fef9100b1 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,20 @@ # Changelog ## [Unreleased] +### Breaking Changes + +- Removed `preloadedSkills` option from `CreateAgentSessionOptions`; skills are no longer inlined into system prompts +- Removed `skills` field from Task schema; subagents now always inherit the session skill set instead of per-task skill selection +- Removed Task tool per-task `tasks[].skills` support; subagents now always inherit the session skill set +- Removed `preloadedSkills` system prompt plumbing and template sections; skills are no longer inlined as a separate preloaded block + +### Changed + +- Simplified task execution to always pass available session skills to subagents instead of resolving per-task skill lists + +### Removed + +- Removed preloaded skills section from system prompt templates; skills are now referenced only as available resources ## [13.3.6] - 2026-02-26 ### Breaking Changes diff --git a/packages/coding-agent/src/prompts/system/custom-system-prompt.md b/packages/coding-agent/src/prompts/system/custom-system-prompt.md index 74969e0c5..88326763f 100644 --- a/packages/coding-agent/src/prompts/system/custom-system-prompt.md +++ b/packages/coding-agent/src/prompts/system/custom-system-prompt.md @@ -40,16 +40,6 @@ If a skill covers your output, you **MUST** read `skill://` before proceed {{/list}} {{/if}} -{{#if preloadedSkills.length}} -Following skills are preloaded in full; you **MUST** apply instructions directly. - -{{#list preloadedSkills join="\n"}} - -{{content}} - -{{/list}} - -{{/if}} {{#if rules.length}} Rules are local constraints. You **MUST** read `rule://` when working in that domain. diff --git a/packages/coding-agent/src/prompts/system/system-prompt.md b/packages/coding-agent/src/prompts/system/system-prompt.md index f0f5e61d7..4753205b0 100644 --- a/packages/coding-agent/src/prompts/system/system-prompt.md +++ b/packages/coding-agent/src/prompts/system/system-prompt.md @@ -93,14 +93,6 @@ You **MUST** use the following skills, to save you time, when working in their d {{/each}} {{/if}} -{{#if preloadedSkills.length}} -Preloaded skills: -{{#each preloadedSkills}} -## {{name}} -{{content}} -{{/each}} -{{/if}} - {{#if rules.length}} # Rules Domain-specific rules from past experience. **MUST** read `rule://` when working in their territory. diff --git a/packages/coding-agent/src/prompts/tools/task.md b/packages/coding-agent/src/prompts/tools/task.md index d703725d3..86cf6bd67 100644 --- a/packages/coding-agent/src/prompts/tools/task.md +++ b/packages/coding-agent/src/prompts/tools/task.md @@ -12,7 +12,6 @@ Subagents lack your conversation history. Every decision, file content, and user - `.id`: CamelCase, max 32 chars - `.description`: UI display only — subagent never sees it - `.assignment`: Complete self-contained instructions. One-liners PROHIBITED; missing acceptance criteria = too vague. - - `.skills`: Skill names to preload - `context`: Shared background prepended to every assignment. Session-specific info only. - `schema`: JTD schema for expected output. Format lives here — **MUST NOT** be duplicated in assignments. - `tasks`: Tasks to execute in parallel. diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index 0a72053e0..cc67e8a2b 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -144,8 +144,6 @@ export interface CreateAgentSessionOptions { /** Skills. Default: discovered from multiple locations */ skills?: Skill[]; - /** Skills to inline into the system prompt instead of listing available skills. */ - preloadedSkills?: Skill[]; /** Rules. Default: discovered from multiple locations */ rules?: Rule[]; /** Context files (AGENTS.md content). Default: discovered walking up from cwd */ @@ -1128,7 +1126,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} const defaultPrompt = await buildSystemPromptInternal({ cwd, skills, - preloadedSkills: options.preloadedSkills, contextFiles, tools, toolNames, @@ -1147,7 +1144,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} return await buildSystemPromptInternal({ cwd, skills, - preloadedSkills: options.preloadedSkills, contextFiles, tools, toolNames, diff --git a/packages/coding-agent/src/system-prompt.ts b/packages/coding-agent/src/system-prompt.ts index 6c3f8c611..438ce2813 100644 --- a/packages/coding-agent/src/system-prompt.ts +++ b/packages/coding-agent/src/system-prompt.ts @@ -16,24 +16,6 @@ import { loadSkills, type Skill } from "./extensibility/skills"; import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" }; import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" }; -type PreloadedSkill = { name: string; content: string }; - -async function loadPreloadedSkillContents(preloadedSkills: Skill[]): Promise { - const contents = await Promise.all( - preloadedSkills.map(async skill => { - try { - const content = await Bun.file(skill.filePath).text(); - return { name: skill.name, content }; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - throw new Error(`Failed to load skill "${skill.name}" from ${skill.filePath}: ${message}`); - } - }), - ); - - return contents; -} - function firstNonEmpty(...values: (string | undefined | null)[]): string | null { for (const value of values) { const trimmed = value?.trim(); @@ -350,11 +332,9 @@ export interface BuildSystemPromptOptions { cwd?: string; /** Pre-loaded context files (skips discovery if provided). */ contextFiles?: Array<{ path: string; content: string; depth?: number }>; - /** Pre-loaded skills (skips discovery if provided). */ + /** Skills provided directly to system prompt construction. */ skills?: Skill[]; - /** Skills to inline into the system prompt instead of listing available skills. */ - preloadedSkills?: Skill[]; - /** Pre-loaded rulebook rules (rules with descriptions, excluding TTSR and always-apply). */ + /** Pre-loaded rulebook rules (descriptions, excluding TTSR and always-apply). */ rules?: Array<{ name: string; description?: string; path: string; globs?: string[] }>; /** Intent field name injected into every tool schema. If set, explains the field in the prompt. */ intentField?: string; @@ -378,13 +358,11 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): cwd, contextFiles: providedContextFiles, skills: providedSkills, - preloadedSkills: providedPreloadedSkills, rules, intentField, eagerTasks = false, } = options; const resolvedCwd = cwd ?? getProjectDir(); - const preloadedSkills = providedPreloadedSkills; const prepPromise = (() => { const systemPromptCustomizationPromise = logger.timeAsync("loadSystemPromptFiles", loadSystemPromptFiles, { @@ -400,9 +378,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): : skillsSettings?.enabled !== false ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills) : Promise.resolve([]); - const preloadedSkillContentsPromise = preloadedSkills - ? logger.timeAsync("loadPreloadedSkills", loadPreloadedSkillContents, preloadedSkills) - : []; return Promise.all([ resolvePromptInput(customPrompt, "system prompt"), @@ -411,7 +386,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): contextFilesPromise, agentsMdSearchPromise, skillsPromise, - preloadedSkillContentsPromise, ]).then( ([ resolvedCustomPrompt, @@ -420,7 +394,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): contextFiles, agentsMdSearch, skills, - preloadedSkillContents, ]) => ({ resolvedCustomPrompt, resolvedAppendPrompt, @@ -428,7 +401,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): contextFiles, agentsMdSearch, skills, - preloadedSkillContents, }), ); })(); @@ -451,7 +423,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): files: [], }; let skills: Skill[] = providedSkills ?? []; - let preloadedSkillContents: PreloadedSkill[] = []; if (prepResult.type === "timeout") { logger.warn("System prompt preparation timed out; using minimal startup context", { @@ -474,7 +445,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): contextFiles = prepResult.value.contextFiles; agentsMdSearch = prepResult.value.agentsMdSearch; skills = prepResult.value.skills; - preloadedSkillContents = prepResult.value.preloadedSkillContents; } const now = new Date(); @@ -517,7 +487,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): // Filter skills to only include those with read tool const hasRead = tools?.has("read"); - const filteredSkills = preloadedSkills === undefined && hasRead ? skills : []; + const filteredSkills = hasRead ? skills : []; const environment = await logger.timeAsync("getEnvironmentInfo", getEnvironmentInfo); const data = { @@ -531,7 +501,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): contextFiles, agentsMdSearch, skills: filteredSkills, - preloadedSkills: preloadedSkillContents, rules: rules ?? [], date, dateTime, diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts index 5ddd9c24c..211d74615 100644 --- a/packages/coding-agent/src/task/executor.ts +++ b/packages/coding-agent/src/task/executor.ts @@ -158,7 +158,6 @@ export interface ExecutorOptions { eventBus?: EventBus; contextFiles?: ContextFileEntry[]; skills?: Skill[]; - preloadedSkills?: Skill[]; promptTemplates?: PromptTemplate[]; mcpManager?: MCPManager; authStorage?: AuthStorage; @@ -950,7 +949,6 @@ export async function runSubprocess(options: ExecutorOptions): Promise renderPromptTemplate(subagentSystemPromptTemplate, { diff --git a/packages/coding-agent/src/task/index.ts b/packages/coding-agent/src/task/index.ts index 27bc4ae6d..e8134904f 100644 --- a/packages/coding-agent/src/task/index.ts +++ b/packages/coding-agent/src/task/index.ts @@ -690,58 +690,13 @@ export class TaskTool implements AgentTool { // Build full prompts with context prepended const tasksWithContext = tasksWithUniqueIds.map(t => renderTemplate(context, t)); + const availableSkills = [...(this.session.skills ?? [])]; const contextFiles = this.session.contextFiles; - const availableSkills = this.session.skills; - const availableSkillList = availableSkills ?? []; const promptTemplates = this.session.promptTemplates; - const skillLookup = new Map(availableSkillList.map(skill => [skill.name, skill])); - const missingSkillsByTask: Array<{ id: string; missing: string[] }> = []; - const tasksWithSkills = tasksWithContext.map(task => { - if (task.skills === undefined) { - return { ...task, resolvedSkills: availableSkills, preloadedSkills: undefined }; - } - const requested = task.skills; - const resolved = [] as typeof availableSkillList; - const missing: string[] = []; - const seen = new Set(); - for (const name of requested) { - const trimmed = name.trim(); - if (!trimmed || seen.has(trimmed)) continue; - seen.add(trimmed); - const skill = skillLookup.get(trimmed); - if (skill) { - resolved.push(skill); - } else { - missing.push(trimmed); - } - } - if (missing.length > 0) { - missingSkillsByTask.push({ id: task.id, missing }); - } - return { ...task, resolvedSkills: resolved, preloadedSkills: resolved }; - }); - - if (missingSkillsByTask.length > 0) { - const available = availableSkillList.map(skill => skill.name).join(", ") || "none"; - const details = missingSkillsByTask.map(entry => `${entry.id}: ${entry.missing.join(", ")}`).join("; "); - return { - content: [ - { - type: "text", - text: `Unknown skills requested: ${details}. Available skills: ${available}`, - }, - ], - details: { - projectAgentsDir, - results: [], - totalDurationMs: Date.now() - startTime, - }, - }; - } // Initialize progress for all tasks - for (let i = 0; i < tasksWithSkills.length; i++) { - const t = tasksWithSkills[i]; + for (let i = 0; i < tasksWithContext.length; i++) { + const t = tasksWithContext[i]; progressMap.set(i, { index: i, id: t.id, @@ -760,7 +715,7 @@ export class TaskTool implements AgentTool { } emitProgress(); - const runTask = async (task: (typeof tasksWithSkills)[number], index: number) => { + const runTask = async (task: (typeof tasksWithContext)[number], index: number) => { if (!isIsolated) { return runSubprocess({ cwd: this.session.cwd, @@ -791,8 +746,7 @@ export class TaskTool implements AgentTool { settings: this.session.settings, mcpManager: this.session.mcpManager, contextFiles, - skills: task.resolvedSkills, - preloadedSkills: task.preloadedSkills, + skills: availableSkills, promptTemplates, }); } @@ -842,8 +796,7 @@ export class TaskTool implements AgentTool { settings: this.session.settings, mcpManager: this.session.mcpManager, contextFiles, - skills: task.resolvedSkills, - preloadedSkills: task.preloadedSkills, + skills: availableSkills, promptTemplates, }); if (mergeMode === "branch" && result.exitCode === 0) { @@ -927,7 +880,7 @@ export class TaskTool implements AgentTool { // Execute in parallel with concurrency limit const { results: partialResults, aborted } = await mapWithConcurrencyLimit( - tasksWithSkills, + tasksWithContext, maxConcurrency, runTask, signal, @@ -938,7 +891,7 @@ export class TaskTool implements AgentTool { if (result !== undefined) { return result; } - const task = tasksWithSkills[index]; + const task = tasksWithContext[index]; return { index, id: task.id, diff --git a/packages/coding-agent/src/task/template.ts b/packages/coding-agent/src/task/template.ts index 310ee6eb6..053037d85 100644 --- a/packages/coding-agent/src/task/template.ts +++ b/packages/coding-agent/src/task/template.ts @@ -7,7 +7,6 @@ interface RenderResult { task: string; id: string; description: string; - skills?: string[]; } /** @@ -16,17 +15,16 @@ interface RenderResult { * If context is provided, it is prepended with a separator. */ export function renderTemplate(context: string | undefined, task: TaskItem): RenderResult { - let { id, description, assignment, skills } = task; + let { id, description, assignment } = task; assignment = assignment.trim(); context = context?.trim(); if (!context || !assignment) { - return { task: assignment || context!, id, description, skills }; + return { task: assignment || context!, id, description }; } return { task: renderPromptTemplate(subagentUserPromptTemplate, { context, assignment }), id, description, - skills, }; } diff --git a/packages/coding-agent/src/task/types.ts b/packages/coding-agent/src/task/types.ts index 80ea0da33..59c23e819 100644 --- a/packages/coding-agent/src/task/types.ts +++ b/packages/coding-agent/src/task/types.ts @@ -44,11 +44,6 @@ export const taskItemSchema = Type.Object({ description: "Complete per-task instructions the subagent executes. Must follow the Target/Change/Edge Cases/Acceptance structure. Only include per-task deltas — shared background belongs in `context`.", }), - skills: Type.Optional( - Type.Array(Type.String(), { - description: "Skill names to preload into the subagent. Use only where it changes correctness.", - }), - ), }); export type TaskItem = Static; diff --git a/packages/coding-agent/test/system-prompt-templates.test.ts b/packages/coding-agent/test/system-prompt-templates.test.ts index 588ca5bbe..19808ef56 100644 --- a/packages/coding-agent/test/system-prompt-templates.test.ts +++ b/packages/coding-agent/test/system-prompt-templates.test.ts @@ -41,7 +41,6 @@ const baseRenderContext: TemplateContext = { planContent: "1. Read code\n2. Add tests", planExists: true, planFilePath: "local://PLAN.md", - preloadedSkills: [{ name: "semantic-compression", content: "preloaded skill content" }], readFiles: ["packages/coding-agent/src/prompts/system/custom-system-prompt.md"], repeatToolDescriptions: true, reentry: false, diff --git a/packages/coding-agent/test/tools/task-template.test.ts b/packages/coding-agent/test/tools/task-template.test.ts index 8389b90f2..c55b6577f 100644 --- a/packages/coding-agent/test/tools/task-template.test.ts +++ b/packages/coding-agent/test/tools/task-template.test.ts @@ -43,14 +43,4 @@ describe("renderTemplate", () => { }); expect(result.task).toBe("just the assignment"); }); - - test("passes through skills", () => { - const result = renderTemplate(undefined, { - id: "X", - description: "label", - assignment: "do stuff", - skills: ["react", "postgres"], - }); - expect(result.skills).toEqual(["react", "postgres"]); - }); });