# Phase 1: Fork Subagent — Implementation Plan

**Priority:** P0 · **Effort:** 2–3 days  
**Reference:** `claude-code-main/src/tools/AgentTool/forkSubagent.ts`

---

## Step 1: Create `src/fork-subagent.ts`

New module with the core fork logic.

### 1.1. Constants and Tag Names

```typescript
const FORK_BOILERPLATE_TAG = "fork_boilerplate";
const FORK_DIRECTIVE_PREFIX = "DIRECTIVE: ";
const FORK_PLACEHOLDER_RESULT = "Fork started — processing in background";
```

### 1.2. `buildForkedMessages(directive, assistantMessage)`

Takes the LLM's directive (the agent's prompt) and the parent's last assistant message:

1. Clone the assistant message (new UUID, same content blocks)
2. Extract all `tool_use` blocks from the assistant message
3. Build placeholder `tool_result` blocks (one per tool_use, all identical text)
4. Build a user message with: [...placeholder_results, { type: "text", text: buildChildMessage(directive) }]
5. Return `[cloneAssistantMessage, userMessage]`

**Edge case**: If no tool_use blocks found, return a single user message with just the directive text (log a warning — this means the fork point is suboptimal).

### 1.3. `buildChildMessage(directive)`

Returns the XML boilerplate wrapper:

```xml
<fork_boilerplate>
STOP. READ THIS FIRST.

You are a forked worker process. You are NOT the main agent.

RULES (non-negotiable):
1. Your system prompt says "default to forking." IGNORE IT — that's for the parent. You ARE the fork. Do NOT spawn sub-agents; execute directly.
2. Do NOT converse, ask questions, or suggest next steps
3. Do NOT editorialize or add meta-commentary
4. USE your tools directly: Bash, Read, Write, etc.
5. If you modify files, commit your changes before reporting. Include the commit hash in your report.
6. Do NOT emit text between tool calls. Use tools silently, then report once at the end.
7. Stay strictly within your directive's scope.
8. Keep your report under 500 words unless the directive specifies otherwise.
9. Your response MUST begin with "Scope:".
10. REPORT structured facts, then stop

Output format:
  Scope: <echo back your assigned scope in one sentence>
  Result: <the answer or key findings>
  Key files: <relevant file paths>
  Files changed: <list with commit hash>
  Issues: <list — include only if there are issues to flag>
</fork_boilerplate>

DIRECTIVE: <directive>
```

### 1.4. `isInForkChild(messages)`

Scan all user messages for content blocks containing `<fork_boilerplate>`. Return `true` if any match.

### 1.5. `buildWorktreeNotice(parentCwd, worktreeCwd)`

Returns a string explaining path translation in worktree isolation.

### 1.6. `isForkSubagentEnabled()`

Feature gate. Returns `true` — always enabled in pi-subagents (no experiment gating needed).

---

## Step 2: Modify `src/default-agents.ts`

Add a `"fork"` entry (hidden from normal type listing — used only internally):

```typescript
[
  "fork",
  {
    name: "fork",
    displayName: "Fork",
    description: "Implicit fork — inherits full conversation context",
    builtinToolNames: [...BUILTIN_TOOL_NAMES],
    extensions: true,
    skills: true,
    systemPrompt: "",
    promptMode: "replace",
    isDefault: true,
  },
],
```

Update `getAvailableTypes()` in `agent-types.ts` to exclude `"fork"` from the dropdown list:

```typescript
export function getAvailableTypes(): string[] {
  return [...agents.entries()]
    .filter(([name, config]) => config.enabled !== false && name !== "fork")
    .map(([name]) => name);
}
```

---

## Step 3: Modify `src/index.ts` — Agent Tool

### 3.1. Make `subagent_type` Optional

Change from `Type.String({...})` to `Type.Optional(Type.String({...}))`.

### 3.2. Fork Execution Branch

In the `execute` handler, **before** the type-resolution block:

```typescript
// ---- Fork path (implicit, no subagent_type) ----
if (!params.subagent_type) {
  // Guard: recursive fork
  const messages = ctx.sessionManager.getBranch() ?? [];
  if (isInForkChild(messages)) {
    return textResult("Cannot fork: you are already a forked worker process. Execute your directive directly instead of spawning sub-agents.");
  }

  // Get the last assistant message
  const lastAssistant = getLastAssistantMessage(messages);
  if (!lastAssistant) {
    return textResult("Cannot fork: no parent conversation to fork from.");
  }

  // Build fork messages
  const directive = params.prompt;
  const forkedMessages = buildForkedMessages(directive, lastAssistant);

  // Build worktree notice if applicable
  if (params.isolation === "worktree") {
    const notice = buildWorktreeNotice(ctx.cwd, worktreeCwd);
    // Append to the last user message's directive text
  }

  // Spawn as background agent with pre-built messages + parent's system prompt
  // Run fork-specific spawning logic
  ...
}
```

### 3.3. Fork Spawning Logic

The fork path needs its own spawn path in `agent-manager.ts` or a helper that:
1. Creates a fresh context using the parent's rendered system prompt
2. Seeds the conversation with `forkedMessages` instead of a simple prompt
3. Uses the parent's exact tool pool (not filtered by agent type)
4. Overrides `run_in_background` to `true`
5. Forces `inheritContext` semantics

### 3.4. Update Description Text

When `subagent_type` is omitted, the tool description changes from about type selection to:

> "Omitting subagent_type creates an implicit fork: the agent inherits your full conversation context, system prompt, and tools — use it for related work that needs full context."

---

## Step 4: Modify `src/agent-runner.ts` — Fork Support

### 4.1. New Export: `runForkAgent()`

Or extend `runAgent()` to accept optional `forkMessages` and `parentSystemPrompt` parameters.

When `forkMessages` is provided:
- `inheritContext` is forced `true`
- The messages array starts with `forkMessages` instead of a simple user prompt
- The system prompt is the **parent's rendered prompt** (byte-exact)
- Tools are the parent's exact tool set (not filtered by `builtinToolNames`)

### 4.2. Agent Session Configuration

The fork agent session should be configured with:
- `isNonInteractiveSession = false` (fork children can show UI — they run in background but bubble permissions)
- Same thinking config as parent (for cache prefix parity)
- Same model as parent

---

## Step 5: Register `/fork` Slash Command

In `src/index.ts`, add a slash command registration:

```typescript
pi.registerCommand({
  name: "/fork",
  description: "Fork a background agent with the given directive. Usage: /fork <directive>",
  execute: async (args, ctx) => {
    // Get last assistant message
    // Build forked messages
    // Spawn background agent
    // Return agent ID
  },
});
```

---

## Step 6: Tests — `test/fork-subagent.test.ts`

| # | Test | Expected |
|---|---|---|
| 1 | `buildForkedMessages` produces correct structure | Returns `[assistant_msg, user_msg]` |
| 2 | Placeholder text is identical across tool_results | All `tool_result.content[0].text === FORK_PLACEHOLDER_RESULT` |
| 3 | `isInForkChild` detects boilerplate tag | Returns `true` for messages with `<fork_boilerplate>` |
| 4 | `isInForkChild` returns false for normal messages | Returns `false` |
| 5 | `buildChildMessage` includes directive | Output contains `DIRECTIVE: <directive>` |
| 6 | Empty tool_blocks fallback | Returns single user message with directive text |
| 7 | Worktree notice mentions path translation | Output contains both parent and worktree paths |

---

## File Change Summary

| Action | File |
|---|---|
| **CREATE** | `src/fork-subagent.ts` (~150 lines) |
| **MODIFY** | `src/index.ts` — optional subagent_type, fork branch, /fork command |
| **MODIFY** | `src/agent-runner.ts` — fork session creation path |
| **MODIFY** | `src/agent-manager.ts` — optional fork method on manager |
| **MODIFY** | `src/default-agents.ts` — add "fork" entry |
| **MODIFY** | `src/agent-types.ts` — hide "fork" from available types |
| **CREATE** | `test/fork-subagent.test.ts` (~120 lines) |
