# Cline source analysis: an open-source coding agent that lives in the IDE and also runs in the terminal

> **Subject**: cline (`github.com/cline/cline`), Apache-2.0, this analysis based on a **main-branch checkout from 2026-08**. The repo has no unified version number: `apps/cli` is **v3.0.49** (every v3.0.49 below means the CLI), `apps/vscode` (package name `claude-dev`) is **4.1.3**, `sdk/packages/*` (shared/llms/agents/core) are all **0.0.69**, and the root `package.json` (`@cline/packages`) carries no version.
> **Size**: sparse checkout of the core, 27MB / 1749 `.ts` + 67 `.tsx` = 1816 TS source files. The `@cline/core` engine is about 140k lines, `@cline/llms` about 119k lines (including 87k lines of auto-generated model catalog), the VS Code extension about 128k lines, the CLI about 71k lines.
> **One-line position**: once “the VS Code plugin agent,” now refactored into a **monorepo that separates engine (SDK) from UI (CLI / VS Code extension)**—the core is an in-process, embeddable, stateless-loop `AgentRuntime`, wrapped in three layers: session orchestration, host driver, application UI. It shares Claude Code’s “model → tool → model again” agent loop, but replaces Claude Code’s “contiguous prefix-cache ordering” with an **explicit, flat, declarative tool catalog + per-model routing**.
> **Audience**: readers who have already finished at least one analysis in this series. This lecture uses the shared frame and cites `file:line`, so you can walk back to source.

---

## Contents

1. [Project overview: from VS Code plugin to “engine + UI” monorepo](#ch1)
2. [Panorama: a five-layer stack + three hosts + one event chain](#ch2)
3. [Startup: CLI / VS Code / hub daemon, three entry points](#ch3)
4. [Input capture and routing: interactive, headless, steering interrupt, pending prompt](#ch4)
5. [Context assembly: MessageBuilder’s API-safe reshaping](#ch5)
6. [The agent loop (the heart): AgentRuntime’s stateless while loop](#ch6)
7. [Tools and approval: 28 tools + five presets + desktop approval IPC](#ch7)
8. [Safety rails: loop-detection, mistake-tracker, subprocess-sandbox](#ch8)
9. [Sessions, checkpoints, and versioning: git snapshot rollback](#ch9)
10. [Context compaction: basic deterministic folding vs agentic LLM summary](#ch10)
11. [Provider layer: Vercel AI SDK + about 180 Provider IDs + 4118 models](#ch11)
12. [Ecosystem: plugins, MCP, hooks, multi-agent teams, cron, connectors, hub](#ch12)
13. [Storage: mixed JSON files + SQLite](#ch13)
14. [Side-by-side: vs Claude Code / opencode / CodePilot / pi / OpenWorker](#ch14)
15. [Summary: the three most distinctive designs and the trade-offs](#ch15)

---

<h2 id="ch1">Chapter 1 · Project overview: from VS Code plugin to “engine + UI” monorepo</h2>

**What is Cline?** The first sentence of the README: *The open source coding agent in your IDE and terminal*. It lives in the VS Code sidebar (the old positioning); now it can also run in the terminal (CLI), and even run headless in CI/CD. Core capabilities:

- **Cross-project editing**: read project structure, understand file relationships, make coordinated multi-file changes, watching linter/compiler errors as it goes (`README.md`).
- **Plan / Act dual mode**: Plan mode only explores, only asks, produces a plan; Act mode executes, and every file edit and terminal command needs approval by default (auto-approve is available).
- **28 built-in tools** (9 default + spawn_agent + 18 team_*), plus MCP, plugins, skills, `.clinerules`.
- **About 180 registerable Provider IDs / 4118 models**: Anthropic/OpenAI/Gemini/OpenRouter/Bedrock/Vertex/Ollama/LM Studio/any OpenAI-compatible endpoint (`ids.ts` has no independent Azure enum item).
- **Multi-agent teams**: coordinator splits the task → delegates to a specialist (`cline --team-name ...`).
- **Scheduled automation** (`cline schedule create --cron ...`), **IM connectors** (`cline connect telegram/slack/discord/...`), **hub** as a local collaboration center.

**Its relationship to Claude Code**: Claude Code is a coding agent in the terminal (Ink TUI); Cline now also does terminal (OpenTUI), but its root is still an IDE plugin. The two projects’ architectural philosophies fork cleanly: Claude Code is a **monolith** (one package, tool prefix-cache ordering, imperative driver); Cline after the refactor is a **layered embeddable engine** (agents a stateless loop / core stateful orchestration / UI layer pluggable).

**Its relationship to open-design**: open-design is “a host that does not write an agent loop”; Cline is the opposite—**the loop itself (`AgentRuntime`) is extracted as a library**, and the hosts (CLI/VS Code) are only its drivers.

### The project shape in numbers

| Dimension | Number | Note |
|---|---|---|
| Packages | 6 SDK packages + 2 apps | shared/llms/agents/core/sdk/ui + apps/cli + apps/vscode |
| Dependency direction | one-way | `shared ← llms ← agents ← core ← apps` (`sdk/ARCHITECTURE.md`) |
| Agent loop | agent-runtime.ts 1969 lines | `AgentRuntime.execute()` explicit while loop (L641) |
| Tools | 28 built-in | 9 default + 1 spawn_agent + 18 team_* |
| Providers | about 179 unique IDs | `BUILT_IN_PROVIDER` enum about 50 + models.dev generated 163 (about 179 after overlap dedup, not 48+163=211) |
| Model catalog | catalog.generated.ts 87274 lines | 4118 models, including contextWindow/pricing |
| Storage | mixed JSON + SQLite | sessions.db / cron.db / connectors.db / secrets.json |
| License | Apache-2.0 | commercially friendly |

---

<h2 id="ch2">Chapter 2 · Panorama: a five-layer stack + three hosts + one event chain</h2>

The official `sdk/ARCHITECTURE.md` layered diagram (one-way dependencies, reverse forbidden):

```mermaid
flowchart LR
    SH["@cline/shared<br/>低层契约：storage/llms/db/cron/hooks/tools"] --> LL["@cline/llms<br/>Provider 运行时：AI SDK 适配 + 模型目录"]
    LL --> AG["@cline/agents<br/>无状态主循环：AgentRuntime"]
    AG --> CO["@cline/core<br/>有状态编排：会话/存储/hub/插件/compaction"]
    CO --> APPS["宿主应用：CLI · VS Code 扩展 · hub"]
```

**Five-layer stack** (bottom up):

1. **shared** (20553 lines)—pure contract layer. Path resolution (`~/.cline/data`), `estimateRequestInputTokens` (conservative 3 chars/token), SQLite connector-store, event enums, tool types, hook events.
2. **llms** (118772 lines)—provider layer. Adaptation on **Vercel AI SDK (`ai@7` + `@ai-sdk/*`)**; `catalog.generated.ts` (87274 lines) is the model catalog auto-generated by build:models; gateway does unified usage / stream normalization.
3. **agents** (4635 lines)—**stateless** agent loop. `AgentRuntime` (`agent-runtime.ts:1969`) is the only core: `execute()` an explicit while loop. **Forbidden** to hold persistence or host lifecycle.
4. **core** (140027 lines)—stateful orchestration. `SessionRuntime` (session façade), `LocalRuntimeHost` (host), `MessageBuilder` (API reshaping), compaction, plugin, cron, connectors, hub.
5. **Application layer**—CLI (OpenTUI + commander), VS Code extension (a single WebView React), hub daemon (WebSocket collaboration center).

**Three hosts (RuntimeHost)** (`core/src/runtime/host/host.ts`): `LocalRuntimeHost` (in-process), `HubRuntimeHost` (connects to a local hub server, shared across processes), `RemoteRuntimeHost` (remote). `createRuntimeHost()` (host.ts:137) in auto mode discovers hub and falls back to local—**the same engine can be driven three ways**.

**The event chain that runs through the stack**:

```
AgentRuntime’s while loop emits an AgentRuntimeEvent at each step
  → SessionRuntime.handleRuntimeEvent (core/src/runtime/orchestration/session-runtime-orchestrator.ts:1061)
  → RuntimeEventAdapter.translate (runtime-event-adapter.ts:183, 14 new events (including `run-failed`) → 9 legacy AgentEvent kinds)
  → AgentEventBridge → CoreSessionEvent
  → UI (CLI TUI / VS Code webview) consumes
```

> 🧠 **One sentence**: hard-splitting “the loop that turns” (agents, stateless) from “the orchestration that remembers state” (core, stateful) is the root architectural decision after Cline’s refactor—that is what lets the same loop engine be driven repeatedly by CLI, VS Code, and hub, while the loop itself does not know who the host is.

---

<h2 id="ch3">Chapter 3 · Startup: CLI / VS Code / hub daemon, three entry points</h2>

### Entry one: CLI (`apps/cli/src/index.ts`, 94 lines)

`#!/usr/bin/env bun`, package.json `bin: { cline: "src/index.ts" }`. index.ts does process-branch decisions (worker / hub daemon / connector process), forwards SIGINT to `active-runtime`, then dynamically `import("./main")`’s `runCli()` (`src/main.ts`, 1197 lines). The frame is **commander v14** + **@clack/prompts** (wizard interaction) + **OpenTUI** (`@opentui/core` + `@opentui/react` + react-reconciler + react 19).

Commands are all registered in `main.ts`: `auth`, `config`, `plugin`, `skill`, `connect`, `mcp`, `doctor`, `hook`, `schedule`, `hub`, `dashboard`, `history`, `team` + default chat. Shared root options live in `commands/program.ts` (`--plan`, `--json`, `--auto-approve`, `-c --cwd`, `-P --provider`, `-k --key`, `-z --zen`, `--config`).

**Integration with core**: `session/session.ts`’s `createCliCore()` → `ClineCore.create()` (from `@cline/core`). Default `backendMode` connects to hub (passes `hub: {clientType: "cli"}`); `forceLocalBackend` can run in-process.

### Entry two: VS Code extension (`apps/vscode/src/extension.ts`, 753 lines)

`activate` (L66) order: ① `setupHostProvider` (must be first) → ② legacy storage migration → ③ `exportVSCodeStorageToSharedFiles` unified into `~/.cline/data/` (shared with CLI/JetBrains) → ④ `initialize` creates `VscodeWebviewProvider` → ⑤ register commands.

**The connection to core is an in-process direct import**: `extension.ts` and `SdkController.ts` (2110 lines) directly `import @cline/core`; the engine runs in the extension host process. `hosts/vscode/hostbridge/` (gRPC) is only a host-capability bridge (diff/terminal/workspace); **it does not carry core**. The entire Cline UI is a **React app inside a single WebView** (`claude-dev.SidebarProvider`, activitybar container).

### Entry three: hub daemon (`apps/cli/src/commands/hub.ts` → `daemon/entry.ts`)

A standalone daemon, a local WebSocket server (`core/src/hub/server/hub-websocket-server.ts`, ws library, protocol version negotiation). Connectors, cron schedule, UI are all hub clients—this is **cross-process sharing of sessions / approvals / notifications**.

### Auth and data

Login is **WorkOS Device Flow + OAuth** (`auth/cline.ts`, `api.workos.com/user_management/*`), local OAuth server callback ports 48801-48811; provider keys live in `~/.cline/data/secrets.json` (chmod 0o600).

---

<h2 id="ch4">Chapter 4 · Input capture and routing: interactive, headless, steering interrupt, pending prompt</h2>

Cline’s “input” sources are more than a terminal agent’s: interactive input box, piped stdin (headless), in-flight steering interrupt, pending prompt queue, IM connectors (Slack/Telegram…), cron schedule, team delegation. Routing:

**① Interactive mode**: `apps/cli/src/runtime/run-interactive.ts` starts the TUI; user input → `session.send()` → host `runTurn()`.

**② Headless mode** (`main.ts` ~944 line decision): `--json || (!stdin.isTTY && !interactive)`. `runtime/run-agent.ts` (425 lines) is a single-shot executor; `--json` emits **NDJSON** (one JSON event per line: `run_start`/`run_abort_requested`/`message`/`error`…, written to stdout line by line by `utils/output.ts`’s `emitJsonLine`)—CI can consume line by line and finish on the exit code. `--zen` goes further: the task is fire-and-forget to a background hub daemon, and the CLI exits immediately.

**③ Steering interrupt (talking while it runs)** (`core/src/runtime/turn-queue/pending-prompt-service.ts:372-379`): `PendingPromptService` (L54) is a pure queue—enqueue dedups by prompt, **steer jumps the head**; `PendingPromptsController` (L207) `scheduleDrain()` (L281) when `agent.canStartRun()`, dispatching `drain()` (L295) to send turns serially. Steer channel: `consumePendingUserMessage` (host L695) is consumed by `AgentRuntime.generateAssistantMessage` when iteration>1 (agent-runtime.ts:968-979)—**in-flight interrupt injection**.

**Contrast**: OpenWorker’s inbox is a persistent async mailbox; CodePilot’s turn-lock refuses concurrency outright. Cline is an **in-process session queue + steerable**—it allows queuing *and* real-time interrupt, without affecting the loop that is currently running (steering is consumed on the next iteration).

**④ IM connectors** (`apps/cli/src/connectors/`): slack / discord / telegram / gchat / whatsapp / linear, six platforms, each an independent adapter process, registered with hub, sessions executed via HubRuntimeHost.

---

<h2 id="ch5">Chapter 5 · Context assembly: MessageBuilder’s API-safe reshaping</h2>

`MessageBuilder` (`core/src/session/services/message-builder.ts`, 1727 lines, L108) is the reshaper from “session messages → provider API request,” entry `buildForApi(messages)` (L166). It does more than you would guess:

**① Incremental reindex** (L278): compare tail references; on a hit, index only the new part, otherwise rebuild fully. **② commitOutdatedRewrites** (L336): a file that was read and later changed has its old content batch-replaced with `[outdated - see the latest file content]`; commit only when it exceeds `minOutdatedRewriteBytes` (default 64KB)—**avoid frequently breaking the provider prefix cache**. **③ addMissingToolResults** (L502): in interrupt scenes, backfill orphan tool_calls with a fake `is_error: true` result. **④ Per-block truncation**: assistant text 12K (repeated tool markup) / 200K (ordinary), file blocks 50K, tool_result 8K (keep head and tail), nested strings truncated deep. **⑤ applyMediaBudget** (L1332): images rate-limited by a byte budget; over budget replaced with `IMAGE_OMITTED_PLACEHOLDER`. **⑥ truncateToTotalTextBudget** (L1162): a global 6MB byte backstop.

**System prompt** (not inside MessageBuilder): assembled by the session orchestration layer `session-runtime-orchestrator.ts`’s `composeSystemPrompt()` (about L679) = `config.systemPrompt` (`buildClineSystemPrompt` concatenates IDE / workspace metadata / rules / mode segments, etc.) + plugin rules `mergeSystemPromptRules` (about L102). **Tool definitions are passed to the provider separately by the orchestrator, not stuffed into the message array**.

> 🧠 **One sentence**: every MessageBuilder design orbits one goal—**keep the provider prefix cache**: incremental index, outdated batch rewrite, truncation order, 6MB backstop, all are engineered “touch the middle of the transcript less.” This is more general than Claude Code’s “built-in tools in a contiguous prefix” (Cline faces about 180 registerable Provider IDs / 4118 models and cannot bet that every provider does prefix cache).

---

<h2 id="ch6">Chapter 6 · The agent loop (the heart): AgentRuntime’s stateless while loop</h2>

The agent loop lives in the `@cline/agents` package’s `agent-runtime.ts` (1969 lines), **not** in core. `AgentRuntime`/`Agent` are two names for the same class (stateless loop). Core `execute()` (L641, loop body L677-789):

```mermaid
flowchart TD
    START["execute()<br/>emit run-started"] --> TURN["while 循环<br/>emit turn-started"]
    TURN --> GEN["generateAssistantMessageWithOverflowRecovery()<br/>（L876，for await 消费 model.stream 流式事件）"]
    GEN --> TOOL{"模型要工具?"}
    TOOL -->|否| STEER{"steering 打断?"}
    STEER -->|有| CONSUME["consumePendingUserMessage<br/>（iteration>1 时，agent-runtime.ts:968）"] --> TURN
    STEER -->|没有| DONE["finishRun(completed)"]
    TOOL -->|要| EXEC["executeToolCalls()<br/>（L1455，sequential/parallel）"]
    EXEC --> WRITE["写回 tool_result 消息"] --> TURN
    TOOL -->|terminal 完成工具<br/>（submit_and_exit）| FINISH["finishRun(completed)"]
```

**Each turn** = one `generateAssistantMessageWithOverflowRecovery()` + some number of `executeToolCalls()`. No tool-call, or hitting `submit_and_exit` (`lifecycle.completesRun: true`), completes.

**The essential difference from Claude Code**:

1. **The loop is encapsulated as a reusable class; the session layer re-enters it repeatedly**. Claude Code is a long-lived while-loop driver; Cline `new AgentRuntime()` on every run, the session layer treats each user message as an independent run, seeds with the **full transcript** (`initialMessages`) then `runtime.run("")`—multi-turn = re-entering the loop repeatedly, not one long-lived object.
2. **Synchronous inside the loop, fully evented outside**. All progress flows out via `AgentRuntimeEvent` subscription—that is the key of the “two-layer architecture”: the engine does not know who is listening.
3. **prepare-turn projection seam**: core injects the compaction strategy as prepareTurn (agent-runtime provides the seam; `@cline/agents` stays stateless).

**config**: a discriminated union (`WithModel`/`WithProvider`); `createAgentRuntimeConfig` (core/src/runtime/config, a pure function); `agent-message-codec` does AgentMessage↔Message encode/decode.

---

<h2 id="ch7">Chapter 7 · Tools and approval: 28 tools + five presets + desktop approval IPC</h2>

### Tool inventory (28)

`extensions/tools/constants.ts`’s `ALL_DEFAULT_TOOL_NAMES` (9): `read_files`, `search_codebase`, `run_commands`, `fetch_web_content`, `apply_patch`, `editor`, `skills`, `ask_question`, `submit_and_exit` (factories in `definitions.ts` L247/343/457/518/611/660/723/780/801). Plus `spawn_agent` (team/spawn-agent-tool.ts) and 18 `team_*` tools (team-tools.ts:195).

**Against Claude Code’s 40**: **missing** WebSearch/Glob/TodoWrite/NotebookEdit/Task and others (web search degrades to `fetch_web_content` only; glob leans on `search_codebase`+`run_commands`); **extra** `ask_question` (interactive question), `submit_and_exit` (headless end-of-run), `skills` (skill invocation), `spawn_agent` and the full `team_*` multi-agent set.

### The editing pair: editor vs apply_patch

Cline splits editing into two mutually exclusive tools (definitions.ts:917 enables only one at a time):

- **editor** (closest to Claude Code’s Edit): `old_text→new_text` literal replace, `insert_line` line insert or create file, precise single-file edit.
- **apply_patch**: aider-style free-form patch (`*** Begin Patch / *** Update File / +- context lines / Add/Delete/Move`), no line numbers, context-based matching, **one call batch-edits many files** (`executors/apply-patch.ts` + `apply-patch-parser.ts`).

### Approval path (tool-approval.ts, 102 lines)

Flow: model emits a tool call → `SessionRuntime` merges global `*` with per-tool policy via `toolPolicies` (session-runtime-orchestrator.ts:117-133) to decide `enabled` → `beforeTool` hook (including loop detection) → if `autoApprove !== true` and the host provides `requestToolApproval` → approval → execute → `afterTool`.

**`requestDesktopToolApproval`** (tool-approval.ts:29) is the desktop IPC implementation: write `<sessionId>.request.<id>.json` into approvalDir, **poll the decision file every 200ms, 5-minute timeout**—a “files as IPC” mechanism that is naive but reliable. `ToolPolicy = {enabled?, autoApprove?}` (`sdk/packages/shared/src/llms/tools.ts:7`), **default enabled + autoApprove**.

### Five presets (presets.ts)

`act` / `plan` / `search` / `minimal` / `yolo`. Grading is implemented via `ToolPolicyPresetName = "default"|"yolo"` (presets.ts:137)—yolo sets `autoApprove: true` on `*` and all default tools.

### Tool registration: a flat declarative catalog vs a contiguous prefix

Claude Code uses a trie for contiguous prefix-match caching. Cline is the opposite: `runtime.ts`’s `BASE_TOOL_CATALOG` (9 entries: tool id/description/headlessToolNames) → resolved through preset + `model-tool-routing` (rules that disable tools by provider/model) + `disabledToolIds` into the enabled set (`getCoreBuiltinToolCatalog`/`resolveCoreSelectedToolIds`, lines 206-245) → `createDefaultTools` instantiates `AgentTool[]` by “switch flag && has executor” (`createTool` wraps name/description/inputSchema/execute/timeoutMs/retryable/maxRetries/lifecycle, `sdk/packages/shared/src/tools/create.ts:81`). **The model-request side is an ordinary JSON Schema array, no prefix ordering**—the alternative is per-model routing rules that explicitly decide which tools are visible.

---

<h2 id="ch8">Chapter 8 · Safety rails: loop-detection, mistake-tracker, subprocess-sandbox</h2>

### Dead-loop detection (runtime/safety/loop-detection.ts, 162 lines)

`toolCallSignature` JSON-serializes input with sorted keys (L50); `checkRepeatedToolCall` counts consecutive identical “tool name + signature” times, **soft=3 / hard=5** (L113). `LoopDetectionTracker.inspect` (L136) returns ok/soft/hard three grades; in SessionRuntime, soft → inject a “try another way” hint into the conversation, hard → feed `forceAtLimit: true` to MistakeTracker and abort the run (orchestrator:1244-1273).

### MistakeTracker (runtime/safety/mistake-tracker.ts, 229 lines)

Three reasons: `api_error / invalid_tool_call / tool_execution_failed`. `record()` accumulates `consecutiveMistakes`; at the cap, decide via the `onLimitReached` callback (default stop); continue then zeros and attaches recovery guidance; stop then writes a stop message and `activeRuntime.abort()`. All records are serialized through an `activeTrackerWork` Promise chain to keep order.

### SubprocessSandbox (runtime/tools/subprocess-sandbox.ts, 346 lines)

Solves “plugin / browser-sandbox code running in the main process is unsafe and uncontrollable”: spawn a node/bun child process over IPC; parent sends `{type:"call"}`, child replies `{type:"response"|"event"}`; on call timeout, SIGTERM→SIGKILL hard kill (lines 210-274); `resolveSubprocessRuntimeExecutable` (L73) resolves the node/bun runtime. **Crash isolation + forced timeout + event forwarding**.

### Rules system (.clinerules, runtime/safety/rules.ts, 49 lines)

Takes a snapshot from `UserInstructionConfigWatcher`, filters disabled, sorts by name, renders as `## name\ninstruction`, concatenated into the system prompt’s `# Rules` segment.

---

<h2 id="ch9">Chapter 9 · Sessions, checkpoints, and versioning: git snapshot rollback</h2>

### Session data model (session/models + stores)

- **Index**: `sessions.index.json` (file-session-service.ts L50)—`{version:1, sessions: {sessionId: SessionRow}}` one file of full JSON, atomicWriteJson temp-file+rename atomic replace, `statusLock` optimistic concurrency (CAS).
- **manifest**: `sessions/<id>/session.json`—session metadata (Zod validated).
- **Messages**: `sessions/<id>/<id>.json`—`{version:1, updated_at, agent, sessionId, messages[], system_prompt}` (session-data.ts L304) rewritten as a whole (not appended).
- `SessionPersistenceAdapter` abstracts the storage layer, default file implementation; hub/remote can swap.

### checkpoint (git implementation!)

**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 compare producing `CheckpointContentDiff[]`. Path-escape protection (L44).

**checkpoint-restore.ts** (413 lines):
- `readSessionCheckpointHistory` (L156): parse `{ref, createdAt, runCount, kind}` history from metadata.
- `findCheckpointForRun` (L202): the most recent checkpoint before runCount.
- `trimMessagesToCheckpoint` (L245): use `getUserRunSpan` to locate the runCount-th user turn, truncate messages.
- `beginWorktreeRestoreTransaction` (L50): before rollback, `git stash push --include-untracked` snapshots the current worktree, objects transferred to a private ref `refs/cline/restore-transactions/<uuid>`—on commit delete the ref; on rollback `reset --hard` + `clean -fd` + `stash apply --index` to restore.
- `applyCheckpointToWorktree` (L357): distinguishes stash vs ordinary-commit reset paths; `clean -fd` only when the snapshot has a third parent (`<ref>^3`, which captured untracked), to avoid irrecoverable deletion.

**What is stored**: a full worktree snapshot as a git commit/stash (three-parent commit when untracked is included); session messages themselves are not stored (kept by truncation). **How rollback works**: `SessionVersioningService.restoreCheckpoint` (session-versioning-service.ts:134) orchestrates the whole flow: validate → restore plan → transaction snapshot → apply → start a new session with truncated messages → commit the transaction; on failure, rollback the worktree + clean up the new session.

> 🧠 **One sentence**: checkpoint reuses the git object store directly as a “rollbackable worktree snapshot,” and makes the rollback itself a **rollbackable transaction** (stash first, then move)—this is one of Cline’s most elegant pieces of engineering.

---

<h2 id="ch10">Chapter 10 · Context compaction: basic deterministic folding vs agentic LLM summary</h2>

**Trigger** (extensions/context/compaction.ts, 710 lines): `createContextCompactionPrepareTurn` (L256) estimates `requestInputTokens` before every model request, `trigger = maxInputTokens * 0.9` (COMPACTION_TRIGGER_RATIO)—a **90% threshold**, much more aggressive than Claude Code’s microcompact. Three modes: `auto` / `manual` (user /compact) / `overflow_recovery` (forced recovery after a provider refusal).

**basic** (basic-compaction.ts, 711 lines, L452): **does not call an LLM**. Typed user prompts are always kept; the latest turn keeps “the latest message + a suffix that fits the budget” (aligned to assistant boundaries so tool pairs are not split); old turns keep their respective final assistant replies (the most recent 3 in full text); the rest is discarded, folded into an adjacent user message’s `<SYSTEM_NOTICE>` dropped-work summary; the last compaction product is frozen (`compaction: preserved`) and not folded again. **Deterministic, zero cost**, used as the overflow-recovery backstop.

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

**State persistence** (`createCompactionStateAwarePrepareTurn`, L643): after each compaction, store `{compacted messages, source_prefix_hash(sha256), source_message_count, system_prompt}` in a sidecar (`session-compaction.ts`); the next round’s `projectSessionCompactionState` (L161) checks the prefix hash matches, then = compacted product + new-message tail, **avoiding a full rebuild every round**.

**Against Claude Code microcompact**: Cline’s trigger threshold is high (0.9× vs microcompact’s about 0.5-0.6× immediate compaction); agentic is one full LLM summary each time (expensive but information-preserving), basic is zero-cost; Claude Code’s microcompact is a lightweight immediate small compaction, Cline is closer to a macroscopic “checkpoint + full-text summary + rollbackable” design.

---

<h2 id="ch11">Chapter 11 · Provider layer: Vercel AI SDK + about 180 Provider IDs + 4118 models</h2>

`@cline/llms` (118772 lines / 66 files) is the provider core, **based on Vercel AI SDK** (depends on `ai@^7` + `@ai-sdk/anthropic/openai/google/google-vertex/amazon-bedrock/mistral/openai-compatible` + `ai-sdk-ollama` + `ai-sdk-provider-claude-code`):

- **Provider count**: `ids.ts`’s `BUILT_IN_PROVIDER` enum about **50** items + models.dev **auto-generated 163** IDs; after dedup, about **179** registerable IDs (not 48+163=211, and hard to call “200+”). `catalog.generated.ts` about 87K lines / **4118** models.
- **Dual-registration Factory**: `factory-registry.ts` (`registerHandler`/`registerAsyncHandler`) + `createHandler(config)`: look up the registry first, otherwise fall through to `gateway.ts`’s `createGatewayApiHandler`; `GatewayRegistry` holds manifest/defaults/createProvider.
- **Key subdirectories**: `vendors/` (AI SDK adapters for anthropic/bedrock/google/vertex/mistral/ollama/openai-compatible), `routing/` (anthropic-compatible prompt cache, GLM/MiniMax thinking, reasoning-options), `middleware/` (retry-empty-response, split-tool-images), `services/` (Langfuse OTel telemetry).
- **Streaming and tokens**: `ai-sdk.ts` uses `streamText` + `formatMessagesForAiSdk`; usage normalized to `GatewayNormalizedUsage` (inputTokens/outputTokens/cacheReadTokens/cacheWriteTokens/reasoningTokenCount/totalCost).
- **Model catalog**: `catalog/catalog.generated.ts` (**87274 lines**, 163 providers / **4118 models**, including contextWindow/pricing/capabilities), generated by `scripts/generate-models.ts` fetching models.dev catalog-live (root script `bun run build:models`).

**Token estimate** (shared/llms/tokens.ts): conservative 3 chars/token `estimateRequestInputTokens`—compaction trigger relies on it.

---

<h2 id="ch12">Chapter 12 · Ecosystem: plugins, MCP, hooks, multi-agent teams, cron, connectors, hub</h2>

### Plugin system (extensions/plugin/)

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

### hooks (10 lifecycle events)

External hook events (shared/hooks/events.ts): `agent_start / agent_resume / agent_abort / agent_end / agent_error / tool_call / tool_result / prompt_submit / pre_compact / session_shutdown`. 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).

### Multi-agent teams (extensions/tools/team/multi-agent.ts, 1852 lines)

`AgentTeamsRuntime` (coordinator core): in-memory state includes **members / tasks / missionLog / mailbox / runs (queue+lease) / outcomes / outcomeFragments**; lead splits tasks via team tools (team-tools.ts 916 lines: create_team_task, spawn_teammate, route_to_teammate, team_outcome, etc.) and delegates to specialists (spawn-agent-tool.ts). Task state machine `pending → in_progress → blocked (blockedBy) → completed`; run queue `maxConcurrentRuns` rate-limit + heartbeat + `buildRecoveredRunMessage` auto-recovery on interrupt. Persistence: `exportState()/hydrateState()` full snapshot → `team-session-coordinator.ts` (240 lines) forwards TeamEvent, lands in `sqlite-team-store.ts` (537 lines) / `file-team-store.ts`.

### cron (31 files)

Spec files `.md` watched by cron-watcher, parsed by spec-parser, synced by reconciler. `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 mechanism prevents multi-process duplicate execution**. Triggers: `one_off / schedule (cron expression) / event`; `schedule-service.ts` (443 lines) polling + concurrency limit.

### connectors (IM)

core only owns lifecycle (active-connectors.ts); actual IM implementations live in `apps/cli/src/connectors/`: slack/discord/telegram/gchat/whatsapp/linear six platforms, each an independent adapter process registered with hub.

### hub (46 files)—local collaboration center

Local WebSocket server; handlers include session/run/capability/approval/connector; `hub-runtime-host.ts` (2156 lines) is a `RuntimeHost` implementation; hooks/tools/checkpoint/compaction negotiated by capability prefix.

### auth / account

`auth/cline.ts`: WorkOS Device Flow + OAuth; `account/cline-account-service.ts`: user-info RPC, featurebase token.

---

<h2 id="ch13">Chapter 13 · Storage: mixed JSON files + SQLite</h2>

| Data | Location | Implementation |
|---|---|---|
| Global settings | `{data}/settings/global-settings.json` | JSON + Zod |
| Provider settings | `{data}/settings/providers.json` | JSON (includes apiKey) |
| Secrets | `{data}/secrets.json` | JSON + chmod 0o600 |
| Session index/messages | `{data}/sessions/` | JSON files (whole rewrite + atomicWrite) |
| Session DB | `{data}/db/sessions.db` | SQLite (sqlite-session-store.ts 292 lines) |
| cron | `{data}/db/cron.db` | SQLite + leases |
| Teams | `{data}/teams/` | SQLite or files |
| connectors | `{data}/db/connectors.db` | SQLite |

Paths are decided by `@cline/shared/storage`’s `resolveClineDataDir()`, overridable with `CLINE_DATA_DIR`. Design principle: **session messages need to be readable into memory as a whole to rebuild (JSON rewrite), but anything that needs concurrency / leases / indexes uses SQLite**.

---

<h2 id="ch14">Chapter 14 · Side-by-side: vs Claude Code / opencode / CodePilot / pi / OpenWorker</h2>

After the refactor (CLI v3.0.49 / VS Code 4.1.3 / SDK 0.0.69) Cline sits in a delicate place: it is both an **IDE plugin** (historical gene), a **CLI** (new shape), and an **embeddable engine** (@cline/sdk). Dimension by dimension below.

### 14.1 Engine architecture: monolith vs layered embeddable

| Dimension | Claude Code | opencode | Cline v3 | CodePilot | pi | OpenWorker |
|---|---|---|---|---|---|---|
| Architecture shape | monolith (one CLI package) | monolith TUI | **layered monorepo + SDK** | Electron desktop + 3 Runtimes | 9-package monorepo | local Python service |
| Agent-loop ownership | long-lived while-loop driver | agent loop + doom_loop | **stateless AgentRuntime (re-enterable)** | Native loop in-process | two-layer while loop | TurnEngine async loop |
| Engine embeddable | no (CLI first) | no | **yes (@cline/sdk)** | half (inside Electron) | yes (npm package) | no (local service) |
| Language / ecosystem | TS + Ink | TS + SolidJS | TS + bun | TS + Next.js | TS + bun | Python + Rust |

### 14.2 Tools and permissions

| Dimension | Claude Code | Cline v3 | OpenWorker |
|---|---|---|---|
| Tool count | 40 (built-in) | 28 (9 default + spawn_agent + 18 team) | 40 connector descriptors + family tools |
| Edit tools | Edit (single-file old/new) | **editor + apply_patch pair** (the latter batch multi-file) | windowed read_file / home-grown grep |
| Permission model | imperative approval + three tool marks | **preset grades (default/yolo) + per-tool policy** | five modes + §25 exact target |
| Shell injection defense | prefix-match whitelist | (via run_commands approval) | **shlex exact token + operator intercept** |
| Approval UI | terminal popup | **desktop IPC file polling (200ms)** | inline card / inbox out-of-band approval |

### 14.3 Context and cache

| Dimension | Claude Code | Cline v3 |
|---|---|---|
| Cache strategy | built-in tools in a **contiguous prefix** (trie) | **MessageBuilder incremental reindex + outdated batch rewrite + 6MB backstop** (does not bet on provider cache) |
| Compaction | microcompact (0.5-0.6× immediate) | **90% threshold + basic deterministic / agentic LLM summary + sidecar prefix hash** |
| Memory | automatic compaction first | no explicit memory store; relies on checkpoint + compaction |

### 14.4 Shape and goal

| Dimension | Claude Code | opencode | Cline | CodePilot | pi | OpenWorker |
|---|---|---|---|---|---|---|
| Primary UI | terminal TUI | terminal TUI | **IDE sidebar + CLI + headless** | desktop GUI | terminal TUI | desktop + Slack + unattended |
| Multi-model | locked to Anthropic | multi-provider | **about 179 provider IDs / 4118 models** | 17+ providers | 38 providers | a dozen-plus + Ollama |
| Multi-agent | sub-agent (Task) | sub-agent | **team (coordinator + specialist)** | Sub-agent | none | persona + explorer + self-wake |
| Scheduling | none (planned) | none | **cron (lease anti-dup)** | cron | none | scheduler (catch-up + skip-on-overlap) |
| Unattended approval | none | none | headless refuse / auto-approve | none | none | **inbox idempotent state machine** |
| Collaboration center | none | none | **hub (local WebSocket)** | Bridge (IM) | server/client protocol | none (local-first) |

### 14.5 One-line division of labor

- **Claude Code**: a Claude-deeply-optimized terminal monolith—tool prefix cache, microcompact, imperative driver all serve “peak experience on a single model.”
- **opencode**: a clean multi-provider terminal TUI; engineering patterns (doom_loop, permission three-state) worth copying.
- **Cline**: **king of breadth**—most models, most shapes (IDE/CLI/CI), engine embeddable. Cost: tools few and sharp (web/glob degraded), bun toolchain.
- **CodePilot**: a desktop three-Runtime client—the same frontend runs Claude SDK / Native / Codex three engines.
- **pi**: protocol-driven composable infrastructure—stateless loop + binary wire protocol + self-extending Extensions; closest in spirit to Cline’s “engine as SDK,” but pi splits the Agent even finer and goes by protocol.
- **OpenWorker**: a digital coworker that ships finished work—center of gravity on unattended approval / connectors / scheduling / inbox, not optimized for writing code.

> 🧠 **One sentence**: Cline’s position after the refactor is “**a neutral vendor of an agent engine**”—not bound to a model (about 180 Provider IDs), not bound to a UI (IDE/CLI/hub), not bound to a language (SDK embeddable anywhere). If you pick one project to learn “how to turn an agent into an engine other people can use,” Cline’s monorepo layering (shared→llms→agents→core→apps) is the most complete textbook.

---

<h2 id="ch15">Chapter 15 · Summary: the three most distinctive designs and the trade-offs</h2>

### Three things (if you remember only three designs)

**1. Hard split of a stateless agent-loop engine (@cline/agents) from stateful orchestration (@cline/core).**
`AgentRuntime` does not know who the host is, holds no persistence; the session layer repeatedly `new AgentRuntime()` re-enters the loop, seeded with the full transcript. That is what makes **the same engine driven by CLI, VS Code, and hub** possible—the root of “engine productization” (@cline/sdk). Claude Code’s monolith cannot do this.

**2. Three-stage translation of the event chain (AgentRuntimeEvent → legacy AgentEvent → CoreSessionEvent).**
The engine emits new events; `RuntimeEventAdapter` (runtime-event-adapter.ts:183) does stateful translation (usage incremental deltas, tool durationMs), then AgentEventBridge to the UI. During the refactor, the adapter keeps compatibility with old event consumers—a textbook large-refactor move.

**3. checkpoint’s git transactional rollback.**
Worktree snapshots stored as git commit/stash (three-parent commit captures untracked); rollback itself stashes first then moves, wholly rollbackable—the ability to “roll back the rollback.” A full magnitude stronger than Claude Code’s simple diff rollback.

### Trade-offs and “who it is for”

| Dimension | Cline’s choice | Cost / who it is for |
|---|---|---|
| Shape | IDE plugin + CLI + headless, three habitats | thickest UI layer (128k-line VS Code); pure terminal experience less smooth than Claude Code |
| Engine | layered embeddable SDK (@cline/sdk) | architecture is clear, but multi-package debug cost is high; bun ecosystem (not the official node toolchain) |
| Models | about 179 Provider IDs / 4118 models, Vercel AI SDK | breadth first; unlike Claude Code’s deep prefix-cache optimization of a single model |
| Tools | 9+1+18 declarative catalog + per-model routing | tools few and sharp; web search / glob capability merged and degraded |
| Permissions | preset grades + desktop approval IPC (file polling) | simple and reliable; no OpenWorker-style exact-target standing rule |
| Compaction | 90% threshold + basic/agentic dual strategy + sidecar cache | agentic is expensive; basic loses a lot of information |

**One-line selection**: want an **open-source coding-agent engine that lives in the IDE, also runs in the terminal/CI, and can be embedded in your own product**—Cline (especially post-refactor @cline/sdk) is currently the most complete choice. Want a peak-depth Claude-optimized monolith, go back to Claude Code; want protocol-driven composable infrastructure, look at pi; want a desktop multi-engine client, look at CodePilot.

### 🔍 Source pointers (walk back to the code)

| Want to see | Go here |
|---|---|
| Agent loop (stateless while loop) | `sdk/packages/agents/src/agent-runtime.ts:641,677,876,1455,968` |
| Session orchestration (cross-turn state) | `sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.ts:278,737,1061` |
| Local host (drives sessions) | `sdk/packages/core/src/runtime/host/local-runtime-host.ts:224,334,946,1551` |
| pending prompt / steering | `sdk/packages/core/src/runtime/turn-queue/pending-prompt-service.ts:54,207,281,295,372` |
| Message reshaping (cache-friendly) | `sdk/packages/core/src/session/services/message-builder.ts:108,166,278,336,1162` |
| Tool catalog + preset | `sdk/packages/core/src/extensions/tools/definitions.ts:247,343,660,917` · `runtime.ts:206-245` · `presets.ts:137` |
| Approval IPC | `sdk/packages/core/src/runtime/tools/tool-approval.ts:29` |
| Loop detection / mistake tracking | `sdk/packages/core/src/runtime/safety/loop-detection.ts:50,113,136` · `mistake-tracker.ts` |
| checkpoint transactional rollback | `sdk/packages/core/src/session/checkpoint-restore.ts:50,245,357` · `session-versioning-service.ts:134` |
| Compaction (basic/agentic) | `sdk/packages/core/src/extensions/context/compaction.ts:256` · `basic-compaction.ts:452` · `agentic-compaction.ts:97` |
| Provider / model catalog | `sdk/packages/llms/src/` (vendors/ · catalog/catalog.generated.ts · factory-registry.ts) |
| CLI entry / headless | `apps/cli/src/index.ts` · `apps/cli/src/main.ts:941-946` (isHeadless decision) · `apps/cli/src/runtime/run-agent.ts` |
| VS Code extension | `apps/vscode/src/extension.ts:66` · `sdk/SdkController.ts` |

---

*This analysis is written against an actual checkout of `cline/cline` main branch, 2026-08 (CLI v3.0.49 / VS Code 4.1.3 / SDK 0.0.69); every conclusion is marked `file:line` and can be walked back to source. Same frame as the other analyses in this series.*
