diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md
index 34230fa70..4b6c20343 100644
--- a/packages/coding-agent/CHANGELOG.md
+++ b/packages/coding-agent/CHANGELOG.md
@@ -2,8 +2,13 @@
## [Unreleased]
+### Added
+- Added subagent support for preloading skill contents into the system prompt instead of listing available skills
+- Added session init entries to capture system prompt, task, tools, and output schema for subagent session logs
+
### Fixed
- Reduced Task tool progress update overhead to keep the UI responsive during high-volume streaming output
+- Fixed subagent session logs dropping pre-assistant entries (user/task metadata) before the first assistant response
### Removed
- Removed enter-plan-mode tool
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 72ab8250f..6f7a13efb 100644
--- a/packages/coding-agent/src/prompts/system/custom-system-prompt.md
+++ b/packages/coding-agent/src/prompts/system/custom-system-prompt.md
@@ -43,6 +43,20 @@ Use the read tool to load a skill's file when the task matches its description.
{{/list}}
{{/if}}
+{{#if preloadedSkills.length}}
+The following skills are preloaded in full. Apply their instructions directly.
+
+
+{{#list preloadedSkills join="\n"}}
+
+skill://{{escapeXml name}}
+
+{{content}}
+
+
+{{/list}}
+
+{{/if}}
{{#if rules.length}}
The following rules define project-specific guidelines and constraints:
diff --git a/packages/coding-agent/src/prompts/system/system-prompt.md b/packages/coding-agent/src/prompts/system/system-prompt.md
index 9e468a832..da877e145 100644
--- a/packages/coding-agent/src/prompts/system/system-prompt.md
+++ b/packages/coding-agent/src/prompts/system/system-prompt.md
@@ -264,6 +264,18 @@ If a skill covers what you're producing, read it before proceeding.
{{/list}}
{{/if}}
+{{#if preloadedSkills.length}}
+
+The following skills are preloaded in full. Apply their instructions directly.
+
+{{#list preloadedSkills join="\n"}}
+
+skill://{{escapeXml name}}
+{{content}}
+
+{{/list}}
+
+{{/if}}
{{#if rules.length}}
Rules are local constraints.
diff --git a/packages/coding-agent/src/prompts/tools/task.md b/packages/coding-agent/src/prompts/tools/task.md
index 3036b92b0..35046b541 100644
--- a/packages/coding-agent/src/prompts/tools/task.md
+++ b/packages/coding-agent/src/prompts/tools/task.md
@@ -38,6 +38,7 @@ Agents with `output="structured"` have a fixed schema enforced via frontmatter;
- `id`: Short CamelCase identifier (max 32 chars, e.g., "SessionStore", "LspRefactor")
- `description`: Short human-readable description of what the task does
- `args`: Object with keys matching `\{{placeholders}}` in context (always include this, even if empty)
+ - `skills`: (optional) Array of skill names to preload into this task's system prompt. When set, the skills index section is omitted and the full SKILL.md contents are embedded.
- `output`: (optional) JTD schema for structured subagent output (used by the complete tool)
diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts
index 728eb0e6f..4653ef8ee 100644
--- a/packages/coding-agent/src/sdk.ts
+++ b/packages/coding-agent/src/sdk.ts
@@ -156,6 +156,8 @@ 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[];
/** Context files (AGENTS.md content). Default: discovered walking up from cwd */
contextFiles?: Array<{ path: string; content: string }>;
/** Prompt templates. Default: discovered from cwd/.omp/prompts/ + agentDir/prompts/ */
@@ -993,6 +995,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
const defaultPrompt = await buildSystemPromptInternal({
cwd,
skills,
+ preloadedSkills: options.preloadedSkills,
contextFiles,
tools,
toolNames,
@@ -1007,6 +1010,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
return await buildSystemPromptInternal({
cwd,
skills,
+ preloadedSkills: options.preloadedSkills,
contextFiles,
tools,
toolNames,
diff --git a/packages/coding-agent/src/session/session-manager.ts b/packages/coding-agent/src/session/session-manager.ts
index d92d0c5e5..dfc72ef79 100644
--- a/packages/coding-agent/src/session/session-manager.ts
+++ b/packages/coding-agent/src/session/session-manager.ts
@@ -110,6 +110,19 @@ export interface TtsrInjectionEntry extends SessionEntryBase {
injectedRules: string[];
}
+/** Session init entry - captures initial context for subagent sessions (debugging/replay). */
+export interface SessionInitEntry extends SessionEntryBase {
+ type: "session_init";
+ /** Full system prompt sent to the model */
+ systemPrompt: string;
+ /** Initial task/user message */
+ task: string;
+ /** Tools available to the agent */
+ tools: string[];
+ /** Output schema if structured output was requested */
+ outputSchema?: unknown;
+}
+
/**
* Custom message entry for extensions to inject messages into LLM context.
* Use customType to identify your extension's entries.
@@ -140,7 +153,8 @@ export type SessionEntry =
| CustomEntry
| CustomMessageEntry
| LabelEntry
- | TtsrInjectionEntry;
+ | TtsrInjectionEntry
+ | SessionInitEntry;
/** Raw file entry (includes header) */
export type FileEntry = SessionHeader | SessionEntry;
@@ -1263,7 +1277,7 @@ export class SessionManager {
if (this.persistError) throw this.persistError;
const hasAssistant = this.fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
- if (!hasAssistant) return;
+ if (!hasAssistant && !this.flushed) return;
if (!this.flushed) {
this.flushed = true;
@@ -1368,6 +1382,19 @@ export class SessionManager {
return entry.id;
}
+ /** Append session init metadata (for subagent debugging/replay). Returns entry id. */
+ appendSessionInit(init: { systemPrompt: string; task: string; tools: string[]; outputSchema?: unknown }): string {
+ const entry: SessionInitEntry = {
+ type: "session_init",
+ id: generateId(this.byId),
+ parentId: this.leafId,
+ timestamp: new Date().toISOString(),
+ ...init,
+ };
+ this._appendEntry(entry);
+ return entry.id;
+ }
+
/** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */
appendCompaction(
summary: string,
diff --git a/packages/coding-agent/src/system-prompt.ts b/packages/coding-agent/src/system-prompt.ts
index 0c1e26717..7e7ec2a0b 100644
--- a/packages/coding-agent/src/system-prompt.ts
+++ b/packages/coding-agent/src/system-prompt.ts
@@ -23,6 +23,24 @@ interface GitContext {
commits: string;
}
+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;
+}
+
/**
* Load git context for the system prompt.
* Returns structured git data or null if not in a git repo.
@@ -643,6 +661,8 @@ export interface BuildSystemPromptOptions {
contextFiles?: Array<{ path: string; content: string; depth?: number }>;
/** Pre-loaded skills (skips discovery if provided). */
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). */
rules?: Array<{ name: string; description?: string; path: string; globs?: string[] }>;
}
@@ -662,6 +682,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
cwd,
contextFiles: providedContextFiles,
skills: providedSkills,
+ preloadedSkills: providedPreloadedSkills,
rules,
} = options;
const resolvedCwd = cwd ?? process.cwd();
@@ -707,13 +728,15 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
const skills =
providedSkills ??
(skillsSettings?.enabled !== false ? (await loadSkills({ ...skillsSettings, cwd: resolvedCwd })).skills : []);
+ const preloadedSkills = providedPreloadedSkills;
+ const preloadedSkillContents = preloadedSkills ? await loadPreloadedSkillContents(preloadedSkills) : [];
// Get git context
const git = await loadGitContext(resolvedCwd);
// Filter skills to only include those with read tool
const hasRead = tools?.has("read");
- const filteredSkills = hasRead ? skills : [];
+ const filteredSkills = preloadedSkills === undefined && hasRead ? skills : [];
if (resolvedCustomPrompt) {
return renderPromptTemplate(customSystemPromptTemplate, {
@@ -724,6 +747,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
agentsMdSearch,
git,
skills: filteredSkills,
+ preloadedSkills: preloadedSkillContents,
rules: rules ?? [],
dateTime,
cwd: resolvedCwd,
@@ -738,6 +762,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
agentsMdSearch,
git,
skills: filteredSkills,
+ preloadedSkills: preloadedSkillContents,
rules: rules ?? [],
dateTime,
cwd: resolvedCwd,
diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts
index fe63a550d..3fd93c8ff 100644
--- a/packages/coding-agent/src/task/executor.ts
+++ b/packages/coding-agent/src/task/executor.ts
@@ -75,6 +75,7 @@ export interface ExecutorOptions {
eventBus?: EventBus;
contextFiles?: ContextFileEntry[];
skills?: Skill[];
+ preloadedSkills?: Skill[];
promptTemplates?: PromptTemplate[];
mcpManager?: MCPManager;
authStorage?: AuthStorage;
@@ -804,6 +805,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise renderTemplate(context, t));
const contextFiles = this.session.contextFiles;
- const skills = this.session.skills;
+ 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 < tasksWithContext.length; i++) {
- const t = tasksWithContext[i];
+ for (let i = 0; i < tasksWithSkills.length; i++) {
+ const t = tasksWithSkills[i];
progressMap.set(i, {
index: i,
id: t.id,
@@ -416,7 +461,7 @@ export class TaskTool implements AgentTool {
+ const runTask = async (task: (typeof tasksWithSkills)[number], index: number) => {
if (!isIsolated) {
return runSubprocess({
cwd: this.session.cwd,
@@ -437,8 +482,8 @@ export class TaskTool implements AgentTool {
progressMap.set(index, {
- ...progress,
- args: tasksWithContext[index]?.args,
+ ...structuredClone(progress),
+ args: tasksWithSkills[index]?.args,
});
emitProgress();
},
@@ -447,7 +492,8 @@ export class TaskTool implements AgentTool {
progressMap.set(index, {
- ...progress,
- args: tasksWithContext[index]?.args,
+ ...structuredClone(progress),
+ args: tasksWithSkills[index]?.args,
});
emitProgress();
},
@@ -490,7 +536,8 @@ export class TaskTool implements AgentTool;
id: string;
description: string;
+ skills?: string[];
};
export function renderTemplate(template: string, task: TaskItem): RenderResult {
- const { id, description, args } = task;
+ const { id, description, args, skills } = task;
let usedPlaceholder = false;
const unknownArguments: string[] = [];
@@ -43,5 +44,6 @@ export function renderTemplate(template: string, task: TaskItem): RenderResult {
args: { id, description, ...args },
id,
description,
+ skills,
};
}
diff --git a/packages/coding-agent/src/task/types.ts b/packages/coding-agent/src/task/types.ts
index a1cf6a14f..7e727ef3d 100644
--- a/packages/coding-agent/src/task/types.ts
+++ b/packages/coding-agent/src/task/types.ts
@@ -49,6 +49,11 @@ export const taskItemSchema = Type.Object({
description: "Arguments to fill {{placeholders}} in context",
}),
),
+ skills: Type.Optional(
+ Type.Array(Type.String(), {
+ description: "Skill names to preload into the subagent system prompt",
+ }),
+ ),
});
export type TaskItem = Static;
diff --git a/packages/coding-agent/src/task/worker-protocol.ts b/packages/coding-agent/src/task/worker-protocol.ts
index 11e586179..289141998 100644
--- a/packages/coding-agent/src/task/worker-protocol.ts
+++ b/packages/coding-agent/src/task/worker-protocol.ts
@@ -107,6 +107,7 @@ export interface SubagentWorkerStartPayload {
pythonPreludeDocs?: PreludeHelper[];
contextFiles?: ContextFileEntry[];
skills?: Skill[];
+ preloadedSkills?: Skill[];
promptTemplates?: PromptTemplate[];
mcpTools?: MCPToolMetadata[];
pythonToolProxy?: boolean;
diff --git a/packages/coding-agent/src/task/worker.ts b/packages/coding-agent/src/task/worker.ts
index 85774c1b6..431b96709 100644
--- a/packages/coding-agent/src/task/worker.ts
+++ b/packages/coding-agent/src/task/worker.ts
@@ -608,6 +608,7 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
requireCompleteTool: true,
contextFiles: payload.contextFiles,
skills: payload.skills,
+ preloadedSkills: payload.preloadedSkills,
promptTemplates: payload.promptTemplates,
// Append system prompt (equivalent to CLI's --append-system-prompt)
systemPrompt: defaultPrompt =>
@@ -627,6 +628,14 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
runState.session = session;
checkAbort();
+ // Write session init metadata for debugging/replay
+ session.sessionManager.appendSessionInit({
+ systemPrompt: session.agent.state.systemPrompt,
+ task: payload.task,
+ tools: session.getAllToolNames(),
+ outputSchema: payload.outputSchema,
+ });
+
signal.addEventListener(
"abort",
() => {