diff --git a/AGENTS.md b/AGENTS.md
index 6a12b20..7299e90 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -44,6 +44,8 @@ src/
├── cache/ # Tool result caching (LRU, Redis) — src/cache/AGENTS.md
├── middleware/ # AI SDK language model middleware — src/middleware/AGENTS.md
├── utils/ # Budget, compaction, context status, helpers — src/utils/AGENTS.md
+├── subagents/ # Subagent control plane foundation — src/subagents/AGENTS.md
+├── runtime/ # Runtime events, plan state, approvals, snapshots — src/runtime/AGENTS.md
├── skills/ # Agent Skills standard — src/skills/AGENTS.md
├── setup/ # Agent environment setup (sandbox bootstrapping) — src/setup/AGENTS.md
├── cli/ # CLI initialization — src/cli/AGENTS.md
@@ -293,5 +295,7 @@ See `src/tools/AGENTS.md` for per-tool config details.
| Add middleware | `src/middleware/AGENTS.md` → "Common Modifications" |
| Add a cache backend | `src/cache/AGENTS.md` → "Common Modifications" |
| Add a context layer or prompt section | `src/context/AGENTS.md` → "Common Modifications" |
+| Add subagent foundation behavior | `src/subagents/AGENTS.md` → "Common Modifications" |
+| Add runtime events, plan state, approvals, or snapshots | `src/runtime/AGENTS.md` → "Common Modifications" |
| Add a skill source | `src/skills/AGENTS.md` → "Common Modifications" |
| Add a config field | Define in `src/types.ts`, consume in the relevant factory via `config?.yourField ?? default` |
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3970c5d
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,22 @@
+# Changelog
+
+## Unreleased
+
+### Breaking
+
+- `createAgentTools` now has a binary coding surface: configure `codemode` to expose Codemode as the parent coding tool, or omit `codemode` to expose direct BashKit tools.
+- Removed the legacy `Task` tool. Use `SpawnAgent` plus `WaitAgent`.
+- Removed the legacy `TodoWrite` tool. Use `UpdatePlan`.
+
+### Added
+
+- Added controller-backed subagent tools and host-facing control panel state.
+- Added profile-scoped subagent policies for tool allowlists, denied-tool behavior, Codemode exposure, context inheritance, and cost/depth/concurrency limits.
+- Added JSON-safe subagent profile loading helpers: `loadSubagentProfilesFromObject`, `loadSubagentProfilesFromJson`, and `loadSubagentProfilesFromFile`.
+- Added normalized runtime events for tool execution, plan updates, approvals, file changes, command output, and agent lifecycle snapshots.
+
+### Migration
+
+- Replace one-shot `Task` flows with `SpawnAgent({ task, task_name })` followed by `WaitAgent({ agent })`.
+- Replace `TodoWrite` with `UpdatePlan`.
+- When adopting Codemode, move direct tool policy into Codemode `includeTools` / `excludeTools` and subagent profile policies.
diff --git a/README.md b/README.md
index c45a9d5..ed2d871 100644
--- a/README.md
+++ b/README.md
@@ -14,14 +14,30 @@ Agentic coding tools for Vercel AI SDK. Give AI agents the ability to execute co
- Edit existing files with string replacement
- Search for files by pattern
- Search file contents with regex
-- Spawn sub-agents for complex tasks
-- Track task progress with todos
+- Coordinate controller-managed subagents
+- Track task progress with UpdatePlan
- Search the web and fetch URLs
- Load skills on-demand via the [Agent Skills](https://agentskills.io) standard
-## Breaking Changes in v0.4.0
+## Breaking Changes
-### Nullable Types for OpenAI Compatibility
+### Next Breaking Release: Codemode, Subagents, and Progress
+
+BashKit is moving to a simpler coding surface:
+
+- If `codemode` is configured, the parent model receives the `codemode` tool as
+ its coding interface. Direct coding tools such as `Bash`, `Read`, `Write`,
+ `Edit`, `Glob`, and `Grep` remain available to Codemode as runtime providers.
+- If `codemode` is not configured, the parent model receives the direct BashKit
+ tools.
+- `Task` has been removed. Use `SpawnAgent` plus `WaitAgent`.
+- `TodoWrite` has been removed. Use `UpdatePlan`, which is included by
+ `createAgentTools()`.
+
+This is intentionally a breaking change. There is no `createAgentTools`
+compatibility switch that exposes direct tools alongside Codemode.
+
+### v0.4.0: Nullable Types for OpenAI Compatibility
All optional tool parameters now use `.nullable()` instead of `.optional()` in Zod schemas. This change enables compatibility with OpenAI's structured outputs, which require all properties to be in the `required` array.
@@ -71,7 +87,7 @@ import { generateText, stepCountIs } from 'ai';
const sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });
// Create tools bound to the sandbox
-const { tools } = createAgentTools(sandbox);
+const { tools } = await createAgentTools(sandbox);
// Use with Vercel AI SDK
const result = await generateText({
@@ -103,7 +119,7 @@ const sandbox = await createVercelSandbox({
resources: { vcpus: 2 },
});
-const { tools } = createAgentTools(sandbox);
+const { tools } = await createAgentTools(sandbox);
const result = streamText({
model: anthropic('claude-sonnet-4-5'),
@@ -118,7 +134,7 @@ await sandbox.destroy();
## Available Tools
-### Default Tools (always included)
+### Direct Coding Tools
| Tool | Purpose | Key Inputs |
|------|---------|------------|
@@ -129,6 +145,10 @@ await sandbox.destroy();
| `Glob` | Find files by pattern | `pattern`, `path?` |
| `Grep` | Search file contents | `pattern`, `path?`, `output_mode?`, `-i?`, `-C?` |
+These tools are parent-visible when `codemode` is not configured. When
+`codemode` is configured, they move behind Codemode as runtime providers instead
+of appearing beside the `codemode` tool.
+
### Optional Tools (via config)
| Tool | Purpose | Config Key |
@@ -141,12 +161,12 @@ await sandbox.destroy();
| `WebSearch` | Search the web | `webSearch: { apiKey }` |
| `WebFetch` | Fetch URL and process with AI | `webFetch: { apiKey, model }` |
-### Workflow Tools (created separately)
+### Workflow Tools
| Tool | Purpose | Factory |
|------|---------|---------|
-| `Task` | Spawn sub-agents | `createTaskTool({ model, tools, subagentTypes? })` |
-| `TodoWrite` | Track task progress | `createTodoWriteTool(state, config?, onUpdate?)` |
+| `UpdatePlan` | Track task progress | Included by `createAgentTools()` |
+| `SpawnAgent` / `WaitAgent` | Control subagents | `subagents: { ... }` or `createSubagentControlTools(controller, config?)` |
### Web Tools (require `parallel-web` peer dependency)
@@ -220,7 +240,7 @@ const reconnected = await createE2BSandbox({
You can configure tools with security restrictions and limits, and enable optional tools:
```typescript
-const { tools, planModeState } = createAgentTools(sandbox, {
+const { tools, planModeState } = await createAgentTools(sandbox, {
// Enable optional tools
askUser: true,
planMode: true, // Enables EnterPlanMode and ExitPlanMode
@@ -295,6 +315,11 @@ await streamText({
});
```
+When `codemode` is configured, `tools` contains `codemode` plus control and
+interactive tools such as `UpdatePlan`, `AskUser`, `EnterPlanMode`,
+`ExitPlanMode`, `Skill`, and subagent controls. Direct coding/file tools are not
+also exposed to the parent model.
+
By default, generated code calls BashKit tools through BashKit's `bashkit.*` namespace, for example `await bashkit.Grep(...)`. Use `providers` when you want additional named tool groups such as `repo.SummarizeFile(...)`.
Client-intervention tools are excluded from codemode automatically: `AskUser`, `EnterPlanMode`, `ExitPlanMode`, tools without `execute`, and tools with `needsApproval`. Top-level `includeTools` narrows the default `bashkit.*` namespace. Named providers can define their own `includeTools` or `excludeTools`.
@@ -317,7 +342,7 @@ Client-intervention tools are excluded from codemode automatically: `AskUser`, `
All tools support AI SDK v6 tool options:
```typescript
-const { tools } = createAgentTools(sandbox, {
+const { tools } = await createAgentTools(sandbox, {
tools: {
Bash: {
timeout: 30000,
@@ -340,103 +365,99 @@ const { tools } = createAgentTools(sandbox, {
- `strict` (boolean): Enable strict schema validation
- `providerOptions` (object): Provider-specific tool options
-## Sub-agents with Task Tool
+## Subagents
-The Task tool spawns new agents for complex subtasks:
+Subagents are controller-managed agents with stable identity, profiles,
+lifecycle events, and explicit supervision tools. Use `SpawnAgent` to start
+separable work, `ListAgents` to inspect running and terminal children, and
+`WaitAgent` to collect the terminal result.
+
+The older one-shot `Task` tool has been removed from the breaking API. Migrate
+`Task(description, prompt)` flows to `SpawnAgent(task, task_name?)` followed by
+`WaitAgent(agent_id)`.
```typescript
-import { createTaskTool } from 'bashkit';
+import {
+ createAgentTools,
+ createLocalSandbox,
+ createStaticSubagentRunner,
+} from 'bashkit';
-const taskTool = createTaskTool({
- model: anthropic('claude-sonnet-4-5'),
- tools: sandboxTools,
- subagentTypes: {
- research: {
- model: anthropic('claude-haiku-4'), // Cheaper model for research
- systemPrompt: 'You are a research specialist. Find information only.',
- tools: ['Read', 'Grep', 'Glob'], // Limited tools
- },
- coding: {
- systemPrompt: 'You are a coding expert. Write clean code.',
- tools: ['Read', 'Write', 'Edit', 'Bash'],
+const sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });
+
+const { tools, getSubagentControlPanelState } = await createAgentTools(
+ sandbox,
+ {
+ subagents: {
+ profiles: [
+ {
+ name: 'researcher',
+ description: 'Read-only code research',
+ allowedTools: ['Read', 'Glob', 'Grep'],
+ deniedTools: ['Write', 'Edit', 'Bash'],
+ },
+ ],
+ runner: createStaticSubagentRunner({
+ status: 'completed',
+ result: 'Research summary',
+ }),
},
},
-});
+);
-// Add to tools
-const allTools = { ...sandboxTools, Task: taskTool };
-```
-
-The parent agent calls Task like any other tool:
-```typescript
-// Agent decides to delegate:
-{ tool: "Task", args: {
- description: "Research API patterns",
- prompt: "Find best practices for REST APIs",
- subagent_type: "research"
-}}
+// Parent model can call SpawnAgent, ListAgents, WaitAgent, SendMessage,
+// FollowupTask, and InterruptAgent.
+console.log(Object.keys(tools));
+console.log(await getSubagentControlPanelState?.());
```
-### Dynamic Agents
+### Subagent Profile Files
-You can create custom agents on the fly by passing `system_prompt` and/or `tools` directly, without predefined subagent types:
+Profiles can be loaded from JSON-safe config and then passed to
+`createAgentTools`. Model entries are string aliases in the file; the host maps
+them to live AI SDK model objects.
-```typescript
-// Agent creates a specialized agent dynamically:
-{ tool: "Task", args: {
- description: "Analyze security vulnerabilities",
- prompt: "Review the auth code for security issues",
- subagent_type: "custom",
- system_prompt: "You are a security expert. Focus on OWASP top 10 vulnerabilities.",
- tools: ["Read", "Grep", "Glob"]
-}}
+```json
+{
+ "defaultProfile": "researcher",
+ "defaults": {
+ "model": "fast",
+ "context": { "recent_turns": 3 },
+ "cost": { "maxDepth": 1 }
+ },
+ "profiles": [
+ {
+ "name": "researcher",
+ "description": "Read-only investigation",
+ "system": "Investigate and cite files. Do not edit.",
+ "allowedTools": ["Read", "Glob", "Grep", "WebSearch"],
+ "deniedTools": ["Write", "Edit", "Bash"]
+ }
+ ]
+}
```
-This is useful when:
-- The parent agent needs to create specialized agents based on context
-- You want agents to delegate with custom instructions
-- Predefined subagent types don't fit the task
+```typescript
+import { loadSubagentProfilesFromJson } from 'bashkit';
-### Streaming Sub-agent Activity to UI
+const loaded = loadSubagentProfilesFromJson(profileJson, {
+ models: {
+ fast: anthropic('claude-haiku-4'),
+ },
+});
-Pass a `streamWriter` to stream real-time sub-agent activity to the UI:
+if ('error' in loaded) throw new Error(loaded.error);
-```typescript
-import { createUIMessageStream } from 'ai';
-
-const stream = createUIMessageStream({
- execute: async ({ writer }) => {
- const taskTool = createTaskTool({
- model,
- tools: sandboxTools,
- streamWriter: writer, // Enable real-time streaming
- subagentTypes: { ... },
- });
-
- // Use with streamText
- const result = streamText({
- model,
- tools: { Task: taskTool },
- ...
- });
-
- writer.merge(result.toUIMessageStream());
+const { tools } = await createAgentTools(sandbox, {
+ subagents: {
+ profiles: loaded.profiles,
+ profileDefaults: loaded.defaults,
+ defaultProfile: loaded.defaultProfile,
+ model: anthropic('claude-sonnet-4-5'),
},
});
```
-When `streamWriter` is provided:
-- Uses `streamText` internally (instead of `generateText`)
-- Emits `data-subagent` events to the UI stream:
- - `start` - Sub-agent begins work
- - `tool-call` - Each tool the sub-agent uses (with args)
- - `done` - Sub-agent finished
- - `complete` - Full messages array for UI access
-
-These appear in `message.parts` on the client as `{ type: "data-subagent", data: SubagentEventData }`.
-
-**Important:** The TaskOutput returned to the lead agent does NOT include messages (to avoid context bloat). The UI accesses the full conversation via the streamed `complete` event.
-
## Context Management
### Conversation Compaction
@@ -484,7 +505,7 @@ if (contextNeedsCompaction(status)) {
Cache tool execution results to avoid repeated expensive operations:
```typescript
-const { tools } = createAgentTools(sandbox, {
+const { tools } = await createAgentTools(sandbox, {
// Enable caching with defaults (LRU, 5min TTL)
cache: true,
});
@@ -493,7 +514,7 @@ const { tools } = createAgentTools(sandbox, {
### Cache Configuration Options
```typescript
-const { tools } = createAgentTools(sandbox, {
+const { tools } = await createAgentTools(sandbox, {
cache: {
// Custom TTL (default: 5 minutes)
ttl: 10 * 60 * 1000,
@@ -549,7 +570,7 @@ const redisStore: CacheStore = {
},
};
-const { tools } = createAgentTools(sandbox, {
+const { tools } = await createAgentTools(sandbox, {
cache: redisStore,
});
```
@@ -638,7 +659,7 @@ import {
} from 'bashkit';
const sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });
-const { tools, planModeState } = createAgentTools(sandbox, { planMode: true });
+const { tools, planModeState } = await createAgentTools(sandbox, { planMode: true });
const wrappedTools = applyContextLayers(tools, [
// Gate: block Bash/Write/Edit while plan mode is active
@@ -729,7 +750,7 @@ import { discoverSkills, skillsToXml, createAgentTools, createLocalSandbox } fro
const skills = await discoverSkills();
const sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });
-const { tools } = createAgentTools(sandbox);
+const { tools } = await createAgentTools(sandbox);
const result = await generateText({
model: anthropic('claude-sonnet-4-5'),
@@ -898,7 +919,7 @@ ${skillsToXml(skills)}
`;
// Create tools and run
-const { tools } = createAgentTools(sandbox);
+const { tools } = await createAgentTools(sandbox);
const result = await generateText({
model: anthropic('claude-sonnet-4-5'),
@@ -916,25 +937,23 @@ const result = await generateText({
### Using with Subagents
-Use the same config for subagent prompts:
+Use the same config for subagent profiles:
```typescript
-const taskTool = createTaskTool({
- model,
- tools,
- subagentTypes: {
- researcher: {
- systemPrompt: `You are a researcher.
+const profiles = [
+ {
+ name: 'researcher',
+ system: `You are a researcher.
Save findings to: ${config.workspace.notes}`,
- tools: ['WebSearch', 'Write'],
- },
- 'report-writer': {
- systemPrompt: `Read from: ${config.workspace.notes}
+ allowedTools: ['WebSearch', 'Write'],
+ },
+ {
+ name: 'report-writer',
+ system: `Read from: ${config.workspace.notes}
Save reports to: ${config.workspace.outputs}`,
- tools: ['Read', 'Glob', 'Write'],
- },
+ allowedTools: ['Read', 'Glob', 'Write'],
},
-});
+];
```
## Sandbox Interface
@@ -976,7 +995,7 @@ class DockerSandbox implements Sandbox {
}
const sandbox = new DockerSandbox();
-const { tools } = createAgentTools(sandbox);
+const { tools } = await createAgentTools(sandbox);
```
## Architecture
@@ -1020,7 +1039,9 @@ const { tools } = createAgentTools(sandbox);
See the `examples/` directory for complete working examples:
-- `basic.ts` - Full example with todos, sub-agents, and prompt caching
+- `basic.ts` - Full example with UpdatePlan and prompt caching
+- `subagents.ts` - Controller-backed subagent control and control-panel state
+- `codemode-subagents.ts` - Codemode-first parent surface with profile-loaded subagents
- `test-tools.ts` - Testing individual tools
- `test-web-tools.ts` - Web search and fetch examples
@@ -1034,7 +1055,9 @@ Creates a set of agent tools bound to a sandbox instance.
- `sandbox` (Sandbox): Sandbox instance for code execution
- `config` (AgentConfig, optional): Configuration for tools and web tools
-**Returns:** Object with tool definitions compatible with Vercel AI SDK
+**Returns:** Promise resolving to tool definitions compatible with Vercel AI SDK,
+plus optional shared state such as `budget`, `planState`,
+`subagentController`, and `getSubagentControlPanelState`.
### Sandbox Factories
@@ -1045,8 +1068,11 @@ Creates a set of agent tools bound to a sandbox instance.
### Workflow Tools
-- `createTaskTool(config)` - Spawn sub-agents for complex tasks
-- `createTodoWriteTool(state, config?, onUpdate?)` - Track task progress
+- `createUpdatePlanTool(state, config?)` - Track progress with the canonical Codex-style plan state
+- `createSubagentControlTools(controller, config?)` - Create Spawn/List/Wait/Message/Interrupt subagent controls
+- `loadSubagentProfilesFromJson(json, options?)` - Load JSON-safe subagent profile configs
+- `loadSubagentProfilesFromObject(input, options?)` - Validate profile config objects
+- `loadSubagentProfilesFromFile(path, reader, options?)` - Load profile config through a host-provided reader
### Optional Tools (also available via config)
@@ -1089,45 +1115,16 @@ Creates a set of agent tools bound to a sandbox instance.
- `createOutputPolicy(config?)` - Truncation + redirection hints + optional disk stash `ContextLayer`
- `createPrepareStep(config)` - Compose compaction + context-status + plan-mode hints into an AI SDK `PrepareStepFunction`
-## Future Roadmap
-
-The following features are planned for future releases:
-
-### Agent Profiles Loader
-
-Load pre-configured subagent types from JSON/TypeScript configs:
-
-```json
-// .bashkit/agents.json
-{
- "subagentTypes": {
- "research": {
- "systemPrompt": "You are a research specialist...",
- "tools": ["Read", "Grep", "Glob", "WebSearch"]
- },
- "coding": {
- "systemPrompt": "You are a coding expert...",
- "tools": ["Read", "Write", "Edit", "Bash"]
- }
- }
-}
-```
-
-Helper function to auto-load profiles:
-```typescript
-import { createTaskToolWithProfiles } from 'bashkit';
-
-const taskTool = createTaskToolWithProfiles({
- model,
- tools,
- profilesPath: '.bashkit/agents.json', // Auto-loads
-});
-```
+## Migration Notes
-This will make it easy to:
-- Share agent configurations across projects
-- Standardize agent patterns within teams
-- Quickly set up specialized agents for different tasks
+- Replace `Task` with `SpawnAgent` plus `WaitAgent`.
+- Replace `TodoWrite` with `UpdatePlan`.
+- When adopting Codemode, expect direct coding tools to disappear from the
+ parent-visible tool set. They are still available inside generated Codemode
+ code through the configured runtime providers.
+- Use `loadSubagentProfilesFromJson`, `loadSubagentProfilesFromObject`, or
+ `loadSubagentProfilesFromFile` to share team-standard subagent profiles across
+ apps.
## Contributing
diff --git a/docs/plans/2026-06-15-001-feat-subagent-foundation-plan.md b/docs/plans/2026-06-15-001-feat-subagent-foundation-plan.md
new file mode 100644
index 0000000..86590a2
--- /dev/null
+++ b/docs/plans/2026-06-15-001-feat-subagent-foundation-plan.md
@@ -0,0 +1,985 @@
+---
+title: "feat: Make Codemode and subagents the default agent model"
+type: "feat"
+date: "2026-06-15"
+---
+
+# feat: Make Codemode and subagents the default agent model
+
+## Summary
+
+This plan makes BashKit a Codemode-first, subagent-first agent framework. The breaking release replaces the current direct-tool default and blocking `Task` delegation pattern with a reusable subagent control plane, profile-scoped Codemode execution, typed lifecycle events, shared cost controls, and host-facing control panel state.
+
+The design borrows Codex's core shape: tools are thin adapters, orchestration lives in a controller, agent identities are stable and addressable, role/profile resolution is configuration-driven, and parent agents supervise child agents through explicit control tools.
+
+---
+
+## Problem Frame
+
+BashKit already has the ingredients for an agentic coding toolkit: sandbox tools, context layers, budget tracking, skills, Codemode integration, and a `Task` tool that can run a child model call. The current default shape still centers direct tool exposure. A parent model receives individual tools such as `Bash`, `Read`, `Write`, and `Edit`, and `Task` behaves like a one-shot nested generation that returns text.
+
+That model is too small for dynamic workflows and serious subagent orchestration. It lacks:
+
+- Stable child identity that survives beyond one tool call.
+- A registry for listing, addressing, and supervising agents.
+- Non-blocking spawn/wait/message/interrupt controls.
+- Profile configuration that changes model, tools, Codemode access, budget, context, and callbacks together.
+- A cost policy that understands parent Codemode, child Codemode, nested generations, concurrency, and spawn depth as one shared budget domain.
+- A host-facing state projection for control panels.
+- A disciplined way to keep full child transcripts out of the parent model context.
+
+Codex has already solved the architectural split. Its multi-agent tools do not own orchestration. `AgentControl`, `AgentRegistry`, role resolution, path-like names, execution limits, lifecycle notifications, and status subscriptions form the actual control plane. BashKit should implement that idea in TypeScript and AI SDK terms, without importing Codex's Rust runtime, TUI assumptions, rollout storage, or cloud task APIs.
+
+---
+
+## Goals
+
+- Make Codemode the default model-facing coding harness.
+- Make first-class subagent controls the default delegation interface.
+- Keep the factory surface binary: Codemode configured means Codemode-first; no Codemode means direct tools.
+- Build a reusable `src/subagents/` architecture that library consumers can use directly without model-visible tools.
+- Keep subagent execution safe through profile quarantine, shared cost controls, depth limits, concurrency limits, and bounded context inheritance.
+- Expose typed state and events so hosts can build control panels without depending on internal controller objects.
+- Provide a breaking migration story from `Task`, `TodoWrite`, and direct tool exposure alongside Codemode.
+
+## Non-Goals
+
+- Do not add subagent concepts to the `Sandbox` interface.
+- Do not ship a bundled React/Vue/Svelte control panel.
+- Do not implement durable resume of active JavaScript execution in this milestone.
+- Do not build a full dynamic workflow authoring system in this milestone.
+- Do not require BashKit to own Codex-style terminal UI, rollout storage, or thread manager behavior.
+
+---
+
+## Requirements
+
+**Breaking API Direction**
+
+- R1. Make Codemode the default way models perform coding work through BashKit when a Codemode executor is configured.
+- R2. Make the breaking change explicit in versioning, migration docs, and release notes.
+- R3. Replace `Task` as the primary subagent API with first-class subagent control tools and controller APIs.
+- R4. Keep tool failures model-visible as `{ error: string }` results, except for construction-time configuration errors.
+- R5. Use nullable tool input fields for optional parameters so OpenAI structured outputs remain compatible.
+- R6. Keep parent coding surface binary: direct tools when Codemode is absent, Codemode when Codemode is configured.
+
+**Subagent Foundation**
+
+- R7. Add stable subagent identity with generated IDs, optional path-like task names, profile type, status, parent/child relationship, and last task message.
+- R8. Introduce a profile registry that resolves built-in, inline, and file-loaded profiles into model, prompt, tools, Codemode policy, context policy, cost policy, stop conditions, callbacks, and metadata.
+- R9. Centralize spawn-time runtime inheritance so children receive the intended model, wrapped tools, budget tracker, context layers, prepare-step behavior, and Codemode harness.
+- R10. Provide non-blocking tools for spawning, listing, waiting, messaging, follow-up tasks, and interrupting subagents.
+- R11. Maintain UI/event visibility for subagent activity without forcing full child transcripts into parent model context.
+- R12. Enforce cost controls across parent Codemode, child runs, nested Codemode calls, and parallel child executions.
+- R13. Use Codemode as the default orchestration tool inside subagents, with profile-level quarantine applied to Codemode runtime providers.
+- R14. Provide a parent-agent control surface that exposes live state, supervision actions, budget usage, warnings, and concise activity history to host UIs.
+- R15. Support lifecycle hooks for subagent start, stop, completion, failure, interruption, message delivery, and follow-up delivery.
+- R16. Support controlled context inheritance so spawn can receive no parent context, full parent context, or a bounded recent-turn window.
+- R17. Document the new architecture and update examples around the Codemode-first, subagent-first API.
+
+**Workflow Readiness**
+
+- R18. Make the controller usable by future workflow harnesses that fan out work, synthesize results, adversarially verify outputs, loop until done, or route models.
+- R19. Provide persistence boundaries for subagent metadata and terminal results, while deferring active-process resume unless the host supplies a durable runner.
+- R20. Support quarantine-style tool scoping so low-trust agents can be restricted from high-privilege write, shell, and subagent-control tools.
+- R21. Keep the implementation fully typed with no new `any` in public or internal subagent contracts.
+- R22. Add explicit completion semantics so child agents terminate by status transition or stop condition rather than heuristic parent-side guessing.
+- R23. Keep every host-visible state snapshot serializable as plain JSON.
+- R24. Preserve context-layer and cache behavior by inheriting already-wrapped tools unless a profile narrows them.
+
+---
+
+## Design Principles
+
+- **Unified orchestrator, varied profiles:** All subagents run through one controller and runner contract. Profiles vary the model, prompt, allowed tools, Codemode policy, context policy, budget cap, and callbacks.
+- **Tools are primitives, not workflows:** `SpawnAgent`, `WaitAgent`, `SendMessage`, and `InterruptAgent` are atomic controls. Fan-out, tournament, verification, and loop-until-done patterns are composed by prompts or host workflow code.
+- **Agent and UI share state:** The controller emits events and snapshots that host UIs can observe. The UI should not scrape model text to understand agent state.
+- **Context is a budgeted resource:** Parent history should not silently flood every child. Spawn calls make context inheritance explicit.
+- **Quarantine is structural:** A restricted profile must not regain blocked tools through Codemode's runtime providers, direct tool exposure, recursive subagents, or extra tools.
+- **Completion is explicit:** The controller records terminal states and completion events. Parent agents should not infer completion from absence of tool calls.
+- **Library boundaries stay clean:** Sandboxes execute commands and file IO. Subagents orchestrate model/tool execution. Host apps render UI.
+
+---
+
+## Key Technical Decisions
+
+- KTD1. Split subagent core from tool adapters: create `src/subagents/` for profiles, identity, registry, controller, stores, events, state projections, policies, and runner contracts. Tool files call into this module.
+- KTD2. Make Codemode the default public harness: `createAgentTools` exposes a Codemode tool by default when a Codemode executor is supplied. Direct tools are runtime providers and optional compatibility exposure.
+- KTD3. Make subagent controls the default delegation API: expose `SpawnAgent`, `ListAgents`, `SendMessage`, `FollowupTask`, `WaitAgent`, and `InterruptAgent` for parent model coordination.
+- KTD4. Remove `Task` entirely in the breaking release: the one-shot delegation abstraction does not fit the controller-managed subagent model. Migrate callers to `SpawnAgent` plus `WaitAgent`.
+- KTD5. Put Codemode inside subagents by default: children receive a profile-scoped Codemode tool that can orchestrate only the tools allowed by the profile.
+- KTD6. Apply quarantine at two layers: profile allowlists filter direct tools and Codemode runtime providers. A restricted profile cannot regain `Write`, `Edit`, `Patch`, `Bash`, `SpawnAgent`, or `FollowupTask` through generated code.
+- KTD7. Model profiles as runtime configuration, not prompt strings: profiles resolve to model, prompt, tool policy, Codemode policy, context policy, cost policy, stop conditions, prepare-step, callbacks, and host metadata.
+- KTD8. Use in-memory orchestration first with host-extensible persistence: the default store tracks live promises and terminal records in memory. A `SubagentStore` interface lets hosts persist metadata and results.
+- KTD9. Treat messaging as mailbox semantics: `SendMessage` queues input, `FollowupTask` queues input and requests a turn, and `WaitAgent` observes status/mailbox changes.
+- KTD10. Inherit wrapped tools by default: child agents receive the same cache/context-wrapped tools as the parent unless narrowed. This preserves execution policy, output policy, and budget behavior.
+- KTD11. Keep child transcripts out of parent context: parent-visible results include terminal summary, status, usage, and references. Full transcripts are available only through host callbacks, event sinks, or explicit transcript APIs.
+- KTD12. Treat the control panel as a host projection: BashKit emits typed state snapshots and events, not a UI framework component.
+- KTD13. Make cost control shared and preemptive: spawns and follow-ups are rejected before model calls when budget, depth, active count, total count, or timeout policy is exhausted.
+- KTD14. Use path-like agent references: task names form stable paths addressable relative to the current parent, while generated IDs remain canonical for storage.
+- KTD15. Add lifecycle hooks as extension points: hosts can observe, audit, enrich, or block lifecycle transitions without changing tool implementations.
+- KTD16. Keep profile-visible descriptions generated from config: parent agents can route to the right profile because model-facing descriptions include profile purpose, allowed capabilities, locked model/cost settings, and quarantine constraints.
+- KTD17. Do not require durable active-run resume in the default runner: the store persists terminal metadata and results, but active process recovery belongs to host-supplied durable runners.
+
+---
+
+## Codex Patterns to Borrow
+
+| Codex pattern | BashKit adaptation | Why it matters |
+| --- | --- | --- |
+| Agent control plane | `SubagentController`, `SubagentRegistry`, `SubagentRunner`, `SubagentStore` | Keeps tools thin and makes the model-visible API stable. |
+| Path-based identity | Optional `task_name` paths plus canonical generated IDs | Lets agents coordinate with readable names such as `research/auth`. |
+| Role/config layering | `SubagentProfileRegistry` with layered defaults and overrides | Makes profile selection safer than ad hoc system prompts. |
+| `fork_turns` context control | `none`, `all`, or bounded recent-turn context | Makes context cost explicit during fan-out. |
+| Lifecycle hooks | `onStart`, `onStop`, `onComplete`, `onInterrupt`, `onMessage` | Enables audit, UI, policy, and telemetry without embedding UI concerns. |
+| Completion notifications | Event sink and control panel updates independent of `WaitAgent` | Keeps supervision state current even when parent is doing other work. |
+| Execution limits | Max active agents, total agents, depth, and per-profile caps | Prevents runaway recursion and local resource overload. |
+| Role-visible descriptions | Generated profile summaries in tool descriptions and config helpers | Helps parent models route work without guessing. |
+
+Do not copy Codex's Rust thread manager, TUI implementation, cloud task APIs, or rollout storage. BashKit should borrow the contracts and invariants, not the product shell.
+
+---
+
+## Codex Reference File Map
+
+When implementing this plan, use these paths relative to a local Codex checkout. The file map is intentionally explicit because the BashKit implementation should borrow Codex's shape, not rediscover it from scratch.
+
+### Read First
+
+| Codex file | Reference purpose | BashKit design area |
+| --- | --- | --- |
+| `codex-rs/core/src/agent/control.rs` | Main control-plane contract for listing agents, sending input, interrupting, and waiting for completion | `SubagentController` public API and ownership boundaries |
+| `codex-rs/core/src/agent/registry.rs` | Agent identity, path/name registration, reservations, and max-thread constraints | `SubagentRegistry`, path resolution, duplicate-name behavior |
+| `codex-rs/core/src/agent/role.rs` | Role resolution, built-in roles, locked settings, model/tool visibility, and role metadata | `SubagentProfileRegistry` and generated profile descriptions |
+| `codex-rs/core/src/tools/handlers/multi_agents_spec.rs` | Model-facing tool schemas and descriptions for multi-agent controls | BashKit control tool schemas and model-facing wording |
+| `codex-rs/core/src/tools/handlers/multi_agents_v2.rs` | Tool registration and dispatch split for the newer multi-agent API | `src/tools/subagents/index.ts` and adapter boundaries |
+
+### Control Plane Internals
+
+| Codex file | Reference purpose | BashKit design area |
+| --- | --- | --- |
+| `codex-rs/core/src/agent/control/spawn.rs` | Spawn orchestration, role resolution, context forking, and child setup | `SubagentController.spawn` and `SubagentRunner` setup |
+| `codex-rs/core/src/agent/control/execution.rs` | Active execution accounting and concurrency limiting | `SubagentExecutionPolicy` and pre-spawn rejection |
+| `codex-rs/core/src/agent/control/residency.rs` | Residency and lifecycle tracking for active agents | Store boundary and active-vs-terminal metadata |
+| `codex-rs/core/src/agent/status.rs` | Agent status representation and status updates | `SubagentStatus`, terminal states, control panel projection |
+| `codex-rs/core/src/agent/agent_resolver.rs` | Resolving agent references from names and paths | `SubagentPath` parsing and relative lookup |
+| `codex-rs/core/src/agent/agent_names.txt` | Human-readable generated agent names | Optional nickname generation and display metadata |
+
+### Tool Adapter Split
+
+| Codex file | Reference purpose | BashKit design area |
+| --- | --- | --- |
+| `codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs` | Thin spawn tool adapter over the control plane | `SpawnAgent` adapter |
+| `codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs` | Compact list output and status display | `ListAgents` output and control panel summaries |
+| `codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs` | Bounded wait behavior and terminal result formatting | `WaitAgent` timeout and result shape |
+| `codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs` | Message delivery to an existing agent | `SendMessage` mailbox behavior |
+| `codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs` | Follow-up task routing to a child agent | `FollowupTask` turn-triggering semantics |
+| `codex-rs/core/src/tools/handlers/multi_agents_v2/interrupt_agent.rs` | Interrupt request behavior | `InterruptAgent` best-effort cancellation |
+| `codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs` | Shared helper behavior for message-like tools | Shared message adapter utilities |
+
+### Role and Profile Examples
+
+| Codex file | Reference purpose | BashKit design area |
+| --- | --- | --- |
+| `codex-rs/core/src/agent/builtins/explorer.toml` | Built-in exploratory role configuration | BashKit `researcher` or `reviewer` profile defaults |
+| `codex-rs/core/src/agent/builtins/awaiter.toml` | Built-in role with specific coordination behavior | BashKit specialized coordination profiles |
+| `codex-rs/core/src/agent/role_tests.rs` | Role resolution edge cases | Profile registry tests |
+| `codex-rs/core/src/agent/registry_tests.rs` | Registry reservations and path behavior | Registry and identity tests |
+| `codex-rs/core/src/agent/control_tests.rs` | Control-plane behavior coverage | Controller tests with fake runners |
+
+### Integration and Regression Tests
+
+| Codex file | Reference purpose | BashKit design area |
+| --- | --- | --- |
+| `codex-rs/core/tests/suite/subagent_notifications.rs` | Parent notification when subagents finish | Completion events and control panel updates |
+| `codex-rs/core/tests/suite/spawn_agent_description.rs` | Model-facing descriptions for spawnable agents | Generated profile descriptions |
+| `codex-rs/core/tests/suite/agent_execution.rs` | Execution lifecycle and active execution behavior | Runner lifecycle and execution limits |
+| `codex-rs/core/tests/suite/hierarchical_agents.rs` | Nested agents and parent/child relationships | Depth policy and hierarchical identity |
+| `codex-rs/core/tests/suite/agent_jobs.rs` | Batch-style agent work | Future workflow harness inspiration, not first milestone scope |
+| `codex-rs/core/tests/suite/agent_websocket.rs` | Event streaming and status visibility | Host event sink and UI state projection |
+
+### Read Later or Treat as Non-Goals
+
+| Codex file | Why it is secondary |
+| --- | --- |
+| `codex-rs/core/src/tools/handlers/multi_agents.rs` | Older multi-agent API; useful for migration comparison but not the target shape. |
+| `codex-rs/core/src/tools/handlers/multi_agents_common.rs` | Shared older helpers; inspect only when porting a specific behavior. |
+| `codex-rs/core/src/tools/handlers/agent_jobs.rs` | Batch agent jobs are useful workflow inspiration, but BashKit should first land the subagent foundation. |
+| `codex-rs/core/src/tools/handlers/agent_jobs_spec.rs` | Same as above; do not let batch jobs expand the first milestone. |
+
+Recommended implementation reading order: start with `control.rs`, `registry.rs`, `role.rs`, and `multi_agents_spec.rs`; then inspect the `multi_agents_v2/` adapters; then read the test files that correspond to the BashKit unit being implemented.
+
+---
+
+## Architecture Overview
+
+```mermaid
+flowchart TB
+ Host["Host app"] --> Factory["createAgentTools"]
+ Factory --> ParentSurface["Parent model tool surface"]
+ ParentSurface --> ParentCodemode["Codemode (default coding tool)"]
+ ParentSurface --> ControlTools["Subagent control tools"]
+
+ ParentCodemode --> ParentProviders["Filtered parent runtime providers"]
+ ControlTools --> Controller["SubagentController"]
+
+ Controller --> Registry["SubagentRegistry"]
+ Controller --> ProfileRegistry["SubagentProfileRegistry"]
+ Controller --> Store["SubagentStore"]
+ Controller --> Cost["SubagentCostController"]
+ Controller --> Events["SubagentEventSink"]
+ Controller --> Runner["SubagentRunner"]
+
+ Runner --> AISDK["AI SDK generateText / streamText"]
+ Runner --> ChildSurface["Child model tool surface"]
+ ChildSurface --> ChildCodemode["Profile-scoped Codemode"]
+ ChildSurface --> ChildDirect["Optional filtered direct tools"]
+ ChildCodemode --> ChildProviders["Profile-filtered runtime providers"]
+
+ Events --> Projection["Control panel projection"]
+ Store --> Projection
+ Cost --> Projection
+ Projection --> UI["Host UI / logs / telemetry"]
+```
+
+The parent receives Codemode as the normal way to perform coding work and subagent controls as the normal way to delegate. `createAgentTools` creates a controller when `subagents` config is present. Tool adapters validate nullable schemas, call the controller, and return compact structured results. The controller resolves profiles, reserves identities, checks cost/execution policy, starts runner work, records status, and emits events.
+
+---
+
+## Runtime Flow
+
+### Spawn and Completion Sequence
+
+```mermaid
+sequenceDiagram
+ participant P as Parent model
+ participant T as SpawnAgent tool
+ participant C as SubagentController
+ participant R as Registry
+ participant PR as ProfileRegistry
+ participant B as CostController
+ participant Runner as SubagentRunner
+ participant E as EventSink
+ participant S as Store
+
+ P->>T: SpawnAgent(task, profile, context_policy)
+ T->>C: spawn(request)
+ C->>PR: resolve(profile, overrides)
+ PR-->>C: resolved profile
+ C->>B: reserveSpawn(profile, parent)
+ B-->>C: allowed or error
+ C->>R: reserve identity and path
+ R-->>C: handle
+ C->>S: write pending metadata
+ C->>E: subagent.started
+ C->>Runner: run(resolved request)
+ C-->>T: handle
+ T-->>P: agent_id, task_name, status
+ Runner->>S: running status
+ Runner->>E: tool events and status events
+ Runner->>S: terminal result
+ Runner->>B: record usage
+ Runner->>E: subagent.completed or subagent.failed
+```
+
+### Wait and Message Sequence
+
+```mermaid
+sequenceDiagram
+ participant P as Parent model
+ participant W as WaitAgent tool
+ participant M as SendMessage / FollowupTask
+ participant C as SubagentController
+ participant S as Store
+ participant Runner as Runner
+ participant E as EventSink
+
+ P->>W: WaitAgent(agent_id, timeout_ms)
+ W->>C: wait(handle, timeout)
+ C->>S: subscribe to status changes
+ S-->>C: terminal result or timeout
+ C-->>W: result, timeout, or error
+ W-->>P: compact structured output
+
+ P->>M: FollowupTask(agent_id, message)
+ M->>C: enqueueMessage(handle, triggerTurn=true)
+ C->>S: append mailbox item
+ C->>E: message.queued
+ C->>Runner: request turn if supported
+ C-->>M: queued
+```
+
+---
+
+## State Model
+
+### Subagent Status State Machine
+
+```mermaid
+stateDiagram-v2
+ [*] --> pending: spawn accepted
+ pending --> running: runner starts
+ pending --> failed: setup error
+ pending --> interrupted: interrupt before start
+ running --> waiting: mailbox empty or paused by runner
+ waiting --> running: follow-up task triggers turn
+ running --> completed: stop condition met
+ running --> failed: model/tool/runner error
+ running --> interrupted: interrupt accepted
+ waiting --> interrupted: interrupt accepted
+ completed --> [*]
+ failed --> [*]
+ interrupted --> [*]
+```
+
+Terminal states are `completed`, `failed`, and `interrupted`. `waiting` is non-terminal and means the agent exists, has context/state, and can receive follow-up work if the runner supports it. If the default AI SDK runner cannot maintain a paused live conversation safely, it may treat every run as terminal after one task and return an unsupported error for follow-up turns. The controller contract should still model `waiting` so durable runners and future workflow harnesses have the right shape.
+
+### Identity Model
+
+Each subagent has two identifiers:
+
+- `agent_id`: generated, unique, stable, and canonical for storage.
+- `task_name`: optional model-facing path such as `research/auth`, `verify/security`, or `fix/tests`.
+
+Path resolution should support:
+
+- Exact current-tree lookup by `task_name`.
+- Relative child lookup from the current parent.
+- Sibling lookup when the caller is a child and names a sibling.
+- Generated ID lookup as the unambiguous fallback.
+
+Duplicate live paths under the same root should return `{ error }`. Terminal paths may be reusable only if the controller policy allows it. The default should reject duplicate live paths and allow explicit reuse only through config.
+
+---
+
+## Public API Shape
+
+The following sketches are directional contracts, not final implementation code.
+
+### Factory Configuration
+
+```ts
+export interface AgentConfig {
+ codemode?: CodemodeConfig;
+ subagents?: SubagentConfig;
+ budget?: BudgetConfig;
+ context?: ContextConfig;
+}
+
+export interface SubagentConfig {
+ model?: LanguageModel;
+ profiles?: SubagentProfileInput[];
+ defaultProfile?: string;
+ codemode?: SubagentCodemodeConfig;
+ store?: SubagentStore;
+ eventSink?: SubagentEventSink;
+ cost?: SubagentCostPolicyInput;
+ contextInheritance?: SubagentContextPolicyInput;
+ lifecycle?: SubagentLifecycleHooks;
+ exposeControlTools?: boolean;
+}
+```
+
+Default behavior:
+
+- If `codemode` is configured, expose Codemode as the primary coding tool.
+- If `codemode` is not configured, expose the direct BashKit tools.
+- If `subagents` is configured, expose subagent control tools.
+- Direct tools remain available as Codemode runtime providers after safety filtering.
+- Child Codemode receives profile-filtered runtime providers.
+
+### Core Controller
+
+```ts
+export interface SubagentController {
+ spawn(request: SubagentSpawnRequest): Promise;
+ list(filter?: SubagentListFilter): Promise;
+ wait(request: SubagentWaitRequest): Promise;
+ sendMessage(request: SubagentMessageRequest): Promise;
+ followupTask(request: SubagentFollowupRequest): Promise;
+ interrupt(request: SubagentInterruptRequest): Promise;
+ snapshot(): Promise;
+}
+```
+
+The controller owns identity, status, policy checks, hooks, events, and persistence writes. Runners execute resolved work. Stores persist metadata and terminal outputs. Tool adapters should contain no orchestration logic beyond schema defaults and result formatting.
+
+### Runner Contract
+
+```ts
+export interface SubagentRunner {
+ capabilities: SubagentRunnerCapabilities;
+ run(request: ResolvedSubagentRunRequest): Promise;
+ interrupt?(handle: SubagentHandle): Promise;
+ requestTurn?(handle: SubagentHandle): Promise;
+}
+```
+
+The default runner should use AI SDK `generateText` or `streamText` and be easy to fake in tests. A future durable runner can implement `requestTurn`, checkpointing, and active-run resume without changing tool schemas.
+
+### Store Contract
+
+```ts
+export interface SubagentStore {
+ create(metadata: SubagentMetadata): Promise;
+ update(handle: SubagentHandle, patch: SubagentMetadataPatch): Promise;
+ appendEvent(event: SubagentEvent): Promise;
+ appendMessage(handle: SubagentHandle, message: SubagentMailboxMessage): Promise;
+ get(handle: SubagentHandle): Promise;
+ list(filter?: SubagentListFilter): Promise;
+}
+```
+
+The default `InMemorySubagentStore` keeps live records and terminal results. Host stores can persist metadata, compact terminal results, event references, and transcript references. Stores should not need to persist model instances or executable functions.
+
+---
+
+## Tool Surface
+
+### Parent Model Tools
+
+| Tool | Purpose | Blocking behavior | Returns |
+| --- | --- | --- | --- |
+| `Codemode` | Default coding harness | Runs until Codemode completes | Codemode result or `{ error }` |
+| `SpawnAgent` | Start a child agent | Non-blocking after reservation | Handle, path, status, profile metadata |
+| `ListAgents` | Inspect active and terminal agents | Immediate | Compact records and supported actions |
+| `WaitAgent` | Await status/result changes | Bounded by timeout policy | Terminal result, status update, or timeout |
+| `SendMessage` | Queue information for an agent | Immediate | Queued result |
+| `FollowupTask` | Queue work and request a turn | Immediate unless runner rejects | Queued/triggered result |
+| `InterruptAgent` | Request cancellation | Immediate best effort | Previous status and new status |
+
+### Tool Schema Rules
+
+- Optional inputs use `.nullable()`, not `.optional()`.
+- Tool outputs return structured objects, not prose-only strings.
+- Errors return `{ error: string }` plus optional typed metadata where useful.
+- `WaitAgent` timeouts must be bounded by config.
+- `SpawnAgent` must reject empty task prompts.
+- `FollowupTask` must reject self-targeting that could deadlock the current agent.
+- Tool descriptions must warn against unnecessary fan-out and recursive spawning.
+
+### Proposed Tool Inputs
+
+| Tool | Key inputs |
+| --- | --- |
+| `SpawnAgent` | `task`, `profile`, `task_name`, `context`, `tools`, `model`, `budget`, `metadata` |
+| `ListAgents` | `status`, `path_prefix`, `include_terminal`, `limit` |
+| `WaitAgent` | `agent`, `timeout_ms`, `until_status` |
+| `SendMessage` | `agent`, `message`, `metadata` |
+| `FollowupTask` | `agent`, `task`, `context`, `metadata` |
+| `InterruptAgent` | `agent`, `reason` |
+
+The final implementation can add fields, but the first version should avoid fields that imply durable active-run resume unless the default runner supports them.
+
+---
+
+## Profile System
+
+Profiles are the safety and specialization boundary. They should be layered in this order:
+
+1. Built-in BashKit defaults.
+2. Host-level subagent defaults.
+3. Named profile config.
+4. Spawn-time overrides allowed by policy.
+
+### Profile Shape
+
+```ts
+export interface SubagentProfile {
+ name: string;
+ description: string;
+ nickname?: string;
+ model?: LanguageModel;
+ system?: string;
+ allowedTools?: string[];
+ deniedTools?: string[];
+ codemode?: SubagentCodemodePolicy;
+ context?: SubagentContextPolicy;
+ cost?: SubagentCostPolicyInput;
+ stopWhen?: StopCondition[];
+ prepareStep?: PrepareStepCallback;
+ lifecycle?: SubagentLifecycleHooks;
+ metadata?: JsonObject;
+}
+```
+
+### Built-In Profiles
+
+| Profile | Intended use | Default tools | Notes |
+| --- | --- | --- | --- |
+| `worker` | General implementation or investigation | Codemode with normal safe providers | Default child profile. |
+| `researcher` | Read-only exploration and synthesis | Read/search/list tools, no writes or shell by default | Good for fan-out and adversarial review. |
+| `reviewer` | Critique, verification, test design | Read/search/list tools, optional test command through guarded Codemode | Should be isolated from write tools by default. |
+| `fixer` | Focused code changes | Codemode with write/patch/test tools | Should usually have tighter budget and depth caps. |
+| `summarizer` | Compact transcripts and terminal results | Read-only tools or no tools | Useful for keeping parent context small. |
+
+Built-ins should be conservative. Hosts can add broader profiles for trusted environments.
+
+### Quarantine Rules
+
+```mermaid
+flowchart TB
+ Profile["Resolved profile"] --> Allowlist["Allowed tool names"]
+ Profile --> Denylist["Denied tool names"]
+ ParentTools["Parent wrapped tool set"] --> Filter["Tool filter"]
+ Allowlist --> Filter
+ Denylist --> Filter
+ Filter --> Direct["Optional direct child tools"]
+ Filter --> Providers["Codemode runtime providers"]
+ Providers --> ChildCodemode["Child Codemode"]
+ Direct --> ChildSurface["Child tool surface"]
+ ChildCodemode --> ChildSurface
+```
+
+Quarantine must be applied after context/cache/budget wrapping and before child exposure. This prevents a restricted child from using unwrapped tools or wider Codemode providers.
+
+Default denied tools for low-trust profiles should include `Write`, `Edit`, `Patch`, `Bash`, `SpawnAgent`, `FollowupTask`, and any tool that can mutate external state. Hosts can opt into wider access when they understand the risk.
+
+---
+
+## Context Inheritance
+
+Context inheritance is a first-class spawn policy:
+
+| Policy | Behavior | Use case |
+| --- | --- | --- |
+| `none` | Child receives only task, profile prompt, static system context, and injected metadata | Cheap, isolated research or verification. |
+| `{ recent_turns: n }` | Child receives the last `n` parent turns plus task | Default for most delegated work. |
+| `all` | Child receives all eligible parent context | Rare, expensive, useful when precise continuity matters. |
+
+The controller should also support `context_summary` as host-supplied compact context. It should not summarize implicitly in the first milestone unless an explicit summarizer profile or callback is configured.
+
+Context inheritance should exclude:
+
+- Full child transcripts from sibling agents.
+- Host-only secrets.
+- Tool outputs that a context layer already redacted.
+- Internal budget telemetry unless the profile asks for it.
+
+---
+
+## Cost and Execution Control
+
+### Cost Domains
+
+```mermaid
+flowchart TB
+ RootBudget["Root BudgetTracker"] --> ParentTurn["Parent model turns"]
+ RootBudget --> ParentCodemode["Parent Codemode"]
+ RootBudget --> ChildA["Child agent A"]
+ RootBudget --> ChildB["Child agent B"]
+ ChildA --> ChildACodemode["Child A Codemode"]
+ ChildB --> ChildBCodemode["Child B Codemode"]
+ RootBudget --> Warnings["Budget warnings"]
+ Warnings --> ControlPanel["Control panel state"]
+ Warnings --> SpawnPolicy["Pre-spawn policy checks"]
+```
+
+All model usage should report into the same root budget tracker by default. Per-profile caps can stop a child early without terminating the whole root run unless the root budget is exhausted.
+
+### Guardrail Policy
+
+| Limit | Default posture | Failure mode |
+| --- | --- | --- |
+| Max active agents | Conservative configurable default | `SpawnAgent` returns `{ error }`. |
+| Max total agents per root run | Configurable | Spawn rejected once exhausted. |
+| Max depth | Default 1 or 2 | Nested spawn rejected. |
+| Per-agent budget cap | Optional profile cap | Child stops with budget status. |
+| Root budget cap | Existing budget config | Parent and children stop using same tracker. |
+| Wait timeout bounds | Required min/max | `WaitAgent` clamps or rejects. |
+| Max mailbox length | Configurable | Messages rejected before unbounded growth. |
+
+Cost policy should run before expensive work. If budget is already exhausted, `SpawnAgent` should fail before reserving a runner. If active count is exhausted, the controller should avoid starting work and should release any tentative reservation.
+
+---
+
+## Lifecycle Hooks and Events
+
+### Hook Contract
+
+Lifecycle hooks let host apps observe or shape behavior without forking tools:
+
+- `onBeforeSpawn`: inspect request, inject metadata, or return `{ error }`.
+- `onStart`: observe accepted handle and resolved profile.
+- `onMessage`: observe queued messages and follow-up tasks.
+- `onToolEvent`: observe child tool activity.
+- `onComplete`: observe terminal successful result.
+- `onFail`: observe terminal failure.
+- `onInterrupt`: observe interrupt requests and results.
+- `onStop`: observe all terminal transitions.
+
+Hooks should be typed, async, and isolated from tool adapter code. Hook failures should be policy-driven:
+
+- Blocking hooks such as `onBeforeSpawn` may return `{ error }`.
+- Observability hooks should emit errors to debug/event channels without changing child status unless configured.
+
+### Event Types
+
+| Event | Emitted when | Host use |
+| --- | --- | --- |
+| `subagent.created` | Identity reserved | Add row to control panel. |
+| `subagent.started` | Runner begins | Mark active. |
+| `subagent.status_changed` | Status changes | Update UI and subscriptions. |
+| `subagent.message_queued` | Message/follow-up queued | Show pending work. |
+| `subagent.tool_call` | Child tool starts | Activity timeline. |
+| `subagent.tool_result` | Child tool completes | Activity timeline and debugging. |
+| `subagent.usage` | Usage reported | Budget panel. |
+| `subagent.completed` | Successful terminal state | Show result reference. |
+| `subagent.failed` | Error terminal state | Show error. |
+| `subagent.interrupted` | Interrupt terminal state | Show reason. |
+
+Events should contain `agent_id`, `task_name`, `parent_id`, `profile`, `status`, timestamp, and compact payload. They must avoid full transcript inclusion by default.
+
+---
+
+## Control Panel State
+
+The control panel is a serializable projection, not a UI component.
+
+```ts
+export interface SubagentControlPanelState {
+ root_id: string;
+ generated_at: string;
+ budget: SubagentBudgetSummary | null;
+ warnings: SubagentWarning[];
+ agents: SubagentControlPanelAgent[];
+ activity: SubagentActivityItem[];
+}
+```
+
+Each agent row should include:
+
+- `agent_id`
+- `task_name`
+- `profile`
+- `nickname`
+- `status`
+- `parent_id`
+- `depth`
+- `last_task_message`
+- `created_at`
+- `updated_at`
+- `usage`
+- `supported_actions`
+- `result_ref`
+- `transcript_ref`
+- `warnings`
+
+Supported actions are computed from status, runner capabilities, and policy:
+
+| Status | Actions |
+| --- | --- |
+| `pending` | `interrupt` when supported |
+| `running` | `message`, `followup`, `interrupt`, `wait` |
+| `waiting` | `message`, `followup`, `interrupt`, `wait` |
+| `completed` | `wait`, `inspect_result` |
+| `failed` | `wait`, `inspect_result` |
+| `interrupted` | `wait`, `inspect_result` |
+
+The state projection should be JSON-serializable and stable enough for docs, devtools, logs, and host UIs.
+
+---
+
+## Persistence and Resume Boundary
+
+The first implementation should persist:
+
+- Subagent metadata.
+- Status transitions.
+- Mailbox items.
+- Compact terminal results.
+- Usage summaries.
+- Event records or event references.
+- Transcript references when the host supplies transcript storage.
+
+The first implementation should not promise:
+
+- Restarting a live AI SDK generation after Node process termination.
+- Rehydrating live `LanguageModel` instances from serialized profile files.
+- Replaying in-flight Codemode execution.
+
+This boundary should be documented as "metadata and terminal result persistence" rather than "durable active-run resume." Future durable runners can add checkpoint/restore behind the same controller contracts.
+
+---
+
+## Migration Strategy
+
+### Breaking Defaults
+
+| Current behavior | New behavior |
+| --- | --- |
+| Direct tools are parent-visible by default | Codemode is parent-visible by default when configured |
+| `Task` is the main delegation tool | Subagent controls are the main delegation tools |
+| Child execution is one nested generation | Child execution is controller-managed |
+| Cost tracking exists mostly at tool/generation boundaries | Cost tracking is shared across parent and children |
+| UI receives optional `data-subagent` events from `Task` | UI can consume typed controller events and snapshots |
+
+### Removed Legacy Tools
+
+- Remove `Task` entirely in the breaking release. The one-shot nested generation model conflicts with stable subagent identity, lifecycle events, control panel state, messaging, waiting, interruption, and profile-scoped Codemode policy.
+- Remove `TodoWrite` entirely in the breaking release. `UpdatePlan` is the canonical Codex-style progress primitive and emits normalized `plan.updated` runtime events for host UIs.
+- Provide migration recipes: `Task(description, prompt)` becomes `SpawnAgent(task, task_name?)` then `WaitAgent(agent_id)`. `TodoWrite(todos)` becomes `UpdatePlan(plan)`.
+
+### Versioning
+
+BashKit is currently pre-1.0, but the contributor guide says breaking changes require a major bump. The implementation should either ship as `1.0.0` or explicitly document the pre-1.0 breaking version policy. Because this change redefines the default tool surface, `1.0.0` is the cleaner signal.
+
+---
+
+## Implementation Units
+
+### U1. Create the Subagent Core Module
+
+- **Goal:** Add foundational subagent types, identity, profile resolution, path handling, and tool filtering.
+- **Requirements:** R7, R8, R9, R16, R20, R21, R24
+- **Dependencies:** None
+- **Files:** `src/subagents/AGENTS.md`, `src/subagents/CLAUDE.md`, `src/subagents/index.ts`, `src/subagents/types.ts`, `src/subagents/identity.ts`, `src/subagents/path.ts`, `src/subagents/profiles.ts`, `src/subagents/profile-descriptions.ts`, `src/subagents/tool-filter.ts`, `src/subagents/registry.ts`, `src/tools/AGENTS.md`, `src/index.ts`, `tests/subagents/identity.test.ts`, `tests/subagents/path.test.ts`, `tests/subagents/profiles.test.ts`, `tests/subagents/tool-filter.test.ts`, `tests/subagents/registry.test.ts`
+- **Approach:** Define public-but-small interfaces for `SubagentProfile`, `ResolvedSubagentProfile`, `SubagentMetadata`, `SubagentStatus`, `SubagentPath`, `SubagentRunResult`, `SubagentCodemodePolicy`, `SubagentContextPolicy`, and `SubagentError`. Keep core request types separate from AI SDK tool schemas. Add path parsing, relative lookup, duplicate live-name checks, and profile-visible description generation.
+- **Patterns to follow:** `src/tools/patch/` for promoting non-trivial internals into a folder; `src/tools/AGENTS.md` for documentation expectations; AI SDK runner tests for model/tool execution conventions.
+- **Test scenarios:** Unique IDs are generated; duplicate live paths return `{ error }`; relative child and sibling paths resolve; terminal path reuse follows policy; profile resolution applies defaults, named profile config, and allowed overrides; profile descriptions include purpose, locked model/cost hints, and capability limits; tool filtering excludes unknown tools without throwing; Codemode policy inherits the same allowlist as direct tools; context policy accepts `none`, `all`, and recent-turn modes; optional nullable fields resolve with `??` defaults.
+- **Verification:** `src/subagents/` exports only stable types/helpers and every new file is covered by `src/subagents/AGENTS.md`.
+
+### U2. Add Controller, Store, Runner, and Event Contracts
+
+- **Goal:** Introduce the runtime control plane for spawn, list, wait, message, follow-up, interrupt, lifecycle hooks, status transitions, and event emission.
+- **Requirements:** R8, R9, R10, R11, R12, R14, R15, R16, R19, R22, R23
+- **Dependencies:** U1
+- **Files:** `src/subagents/controller.ts`, `src/subagents/store.ts`, `src/subagents/runner.ts`, `src/subagents/events.ts`, `src/subagents/status.ts`, `src/subagents/mailbox.ts`, `tests/subagents/controller.test.ts`, `tests/subagents/store.test.ts`, `tests/subagents/runner.test.ts`, `tests/subagents/events.test.ts`
+- **Approach:** Implement `SubagentController` over `SubagentStore`, `SubagentRunner`, lifecycle hooks, and `SubagentEventSink`. Provide `InMemorySubagentStore` and an AI SDK runner. The controller owns reservations, status transitions, context inheritance, mailbox writes, completion notifications, and event ordering. The runner executes resolved runs and reports usage/events back through typed callbacks.
+- **Patterns to follow:** Codex `AgentControl` and `AgentRegistry`; BashKit `BudgetTracker` stopWhen/onStepFinish composition in `src/subagents/ai-sdk-runner.ts`; debug parent attribution in `src/utils/debug.ts`.
+- **Test scenarios:** Spawn moves `pending -> running -> completed`; setup failures move to `failed`; lifecycle hooks fire in deterministic order; completion notification emits even when no one waits; runner rejection records errored status and returns `{ error }`; interrupt changes status and aborts when supported; unsupported interrupt returns `{ error }`; wait timeout does not change status; mailbox updates `last_task_message`; follow-up requests a turn only when runner supports it; snapshots are JSON-serializable; fake runners can test behavior without real models.
+- **Verification:** Controller tests use fake stores and fake runners; no live AI SDK calls are needed.
+
+### U3. Add Shared Cost and Execution Guardrails
+
+- **Goal:** Enforce cost, depth, concurrency, total count, mailbox, and wait limits before work fans out.
+- **Requirements:** R12, R14, R18, R19, R22
+- **Dependencies:** U1, U2
+- **Files:** `src/subagents/cost-control.ts`, `src/subagents/execution-limits.ts`, `src/subagents/controller.ts`, `src/subagents/types.ts`, `src/utils/budget-tracking.ts`, `tests/subagents/cost-control.test.ts`, `tests/subagents/execution-limits.test.ts`, `tests/tools/budget-integration.test.ts`
+- **Approach:** Add `SubagentCostPolicy` and `SubagentExecutionPolicy`. Compose existing `BudgetTracker` with subagent-specific limits: max active agents, max total agents per root run, max depth, per-profile budget caps, wait timeout bounds, mailbox length, and optional step caps. Check policy before spawn and follow-up. Report parent Codemode and child usage into the same tracker.
+- **Patterns to follow:** Existing budget tracker `stopWhen` and `onStepFinish`; Codex active execution limiter; Codex spawn depth checks.
+- **Test scenarios:** Spawn is rejected when root budget is exceeded; spawn is rejected at max active agents; nested spawn is rejected past max depth; total spawned count cannot exceed policy; child usage increments shared budget; parent Codemode and children appear in one budget status; per-profile caps stop expensive agents; wait timeout bounds prevent tight polling; mailbox limits reject unbounded queued work.
+- **Verification:** Guardrails are tested with fake runners and fake usage records.
+
+### U4. Implement Subagent Control Tools
+
+- **Goal:** Add model-facing tools for multi-agent orchestration as the canonical delegation surface.
+- **Requirements:** R3, R5, R7, R10, R11, R14, R20, R21, R22
+- **Dependencies:** U1, U2, U3
+- **Files:** `src/tools/subagents/AGENTS.md`, `src/tools/subagents/CLAUDE.md`, `src/tools/subagents/index.ts`, `src/tools/subagents/spawn-agent.ts`, `src/tools/subagents/list-agents.ts`, `src/tools/subagents/send-message.ts`, `src/tools/subagents/followup-task.ts`, `src/tools/subagents/wait-agent.ts`, `src/tools/subagents/interrupt-agent.ts`, `tests/tools/subagents/spawn-agent.test.ts`, `tests/tools/subagents/list-agents.test.ts`, `tests/tools/subagents/message-tools.test.ts`, `tests/tools/subagents/wait-agent.test.ts`, `tests/tools/subagents/interrupt-agent.test.ts`
+- **Approach:** Keep each adapter thin: parse nullable input, apply `??` defaults, call the controller, and return compact structured output. Tool descriptions should teach models that subagents are for separable work and warn against redundant fan-out.
+- **Patterns to follow:** Codex multi-agent tool split; BashKit tool factories returning `Tool`; nullable schema convention in `src/tools/AGENTS.md`.
+- **Test scenarios:** `SpawnAgent` rejects empty tasks; duplicate `task_name` returns `{ error }`; `ListAgents` filters by status and path prefix; `SendMessage` accepts valid IDs and paths; `FollowupTask` rejects self-targeting deadlock cases; `WaitAgent` enforces timeout policy; `InterruptAgent` returns previous and new status; every optional schema field is nullable; tool outputs are stable structured objects.
+- **Verification:** Tool tests assert structured results and do not require live model execution.
+
+### U5. Implement the Default AI SDK Runner
+
+- **Goal:** Provide the default execution backend for subagents using AI SDK generation and profile-scoped Codemode.
+- **Requirements:** R9, R11, R12, R13, R16, R20, R22, R24
+- **Dependencies:** U1, U2, U3
+- **Files:** `src/subagents/ai-sdk-runner.ts`, `src/subagents/tool-surface.ts`, `src/subagents/context-inheritance.ts`, `src/subagents/transcripts.ts`, `tests/subagents/ai-sdk-runner.test.ts`, `tests/subagents/context-inheritance.test.ts`, `tests/subagents/tool-surface.test.ts`
+- **Approach:** Construct a child tool surface from the resolved profile. Build Codemode providers after profile filtering. Include optional direct tools only when the profile asks for them. Apply context inheritance before the model call. Route usage, tool events, and terminal summaries back through runner callbacks.
+- **Patterns to follow:** `src/subagents/ai-sdk-runner.ts` AI SDK generation flow; `src/tools/codemode.ts` provider selection and `selectCodemodeTools`; `src/context/index.ts` wrapping behavior.
+- **Test scenarios:** Child receives Codemode by default; child direct tools are hidden unless profile opts in; blocked tools are absent from Codemode providers; recent-turn context includes only the configured number of turns; `none` context excludes parent history; runner reports usage to the controller; generated terminal summary excludes full transcript; stop conditions produce `completed`; model/tool failure produces `failed` with `{ error }`.
+- **Verification:** Runner tests use mock models/tools where possible and avoid networked model calls.
+
+### U6. Remove Legacy `Task` and `TodoWrite`
+
+- **Goal:** Make the breaking delegation/progress decision explicit by deleting old model-facing compatibility tools.
+- **Requirements:** R1, R2, R3, R4, R5, R8, R10, R11, R13
+- **Dependencies:** U1, U2, U3, U5
+- **Files:** `src/tools/task.ts`, `src/tools/todo-write.ts`, `src/tools/index.ts`, `src/index.ts`, `tests/tools/todo-write.test.ts`, `tests/tools/budget-integration.test.ts`, `README.md`, docs pages
+- **Approach:** Delete `Task` and `TodoWrite` factories/types/tests/exports. Keep `UpdatePlan` as the canonical progress tool and subagent controls as the canonical delegation API. Move useful budget and event assertions into controller/runtime tests rather than preserving adapter behavior.
+- **Patterns to follow:** `UpdatePlan` runtime event behavior; subagent control tool tests; `createAgentTools` export surface tests.
+- **Test scenarios:** `Task` and `TodoWrite` are absent from package exports; `UpdatePlan` remains default; budget tests no longer import legacy tools; docs/examples contain migration wording instead of legacy usage examples.
+- **Verification:** New docs do not teach `Task` or `TodoWrite` as supported APIs.
+
+### U7. Add Parent-Agent Control Panel State
+
+- **Goal:** Expose typed host-facing state for supervising subagents from the parent session.
+- **Requirements:** R10, R11, R12, R14, R19, R23
+- **Dependencies:** U1, U2, U3, U4, U6
+- **Files:** `src/subagents/control-panel.ts`, `src/subagents/events.ts`, `src/subagents/index.ts`, `tests/subagents/control-panel.test.ts`, `README.md`, `docs/src/app/tools/page.tsx`
+- **Approach:** Add a projection layer that turns controller state, store records, events, and budget summaries into `SubagentControlPanelState`. Include live and terminal agents, status, profile/type, resolved model summary, task name, nickname, last task message, parent/child relationship, budget summary, warnings, supported actions, and transcript/result references.
+- **Patterns to follow:** Codex `list_agents` status output; BashKit budget status shape in `src/utils/budget-tracking.ts`; normalized runtime event snapshots.
+- **Test scenarios:** Starting a child adds it to active state; completion moves it to terminal state without dropping result references; budget updates and warnings appear; unsupported actions are absent; full transcript text is excluded by default; snapshots serialize to plain JSON; path filters produce stable subsets.
+- **Verification:** Host apps can build a control panel without reaching into controller internals.
+
+### U8. Make Codemode and Subagents the Default Factory Surface
+
+- **Goal:** Change BashKit's factory and package surface to Codemode-first and subagent-first defaults.
+- **Requirements:** R1, R2, R6, R8, R9, R10, R12, R13, R15, R16, R21, R24
+- **Dependencies:** U1, U2, U3, U4, U5, U6, U7
+- **Files:** `src/types.ts`, `src/tools/index.ts`, `src/index.ts`, `tests/tools/index.test.ts`, `tests/context/integration.test.ts`
+- **Approach:** Reshape `createAgentTools` so Codemode config creates a Codemode-first surface. Direct tools remain as Codemode runtime providers, not parent-visible tools, when Codemode is configured. Without Codemode, `createAgentTools` exposes the direct BashKit tools. Add top-level `subagents` config for model defaults, profiles, limits, store, event sink, lifecycle hooks, context policy, and Codemode executor/config. Pass already-wrapped tools into Codemode and the controller.
+- **Patterns to follow:** Config-presence pattern for `webSearch`, `webFetch`, `skill`, `codemode`, and `budget`; `context.extraTools` wrapping order in `src/tools/index.ts`; `selectCodemodeTools` safety filtering in `src/tools/codemode.ts`.
+- **Test scenarios:** Codemode config creates Codemode-first parent surface; no-Codemode config exposes direct tools; `subagents` creates control tools; missing model/executor returns clear construction error; context-wrapped tools are inherited; budget enters the controller; max concurrent agents rejects excess spawns; profile restrictions apply to direct tools and Codemode providers; exported types are available from `src/index.ts`.
+- **Verification:** Factory tests describe Codemode-default, no-Codemode direct surface, subagent-default, context-layer, and profile-restricted Codemode cases.
+
+### U9. Add Profile Loading Helpers
+
+- **Goal:** Provide a shareable profile format for team-standard subagent definitions and future workflow templates.
+- **Requirements:** R8, R13, R17, R18, R20, R21
+- **Dependencies:** U1, U8
+- **Files:** `src/subagents/profile-loader.ts`, `src/subagents/profile-loader.schema.ts`, `tests/subagents/profile-loader.test.ts`, `README.md`, `docs/src/app/tools/page.tsx`
+- **Approach:** Add explicit helpers for loading JSON profile files from a sandbox or local filesystem abstraction. Validate with Zod. Keep live model instances and Codemode executors supplied by the host because serialized files cannot contain functions or model objects.
+- **Patterns to follow:** `src/skills/discovery.ts` and `src/setup/setup-environment.ts` for explicit discovery; README's Agent Profiles Loader roadmap section.
+- **Test scenarios:** Valid JSON profiles load; invalid profiles return validation errors; tool names are preserved but filtered later; missing file returns `{ error }`; failed loads do not mutate the registry; profile metadata produces model-visible descriptions.
+- **Verification:** The README roadmap becomes current usage guidance instead of an unimplemented promise.
+
+### U10. Update Documentation, Examples, and Release Notes
+
+- **Goal:** Teach the new default model and provide a clear migration path.
+- **Requirements:** R2, R14, R17, R18, R19, R20
+- **Dependencies:** U4, U6, U7, U8, U9
+- **Files:** `README.md`, `docs/src/app/tools/page.tsx`, `docs/src/app/api-reference/page.tsx`, `examples/basic.ts`, `examples/subagents.ts`, `examples/codemode-subagents.ts`, `CHANGELOG.md`
+- **Approach:** Update docs to teach Codemode first for coding and subagent controls first for delegation. Add examples for fan-out-and-synthesize, adversarial verification, profile/model routing, Codemode-backed children, and quarantine. Include migration notes from direct tool exposure to Codemode and from `Task` to `SpawnAgent` plus `WaitAgent`.
+- **Patterns to follow:** Current README examples for streaming subagent activity, budget tracking, Codemode, and skills.
+- **Test scenarios:** Documentation examples typecheck where repo patterns support it; docs mention active-process resume requires a host durable runner; examples avoid unrestricted recursive subagent controls unless intentionally shown; release notes call out breaking default behavior.
+- **Verification:** Users can identify the default API and understand what changed.
+
+---
+
+## Acceptance Examples
+
+- AE1. Given a host configures subagents, when the parent model receives tools, then `SpawnAgent`, `ListAgents`, `WaitAgent`, `SendMessage`, `FollowupTask`, and `InterruptAgent` are the model-visible delegation tools.
+- AE2. Given a host configures Codemode, when the parent model receives tools, then Codemode is the coding tool and direct sandbox tools are hidden from the parent surface.
+- AE3. Given the parent calls `SpawnAgent`, then it receives a stable handle and can later call `ListAgents` or `WaitAgent` without re-running the child in parent context.
+- AE4. Given a child subagent starts, then the child receives a Codemode tool whose runtime providers are limited by the child's resolved profile.
+- AE5. Given two subagents run concurrently under one budget tracker, when both finish, then shared budget status includes both child usages.
+- AE6. Given a read-only research profile, when it is spawned with `tools: ["Read", "Grep", "Glob"]`, then write and shell tools are unavailable directly and through Codemode-generated code.
+- AE7. Given shared budget is exhausted or active-agent limit is reached, when the parent requests another spawn or follow-up task, then BashKit rejects it before starting a costly child turn.
+- AE8. Given a consumer still imports removed `Task` or `TodoWrite` APIs, when they upgrade, then TypeScript and migration docs point them to `SpawnAgent`/`WaitAgent` and `UpdatePlan`.
+- AE9. Given a future workflow harness needs fan-out-and-synthesize, when it uses the controller directly, then it can spawn multiple child runs, await terminal results, and synthesize without model-visible `Task` calls.
+- AE10. Given the parent uses `context: { recent_turns: 3 }`, when the child starts, then it receives only the bounded recent context plus explicit spawn task and profile prompt.
+- AE11. Given a host renders `SubagentControlPanelState`, when a child completes while the parent is not waiting, then the control panel still updates with terminal status, usage, and result reference.
+
+---
+
+## Testing Strategy
+
+### Unit Tests
+
+- Identity and path parsing.
+- Profile resolution and generated descriptions.
+- Tool filtering and quarantine.
+- Context inheritance policies.
+- Registry reservation and release behavior.
+- Controller state transitions.
+- Store serialization.
+- Event ordering.
+- Cost and execution limits.
+- Control panel projection.
+- Tool schema nullability and output shapes.
+
+### Integration Tests
+
+- `createAgentTools` Codemode-default behavior.
+- No-Codemode direct tool surface.
+- Subagent control tools with fake runner.
+- Budget integration across parent and child.
+- Context layers inherited by child tools.
+- Removed `Task`/`TodoWrite` public export checks.
+
+### Regression Tests
+
+- Restricted profiles cannot access blocked tools through Codemode providers.
+- `WaitAgent` timeout never changes child status.
+- Interrupting completed agents returns stable previous status.
+- Duplicate task paths return `{ error }` without leaking reservations.
+- Missing Codemode executor fails at construction with a clear error.
+- Full child transcript is not returned to parent by default.
+
+### Manual Verification
+
+- Run `bun run typecheck`.
+- Run `bun run check`.
+- Run `bun run test`.
+- Run `bun run check:agents`.
+- Exercise `examples/subagents.ts` with fake or local model configuration.
+- Exercise `examples/codemode-subagents.ts` with a real Codemode executor when available.
+
+---
+
+## Rollout Plan
+
+1. Land subagent core and tests behind exports without changing defaults.
+2. Add controller, fake-runner tests, event/store contracts, and cost policy.
+3. Add AI SDK runner and profile-scoped Codemode tool surface.
+4. Add model-facing control tools.
+5. Add control panel projection.
+6. Remove legacy `Task` and `TodoWrite` APIs.
+7. Flip factory defaults to Codemode-first and subagent-first in the breaking release branch.
+8. Update examples, docs, and release notes.
+9. Run full local gates and review public API surface before release.
+
+This sequence keeps the hardest internals testable before changing public defaults.
+
+---
+
+## System-Wide Impact
+
+This work changes BashKit's public API, default tool surface, docs, examples, Codemode dependency posture, context-layer inheritance, event model, and budget semantics. It should be treated as a breaking release because it changes both the default coding harness and the default delegation model.
+
+The `Sandbox` interface should remain unchanged. Subagents orchestrate model/tool execution. Sandboxes remain responsible for filesystem and process operations.
+
+Optional peer dependency strategy needs a release decision. The factory should remain binary: hosts either configure Codemode and get a Codemode-first surface, or omit Codemode and get direct tools. The cleaner API is Codemode-first with an explicit construction-time error when a caller asks for Codemode but does not provide the executor.
+
+---
+
+## Risks & Mitigations
+
+| Risk | Impact | Mitigation |
+| --- | --- | --- |
+| AI SDK cancellation support is limited | Interrupt may be best effort | Return explicit unsupported errors and keep status transitions truthful. |
+| Codemode dependency boundary changes | Consumers may need new setup | Document peer dependency and the Codemode/no-Codemode factory split clearly. |
+| Recursive spawning runs away | Cost and resource exhaustion | Enforce depth, active count, total count, and budget before spawn. |
+| Quarantine bypass through Codemode | Restricted agents mutate state | Filter Codemode providers after profile resolution and test blocked tools. |
+| Context bloat from fan-out | Cost spikes and parent context pressure | Make context inheritance explicit and default to bounded recent turns. |
+| UI expects transcripts in model output | Parent context fills with child logs | Provide event sinks and transcript references instead. |
+| Durable resume expectations | Users assume active-run recovery | Document metadata/result persistence and defer active-run resume. |
+| API surface becomes too complex | Hard adoption | Keep defaults simple and put advanced knobs in profile/config objects. |
+| Removed legacy tools surprise consumers | Migration confusion | Treat as a major breaking change and document `Task` -> `SpawnAgent`/`WaitAgent`, `TodoWrite` -> `UpdatePlan`. |
+
+---
+
+## Open Questions
+
+- OQ1. Should this ship as `1.0.0` to make the breaking change obvious, or as a pre-1.0 breaking minor with unusually strong migration notes?
+- OQ2. Resolved: remove `Task` entirely in the breaking release.
+- OQ3. What should the default max depth be: `1` for safest behavior or `2` to allow supervisor -> worker -> specialist patterns?
+- OQ4. Should the default `worker` profile expose subagent control tools, or should recursive spawning require explicit profile opt-in?
+- OQ5. Should profile files live under a conventional path such as `.bashkit/agents/*.json`, or should loading remain completely caller-directed?
+
+---
+
+## Documentation Notes
+
+The docs should introduce the model in this order:
+
+1. Codemode is the default coding harness.
+2. Subagent controls are the default delegation surface.
+3. Profiles define capability, safety, cost, and context boundaries.
+4. Control panel state is host-rendered from typed snapshots.
+5. `Task` and `TodoWrite` are removed.
+
+The examples should show:
+
+- Basic Codemode-first setup.
+- Spawn/wait/list lifecycle.
+- Read-only researcher profile.
+- Reviewer/fixer fan-out.
+- Budget exhaustion behavior.
+- Control panel snapshot rendering.
+- Migration from `Task`.
+
+---
+
+## Sources & Research
+
+- BashKit current implementation: `src/subagents/`, `src/tools/subagents/`, `src/tools/codemode.ts`, `src/tools/index.ts`, `src/types.ts`, `tests/tools/budget-integration.test.ts`, `README.md`
+- BashKit module guidance: `src/tools/AGENTS.md`, `src/tools/patch/AGENTS.md`
+- Codex reference concepts: `codex-rs/core/src/agent/control.rs`, `codex-rs/core/src/agent/registry.rs`, `codex-rs/core/src/agent/role.rs`, `codex-rs/core/src/tools/handlers/multi_agents_v2.rs`, `codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs`, `codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs`, `codex-rs/core/src/tools/handlers/multi_agents_spec.rs`
+- Codex test coverage patterns: `codex-rs/core/tests/suite/subagent_notifications.rs`, `codex-rs/core/tests/suite/spawn_agent_description.rs`, `codex-rs/core/tests/suite/agent_execution.rs`, `codex-rs/core/tests/suite/hierarchical_agents.rs`
+- Agent-native architecture guidance: unified orchestrator, primitive tools, explicit completion, shared state, context limits, profile/model tier selection, and agent-to-UI event projection.
+- Workflow direction from the provided dynamic workflows note: fan-out-and-synthesize, adversarial verification, generate-and-filter, tournament, loop-until-done, quarantine, model routing, and resume pressure.
diff --git a/docs/src/app/MobileNav.tsx b/docs/src/app/MobileNav.tsx
index c607732..36651b1 100644
--- a/docs/src/app/MobileNav.tsx
+++ b/docs/src/app/MobileNav.tsx
@@ -13,6 +13,7 @@ const links = [
{ href: "/codemode", label: "Codemode" },
{ href: "/sandboxes", label: "Sandboxes" },
{ href: "/context", label: "Context" },
+ { href: "/runtime", label: "Runtime" },
{ href: "/api-reference", label: "API Reference" },
];
diff --git a/docs/src/app/SideNav.tsx b/docs/src/app/SideNav.tsx
index 4bdb61f..bb4cb02 100644
--- a/docs/src/app/SideNav.tsx
+++ b/docs/src/app/SideNav.tsx
@@ -49,8 +49,8 @@ const links: {
{ id: "grep", text: "Grep" },
{ id: "websearch", text: "WebSearch" },
{ id: "webfetch", text: "WebFetch" },
- { id: "task", text: "Task" },
- { id: "todowrite", text: "TodoWrite" },
+ { id: "updateplan", text: "UpdatePlan" },
+ { id: "subagents", text: "Subagents" },
],
},
{
@@ -87,6 +87,20 @@ const links: {
{ id: "full-example", text: "Full Example" },
],
},
+ {
+ href: "/runtime",
+ label: "Runtime",
+ items: [
+ { id: "overview", text: "Overview" },
+ { id: "event-sink", text: "Event Sink" },
+ { id: "plan-state", text: "Plan State" },
+ { id: "ai-sdk-ui-streams", text: "AI SDK UI Streams" },
+ { id: "file-changes", text: "File Changes" },
+ { id: "approvals", text: "Approvals" },
+ { id: "snapshots", text: "Snapshots" },
+ { id: "host-responsibilities", text: "Host Responsibilities" },
+ ],
+ },
{
href: "/api-reference",
label: "API Reference",
diff --git a/docs/src/app/api-reference/page.tsx b/docs/src/app/api-reference/page.tsx
index f9e8cff..c85a88d 100644
--- a/docs/src/app/api-reference/page.tsx
+++ b/docs/src/app/api-reference/page.tsx
@@ -100,11 +100,18 @@ const { tools, budget } = await createAgentTools(sandbox, {
Cloudflare Codemode adapter. Adds a single codemode{" "}
- tool that can orchestrate selected executable tools. Excludes
+ tool that becomes the parent-visible coding surface. Direct coding
+ tools remain available as Codemode runtime providers rather than
+ appearing beside codemode. Excludes
client-intervention tools and tools with{" "}
needsApproval. Supports extra default-namespace tools
and named providers.
+
+ Controller-backed subagent configuration. Presence enables
+ SpawnAgent, ListAgents, WaitAgent, SendMessage, FollowupTask, and
+ InterruptAgent tools, plus optional control-panel state.
+
Enable tool result caching. Pass true for defaults or
an object for fine-grained control.
@@ -246,8 +253,8 @@ const result = await generateText({
});`}
/>
- Budget tracking auto-wires into Task tool sub-agents when
- configured.
+ Budget tracking can be shared with subagent controllers so parent
+ and child work are governed by one budget domain.
Model ID Matching
diff --git a/docs/src/app/codemode/page.tsx b/docs/src/app/codemode/page.tsx
index 1a0e050..2d0bfe6 100644
--- a/docs/src/app/codemode/page.tsx
+++ b/docs/src/app/codemode/page.tsx
@@ -29,6 +29,13 @@ export default function Codemode() {
policy-wrapped tools. That means context layers, plan-mode gates,
output policy, and sandbox restrictions keep applying.
+
+ The factory surface is binary: configure codemode and
+ the parent model gets Codemode as its coding tool; omit{" "}
+ codemode and the parent model gets direct BashKit
+ tools. There is no parent-surface compatibility mode that exposes
+ both.
+
@@ -142,7 +149,7 @@ async () => {
Tool Selection
- Keep the inner tool set narrow. includeTools is the
+ Keep the runtime tool set narrow. includeTools is the
safest default because generated code can loop and fan out calls.
{
ExitPlanMode, tools without an execute{" "}
function, and tools with needsApproval.
+
+ Parent-visible control tools such as UpdatePlan and
+ subagent controls can still be exposed beside codemode.
+ Direct coding/file tools stay inside the runtime provider surface.
+
- BashKit provides a comprehensive toolkit for building AI coding
- agents using the{" "}
+ BashKit is a runtime foundation for building AI coding agents on the{" "}
Vercel AI SDK
- . It bridges the gap between AI models and actual code execution
- environments, giving your agents the ability to run commands, read
- and modify files, search codebases, and more.
+ . It gives models a coding surface, a sandbox-backed tool layer,
+ Codemode orchestration, Codex-style plans, controller-managed
+ subagents, budget controls, approvals, and host-facing runtime
+ events.
- Tool signatures are designed to match{" "}
- Claude Code patterns, so AI models already know how
- to use them effectively. Bring your own sandbox — start with{" "}
- LocalSandbox for development, swap to Vercel or E2B for
- production.
+ The goal is not just to call Bash from a model. It is
+ to give host apps the primitives they need to build a Codex-like
+ coding experience while still owning their server, storage, UI, and
+ approval flow.
-
10 Tools
+
Runtime Primitives
- Bash — Execute shell commands with timeout
- control
+ Codemode — Let the model write code that
+ orchestrates BashKit tools in batches
- Read — Read files and list directories
+ Sandbox tools — Read, search, edit, write,
+ and run commands through your chosen sandbox
- Write — Create or overwrite files
+ UpdatePlan — Maintain Codex-style progress
+ state that host UIs can render
- Edit — Replace strings in existing files
+ Subagents — Spawn, list, wait for, and
+ control focused child agents
- Glob — Find files by pattern matching
+ Runtime events — Observe tools, commands,
+ plans, approvals, files, and agents through typed event sinks
- Grep — Search file contents with regex
-
-
- WebSearch — Web search with domain
- filtering
-
-
- WebFetch — Fetch and extract web content
-
-
- Task — Spawn sub-agents for complex work
-
-
- TodoWrite — Manage structured task lists
+ Cost controls — Track spend and share
+ budget limits with parent and child runs
+ AI SDK Native — Tools, models, steps, and
+ stop conditions stay in the Vercel AI SDK flow
+
+
+ Codemode Ready — Expose one coding tool to
+ the parent model while keeping direct tools inside the runtime
+
+
+ Subagent Control Plane — Identity,
+ profiles, lifecycle state, control-panel snapshots, and guardrails
+ for child agents
+
+
+ Runtime Events — Feed logs, progress
+ panels, approval UI, and changes views from the same event stream
+
Bring Your Own Sandbox — LocalSandbox for
dev, VercelSandbox or E2BSandbox for production
@@ -234,6 +259,11 @@ console.log(text);`}
Explore Tools →
+
+
+ Runtime Events →
+
+
Sandbox Options →
diff --git a/docs/src/app/runtime/page.tsx b/docs/src/app/runtime/page.tsx
new file mode 100644
index 0000000..dd419f0
--- /dev/null
+++ b/docs/src/app/runtime/page.tsx
@@ -0,0 +1,339 @@
+"use client";
+
+import { CodeBlock } from "../components/CodeBlock";
+import { Footer } from "../Footer";
+
+export default function Runtime() {
+ return (
+ <>
+
+
+
Runtime
+
+ Host-facing events, plan state, approvals, and snapshots for
+ Codex-style interfaces.
+
+
+
+
+
Overview
+
+ Runtime APIs are not tools the model calls. They are the typed state
+ and event layer that host apps can observe while BashKit tools,
+ Codemode, plans, approvals, and subagents run.
+
+
+ This keeps BashKit useful for building a Codex-like UI without
+ making BashKit own your app server, database, websocket layer,
+ terminal UI, or persistence model.
+
+ A RuntimeEventSink receives normalized runtime events.
+ The built-in memory sink is useful for tests, demos, and host-side
+ projection. Production hosts usually forward events to their own
+ stream, log, or persistence layer.
+
+ UpdatePlan writes to a canonical plan state. Hosts can
+ initialize the plan, observe plan.updated events, and
+ project the latest plan into a progress panel.
+
+
+
+
+
+
AI SDK UI Streams
+
+ Runtime events stay outside the model message list by default, but
+ hosts can forward them to the browser as AI SDK UI data parts. The
+ model still receives ordinary tool results; the UI receives typed
+ progress, changes, approvals, and subagent activity alongside the
+ assistant stream.
+
+ When a runtime eventSink is configured, BashKit emits{" "}
+ file.changed events after successful calls to{" "}
+ Bash, Write, Edit, and{" "}
+ Patch. Each event includes the path, change type,
+ originating tool, tool call id, and a unified diff when BashKit can
+ safely capture one. Bash changes are detected by snapshotting
+ watched roots before and after the command.
+
+ BashKit exposes approval event helpers so hosts can render and audit
+ approval flows consistently. A denied tool call denies that specific
+ execution, not the whole tool forever.
+
+
+
+
+
+
Snapshots
+
+ Snapshot helpers turn event history into host-facing state. They are
+ deliberately plain JSON so a server can send them to any UI
+ framework.
+
+
+
+
+
+
Host Responsibilities
+
+ BashKit emits events and maintains typed runtime primitives. The
+ host still owns the outer product concerns:
+
+
+
+ Persisting event history, transcripts, and result references
+
+
Streaming runtime events to browsers or desktop views
+
Rendering progress, changes, approvals, and subagent panels
+
Mapping approval decisions back to AI SDK tool execution
+
Choosing cancellation, auth, retention, and audit policies
- Spawn sub-agents for complex, multi-step work. Each sub-agent gets
- its own tool set and conversation context. When budget tracking is
- enabled, costs are shared across parent and child agents.
+ Track multi-step progress with the canonical Codex-style plan state.
+ Hosts can observe plan updates through runtime events.
-
TodoWrite
+
Subagents
- Manage structured task lists. Useful for agents that need to track
- multi-step workflows.
+ Use controller-backed subagent tools for delegation. SpawnAgent
+ starts separable work, ListAgents inspects status, and WaitAgent
+ collects terminal results. Configure subagents through{" "}
+ createAgentTools to expose the control tools to the
+ parent model.
+
+ Profile files can be loaded with{" "}
+ loadSubagentProfilesFromJson,{" "}
+ loadSubagentProfilesFromObject, or{" "}
+ loadSubagentProfilesFromFile. Model names inside the
+ file are aliases that the host maps to live AI SDK model objects.
+
+
diff --git a/examples/basic.ts b/examples/basic.ts
index f8e880b..f3f9883 100644
--- a/examples/basic.ts
+++ b/examples/basic.ts
@@ -10,23 +10,14 @@ import {
anthropicPromptCacheMiddleware,
createAgentTools,
createLocalSandbox,
- createTaskTool,
- createTodoWriteTool,
- type TodoState,
} from "../src";
async function main() {
// Create sandbox in a temp directory
const sandbox = createLocalSandbox({ cwd: "/tmp/bashkit-test" });
- // Create sandbox-based tools
- const { tools: sandboxTools } = await createAgentTools(sandbox);
-
- // Create state for todos
- const todoState: TodoState = { todos: [] };
- const todoTool = createTodoWriteTool(todoState, undefined, (todos) => {
- console.log("📝 Todos updated:", JSON.stringify(todos, null, 2));
- });
+ // Create sandbox-based tools plus the canonical UpdatePlan progress tool.
+ const { tools } = await createAgentTools(sandbox);
// Wrap model with prompt caching middleware for better performance
const model = wrapLanguageModel({
@@ -34,29 +25,6 @@ async function main() {
middleware: anthropicPromptCacheMiddleware,
});
- // Create task tool for sub-agents (also uses cached model)
- const taskTool = createTaskTool({
- model,
- tools: sandboxTools,
- subagentTypes: {
- research: {
- systemPrompt: "You are a research specialist. Find information only.",
- tools: ["Read", "Grep", "Glob"],
- },
- coding: {
- systemPrompt: "You are a coding expert. Write clean code.",
- tools: ["Read", "Write", "Edit", "Bash"],
- },
- },
- });
-
- // Combine all tools
- const tools = {
- ...sandboxTools,
- TodoWrite: todoTool,
- Task: taskTool,
- };
-
console.log("🚀 Starting bashkit test...\n");
console.log("Available tools:", Object.keys(tools).join(", "));
console.log("Prompt caching: enabled");
@@ -71,9 +39,9 @@ async function main() {
system: `You are a helpful coding assistant with access to tools for file operations and code execution.
When given a task:
-1. Use TodoWrite to plan your steps
+1. Use UpdatePlan to plan your steps
2. Execute each step using the appropriate tools
-3. Mark todos as completed as you go`,
+3. Mark plan items as completed as you go`,
prompt: `Create a simple "hello world" TypeScript file at /tmp/bashkit-test/hello.ts and then run it with bun.`,
stopWhen: stepCountIs(10), // Allow up to 10 steps
onStepFinish: ({ finishReason, toolCalls, toolResults, text, usage }) => {
diff --git a/examples/codemode-subagents.ts b/examples/codemode-subagents.ts
new file mode 100644
index 0000000..f24f162
--- /dev/null
+++ b/examples/codemode-subagents.ts
@@ -0,0 +1,86 @@
+/**
+ * Codemode + subagent wiring example.
+ *
+ * This file avoids importing @cloudflare/codemode directly so it can typecheck
+ * in repos that keep Codemode as an optional peer. Pass a real executor from
+ * your host app, for example a DynamicWorkerExecutor.
+ */
+
+import type { LanguageModel } from "ai";
+import type { CodemodeExecutor, Sandbox } from "../src";
+import {
+ createAgentTools,
+ loadSubagentProfilesFromJson,
+ type SubagentConfig,
+} from "../src";
+
+export async function createCodemodeSubagentTools(options: {
+ sandbox: Sandbox;
+ executor: CodemodeExecutor;
+ defaultModel: LanguageModel;
+ researcherModel: LanguageModel;
+}) {
+ const loaded = loadSubagentProfilesFromJson(
+ JSON.stringify({
+ defaultProfile: "researcher",
+ defaults: {
+ model: "default",
+ context: { recent_turns: 3 },
+ cost: {
+ maxActiveAgents: 3,
+ maxDepth: 1,
+ },
+ },
+ profiles: [
+ {
+ name: "researcher",
+ description: "Read-only repository research",
+ model: "researcher",
+ system: "Investigate the task. Cite files. Do not edit.",
+ allowedTools: ["Read", "Glob", "Grep"],
+ deniedTools: ["Write", "Edit", "Bash"],
+ codemode: {
+ enabled: true,
+ exposeDirectTools: false,
+ includeTools: ["Read", "Glob", "Grep"],
+ },
+ },
+ {
+ name: "implementer",
+ description: "Scoped implementation agent",
+ system: "Make small, well-tested code changes.",
+ allowedTools: ["Read", "Glob", "Grep", "Write", "Edit", "Bash"],
+ deniedTools: ["SpawnAgent", "FollowupTask"],
+ codemode: {
+ enabled: true,
+ exposeDirectTools: false,
+ includeTools: ["Read", "Glob", "Grep", "Write", "Edit", "Bash"],
+ },
+ },
+ ],
+ }),
+ {
+ models: {
+ default: options.defaultModel,
+ researcher: options.researcherModel,
+ },
+ },
+ );
+
+ if ("error" in loaded) throw new Error(loaded.error);
+
+ const subagents: SubagentConfig = {
+ model: options.defaultModel,
+ profiles: loaded.profiles,
+ profileDefaults: loaded.defaults,
+ defaultProfile: loaded.defaultProfile,
+ };
+
+ return createAgentTools(options.sandbox, {
+ codemode: {
+ executor: options.executor,
+ includeTools: ["Read", "Glob", "Grep", "Bash"],
+ },
+ subagents,
+ });
+}
diff --git a/examples/subagents.ts b/examples/subagents.ts
new file mode 100644
index 0000000..cbd3a5d
--- /dev/null
+++ b/examples/subagents.ts
@@ -0,0 +1,75 @@
+/**
+ * Controller-backed subagent example without a model provider.
+ *
+ * Run with: bun examples/subagents.ts
+ */
+
+import {
+ createAgentTools,
+ createLocalSandbox,
+ createStaticSubagentRunner,
+ loadSubagentProfilesFromJson,
+} from "../src";
+
+async function main() {
+ const sandbox = createLocalSandbox({ cwd: "/tmp/bashkit-subagents" });
+
+ const loaded = loadSubagentProfilesFromJson(
+ JSON.stringify({
+ defaultProfile: "researcher",
+ profiles: [
+ {
+ name: "researcher",
+ description: "Read-only code researcher",
+ system: "Investigate the task and cite relevant files.",
+ allowedTools: ["Read", "Glob", "Grep"],
+ deniedTools: ["Write", "Edit", "Bash"],
+ context: { recent_turns: 2 },
+ cost: { maxDepth: 1 },
+ },
+ ],
+ }),
+ );
+
+ if ("error" in loaded) throw new Error(loaded.error);
+
+ const { subagentController, getSubagentControlPanelState } =
+ await createAgentTools(sandbox, {
+ subagents: {
+ profiles: loaded.profiles,
+ defaultProfile: loaded.defaultProfile,
+ runner: createStaticSubagentRunner({
+ status: "completed",
+ result: "Found the relevant files and summarized the implementation.",
+ usage: { stepsCompleted: 1 },
+ }),
+ },
+ });
+
+ if (!subagentController) throw new Error("expected subagent controller");
+
+ const handle = await subagentController.spawn({
+ task: "Find where BashKit creates subagent control tools.",
+ task_name: "research/control-tools",
+ });
+ if ("error" in handle) throw new Error(handle.error);
+
+ const result = await subagentController.wait({
+ agent: handle.agent_id,
+ timeoutMs: 1_000,
+ });
+ if ("error" in result) throw new Error(result.error);
+
+ console.log("Subagent result:");
+ console.log(JSON.stringify(result, null, 2));
+
+ console.log("\nControl panel snapshot:");
+ console.log(JSON.stringify(await getSubagentControlPanelState?.(), null, 2));
+
+ await sandbox.destroy();
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+});
diff --git a/src/context/AGENTS.md b/src/context/AGENTS.md
index 538d89e..c57ca43 100644
--- a/src/context/AGENTS.md
+++ b/src/context/AGENTS.md
@@ -13,6 +13,8 @@ Builds the agent's runtime context: static system prompt assembly (instructions,
| `tool-guidance.ts` | `buildToolGuidance()` — one-line hint list keyed by registered tool names |
| `execution-policy.ts` | `createExecutionPolicy()` — plan-mode + custom gate layer (`beforeExecute`) |
| `output-policy.ts` | `createOutputPolicy()` — truncation + redirection hints + optional disk stash (`afterExecute`) |
+| `runtime-events.ts` | `createRuntimeEventLayer()` — emits normalized `tool.started`, `tool.completed`, and `tool.failed` events |
+| `file-changes.ts` | `createFileChangeEventLayer()` — snapshots mutating tool targets / watched Bash roots and emits normalized `file.changed` events with unified diffs |
| `prepare-step.ts` | `createPrepareStep()` — composes compaction + context-status + plan-mode hints for AI SDK `prepareStep` |
## Key Exports
@@ -23,6 +25,8 @@ Builds the agent's runtime context: static system prompt assembly (instructions,
- `buildSystemContext(sandbox, config?)` -- Returns `{ instructions, environment, toolGuidance, combined, meta }`; designed to be called **once at init** for prompt-cache stability
- `createExecutionPolicy(planModeState, config?)` -- Blocks `["Bash", "Write", "Edit"]` by default when plan mode is active
- `createOutputPolicy(config?)` -- Defaults: `maxOutputLength: 30000`, `redirectionThreshold: 20000`, uses `middleTruncate`
+- `createRuntimeEventLayer(config)` -- Emits host-facing runtime tool lifecycle events through a `RuntimeEventSink`
+- `createFileChangeEventLayer(config)` -- Emits host-facing `file.changed` events for successful `Bash`, `Write`, `Edit`, and `Patch` calls by comparing before/after sandbox file snapshots. `Bash` uses capped watched-root snapshots because commands do not declare target files.
- `createPrepareStep(config)` -- Returns a `PrepareStepFunction`; **never touches `system`** (prompt cache)
- `discoverInstructions`, `collectEnvironment`, `formatEnvironment`, `buildToolGuidance` -- individual section builders
@@ -66,6 +70,7 @@ prepare-step.ts ──→ ../utils/compact-conversation + ../utils/context-s
**Depends on**:
- `../sandbox/interface` — `Sandbox` type for `discoverInstructions`/`collectEnvironment`/`stashOutput`
- `../tools/enter-plan-mode` — `PlanModeState` type (execution-policy + prepare-step)
+- `../runtime` — runtime event sink and normalized event contracts (runtime-events)
- `../utils/helpers` — `middleTruncate` (output-policy)
- `../utils/compact-conversation`, `../utils/context-status` — prepare-step pipeline
- `ai` — `Tool`, `ToolSet`, `ModelMessage`, `PrepareStepFunction`, `PrepareStepResult`
@@ -117,6 +122,7 @@ Pass a `PrepareStepFunction` as `config.extend` to `createPrepareStep`. It runs
- `build-context.test.ts` (434 lines) — system prompt assembly, section enabling/disabling, combined output
- `execution-policy.test.ts` (145 lines) — plan-mode blocking, custom predicates
- `output-policy.test.ts` (520 lines) — truncation, hints, stash-to-disk, custom builders
+- `runtime-events.test.ts` — tool lifecycle event emission and createAgentTools auto-wiring
- `prepare-step.test.ts` (196 lines) — compaction, context-status injection, plan-mode hint, extend composition
- `with-context.test.ts` (346 lines) — layer wrapping, gate short-circuit, transform pipe, no-execute passthrough
- `parallel.test.ts` (175 lines) — layer isolation under concurrent tool calls
diff --git a/src/context/file-changes.ts b/src/context/file-changes.ts
new file mode 100644
index 0000000..c70a9e3
--- /dev/null
+++ b/src/context/file-changes.ts
@@ -0,0 +1,320 @@
+import type { Sandbox } from "../sandbox/interface";
+import {
+ createRuntimeEvent,
+ type FileChangeKind,
+ type RuntimeEventSink,
+} from "../runtime";
+import type { ContextLayer } from "./index";
+import { getRuntimeToolCallMeta } from "./runtime-events";
+
+export interface FileChangeEventLayerConfig {
+ sandbox: Sandbox;
+ eventSink: RuntimeEventSink;
+ agentId?: string | null;
+ threadId?: string | null;
+ turnId?: string | null;
+ /** Tools to observe. Defaults to Bash, Write, Edit, and Patch. */
+ includeTools?: readonly string[];
+ /** Roots to snapshot around Bash calls. Defaults to the sandbox working directory via ".". */
+ rootPaths?: readonly string[];
+ /** Maximum number of files captured per Bash before/after snapshot. */
+ maxSnapshotFiles?: number;
+ /** Maximum directory depth captured per Bash before/after snapshot. */
+ maxSnapshotDepth?: number;
+ /** Directory/file path segments skipped during Bash snapshots. */
+ excludePathSegments?: readonly string[];
+ /** Maximum emitted unified diff size per file. */
+ maxDiffBytes?: number;
+}
+
+interface FileSnapshot {
+ exists: boolean;
+ content: string | null;
+}
+
+const DEFAULT_TOOLS = new Set(["Bash", "Write", "Edit", "Patch"]);
+const DEFAULT_MAX_DIFF_BYTES = 60_000;
+const DEFAULT_ROOT_PATHS = ["."] as const;
+const DEFAULT_MAX_SNAPSHOT_FILES = 500;
+const DEFAULT_MAX_SNAPSHOT_DEPTH = 6;
+const DEFAULT_EXCLUDED_PATH_SEGMENTS = [
+ ".git",
+ ".next",
+ "dist",
+ "build",
+ "coverage",
+ "node_modules",
+] as const;
+
+function stringFromUnknown(value: unknown): string | null {
+ return typeof value === "string" && value.length > 0 ? value : null;
+}
+
+function pathsFromPatch(patch: string): string[] {
+ const paths = new Set();
+ for (const line of patch.split("\n")) {
+ for (const marker of [
+ "*** Add File: ",
+ "*** Delete File: ",
+ "*** Update File: ",
+ "*** Move to: ",
+ ]) {
+ if (line.startsWith(marker)) {
+ const path = line.slice(marker.length).trim();
+ if (path) paths.add(path);
+ }
+ }
+ }
+ return [...paths];
+}
+
+function pathsFromParams(toolName: string, params: Record) {
+ if (toolName === "Write" || toolName === "Edit") {
+ const filePath = stringFromUnknown(params.file_path);
+ return filePath ? [filePath] : [];
+ }
+
+ if (toolName === "Patch") {
+ const patch = stringFromUnknown(params.patch);
+ return patch ? pathsFromPatch(patch) : [];
+ }
+
+ return [];
+}
+
+function pathsFromResult(result: Record): string[] {
+ const paths = new Set();
+ const filePath = stringFromUnknown(result.file_path);
+ if (filePath) paths.add(filePath);
+
+ if (Array.isArray(result.files)) {
+ for (const item of result.files) {
+ if (!item || typeof item !== "object") continue;
+ const path = stringFromUnknown((item as Record).path);
+ if (path) paths.add(path);
+ }
+ }
+
+ return [...paths];
+}
+
+function joinSandboxPath(parent: string, child: string): string {
+ if (parent === "." || parent === "") return child;
+ if (parent.endsWith("/")) return `${parent}${child}`;
+ return `${parent}/${child}`;
+}
+
+function pathSegments(path: string): string[] {
+ return path.split("/").filter((segment) => segment.length > 0);
+}
+
+function shouldExcludePath(
+ path: string,
+ excludePathSegments: readonly string[],
+): boolean {
+ const segments = new Set(pathSegments(path));
+ return excludePathSegments.some((segment) => segments.has(segment));
+}
+
+async function snapshotFile(
+ sandbox: Sandbox,
+ path: string,
+): Promise {
+ try {
+ const exists = await sandbox.fileExists(path);
+ if (!exists) return { exists: false, content: null };
+ const content = await sandbox.readFile(path);
+ return { exists: true, content };
+ } catch {
+ return { exists: false, content: null };
+ }
+}
+
+async function snapshotTree(
+ sandbox: Sandbox,
+ rootPaths: readonly string[],
+ options: {
+ maxFiles: number;
+ maxDepth: number;
+ excludePathSegments: readonly string[];
+ },
+): Promise