# Building Cline from scratch: a programming agent that lives in the IDE and also runs in the terminal

> **What this tutorial is**: a from-scratch build plan whose blueprint is **Cline** (`github.com/cline/cline`, Apache-2.0, `main` branch checked out 2026-08; CLI v3.0.49 / VS Code extension 4.1.3 / SDK package 0.0.69). You will act as a developer who can also do product work: start from a PRD, then explode Cline into an executable development plan—**each functional module listed in build order, with purpose, implementation shape, stack, definition trade-offs, strengths and costs, and how the pieces couple**. No technical detail is too small, including the basics.
> **Core narrative**: follow the action “the user types a prompt in the chat box and hits enter” from zero to the end—how it is packed, how it is sent to the engine, how it runs repeatedly in the main loop (model ↔ tools), until streamed results return to the chat window and render. That chain is the skeleton of the whole project.
> **How to read**: read chapter 1 (PRD) and chapter 2 (overview and milestones) first, then proceed in order. Chapter 20 (the full workflow) is the climax; read it closely after modules 1–9.

---

## Contents

### Part I · Charter and foundation
1. [PRD: what we will and will not build](#ch1)
2. [Overview: five layers, milestones, team](#ch2)
3. [Module 0: Monorepo scaffold and package boundaries](#ch3)

### Part II · Engine core (build in order)
4. [Module 1: Event contracts and the shared layer (`shared`)](#ch4)
5. [Module 2: Provider abstraction and the model catalog (`llms`)](#ch5)
6. [Module 3: Stateless main loop (`agents`) — `AgentRuntime`](#ch6)
7. [Module 4: Message shaper (`MessageBuilder`)](#ch7)
8. [Module 5: Tool system and approval (28 tools + presets)](#ch8)
9. [Module 6: Safety rails (loop-detection / mistake-tracker / sandbox)](#ch9)
10. [Module 7: Session orchestration (`SessionRuntime`) and storage](#ch10)

### Part III · Surfaces and drivers
11. [Module 8: Host abstraction (`RuntimeHost`) and the `ClineCore` façade](#ch11)
12. [Module 9: CLI — OpenTUI interaction + headless](#ch12)
13. [Module 10: VS Code extension and WebView](#ch13)

### Part IV · Advanced capabilities
14. [Module 11: Context compaction (`basic` / `agentic`)](#ch14)
15. [Module 12: Checkpoint and versioned rollback](#ch15)
16. [Module 13: Multi-agent teams](#ch16)
17. [Module 14: Cron schedules and connectors](#ch17)
18. [Module 15: Plugins, MCP, hooks, hub](#ch18)

### Part V · Full workflow and close
19. [Module 16: Rules and skills (`.clinerules` / skills)](#ch19)
20. [“After the user submits a prompt”: the full workflow](#ch20)
21. [Testing strategy and evaluation](#ch21)
22. [Release, packaging, and CI/CD](#ch22)

---

<h2 id="ch1">Chapter 1 PRD: what we will and will not build</h2>

### 1.1 One-sentence product definition

> **Cline is an open-source programming agent: it lives in your IDE (the VS Code sidebar), also runs in the terminal (CLI), and also runs in CI/CD (headless). It uses any mainstream model (179 registrable Provider IDs / 4118 models). Through Plan/Act dual modes and 28 tools, it edits code across files, runs commands, and browses the web—and every consequential operation asks you first.**

### 1.2 Users and scenarios

| User | Scenario | Core need |
|---|---|---|
| Developer (IDE user) | Let the agent edit code inside VS Code | Sidebar visibility, reviewable diffs, checkpoint rollback |
| Developer (terminal user) | Agent at the command line | Fast, scriptable, headless-capable |
| CI/CD engineer | Agent in a pipeline | `--json` NDJSON event stream, exit codes, no interaction |
| Enterprise / integrator | Embed the agent in their own product | SDK (`@cline/sdk`), programmable, plugins |

### 1.3 Functional requirements (FR) and priority

**P0 (must exist in the MVP)**:
- FR-1 Multi-provider chat (Anthropic/OpenAI/Gemini + custom endpoints)
- FR-2 Core agent loop: model → tools → model again
- FR-3 Core tools: read files, write files (`editor`/`apply_patch`), run commands, search the codebase
- FR-4 Plan/Act dual modes
- FR-5 Tool approval (consequential ops ask first) + auto-approve
- FR-6 Session persistence (continue after restart)
- FR-7 Streaming render (thinking / text / tools shown separately)

**P1 (high priority)**:
- FR-8 VS Code extension (WebView UI)
- FR-9 CLI (TUI + headless `--json`)
- FR-10 Context compaction (long sessions must not explode)
- FR-11 Checkpoint rollback
- FR-12 MCP ingress
- FR-13 `.clinerules` rules

**P2 (differentiation)**:
- FR-14 Multi-agent teams
- FR-15 Cron scheduled tasks
- FR-16 IM connectors (Slack/Telegram…)
- FR-17 Hub collaboration center
- FR-18 Plugin system

### 1.4 Non-functional requirements (NFR)

- **NFR-1 Cache-friendly**: system prompt and message assembly must minimize damage to the provider prefix cache (save tokens)
- **NFR-2 Embeddable**: the engine core (the loop) must be stateless; the host must be swappable
- **NFR-3 Safety**: approval by default, loop detection, error ceilings
- **NFR-4 Cost-controllable**: compaction policy, token estimation, model routing

### 1.5 Explicit non-goals

- ❌ Not a cloud platform (local-first; hub is local collaboration only)
- ❌ Not a memory store / vector retrieval (compaction + checkpoint instead)
- ❌ Not a GUI framework (the VS Code WebView is React you draw yourself)
- ❌ Not a general-task agent (focus on programming)

### 1.6 PRD acceptance bar

> The user types: “Change every `parseInt(x, 10)` in `src/parser.ts` to `Number(x)`, and run the tests.” Expected: Plan mode first proposes a plan → switch to Act → after approval, `editor`/`apply_patch` edits the file → `run_commands` runs tests → results render in the chat window → rollback is available. Full flow < 5 minutes; P99 tool call < 30s.

---

<h2 id="ch2">Chapter 2 Overview: five layers, milestones, team</h2>

### 2.1 Target architecture (aligned with Cline v3)

```mermaid
flowchart TB
    subgraph APP["Host application layer"]
        CLI["CLI (OpenTUI + commander)"]
        VSC["VS Code extension (WebView React)"]
        HUB["hub daemon (WebSocket collab hub)"]
    end
    subgraph CORE["core package · stateful orchestration"]
        CC["ClineCore façade"]
        LH["RuntimeHost (local/hub/remote)"]
        SR["SessionRuntime session orchestration"]
        MB["MessageBuilder message shaping"]
        CP["compaction strategy"]
        ST["storage (JSON + SQLite)"]
    end
    subgraph AGENTS["agents package · stateless main loop"]
        AR["AgentRuntime while loop"]
    end
    subgraph LLMS["llms package · Provider layer"]
        GATE["gateway + factory"]
        CAT["model catalog (4118 models)"]
        VENDORS["vendors (AI SDK adapters)"]
    end
    subgraph SHARED["shared package · contract layer"]
        PATH["paths / events / tool types / token estimate"]
    end
    CLI --> CORE
    VSC --> CORE
    HUB --> CORE
    CC --> LH --> SR
    SR --> MB & CP & ST
    SR --> AR
    AR --> LLMS
    LLMS --> SHARED
    CORE --> SHARED
```

### 2.2 Milestones (12 months, 6-person team)

| Milestone | When | Delivery |
|---|---|---|
| M0 Foundation | Months 1–2 | Monorepo scaffold + shared + llms minimal Provider + single-file loop demo |
| M1 Engine | Months 3–4 | AgentRuntime + 4 core tools + approval + session storage |
| M2 Desktop | Months 5–6 | VS Code extension WebView complete UI |
| M3 Terminal | Months 7–8 | CLI (TUI + headless `--json`) + compaction + checkpoint |
| M4 Ecosystem | Months 9–10 | MCP/plugins/hooks + multi-agent teams + cron + connectors |
| M5 Polish | Months 11–12 | hub + test hardening + release/CI/CD |

### 2.3 Team split (6 people)

- **TL / architecture** (1): module boundaries, frozen interfaces, code review
- **Engine engineers** (2): agents / core / llms
- **Frontend engineer** (1): VS Code WebView / CLI TUI
- **Full-stack** (1): cron / connectors / hub / plugins
- **Test / QA** (1): e2e golden scenarios, regression

### 2.4 Engineering discipline (three red lines)

1. **Dependency direction is one-way**: `shared ← llms ← agents ← core ← apps`. Whoever violates it refactors (enforced by bun workspaces + `package.json` dependencies).
2. **The `agents` package is stateless**: no persistence in `agents`, no host lifecycle—it is a pure loop library.
3. **Event contracts freeze first**: freeze the `AgentRuntimeEvent` enum first; a shape change must sync the whole chain.

---

<h2 id="ch3">Chapter 3 Module 0: Monorepo scaffold and package boundaries</h2>

### Purpose

One repo holds 6 packages + 2 apps. Dependencies are one-way. Build / test / publish are unified.

### Implementation shape (aligned with Cline’s `package.json`)

- **Package manager**: bun (workspaces + `bun -F <package>` filtered commands). Cline uses bun 1.3.13, node >= 22. **Trade-off**: bun is fast, has a built-in test runner, and is native to TypeScript; the ecosystem is less settled than npm. If the team will not take bun, pnpm workspaces are conceptually equivalent.
- **Root `package.json`**: `"workspaces": ["sdk/packages/*", "apps/*", ...]`; scripts target with `bun -F '@cline/xxx'`.
- **Package inventory**:

| Package | Path | Responsibility | Depends on |
|---|---|---|---|
| `@cline/shared` | `sdk/packages/shared` | Contracts, paths, events, tool types | none |
| `@cline/llms` | `sdk/packages/llms` | Provider adapters, model catalog | shared |
| `@cline/agents` | `sdk/packages/agents` | Stateless main loop | shared, llms |
| `@cline/core` | `sdk/packages/core` | Stateful orchestration | agents, llms, shared |
| `@cline/sdk` | `sdk/packages/sdk` | Façade (re-export core) | core |
| `@cline/ui` | `sdk/packages/ui` | Shared UI components | — |
| `cline` (CLI) | `apps/cli` | Terminal app | core, ui |
| `@cline/code` (extension) | `apps/vscode` | VS Code extension | core, ui |

### Stack detail

- Language: TypeScript 5.9 (`strict`)
- Lint/Format: Biome (Cline uses 2.4.5)
- Tests: vitest 4 (`vitest.config.ts` per package)
- Commits: husky + lint-staged
- Versioning: changesets (`.changeset/`)

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| bun rather than npm/pnpm | Fast, TS-native, built-in bundler | Team learning cost |
| Independent packages rather than one package | Forced boundaries, independent release, SDK can be trimmed | Debug / publish complexity |
| Biome rather than ESLint+Prettier | One tool for both jobs, fast | Fewer ecosystem plugins |

### Strengths and costs

**Strength**: boundaries are sharp (compile time catches dependency-direction violations); `@cline/sdk` is naturally publishable; tests isolate.
**Cost**: a change that touches 6 packages needs a full build; bun workspace `bun install` is occasionally painful.

### How the pieces couple

Upper packages import lower packages. `@cline/sdk` is the only public export. **Build the package skeleton first (empty `index.ts` + `tsconfig` per package), then fill it**—boundaries before implementation.

---

<h2 id="ch4">Chapter 4 Module 1: Event contracts and the shared layer (`shared`)</h2>

### Purpose

The project’s “constitution”: event enums, tool types, path resolution, token estimation, hook events. The lower package has no dependencies; every upper package depends on it.

### Implementation shape (aligned with `@cline/shared`, 15417 lines / 88 files)

Key submodules:

1. **`storage/` (644-line `paths.ts`)**: `resolveClineDataDir()` → `~/.cline/data` (overridable by `CLINE_DATA_DIR`); sub-paths `resolveCronDbPath` / `resolveProviderSettingsPath` / `resolveGlobalSettingsPath` / `resolveSessionDataDir`.
2. **`llms/tokens.ts`**: `estimateRequestInputTokens` — **conservative 3 chars/token estimate** (no exact tokenizer, because hundreds of models have different tokenizers; the estimate only needs to be “accurate enough” for compaction-trigger decisions).
3. **`llms/tools.ts`**: `ToolPolicy = {enabled?, autoApprove?}`.
4. **`hooks/events.ts`**: 10 external hook events: `agent_start / agent_resume / agent_abort / agent_end / agent_error / tool_call / tool_result / prompt_submit / pre_compact / session_shutdown`.
5. **`tools/create.ts`**: `createTool({name, description, inputSchema, execute, timeoutMs, retryable, maxRetries, lifecycle})` tool factory (L81).

### Definition trade-offs

- **Why token estimate uses character count rather than a tokenizer**: hundreds of providers have different tokenizers; exact estimate is unrealistic and unnecessary—compaction-trigger thresholds tolerate ±20% error.
- **Why paths live in `shared`**: CLI and the VS Code extension must share one data set (`exportVSCodeStorageToSharedFiles` exists to unify them); paths must have a single source.

### Strengths and costs

**Strength**: contracts are centralized, cross-package type-safe, paths/events are not reimplemented.
**Cost**: `shared` bloat risk (everything gets dumped in)—discipline required: only put what is “shared across packages”; package-private stays in each package.

### How the pieces couple

- **Event enums** are referenced by three sides: agents (emit), core (translate), apps (consume).
- **`ToolPolicy`** is referenced by core approval and apps configuration.
- **Token estimate** is referenced by core compaction triggers.

### Acceptance

> `import { estimateRequestInputTokens } from "@cline/shared"` works in three packages; an event-enum change explodes every caller at compile time.

---

<h2 id="ch5">Chapter 5 Module 2: Provider abstraction and the model catalog (`llms`)</h2>

### Purpose

The main loop should only know “an interface that can stream tokens.” It must not care whether the back end is Anthropic, Ollama, or a self-hosted endpoint. At the same time, give 179 provider IDs / 4118 models a queryable catalog.

### Implementation shape (aligned with `@cline/llms`, 118772 lines)

1. **Depend on the Vercel AI SDK** (`ai@7` + `@ai-sdk/anthropic/openai/google/google-vertex/amazon-bedrock/mistral/openai-compatible` + `ai-sdk-ollama`). **Trade-off**: a homegrown provider layer (as in OpenWorker) vs using the AI SDK—the AI SDK saves ~80% of adapter work, at the cost of being constrained by its API.
2. **Dual-registration Factory** (`factory-registry.ts`): `registerHandler(fn)` / `registerAsyncHandler(fn)` + `createHandler(config)` façade: look up the registry first, otherwise fall through to `gateway.ts`’s `createGatewayApiHandler` (OpenAI-compatible fallback).
3. **`vendors/`**: AI SDK adapters per provider (anthropic/bedrock/google/vertex/mistral/ollama/openai-compatible).
4. **Model catalog** (`catalog/catalog.generated.ts`, 87274 lines): **generated by a script** (`scripts/generate-models.ts` fetches from the models.dev catalog-live), containing contextWindow/pricing/capabilities for 4118 models. Root script `bun run build:models` = `bun -F @cline/llms generate:models`.
5. **Usage normalization** (`ai-sdk.ts`): `GatewayNormalizedUsage` (`inputTokens`/`outputTokens`/`cacheReadTokens`/`cacheWriteTokens`/`reasoningTokenCount`/`totalCost`)—every provider is normalized to this one shape; upper layers only know that shape.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| AI SDK rather than homegrown | Fast, stable, tracks updates | API evolution depends on upstream |
| Auto-generated model catalog | Hand-writing 4118 models is impossible | Needs network fetch + script maintenance |
| OpenAI-compatible fallback | Any self-hosted endpoint can be used | Compatibility is uneven (tool-call format) |

### Strengths and costs

**Strength**: unmatched breadth (179 provider IDs); adding a model is running a script; usage normalization unifies cost accounting.
**Cost**: a 87k-line generated file in git (high churn); the AI SDK abstraction occasionally leaks (a provider’s special capability needs a hack).

### How the pieces couple

- **`agents`** calls `createHandler(config)` to obtain a provider and feeds it to `model.stream()`.
- **`core`’s `local-provider-service.ts` (963 lines)** owns CRUD for custom providers and writes them into the models file for registration.
- **`core` compaction** uses `GatewayNormalizedUsage.inputTokens` to decide whether to trigger.

### Acceptance

> One request can stream content back from Anthropic, Ollama, and an OpenAI-compatible endpoint; usage normalizes consistently; switching models only changes a config field.

---

<h2 id="ch6">Chapter 6 Module 3: Stateless main loop (`agents`) — `AgentRuntime`</h2>

### Purpose

**This is the project’s heart**: a re-entrant while loop. Model ↔ tools run until done. It must be stateless (no persistence, no host awareness), so CLI / VS Code / hub can all drive it.

### Implementation shape (aligned with `@cline/agents/agent-runtime.ts`, 1969 lines)

Core `execute()` (L641, loop body L677–789):

```mermaid
flowchart TD
    START["execute()<br/>emit run-started"] --> TURN["while loop<br/>emit turn-started"]
    TURN --> GEN["generateAssistantMessageWithOverflowRecovery()<br/>L876 for await model.stream"]
    GEN --> TOOL{"model wants a tool?"}
    TOOL -->|no| STEER{"steering interrupt?"}
    STEER -->|yes| CONSUME["consumePendingUserMessage<br/>when iteration>1 L968"] --> TURN
    STEER -->|no| DONE["finishRun(completed)"]
    TOOL -->|yes| EXEC["executeToolCalls()<br/>L1455 sequential/parallel"]
    EXEC --> WRITE["write back tool_result"] --> TURN
    TOOL -->|terminal tool submit_and_exit| FINISH["finishRun(completed)"]
```

Key mechanisms:

1. **Each run is an independent instance**: the session layer does `new AgentRuntime({...})`, seeds with a full transcript via `initialMessages`, then `runtime.run("")` (empty input to avoid duplication). Multiple turns = re-enter repeatedly.
2. **Streaming consume**: inside `generateAssistantMessageWithOverflowRecovery`, `for await (const event of model.stream(request))`, converting stream events into `AgentRuntimeEvent` emissions.
3. **Overflow recovery**: when the provider rejects the request (context overflow), automatically take the recovery path (switch to compaction).
4. **Steering consume**: when `iteration > 1`, check the pending prompt queue; if there is an interrupt, inject it.
5. **Discriminated config union**: `WithModel | WithProvider`—either pass a model instance or pass provider config.
6. **`prepare-turn` projection seam**: core injects compaction `prepareTurn`—`agents` stays stateless but allows “be projected before each turn.”

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Stateless class + re-entry | Host is pluggable; sessions can be many | Rebuild cost on every run |
| Events all go out | The engine does not know who is listening | Event protocol must stay stable |
| Synchronous loop + external events | Logic is straight, easy to test | Blocking loop (the host must put it on a worker) |

### Strengths and costs

**Strength**: engine is zero-coupled, testable (feed a fake provider to unit-test loop logic), embeddable (the core of `@cline/sdk`).
**Cost**: re-entry = every user message rebuilds loop context; the `run("")` empty-input pattern is hard for newcomers.

### How the pieces couple

- **`core`’s `SessionRuntime.executeRunInternal`** (orchestrator L737) creates `AgentRuntime` (L856) and `run("")` (L878).
- **`llms`** supplies the model (`createAgentModelFromConfig`).
- **`core`** injects `prepareTurn` (compaction).

### Acceptance

> Unit test: a fake provider returns text → `tool_call` → `tool_result` → text in sequence; the loop runs exactly 3 turns and ends; interrupt can stop at any point; steering can inject at iteration 2.

---

<h2 id="ch7">Chapter 7 Module 4: Message shaper (`MessageBuilder`)</h2>

### Purpose

Turn a “session message array” into a “provider API request”—while doing cache protection, truncation, orphan repair, and a media budget. **This is the key to saving money (tokens) and not exploding (overflow).**

### Implementation shape (aligned with `core/src/session/services/message-builder.ts`, 1727 lines)

`buildForApi(messages)` (L166), six steps:

1. **`reindex` (L278)**: incremental index—compare tail references; on hit, index only the new tail, otherwise rebuild fully. **Cache-friendly**.
2. **`commitOutdatedRewrites` (L336)**: files that were read and later changed have old content batch-replaced with `[outdated - see the latest file content]`; commit only when over `minOutdatedRewriteBytes` (64KB)—avoid frequent damage to the provider prefix cache.
3. **`addMissingToolResults` (L502)**: in interrupt scenarios, give orphan `tool_call`s a fake `is_error: true` result.
4. **Per-block truncation**: assistant text 12K (repeated tool markup) / 200K (ordinary), file blocks 50K, `tool_result` 8K (keep head and tail), nested-depth truncation.
5. **`applyMediaBudget` (L1332)**: images rate-limited by a byte budget; over budget, swap in `IMAGE_OMITTED_PLACEHOLDER`.
6. **`truncateToTotalTextBudget` (L1162)**: global 6MB backstop; candidates truncated descending by size; `tool_use` arguments last.

### Definition trade-offs

- **Why “touch the middle as little as possible”**: provider prefix cache (prompt cache) hits only when the message prefix is unchanged. Every design (incremental, batched rewrite, truncation order) exists to keep the prefix.
- **Why a 6MB backstop**: provider hard limits differ; 6MB is a safe global floor.

### Strengths and costs

**Strength**: saves tokens (high cache hit rate), prevents provider rejection (overflow backstop), fault-tolerant (orphan repair).
**Cost**: logic is complex (6-step pipeline + a pile of threshold constants); hard to debug (env vars `CLINE_MESSAGE_BUILDER_*` can override).

### How the pieces couple

- **`core`’s orchestrator `prepareProviderMessagesForApi` (L1049)** first runs plugin-registered `messageBuilder`s, then calls `buildForApi`.
- **`agents`** receives the assembled messages and sends the request.
- The system prompt is assembled separately by `composeSystemPrompt` (L679); **tool definitions are passed separately, not stuffed into the message array**.

### Acceptance

> Long-session test: after 10 rounds of tool calls, the prefix cache still hits (observable as provider-returned `cacheReadTokens > 0`); images over budget are replaced by the placeholder; orphan `tool_call`s from interrupt do not make the provider error.

---

<h2 id="ch8">Chapter 8 Module 5: Tool system and approval (28 tools + presets)</h2>

### Purpose

Give the model a set of “hands,” and run every consequential operation through approval. Tools split into two classes: built-in (9 defaults + `spawn_agent` + 18 team) and external (MCP/plugins).

### Implementation shape (aligned with `core/src/extensions/tools/`)

**Tool catalog** (`runtime.ts`): `BASE_TOOL_CATALOG` (9 entries: tool id / description / `headlessToolNames`) → preset (`act`/`plan`/`search`/`minimal`/`yolo`) + `model-tool-routing` (per-model disable rules) + `disabledToolIds` → `resolveCoreSelectedToolIds` (lines 206–245) → `createDefaultTools` instantiates by “switch && has executor.”

**9 default tools** (`definitions.ts`):

| Tool | Factory line | What it does |
|---|---|---|
| `read_files` | 247 | Read files (windowed) |
| `search_codebase` | 343 | Search the codebase (replaces glob) |
| `run_commands` | 457 | Run terminal commands |
| `fetch_web_content` | 518 | Fetch web pages (a degraded web search) |
| `apply_patch` | 611 | Aider-style batch patches |
| `editor` | 660 | Precise single-file edit |
| `skills` | 723 | Skill invocation |
| `ask_question` | 780 | Ask the user a question |
| `submit_and_exit` | 801 | Headless end-of-run (`lifecycle.completesRun`) |

**Approval chain** (`tool-approval.ts`, 102 lines):

```
model tool call → SessionRuntime merges toolPolicies (global * + per-tool, orchestrator:117-133)
→ enabled? → beforeTool hook (includes loop detection)
→ autoApprove? → yes: execute; no: host requestToolApproval
→ requestDesktopToolApproval (L29): write <sessionId>.request.<id>.json, poll decision file every 200ms, 5-minute timeout
→ execute → afterTool
```

**Preset grades** (`presets.ts:137`): `default | yolo`—yolo sets `autoApprove: true` on `*` and all default tools (the other presets: `act`/`plan`/`search`/`minimal` are tool-set presets, not permission presets).

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| 9 lean default tools | Few and focused on programming | Web search / glob capability degrades |
| `editor` + `apply_patch` dual edit | `editor` is precise single-file / `apply_patch` is batch multi-file | The two tools are mutually exclusive; the model must choose |
| File-polling approval IPC | Simple, reliable, cross-process decoupled | 5-minute timeout is hard-coded; no push |
| Per-model routing disables tools | Replaces Claude Code’s consecutive-prefix sort; fits thousands of models | Routing rules must be maintained |

### Strengths and costs

**Strength**: few tools, flat catalog, precise per-model control; yolo is one-click full allow (fits CI).
**Cost**: approval is polling, not push (latency up to 200ms + file IO); no OpenWorker-style precise target-level standing rule.

### How the pieces couple

- **`agents`’ `executeToolCalls` (L1455)** calls the executor.
- **`core`’s orchestrator** owns `toolPolicies` merge and approval.
- **UI (apps)** renders approval cards (`packages/ui/components/agent-approval-card.tsx`).
- **MCP/plugin** tools are wrapped into the same catalog via `createTool`.

### Acceptance

> Headless mode (`--json` + not a TTY): commands that are not auto-approved are refused immediately (no hang); auto-approve mode executes all; yolo fully allows.

---

<h2 id="ch9">Chapter 9 Module 6: Safety rails (loop-detection / mistake-tracker / sandbox)</h2>

### Purpose

Agents “jam” (dead loops), “miss repeatedly” (consecutive failures), and “get exploited” (malicious code). Three rails backstop that.

### Implementation shape (aligned with `core/src/runtime/safety/` + `tools/subprocess-sandbox.ts`)

**① Loop detection** (`loop-detection.ts`, 162 lines):
- `toolCallSignature` (L50): **sorted-key JSON serialization** of input (keys ordered so semantically identical calls share a signature).
- `checkRepeatedToolCall` (L113): consecutive identical “tool name + signature” count, **soft=3 / hard=5**.
- `LoopDetectionTracker.inspect` (L136) returns `ok`/`soft`/`hard`; SessionRuntime: soft → inject a “try another way” hint into the conversation; hard → `forceAtLimit` fed to MistakeTracker and abort (orchestrator:1244–1273).

**② Mistake tracking** (`mistake-tracker.ts`, 229 lines):
- Three reasons: `api_error / invalid_tool_call / tool_execution_failed`.
- `record()` accumulates `consecutiveMistakes`; at the limit, `onLimitReached` decides (default stop); continue zeros and attaches recovery guidance; stop writes a stop message + `activeRuntime.abort()`.
- All `record`s are serialized through an `activeTrackerWork` Promise chain (order-preserving).

**③ Subprocess sandbox** (`subprocess-sandbox.ts`, 346 lines):
- Spawn a node/bun child over IPC: parent sends `{type:"call"}`, child replies `{type:"response"|"event"}`.
- Call timeout SIGTERM→SIGKILL hard-kill (lines 210–274).
- `resolveSubprocessRuntimeExecutable` (L73) resolves node/bun.
- **Use**: plugin / browser sandbox code is unsafe in the main process—isolate crashes in a child.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Soft/hard dual thresholds | Soft gives a chance, hard hard-stops | Thresholds (3/5) are guesses; need tuning |
| Sorted-key signature | Semantically identical calls (different key order) are still recognized | JSON serialization cost |
| Subprocess sandbox | Crash isolation + timeout hard-kill | IPC complexity |

### Strengths and costs

**Strength**: three rails cover jam / consecutive miss / exploit; sandbox hard-kill is reliable.
**Cost**: no OpenWorker-style shell injection defense (command whitelist with precise token match); the rails are “after the fact,” not “structurally safe beforehand.”

### How the pieces couple

- Loop detection hangs on the **`beforeTool` hook** (wired in the orchestrator).
- MistakeTracker stop calls `activeRuntime.abort()`—**`agents`’ interrupt entry**.
- Sandbox service **plugin system** (plugin sandbox) and browser tools.

### Acceptance

> e2e: model calls the same tool 6 times → hard triggers abort; 3 consecutive API errors → mistake limit stops; a plugin crash does not take down the main process.

---

<h2 id="ch10">Chapter 10 Module 7: Session orchestration (`SessionRuntime`) and storage</h2>

### Purpose

The layer that “remembers state”: cross-turn state (messages, usage, mistake, loop tracking), persistence, event translation. This is the heart of the `core` package.

### Implementation shape (aligned with `core/src/runtime/orchestration/session-runtime-orchestrator.ts`, 1429 lines)

`SessionRuntime` (L278) holds: `ConversationStore`, `MistakeTracker` (L405), `LoopDetectionTracker` (L443), `MessageBuilder`, `contributionRegistry`, `RuntimeEventAdapter` (L331).

**`executeRun` chain**: `run()` (L647) → `executeRun()` (L690) → `executeRunWithAuthRetry()` (L712, OAuth-failure retry) → `executeRunInternal()` (L737):
1. append the user message to ConversationStore (L783)
2. assemble config (L836)
3. `new AgentRuntime` (L856) → `runtime.run("")` (L878)
4. after completion, write `runResult.messages` back to the session (L905)

**Event translation**: `handleRuntimeEvent()` (L1061) synchronously maintains tool records / usage / error tracking, then via `RuntimeEventAdapter.translate()` (`runtime-event-adapter.ts:183`) maps **14 `AgentRuntimeEvent` kinds → 9 legacy `AgentEvent` kinds** (stateful: usage increments use deltas, tool `durationMs`).

**Storage** (aligned with `core/src/services/storage/` + `session/stores/`):
- Session index: `sessions.index.json` (single-file full JSON + atomicWrite + statusLock CAS)
- Manifest: `sessions/<id>/session.json`
- Messages: `sessions/<id>/<id>.json` (rewrite as a whole, `{version, messages[], system_prompt}`)
- SQLite: `db/sessions.db` (`sqlite-session-store.ts` 292 lines), `db/cron.db`, `db/connectors.db`
- Secrets: `secrets.json` (`chmod 0o600`)
- `SessionPersistenceAdapter` abstraction; default file implementation; hub/remote can swap

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Message JSON rewritten as a whole | A session must be readable into memory as a whole to rebuild | Large sessions write slowly |
| Index + manifest + messages as three files | Index reads are fast; changing the manifest does not touch messages | Many files |
| New events → legacy event adapter | Compatibility with old consumers during the refactor | Translation layer must be maintained |

### Strengths and costs

**Strength**: cross-turn state is centralized, event translation is isolated, storage is pluggable (future full SQLite).
**Cost**: orchestrator is too fat (1429-line single file); JSON rewrite is a performance risk for large sessions.

### How the pieces couple

- **`host` (`LocalRuntimeHost`)** creates one SessionRuntime per session and drives `run`.
- **`MessageBuilder` / compaction / tools** are all parts of the orchestrator.
- **`ClineCore` (649 lines)** is the façade: `create()` (L203) builds the host, `start()` (L280) → `host.startSession`, remaining methods pass through to host.

### Acceptance

> After process restart, `readMessages` restores the full session; two concurrent messages do not overwrite each other (`statusLock`); translated legacy events render correctly in the UI.

---

<h2 id="ch11">Chapter 11 Module 8: Host abstraction (`RuntimeHost`) and the `ClineCore` façade</h2>

### Purpose

Above the engine (`agents`) and the session (`core`), you still need an abstraction for “who is driving it”—inside a CLI process, inside a VS Code process, across processes via hub. `RuntimeHost` is that layer.

### Implementation shape (aligned with `core/src/runtime/host/`)

**`RuntimeHost` interface** (`runtime-host.ts:315`): `startSession / runTurn / abort / stopSession / dispose / subscribe`. **Three implementations**:
- `LocalRuntimeHost` (`local-runtime-host.ts`, 2396 lines): in-process. `startSession()` (L334) → `startResolvedSession()` (L366) builds manifest/provider config → `DefaultRuntimeBuilder.build()` (`runtime-builder.ts:348`) assembles tools/team/extensions → `createAgentInstance` builds SessionRuntime (L748) → `ActiveSession` (L785) into `sessions: Map`. `runTurn()` (L946) runs immediately or queues by delivery; `executeTurn()` (L1551) → `executeAgentTurn()` (L1664, chooses `run`/`continue` by `session.started`, including a team auto-continue loop L1582); `shutdownSession` (L1989) persists state + cleanup. Events go through `AgentEventBridge` (`local/agent-event-bridge`) into `CoreSessionEvent`.
- `HubRuntimeHost` (`hub-runtime-host.ts`, 2156 lines): connects to a local hub server; hooks/tools/checkpoint/compaction are negotiated by capability prefix.
- `RemoteRuntimeHost`: remote.

**Factory** (`host.ts:137`): `createRuntimeHost()`, auto mode auto-discovers hub and falls back to local.

**`ClineCore` façade** (`ClineCore.ts`, 649 lines): `ClineCore.create()` (L203) creates host + FeatureFlags + cron; `start()` (L280) → `host.startSession`; `send / abort / stop / get / list / delete / update / readMessages / readLiveMessages / restore / compareCheckpoint` all pass through to host. `automation / settings / pendingPrompts` are the public API.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Three host implementations | Same engine supports in-process / hub / remote | Three implementations must stay in sync |
| Auto-mode fallback | Can run without hub | Implicit behavior |

### Strengths and costs

**Strength**: host is pluggable; hub mode lets CLI / connectors / cron share sessions across processes.
**Cost**: LocalRuntimeHost at 2396 lines is large; capability differences across three hosts (capability negotiation) need care.

### How the pieces couple

- CLI defaults to hub (`backendMode: hub`); `forceLocalBackend` is in-process.
- VS Code runs LocalRuntimeHost directly in-process.
- `ClineCore` is the single entry for all applications.

### Acceptance

> The same session, opened in the CLI, is also visible in VS Code (shared storage); in hub mode, after the CLI exits the session still runs (zen mode).

---

<h2 id="ch12">Chapter 12 Module 9: CLI — OpenTUI interaction + headless</h2>

### Purpose

The terminal façade: interactive chat (TUI) + non-interactive automation (headless `--json`) + a slate of management commands (`auth`/`config`/`mcp`/`schedule`/`connect`/`team`…).

### Implementation shape (aligned with `apps/cli/`, 71070 lines)

**Entry** (`index.ts`, 94 lines): `#!/usr/bin/env bun`, handles worker / hub daemon / connector process branches, SIGINT forwarded to `active-runtime`, dynamic `import("./main")`’s `runCli()` (`main.ts`, 1197 lines).

**Framework**: commander v14 (args) + `@clack/prompts` (wizard) + **OpenTUI** (`@opentui/core` + `@opentui/react` + `react-reconciler` + react 19). **The TUI is a homegrown React terminal renderer** (`tui/index.tsx`’s `renderOpenTui` → `createCliRenderer` → `createRoot` → `Root` component tree).

**Commands**: `auth/config/plugin/skill/connect/mcp/doctor/hook/schedule/hub/dashboard/history/team` + default chat. Root options (`commands/program.ts`): `--plan/--json/--auto-approve/-c --cwd/-P --provider/-k --key/-z --zen/--config`.

**Headless** (`main.ts` ~line 944 decides `--json || (!stdin.isTTY && !interactive)`): `runtime/run-agent.ts` (425 lines) single-shot execution; `--json` emits **NDJSON** (`emitJsonLine`, `utils/output.ts`): `run_start / run_abort_requested / message / error` etc., line by line on stdout, so CI can consume incrementally. `--zen`: the task is handed to a background hub daemon; the CLI exits immediately.

**Three approval grades** (`utils/approval.ts`): `CLINE_TOOL_APPROVAL_MODE=desktop` uses core desktop-approval IPC; on a TTY, readline `[y/N]`; not a TTY, refuse immediately.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Homegrown OpenTUI rather than Ink | Cline maintains it; native terminal rendering | Ecosystem is yours to keep |
| NDJSON rather than one JSON | Streaming events are line-consumable | Consumers must parse line by line |
| Headless decided by `--json || !isTTY` | A pipe is headless | Implicit behavior is easy to miss |

### Strengths and costs

**Strength**: one command, three modes (interactive / headless / zen); NDJSON is CI-friendly; command surface is broad (14).
**Cost**: TUI homegrown renderer maintenance; `--zen` depends on a running hub daemon.

### How the pieces couple

- `createCliCore()` (`session/session.ts`) → `ClineCore.create()`.
- Headless `run-agent` uses `AgentRuntime`-level APIs directly.
- The three approval grades reuse core’s `requestToolApproval`.

### Acceptance

> `echo "fix tests" | cline --json --auto-approve | jq` runs in CI; `cline --zen "task"` returns immediately and the task executes in the background.

---

<h2 id="ch13">Chapter 13 Module 10: VS Code extension and WebView</h2>

### Purpose

The IDE façade: sidebar chat, diff review, checkpoint rollback buttons, approval cards—all in one WebView.

### Implementation shape (aligned with `apps/vscode/`, 127726 lines)

**Entry** (`extension.ts`, 753 lines): `activate` (L66): ① `setupHostProvider` (must be first) → ② legacy storage migration → ③ `exportVSCodeStorageToSharedFiles` unifies into `~/.cline/data` (shared with CLI/JetBrains) → ④ `initialize` creates `VscodeWebviewProvider` → ⑤ register commands.

**Connection to core**: **in-process direct import**—`extension.ts` and `SdkController.ts` (2110 lines) `import @cline/core` directly. `hosts/vscode/hostbridge/` (gRPC) is only a host-capability bridge (diff/terminal/workspace); **it does not carry core**.

**WebView**: a single `WebviewView` (`claude-dev.SidebarProvider`) → an internal React app (`webview-ui`). Message render: thinking / text / tool calls shown in blocks; approval cards, diff preview (`VscodeEditPreview`), checkpoint buttons are React components. **Protocol**: webview ↔ extension host `postMessage` round trips.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Run core in-process | Simple, no IPC complexity | Extension crash takes down the engine (sandbox helps) |
| Single WebView + React | One UI covers everything | Large single-page React app complexity |
| Storage unified at `~/.cline/data` | CLI / extension / IDE share sessions | Migration logic (legacy → shared) must be written |

### Strengths and costs

**Strength**: native IDE experience (diff/terminal/workspace are VS Code capabilities); shared storage lets CLI and IDE switch seamlessly.
**Cost**: extension is large (127k lines); `webview-ui` is not open-sourced (Cline’s own)—you write the React UI yourself.

### How the pieces couple

- The extension intercepts tool requests via `SdkController.interactions.handleRequestToolApproval` → WebView renders the approval card → user clicks → decision returns.
- Diff/terminal/workspace capabilities are exposed to core’s tool executors via hostbridge.

### Acceptance

> Open a project in VS Code → sidebar chat → ask it to edit a file → diff is visible → approval passes → edit completes → click checkpoint to roll back to the previous state.

---

<h2 id="ch14">Chapter 14 Module 11: Context compaction (`basic` / `agentic`)</h2>

### Purpose

Long sessions will overflow the window. Compaction policy decides “when to compact, how to compact, how to continue after.”

### Implementation shape (aligned with `core/src/extensions/context/compaction.ts`, 710 lines)

**Trigger**: `createContextCompactionPrepareTurn` (L256) estimates `requestInputTokens` (system+message+tools) before every model request; `trigger = maxInputTokens * 0.9` (90% threshold). Three modes: `auto / manual (/compact) / overflow_recovery`.

**`basic`** (`basic-compaction.ts`, 711 lines, L452): **does not call an LLM**. Typed user prompts are always kept; the latest turn keeps “latest message + budget suffix” (aligned to assistant boundaries); older turns keep their final assistant replies (the most recent 3 in original text); the rest is discarded and folded into a `<SYSTEM_NOTICE>` dropped-work summary; freeze the last compaction product so it is not folded again. **Deterministic, zero cost**.

**`agentic`** (`agentic-compaction.ts`, 283 lines, L97): **calls a summarize model**. `findCutIndex` splits by `preserveRecentTokens` (20K); the old segment is serialized and sent to the LLM to produce a “continuation note,” replaced by a user message with `metadata.kind=compaction_summary` (`userRunSpan=` number of folded turns); an existing summary only folds new messages after it (incremental). Failure falls back to `basic`.

**Sidecar cache** (`createCompactionStateAwarePrepareTurn`, L643): after compaction, store `{compacted messages, source_prefix_hash(sha256), source_message_count, system_prompt}` in the `session-compaction.ts` sidecar; next turn `projectSessionCompactionState` (L161) checks prefix-hash match → compaction product + new message tail, **avoiding a full rebuild every turn**.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| 90% high threshold | Compact as late as possible; keep information | Near the edge, one compact may drop a lot |
| `basic`/`agentic` dual strategy | `basic` is a zero-cost backstop / `agentic` keeps information | Two implementations |
| Sidecar + prefix hash | Compaction state reused across turns | sha256 compute |

### Strengths and costs

**Strength**: `agentic` retains information well (LLM summary); `basic` is deterministic and testable; sidecar avoids repeated compaction.
**Cost**: `agentic`’s full-summary request is expensive each time; the 90% threshold is more aggressive than Claude Code’s microcompact (0.5–0.6×); critical jitter can be large.

### How the pieces couple

- Injected into **AgentRuntime via the `prepareTurn` seam** (checked before every turn).
- Uses **`shared` token estimation** to decide the trigger.
- Compaction products are assembled into the request by **`MessageBuilder`**.

### Acceptance

> Long-session e2e: messages approach 90% of the window → trigger `agentic` (or manual-trigger `basic`) → later turns continue normally; after restart the sidecar hash hits and compaction is not repeated.

---

<h2 id="ch15">Chapter 15 Module 12: Checkpoint and versioned rollback</h2>

### Purpose

Make the user willing to let the agent edit code—**a workspace snapshot at every key point + one-click rollback**.

### Implementation shape (aligned with `core/src/session/checkpoint-*.ts`, directly reusing git)

**Snapshot**: the full workspace state is stored as a **git commit/stash**. When untracked files exist, use a **three-parent commit** (`<ref>^3` captures untracked files). Session metadata records `checkpoint.history: [{ref, createdAt, runCount, kind}]`.

**Diff** (`checkpoint-diff.ts`, 150 lines): `compareCheckpointToWorkspace` (L142) → `listChangedPaths` (L76) uses `git diff --name-only -z <ref>` + `git ls-files --others` to find changed files, then per-file `git show <ref>:<path>` vs workspace → `CheckpointContentDiff[]`. Path-escape protection (L44).

**Rollback transaction** (`checkpoint-restore.ts`, 413 lines):
1. `readSessionCheckpointHistory` (L156) parses history.
2. `findCheckpointForRun` (L202) finds the nearest checkpoint before `runCount`.
3. `trimMessagesToCheckpoint` (L245) uses `getUserRunSpan` to locate the `runCount`-th user turn and truncates messages.
4. `beginWorktreeRestoreTransaction` (L50): first `git stash push --include-untracked` snapshots the current workspace; objects go to a private ref `refs/cline/restore-transactions/<uuid>`—**rollback itself must be rollback-able**.
5. `applyCheckpointToWorktree` (L357): stash uses `reset --hard <ref>^1` + `stash apply <ref>`; ordinary commits reset directly; `clean -fd` only when a `^3` parent exists (avoid unrecoverable deletion).
6. Start a **new session** with the truncated messages as `initialMessages`.

**Orchestration**: `SessionVersioningService.restoreCheckpoint` (`session-versioning-service.ts:134`): validate → restore plan → transaction snapshot → apply → new session → commit the transaction; on failure, rollback + clean up the new session.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Reuse git rather than a homegrown snapshot | Zero cost, free diffs | Depends on the project being a git repo |
| Rollback is rollback-able (transaction) | Prevents a mistaken op from destroying the workspace | High complexity (private ref management) |
| Rollback = open a new session | Messages and history are naturally isolated | Old session stays archived |

### Strengths and costs

**Strength**: snapshots are free (git objects), diffs are precise, rollback is safe (transaction). This is one of Cline’s most elegant pieces of engineering.
**Cost**: non-git projects cannot checkpoint; the cautious `clean -fd` logic is complex.

### How the pieces couple

- Wired by `LocalRuntimeHost.restoreSession` (host L913).
- After every run (or at key nodes) an automatic checkpoint is taken (`kind` distinguishes auto/manual).
- Message truncation uses `userRunSpan` (`user-run-messages.ts:133`) and couples with compaction.

### Acceptance

> Let the agent break a file → click rollback → workspace restored + the session continues from messages before the checkpoint; interrupt during rollback → workspace still intact (transaction rollback).

---

<h2 id="ch16">Chapter 16 Module 13: Multi-agent teams</h2>

### Purpose

Tasks one agent cannot finish: a coordinator splits work to specialists—multi-agent parallel collaboration.

### Implementation shape (aligned with `core/src/extensions/tools/team/multi-agent.ts`, 1852 lines)

**`AgentTeamsRuntime`** (coordinator core) in-memory state: `members / tasks / missionLog / mailbox / runs (queue+lease) / outcomes / outcomeFragments`.

**18 team tools** (`team-tools.ts:916`): `create_team_task / spawn_teammate / route_to_teammate / team_outcome` etc.—the lead uses these to split tasks, delegate, and reclaim results.

**Task state machine**: `pending → in_progress → blocked (`blockedBy` dependency chain) → completed`. Run queue `maxConcurrentRuns` rate-limits + heartbeat + `buildRecoveredRunMessage` auto-recovers interrupts.

**Persistence**: `exportState()`/`hydrateState()` full snapshot → `session/team/team-session-coordinator.ts` (240 lines) forwards `TeamEvent` (`onTeamTaskStart`/`Progress`/`End`) → lands in `sqlite-team-store.ts` (537 lines) / `file-team-store.ts`. `waitForTeamRunUpdates` splices child-agent async results into the lead’s next-turn prompt to continue coordinating.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| In-memory state + snapshot persistence | Fast, recover across sessions | Large teams occupy memory |
| Task dependency chain `blocked` | Supports DAG orchestration | No deadlock detection |
| `spawn` in-place child SessionRuntime | Reuses the same engine | Context is not isolated (shared session) |

### Strengths and costs

**Strength**: compared with Claude Code’s Task sub-agent, Cline is a real “team” (lead keeps coordinating, dependency chain, result reclaim); state is recoverable.
**Cost**: 18 `team_*` tools occupy tool space; coordination logic is complex (1852-line single file).

### How the pieces couple

- `AgentTeamsRuntime` is assembled into `BuiltRuntime` by **`runtime-builder`**.
- Child agents are `SessionRuntime` instances (reuse module 7).
- `spawn_agent` is single-shot delegation; `team_*` is ongoing coordination.

### Acceptance

> `cline --team-name auth-sprint "implement user auth with tests"`: coordinator splits 3 subtasks → parallel delegation → summarizes results → produces a final report; after interrupt, `--team-name` recovers.

---

<h2 id="ch17">Chapter 17 Module 14: Cron schedules and connectors</h2>

### Purpose

Let the agent “wake itself and work”: scheduled tasks (cron) + ingress from IM platforms (connectors).

### Implementation shape (aligned with `core/src/cron/` + `apps/cli/src/connectors/`)

**cron** (31 files):
- Spec files `.md`: `cron-watcher` watches → `cron-spec-parser` parses → `cron-reconciler` syncs.
- `sqlite-cron-store.ts` (1719 lines): independent `{data}/db/cron.db`, tables `cron_specs / cron_runs / cron_event_log`; **`claim_token` + `claim_until_at` lease to prevent multi-process duplicate execution** (same idea as OpenWorker’s skip-on-overlap).
- Triggers: `one_off / schedule (cron expression) / event`; `schedule-service.ts` (443 lines) polls + concurrency limit.

**connectors** (IM): core only owns lifecycle (`active-connectors.ts`); actual implementations live in `apps/cli/src/connectors/`: slack / discord / telegram / gchat / whatsapp / linear, six platforms. Each platform is an independent adapter process → registered with hub → sessions executed via `HubRuntimeHost`. Credentials in `db/connectors.db` + `connectors/` directory.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Cron specs as `.md` files | Human-readable, versionable | Parser must be maintained |
| SQLite lease against duplicates | Multi-process safe | Lease expiry must be handled |
| Connector as independent adapter process | Crash isolation | Process management |

### Strengths and costs

**Strength**: the lease mechanism is solid; cron spec files are user-friendly (edit `.md` to edit the schedule).
**Cost**: six platform adapters each need maintenance; IM permission control is weaker than OpenWorker (no precise target standing rule).

### How the pieces couple

- Cron fires → starts an automation session via `ClineCore`.
- Connectors go hub → `HubRuntimeHost` → `SessionRuntime`.
- All share storage under `~/.cline/data/db/*.db`.

### Acceptance

> `cline schedule create "PR summary" --cron "0 9 * * MON-FRI" --prompt ...` runs Monday–Friday at 9; two processes running the same cron execute it only once (lease).

---

<h2 id="ch18">Chapter 18 Module 15: Plugins, MCP, hooks, hub</h2>

### Purpose

Turn the engine into a platform: third parties can add tools (plugins), attach external services (MCP), hang lifecycle hooks (hooks), collaborate across processes (hub).

### Implementation shape (aligned with `core/src/extensions/` + `hub/`)

**Plugin system** (`extensions/plugin/`): plugins load via **`SubprocessSandbox`** child processes, JSON IPC (`initialize / executeTool / invokeHook / buildMessages`). Plugins can contribute: **tools, commands, rules, messageBuilders, providers, automationEventTypes, mcpServers, shortcuts, flags**. Install sources `npm / git / local / remote` (`plugin-install.ts`, 1218 lines, official slug validation).

**MCP** (`extensions/mcp/`): MCP manager + OAuth + policies; any MCP tool is wrapped into the tool catalog via `createTool`.

**Hooks**: 10 external hook events (defined in module 1); runtime hooks `AgentHooks`: `beforeRun / afterRun / beforeModel / afterModel / beforeTool / afterTool / onEvent`. Implementation: `hook-file-hooks.ts` (1010 lines, reads `.hooks/` config) + `subprocess-runner.ts` (forwards to the agent hook subprocess). Plugins register same-named hooks via `createSandboxRuntimeHooks`, RPC across the sandbox.

**hub** (46 files): local WebSocket server (`hub-websocket-server.ts`, protocol version negotiation), handlers: session/run/capability/approval/connector. `HubRuntimeHost` (2156 lines) connects to a remote hub to drive sessions; hooks/tools/checkpoint/compaction negotiated by capability prefix.

**auth/account**: WorkOS Device Flow + OAuth (`auth/cline.ts`), local OAuth server ports 48801–48811; `account/cline-account-service.ts` user-info RPC.

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| Plugins run in a subprocess sandbox | Safety (crash isolation + permissions) | IPC round-trip latency |
| Hub local rather than cloud | Data never leaves the machine | Multi-machine collab is unavailable |
| 10 external hooks + 7 runtime hooks | Broad coverage | Two hook systems must stay in sync |

### Strengths and costs

**Strength**: plugin contribution surface is large (9 capability classes); hub lets CLI/connectors/cron share; hooks can be used for audit/policy.
**Cost**: sandbox IPC is complex; no cloud hub = no remote collaboration.

### How the pieces couple

- Plugin tools are listed via `plugin-tools.ts` (259 lines) → into the tool catalog.
- Plugin `messageBuilder`s participate in assembly via orchestrator `registerMessageBuilder`.
- Hub is the shared backend for CLI `--zen`, connectors, and cron.

### Acceptance

> After installing an official plugin example (SDK-registered `createTool`), the new tool appears in the catalog; a shell script in `.hooks/` is invoked on `tool_call`; two CLI processes share the same session through hub.

---

<h2 id="ch19">Chapter 19 Module 16: Rules and skills (`.clinerules` / skills)</h2>

### Purpose

Project conventions (architecture norms, test requirements) automatically enter the system prompt; skills (loadable capability packs) load on demand.

### Implementation shape (aligned with `core/src/runtime/safety/rules.ts` + `extensions/`)

**`.clinerules`** (`rules.ts`, 49 lines): `UserInstructionConfigWatcher` takes a snapshot → filter disabled → sort by name → render `## name\ninstruction` → splice into the system prompt’s `# Rules` section (inside `composeSystemPrompt`).

**Skills**: Anthropic-format loadable capabilities. The `skills` tool (`definitions.ts:723`) lets the model load a skill’s full text on demand—**progressive disclosure** (the catalog gives a list; load gives the full text).

### Definition trade-offs

| Choice | Why | Cost |
|---|---|---|
| `.clinerules` automatically enter the prompt | Project conventions take effect with zero config | Many rules occupy tokens |
| Skills progressive disclosure | Saves tokens, stays focused | The model must “remember to load” |

### Strengths and costs

**Strength**: same lineage as Claude Code’s `CLAUDE.md` mechanism; the skill ecosystem is compatible with Anthropic format.
**Cost**: rules have no priority / conflict resolution; skill loading depends on the model’s initiative.

### How the pieces couple

- Rules enter `composeSystemPrompt` (module 4).
- The `skills` tool enters the tool catalog (module 5).

### Acceptance

> After placing `.clinerules` in the project, the agent-generated code obeys those conventions; the `skills` tool can list and load a skill’s full text.

---

<h2 id="ch20">Chapter 20 “After the user submits a prompt”: the full workflow</h2>

> **This chapter is the core of the book.** From the user typing in the chat box and hitting enter, to streamed results returning to the window—string the previous 19 chapters into one complete chain. The scenario is the **VS Code extension (in-process `LocalRuntimeHost`)**.

### 20.1 Full sequence

```mermaid
sequenceDiagram
    participant U as User
    participant WV as WebView(React)
    participant EXT as Extension host(extension.ts)
    participant CC as ClineCore
    participant LH as LocalRuntimeHost
    participant SR as SessionRuntime
    participant MB as MessageBuilder
    participant AR as AgentRuntime
    participant PR as Provider(llms)
    participant TOOL as Tool executor
    participant ST as Storage(SQLite/JSON)

    U->>WV: type prompt, enter
    WV->>EXT: postMessage({type:'user_prompt'})
    EXT->>CC: core.send(sessionId, prompt)
    CC->>LH: host.runTurn(sessionId, prompt)
    LH->>SR: sessionRuntime.run(prompt)
    SR->>ST: append user message (persist)
    SR->>SR: assemble config (model/tools/policies)
    SR->>AR: new AgentRuntime(config) → runtime.run("")
    loop main loop (while)
        AR->>PR: model.stream(request)  ← real model call here
        PR-->>AR: stream events (thinking/text)
        AR-->>SR: AgentRuntimeEvent
        SR-->>EXT: translate→legacy AgentEvent
        EXT-->>WV: postMessage render thinking/text
        AR-->>AR: model wants a tool?
        alt wants a tool
            AR->>SR: tool-call request
            SR->>SR: beforeTool + approval decision
            alt needs approval
                SR-->>EXT: requestToolApproval
                EXT-->>WV: approval card
                U-->>WV: approve/deny
                WV-->>SR: decision
            end
            SR->>TOOL: execute executor
            TOOL-->>AR: tool_result
            AR->>ST: record usage/audit
        end
    end
    AR-->>SR: runResult.messages
    SR->>ST: write session back (persist)
    SR-->>EXT: TURN_END / COMPLETED
    EXT-->>WV: render final result
    WV-->>U: streamed result in the chat window
```

### 20.2 Detail of each hop

**① Pack the input (WebView → extension host)**
- User types in the React input → `onSubmit` → `postMessage({type: "user_prompt", text})` to the extension host.
- Extension host `SdkController` receives it → calls `core.send(sessionId, text)`.

**② Locate the session (`ClineCore` → `LocalRuntimeHost`)**
- `ClineCore.send` (`ClineCore.ts`) → `host.runTurn(sessionId, ...)` (`LocalRuntimeHost` L946).
- `runTurn` decides immediate run vs enqueue by delivery (pending prompt queue, module 4).

**③ Persist the message + assemble (`SessionRuntime`)**
- `SessionRuntime.run(prompt)` (orchestrator L647) → `executeRunInternal` (L737):
  - **First append the user message to ConversationStore** (L783)—messages survive restart.
  - Assemble config (L836): model (`createAgentModelFromConfig`), tools (`runtime-builder`’s `BuiltRuntime`), `toolPolicies`.

**④ Start the main loop (`AgentRuntime`)**
- `new AgentRuntime(config)` (L856) → `runtime.run("")` (L878, empty input because the user message is already in the message array).
- while loop starts (`agent-runtime.ts:677`).

**⑤ Model call and streaming (`AgentRuntime` → Provider)**
- `generateAssistantMessageWithOverflowRecovery` (L876) → `model.stream(request)` (L1220).
- request = system prompt (`composeSystemPrompt`) + messages (`MessageBuilder.buildForApi`) + tools (tool-definition array).
- Provider (`llms` gateway) streams events → `for await` consume → emit as `AgentRuntimeEvent`.

**⑥ Events onto the screen (SR → EXT → WV)**
- `SessionRuntime.handleRuntimeEvent` (L1061) → `RuntimeEventAdapter.translate` (L183) → legacy `AgentEvent` → `AgentEventBridge` → `CoreSessionEvent` → extension `postMessage` → WebView React render (thinking collapsed block / streaming text / tool card).

**⑦ Tool execution and approval (back to SR)**
- Model requests a tool → `executeToolCalls` (L1455) → each tool goes through `beforeTool` → approval decision (module 5) → executor runs → `tool_result` written back into messages.
- Desktop approval: `requestDesktopToolApproval` (`tool-approval.ts:29`) writes a request file → extension side polls the decision file every 200ms → result returns.

**⑧ Repeat (loop continues)**
- `tool_result` enters the message array as a new assistant message → next iteration → the model sees the result → decides to continue or wrap up.
- Before each turn, compaction `prepareTurn` checks (90% threshold, module 11).
- Loop end conditions: no tool-call, `submit_and_exit`, interrupt, error ceiling (module 6).

**⑨ Result returns (AR → SR → EXT → WV)**
- `runResult.messages` written back to the session (L905) → persist (module 7 storage).
- `TURN_END` event onto the screen → WebView renders final state (usage, cost, checkpoint button).

### 20.3 What “pack, upload, receive” actually means (FAQ)

Many people ask “how is the prompt uploaded”—Cline is a local engine; there is no “upload to a server” step. The precise flow is:

1. **Pack**: prompt + system prompt + history + tool definitions = one `model.stream(request)` request body (assembled in memory, shaped by `MessageBuilder`).
2. **Send**: the request is sent to the **locally configured model endpoint** (Anthropic API / OpenAI / local Ollama…) — this is the only network IO, and it sends “context,” not “session files.”
3. **Receive**: stream events come back chunk by chunk (thinking / text / tool calls).
4. **Repeat**: tool results written back → request again → receive again → until done.
5. **Render**: each event is translated down the event chain → `postMessage` → React renders into the chat window.

**Inventory of existing feature modules / interfaces** (external pieces the whole project depends on):
- Model endpoints: Anthropic / OpenAI / Gemini / OpenRouter / Ollama etc. (via `@cline/llms` + Vercel AI SDK)
- Filesystem: read/write/search (VS Code workspace API + Node `fs` + a ripgrep-class implementation)
- Terminal: VS Code terminal API / Node `child_process` (`run_commands`)
- git: checkpoint (reuses the git object store)
- MCP servers: third-party (via MCP manager)
- Plugins: Cline plugins installed from npm/git
- Browser (`fetch_web_content`): HTTP fetch

---

<h2 id="ch21">Chapter 21 Testing strategy and evaluation</h2>

### 21.1 Layered tests

| Layer | What it tests | Tool |
|---|---|---|
| Unit (`agents`) | Loop logic (fake provider feeds events) | vitest |
| Unit (`core`) | `MessageBuilder` truncation/orphan repair, loop-detection, mistake-tracker | vitest |
| Integration (`host`) | `LocalRuntimeHost` session lifecycle, persistence | vitest + temp dir |
| e2e | Golden scenarios (edit file → run tests → rollback) | vitest.e2e (`bun test:e2e`) |
| Protocol tests | `RuntimeEventAdapter` 13→9 mapping | vitest |

### 21.2 Golden scenarios (regression protection)

1. **Edit-code closed loop**: input → Plan → Act → approval → edit → test → done.
2. **Long session**: 50 turns → compaction triggers → continues normally.
3. **Interrupt**: Stop mid-stream → no orphan `tool_call` → resume.
4. **Rollback**: break it → checkpoint rollback → workspace restored.
5. **Headless**: `--json` event stream complete, exit code correct.
6. **Multi-model**: the same scenario passes on Anthropic / Ollama / OpenAI-compatible endpoints.

### 21.3 Evaluation suggestions (aligned with this repo’s *Agent evaluation and benchmarks*)

- **SWE-bench Verified**: real engineering ability (Cline does this officially).
- **Aider Polyglot**: multilingual editing (`apply_patch` is the focus).
- **Terminal-Bench**: command execution.
- **Note**: eval results depend heavily on model choice—Cline is “a neutral engine for 4118 models”; report evals by model band.

---

<h2 id="ch22">Chapter 22 Release, packaging, and CI/CD</h2>

### 22.1 Artifacts

| Artifact | Pack | Distribute |
|---|---|---|
| `@cline/sdk` | bun build → npm publish | npm |
| CLI | bun build → bin | `npm i -g cline` / brew |
| VS Code extension | vsce/vsix | VS Marketplace |
| hub daemon | ships with CLI | — |

### 22.2 CI/CD pipeline

```
bun install → bun check (biome lint + typecheck) → bun test (vitest)
→ build:sdk → build:apps → package (vsix/npm) → e2e (golden scenarios) → release (changesets)
```

### 22.3 Release discipline

- **Version**: changesets auto-version + CHANGELOG.
- **Compat**: event protocol backward compatible (adapter layer as backstop); SDK semantic versioning.
- **Telemetry**: optional (PostHog/OTel, `telemetryOptOut` switch—Cline has it; your product must be compliant).

---

## Appendix: build-order cheat sheet

| Order | Module | Why this order |
|---|---|---|
| 0 | Monorepo scaffold | Boundaries before implementation |
| 1 | `shared` contracts | The whole project depends on it |
| 2 | `llms` Provider | The engine needs a model to call |
| 3 | `agents` main loop | The heart |
| 4 | `MessageBuilder` | The engine must be fed safe messages |
| 5 | Tools + approval | The engine must do work |
| 6 | Safety rails | The engine must not cause disasters |
| 7 | Session orchestration + storage | It must remember state |
| 8 | host + `ClineCore` | Host is pluggable |
| 9 | CLI | First usable surface |
| 10 | VS Code extension | Second surface |
| 11 | Compaction | Long sessions become usable |
| 12 | Checkpoint | You dare let it edit code |
| 13–15 | Teams / cron / plugins | Become a platform |
| 16 | Rules / skills | Polish |
| — | Full workflow (chapter 20) | Acceptance mainline |

---

*This tutorial is based on the actual source of `cline/cline` `main` as of 2026-08 (CLI v3.0.49 / VS Code 4.1.3 / SDK 0.0.69). Every module description is annotated with `file:line` so you can check it against the source.*
