English lecture · source-traced

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, implementa

EN
lecture
source
file:line
no
embellishment
中文
sibling

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.


Part 2

Chapter 1 PRD: what we will and will not build

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.


Part 3

Chapter 2 Overview: five layers, milestones, team

2.1 Target architecture (aligned with Cline v3)

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.

Part 4

Chapter 3 Module 0: Monorepo scaffold and package boundaries

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.


Part 5

Chapter 4 Module 1: Event contracts and the shared layer (`shared`)

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.


Part 6

Chapter 5 Module 2: Provider abstraction and the model catalog (`llms`)

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.


Part 7

Chapter 6 Module 3: Stateless main loop (`agents`) — `AgentRuntime`

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):

flowchart TD
    START["execute()
emit run-started"] --> TURN["while loop
emit turn-started"] TURN --> GEN["generateAssistantMessageWithOverflowRecovery()
L876 for await model.stream"] GEN --> TOOL{"model wants a tool?"} TOOL -->|no| STEER{"steering interrupt?"} STEER -->|yes| CONSUME["consumePendingUserMessage
when iteration>1 L968"] --> TURN STEER -->|no| DONE["finishRun(completed)"] TOOL -->|yes| EXEC["executeToolCalls()
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.


Part 8

Chapter 7 Module 4: Message shaper (`MessageBuilder`)

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_calls 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 messageBuilders, 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_calls from interrupt do not make the provider error.


Part 9

Chapter 8 Module 5: Tool system and approval (28 tools + presets)

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.


Part 10

Chapter 9 Module 6: Safety rails (loop-detection / mistake-tracker / sandbox)

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 records 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.


Part 11

Chapter 10 Module 7: Session orchestration (`SessionRuntime`) and storage

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.


Part 12

Chapter 11 Module 8: Host abstraction (`RuntimeHost`) and the `ClineCore` façade

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).


Part 13

Chapter 12 Module 9: CLI — OpenTUI interaction + headless

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.


Part 14

Chapter 13 Module 10: VS Code extension and WebView

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.


Part 15

Chapter 14 Module 11: Context compaction (`basic` / `agentic`)

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.


Part 16

Chapter 15 Module 12: Checkpoint and versioned rollback

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).


Part 17

Chapter 16 Module 13: Multi-agent teams

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 (blockedBydependency 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.


Part 18

Chapter 17 Module 14: Cron schedules and connectors

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).


Part 19

Chapter 18 Module 15: Plugins, MCP, hooks, hub

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 messageBuilders 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.


Part 20

Chapter 19 Module 16: Rules and skills (`.clinerules` / skills)

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.


Part 21

Chapter 20 “After the user submits a prompt”: the full workflow

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

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


Part 22

Chapter 21 Testing strategy and evaluation

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.

Part 23

Chapter 22 Release, packaging, and CI/CD

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).

Part 24

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.

↑