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/cliis v3.0.49 (every v3.0.49 below means the CLI),apps/vscode(package nameclaude-dev) is 4.1.3,sdk/packages/*(shared/llms/agents/core) are all 0.0.69, and the rootpackage.json(@cline/packages) carries no version. Size: sparse checkout of the core, 27MB / 1749.ts+ 67.tsx= 1816 TS source files. The@cline/coreengine is about 140k lines,@cline/llmsabout 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-loopAgentRuntime, 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 citesfile:line, so you can walk back to source.
Contents
- Project overview: from VS Code plugin to “engine + UI” monorepo
- Panorama: a five-layer stack + three hosts + one event chain
- Startup: CLI / VS Code / hub daemon, three entry points
- Input capture and routing: interactive, headless, steering interrupt, pending prompt
- Context assembly: MessageBuilder’s API-safe reshaping
- The agent loop (the heart): AgentRuntime’s stateless while loop
- Tools and approval: 28 tools + five presets + desktop approval IPC
- Safety rails: loop-detection, mistake-tracker, subprocess-sandbox
- Sessions, checkpoints, and versioning: git snapshot rollback
- Context compaction: basic deterministic folding vs agentic LLM summary
- Provider layer: Vercel AI SDK + about 180 Provider IDs + 4118 models
- Ecosystem: plugins, MCP, hooks, multi-agent teams, cron, connectors, hub
- Storage: mixed JSON files + SQLite
- Side-by-side: vs Claude Code / opencode / CodePilot / pi / OpenWorker
- Summary: the three most distinctive designs and the trade-offs
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.tshas 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 |
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):
- shared (20553 lines)—pure contract layer. Path resolution (
~/.cline/data),estimateRequestInputTokens(conservative 3 chars/token), SQLite connector-store, event enums, tool types, hook events. - 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. - 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. - core (140027 lines)—stateful orchestration.
SessionRuntime(session façade),LocalRuntimeHost(host),MessageBuilder(API reshaping), compaction, plugin, cron, connectors, hub. - 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.
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).
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.
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).
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:
- 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) thenruntime.run("")—multi-turn = re-entering the loop repeatedly, not one long-lived object. - Synchronous inside the loop, fully evented outside. All progress flows out via
AgentRuntimeEventsubscription—that is the key of the “two-layer architecture”: the engine does not know who is listening. - prepare-turn projection seam: core injects the compaction strategy as prepareTurn (agent-runtime provides the seam;
@cline/agentsstays stateless).
config: a discriminated union (WithModel/WithProvider); createAgentRuntimeConfig (core/src/runtime/config, a pure function); agent-message-codec does AgentMessage↔Message encode/decode.
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_textliteral replace,insert_lineline 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.
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.
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,statusLockoptimistic 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). SessionPersistenceAdapterabstracts 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.
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.
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’sBUILT_IN_PROVIDERenum 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.tsabout 87K lines / 4118 models. - Dual-registration Factory:
factory-registry.ts(registerHandler/registerAsyncHandler) +createHandler(config): look up the registry first, otherwise fall through togateway.ts’screateGatewayApiHandler;GatewayRegistryholds 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.tsusesstreamText+formatMessagesForAiSdk; usage normalized toGatewayNormalizedUsage(inputTokens/outputTokens/cacheReadTokens/cacheWriteTokens/reasoningTokenCount/totalCost). - Model catalog:
catalog/catalog.generated.ts(87274 lines, 163 providers / 4118 models, including contextWindow/pricing/capabilities), generated byscripts/generate-models.tsfetching models.dev catalog-live (root scriptbun run build:models).
Token estimate (shared/llms/tokens.ts): conservative 3 chars/token estimateRequestInputTokens—compaction trigger relies on it.
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.
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.
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.
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.