English lecture · source-traced

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

**Subject**: cline (`github.com/cline/cline`), Apache-2.0, this analysis based on a **main-branch checkout from 2026-08**. The repo has no unified version number: `apps/cli` is **v3.0.49** (every v3.0.49 below means the CLI), `apps/vscode` (package name `claude-dev`) is **4.1.3**, `sdk/packages/*` (shared/llms/agents/core) are all **0.0.69**, and the root `package.json` (`@cline/packages`) carries no version. **Size*

EN
lecture
source
file:line
no
embellishment
中文
sibling

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


Part 2

Chapter 1 · Project overview: from VS Code plugin to “engine + UI” monorepo

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

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

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

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

The project shape in numbers

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

Part 3

Chapter 2 · Panorama: a five-layer stack + three hosts + one event chain

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

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

Five-layer stack (bottom up):

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

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

The event chain that runs through the stack:

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

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


Part 4

Chapter 3 · Startup: CLI / VS Code / hub daemon, three entry points

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

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

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

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

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

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

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

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

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

Auth and data

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


Part 5

Chapter 4 · Input capture and routing: interactive, headless, steering interrupt, pending prompt

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

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

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

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

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

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


Part 6

Chapter 5 · Context assembly: MessageBuilder’s API-safe reshaping

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

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

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

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


Part 7

Chapter 6 · The agent loop (the heart): AgentRuntime’s stateless while loop

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

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

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

The essential difference from Claude Code:

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

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


Part 8

Chapter 7 · Tools and approval: 28 tools + five presets + desktop approval IPC

Tool inventory (28)

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

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

The editing pair: editor vs apply_patch

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

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

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

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

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

Five presets (presets.ts)

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

Tool registration: a flat declarative catalog vs a contiguous prefix

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


Part 9

Chapter 8 · Safety rails: loop-detection, mistake-tracker, subprocess-sandbox

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

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

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

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

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

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

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

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


Part 10

Chapter 9 · Sessions, checkpoints, and versioning: git snapshot rollback

Session data model (session/models + stores)

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

checkpoint (git implementation!)

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

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

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

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


Part 11

Chapter 10 · Context compaction: basic deterministic folding vs agentic LLM summary

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

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

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

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

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


Part 12

Chapter 11 · Provider layer: Vercel AI SDK + about 180 Provider IDs + 4118 models

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

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

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


Part 13

Chapter 12 · Ecosystem: plugins, MCP, hooks, multi-agent teams, cron, connectors, hub

Plugin system (extensions/plugin/)

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

hooks (10 lifecycle events)

External hook events (shared/hooks/events.ts): agent_start / agent_resume / agent_abort / agent_end / agent_error / tool_call / tool_result / prompt_submit / pre_compact / session_shutdown. Runtime hooks AgentHooks: beforeRun / afterRun / beforeModel / afterModel / beforeTool / afterTool / onEvent. Implementation: hook-file-hooks.ts (1010 lines, reads .hooks/ config) + subprocess-runner.ts (forwards to the agent hook subprocess).

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

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

cron (31 files)

Spec files .md watched by cron-watcher, parsed by spec-parser, synced by reconciler. sqlite-cron-store.ts (1719 lines): independent {data}/db/cron.db, tables cron_specs/cron_runs/cron_event_log; claim_token+claim_until_at lease mechanism prevents multi-process duplicate execution. Triggers: one_off / schedule (cron expression) / event; schedule-service.ts (443 lines) polling + concurrency limit.

connectors (IM)

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

hub (46 files)—local collaboration center

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

auth / account

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


Part 14

Chapter 13 · Storage: mixed JSON files + SQLite

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

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


Part 15

Chapter 14 · Side-by-side: vs Claude Code / opencode / CodePilot / pi / OpenWorker

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

14.1 Engine architecture: monolith vs layered embeddable

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

14.2 Tools and permissions

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

14.3 Context and cache

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

14.4 Shape and goal

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

14.5 One-line division of labor

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

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


Part 16

Chapter 15 · Summary: the three most distinctive designs and the trade-offs

Three things (if you remember only three designs)

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

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

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

Trade-offs and “who it is for”

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

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

🔍 Source pointers (walk back to the code)

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

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

↑