What this tutorial is: a from-scratch build plan whose blueprint is Cline (
github.com/cline/cline, Apache-2.0,mainbranch checked out 2026-08; CLI v3.0.49 / VS Code extension 4.1.3 / SDK package 0.0.69). You will act as a developer who can also do product work: start from a PRD, then explode Cline into an executable development plan—each functional module listed in build order, with purpose, implementation shape, stack, definition trade-offs, strengths and costs, and how the pieces couple. No technical detail is too small, including the basics. Core narrative: follow the action “the user types a prompt in the chat box and hits enter” from zero to the end—how it is packed, how it is sent to the engine, how it runs repeatedly in the main loop (model ↔ tools), until streamed results return to the chat window and render. That chain is the skeleton of the whole project. How to read: read chapter 1 (PRD) and chapter 2 (overview and milestones) first, then proceed in order. Chapter 20 (the full workflow) is the climax; read it closely after modules 1–9.
Contents
Part I · Charter and foundation
Part II · Engine core (build in order)
- Module 1: Event contracts and the shared layer (
shared) - Module 2: Provider abstraction and the model catalog (
llms) - Module 3: Stateless main loop (
agents) —AgentRuntime - Module 4: Message shaper (
MessageBuilder) - Module 5: Tool system and approval (28 tools + presets)
- Module 6: Safety rails (loop-detection / mistake-tracker / sandbox)
- Module 7: Session orchestration (
SessionRuntime) and storage
Part III · Surfaces and drivers
Part IV · Advanced capabilities
Part V · Full workflow and close
- Module 16: Rules and skills (
.clinerules/ skills) - “After the user submits a prompt”: the full workflow
- Testing strategy and evaluation
- Release, packaging, and CI/CD
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)insrc/parser.tstoNumber(x), and run the tests.” Expected: Plan mode first proposes a plan → switch to Act → after approval,editor/apply_patchedits the file →run_commandsruns tests → results render in the chat window → rollback is available. Full flow < 5 minutes; P99 tool call < 30s.
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)
- Dependency direction is one-way:
shared ← llms ← agents ← core ← apps. Whoever violates it refactors (enforced by bun workspaces +package.jsondependencies). - The
agentspackage is stateless: no persistence inagents, no host lifecycle—it is a pure loop library. - Event contracts freeze first: freeze the
AgentRuntimeEventenum first; a shape change must sync the whole chain.
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 withbun -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.tsper 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.
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.
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 (exportVSCodeStorageToSharedFilesexists 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).
ToolPolicyis 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.
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)
- 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. - Dual-registration Factory (
factory-registry.ts):registerHandler(fn)/registerAsyncHandler(fn)+createHandler(config)façade: look up the registry first, otherwise fall through togateway.ts’screateGatewayApiHandler(OpenAI-compatible fallback). vendors/: AI SDK adapters per provider (anthropic/bedrock/google/vertex/mistral/ollama/openai-compatible).- Model catalog (
catalog/catalog.generated.ts, 87274 lines): generated by a script (scripts/generate-models.tsfetches from the models.dev catalog-live), containing contextWindow/pricing/capabilities for 4118 models. Root scriptbun run build:models=bun -F @cline/llms generate:models. - 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
agentscallscreateHandler(config)to obtain a provider and feeds it tomodel.stream().core’slocal-provider-service.ts(963 lines) owns CRUD for custom providers and writes them into the models file for registration.corecompaction usesGatewayNormalizedUsage.inputTokensto 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.
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:
- Each run is an independent instance: the session layer does
new AgentRuntime({...}), seeds with a full transcript viainitialMessages, thenruntime.run("")(empty input to avoid duplication). Multiple turns = re-enter repeatedly. - Streaming consume: inside
generateAssistantMessageWithOverflowRecovery,for await (const event of model.stream(request)), converting stream events intoAgentRuntimeEventemissions. - Overflow recovery: when the provider rejects the request (context overflow), automatically take the recovery path (switch to compaction).
- Steering consume: when
iteration > 1, check the pending prompt queue; if there is an interrupt, inject it. - Discriminated config union:
WithModel | WithProvider—either pass a model instance or pass provider config. prepare-turnprojection seam: core injects compactionprepareTurn—agentsstays 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’sSessionRuntime.executeRunInternal(orchestrator L737) createsAgentRuntime(L856) andrun("")(L878).llmssupplies the model (createAgentModelFromConfig).coreinjectsprepareTurn(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.
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:
reindex(L278): incremental index—compare tail references; on hit, index only the new tail, otherwise rebuild fully. Cache-friendly.commitOutdatedRewrites(L336): files that were read and later changed have old content batch-replaced with[outdated - see the latest file content]; commit only when overminOutdatedRewriteBytes(64KB)—avoid frequent damage to the provider prefix cache.addMissingToolResults(L502): in interrupt scenarios, give orphantool_calls a fakeis_error: trueresult.- Per-block truncation: assistant text 12K (repeated tool markup) / 200K (ordinary), file blocks 50K,
tool_result8K (keep head and tail), nested-depth truncation. applyMediaBudget(L1332): images rate-limited by a byte budget; over budget, swap inIMAGE_OMITTED_PLACEHOLDER.truncateToTotalTextBudget(L1162): global 6MB backstop; candidates truncated descending by size;tool_usearguments 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 orchestratorprepareProviderMessagesForApi(L1049) first runs plugin-registeredmessageBuilders, then callsbuildForApi.agentsreceives 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; orphantool_calls from interrupt do not make the provider error.
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 ownstoolPoliciesmerge 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.
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
beforeToolhook (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.
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 drivesrun.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,
readMessagesrestores the full session; two concurrent messages do not overwrite each other (statusLock); translated legacy events render correctly in the UI.
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);forceLocalBackendis in-process. - VS Code runs LocalRuntimeHost directly in-process.
ClineCoreis 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).
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-agentusesAgentRuntime-level APIs directly. - The three approval grades reuse core’s
requestToolApproval.
Acceptance
echo "fix tests" | cline --json --auto-approve | jqruns in CI;cline --zen "task"returns immediately and the task executes in the background.
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.
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
prepareTurnseam (checked before every turn). - Uses
sharedtoken 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-triggerbasic) → later turns continue normally; after restart the sidecar hash hits and compaction is not repeated.
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 (
kinddistinguishes 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).
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
AgentTeamsRuntimeis assembled intoBuiltRuntimebyruntime-builder.- Child agents are
SessionRuntimeinstances (reuse module 7). spawn_agentis 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-namerecovers.
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).
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 orchestratorregisterMessageBuilder. - 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 ontool_call; two CLI processes share the same session through hub.
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
skillstool enters the tool catalog (module 5).
Acceptance
After placing
.clinerulesin the project, the agent-generated code obeys those conventions; theskillstool can list and load a skill’s full text.
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:
- Pack: prompt + system prompt + history + tool definitions = one
model.stream(request)request body (assembled in memory, shaped byMessageBuilder). - 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.”
- Receive: stream events come back chunk by chunk (thinking / text / tool calls).
- Repeat: tool results written back → request again → receive again → until done.
- 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
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)
- Edit-code closed loop: input → Plan → Act → approval → edit → test → done.
- Long session: 50 turns → compaction triggers → continues normally.
- Interrupt: Stop mid-stream → no orphan
tool_call→ resume. - Rollback: break it → checkpoint rollback → workspace restored.
- Headless:
--jsonevent stream complete, exit code correct. - 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_patchis 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.
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,
telemetryOptOutswitch—Cline has it; your product must be compliant).
Appendix: build-order cheat sheet
| Order | Module | Why this order |
|---|---|---|
| 0 | Monorepo scaffold | Boundaries before implementation |
| 1 | shared contracts |
The whole project depends on it |
| 2 | llms Provider |
The engine needs a model to call |
| 3 | agents main loop |
The heart |
| 4 | MessageBuilder |
The engine must be fed safe messages |
| 5 | Tools + approval | The engine must do work |
| 6 | Safety rails | The engine must not cause disasters |
| 7 | Session orchestration + storage | It must remember state |
| 8 | host + ClineCore |
Host is pluggable |
| 9 | CLI | First usable surface |
| 10 | VS Code extension | Second surface |
| 11 | Compaction | Long sessions become usable |
| 12 | Checkpoint | You dare let it edit code |
| 13–15 | Teams / cron / plugins | Become a platform |
| 16 | Rules / skills | Polish |
| — | Full workflow (chapter 20) | Acceptance mainline |
This tutorial is based on the actual source of cline/cline main as of 2026-08 (CLI v3.0.49 / VS Code 4.1.3 / SDK 0.0.69). Every module description is annotated with file:line so you can check it against the source.