跳到主要内容

数据截至 (上游 commit afe54827dd65)

Crush — 架构与原理

30 秒导读: Crush 是 Charm 出的终端编码助手——你在终端里用自然语言提要求,它自己读文件、 跑命令、改代码,每个有副作用的动作先弹窗问你「准不准」。本文讲清它内部怎么转。


1. 这是什么(零基础也能懂)

一句话定义

Crush 是一个用 Go 写的终端编码 agent:一个全屏 TUI(终端图形界面)程序,把 LLM 接到你本机的文件、shell、语言服务器和外部工具上,让模型代替你动手改代码。

解决什么问题、给谁用

想象你在一个几万行的项目里,要把某个函数的错误处理补全。你得自己 grep 找调用点、 打开文件、改、跑测试。Crush 的定位是:你只说「把 login 函数的错误处理补上并跑测试」, 它自己完成这一串动作,你只负责在它要写文件、要执行命令时点「同意」。

它面向的是已经会写代码的工程师——不是给零基础的人生成 demo,而是在真实仓库里干活。

它能做什么

  • 多模型:Anthropic / OpenAI / Google / Bedrock / Vertex / OpenRouter / Copilot / Azure 等, 会话中途可以换模型而不丢上下文(internal/agent/coordinator.go:1099 buildProvider)。
  • 会话持久化:每个项目多条会话,消息、文件历史、token 花费都落 SQLite(internal/db/)。 本文只讲什么时候落库、落库和事件广播的先后(01 章 §4、§7),不展开表结构。
  • 一整套内置工具:读/写/改文件、glob、grep、bash、下载、抓网页、待办清单等 (internal/agent/tools/)。
  • LSP 增强:自动拉起语言服务器,让模型能拿到诊断、定义、引用、重命名(internal/lsp/)。
  • MCP 扩展:接入 stdio / http / sse 三种 MCP(Model Context Protocol,一种给 LLM 挂外部工具的标准协议)服务器(internal/agent/tools/mcp/init.go)。
  • 权限闸门:非只读动作一律先问用户(internal/permission/permission.go:181 Request)。
  • Hooks:用户可配 shell 脚本在工具调用前拦截、改写参数、直接否决(internal/hooks/)。

用起来什么样

这些是 rootCmd / runCmd 里写死的真实用例(internal/cmd/root.go:83internal/cmd/run.go:31):

# 交互模式:进 TUI
crush

# 一次性非交互跑一条 prompt,然后退出
crush run "Guess my 5 favorite Pokémon"

# 管道进、重定向出
cat README.md | crush run "make this more glamorous" > GLAMOROUS_README.md

# 继续上一条会话
crush --continue

# 危险模式:自动同意所有权限请求
crush --yolo

一句话直觉

把 Crush 当成一个「带审批流的实习生」:模型是实习生的脑子,工具是他的手脚, 权限闸门是你这个 reviewer——他每次要动真格(写文件、执行命令)都得举手,你点头才落地。 本文剩下的部分,讲的就是这个审批流和这双手脚具体怎么实现。


2. 顶层全景(它大概怎么转)

一张图

怎么读这张图:从上往下是一次用户 prompt 的流向;方框圈出的是「本机边界」—— 所有工具执行都发生在你自己的机器上,只有模型推理走网络。

用户输入(TUI 里打字 / crush run "...")


① Workspace 门面(前端唯一的 API)
进程内直连 · 或 · 走 Unix socket 到 server


② Coordinator(选模型 / 装工具 / 管鉴权重试)


③ SessionAgent(一次 turn 的并发状态机)
│ ⇅ 步进循环由 fantasy SDK 驱动
│ 模型说「调用 edit(...)」

┌─── ④ 工具层 bash / edit / grep / LSP / MCP ────────┐
│ │ │
│ ▼ │ 本机
│ ⑤ 权限闸门 ──► 弹窗,等用户点同意/拒绝 │
└────────────┼───────────────────────────────────────┘

⑥ 落库 SQLite + 广播事件 ──► 回到 UI 刷新

部件一句话职责

部件干什么在哪个文件
Workspace(接口)前端(TUI/CLI)唯一认的门面,屏蔽「本地还是远程」internal/workspace/workspace.go:117
Coordinator建 provider、装工具集、跑一次 Run、处理 401 重试internal/agent/coordinator.go:88
SessionAgent一次 turn 的状态机:接受/排队/取消/流式落库internal/agent/agent.go:135
工具层每个工具一个 fantasy.AgentToolinternal/agent/tools/
permission.Service副作用动作的统一闸门internal/permission/permission.go:65
hooks.Runner工具调用前跑用户脚本,可否决/改参数internal/hooks/runner.go:35
message.Service消息落库 + 流式更新去抖(33ms)internal/message/message.go:46
pubsub.Broker进程内事件扇出,区分「尽力送达」和「必达」internal/pubsub/broker.go
lsp.Manager按文件类型自动拉起语言服务器internal/lsp/manager.go:27
mcp(包级单例)异步连接 MCP 服务器,工具变化时通知internal/agent/tools/mcp/init.go:292
Backend服务端形态下按目录管理多个 workspaceinternal/backend/backend.go:89
TUIBubble Tea 全屏界面internal/ui/model/ui.go:182

主线走一遍(高层,不进代码)

  1. 入口分叉。 crush 启动时先决定形态:默认在自己进程里建一个 app.App; 若设了环境变量 CRUSH_CLIENT_SERVER=1,则连(或拉起)一个后台 server 进程。 两条路最后都返回同一个 Workspace 接口(internal/cmd/root.go:322 setupWorkspace)。

  2. 一次提问。 UI 把 prompt 交给 Coordinator.Run。Coordinator 先刷新模型配置、 合并调用参数、准备好 OAuth 刷新回调,再调 SessionAgent.Run

  3. 状态机决策。 SessionAgent.Run每会话一把锁下三选一:这条 prompt 是 进来就已被取消会话忙需要排队、还是成为当前活跃 runinternal/agent/agent.go:589 起)。

  4. 步进循环。 活跃 run 交给 fantasy SDK 的 agent.Stream:模型输出文本 → 可能发起工具调用 → 工具执行 → 结果回灌 → 再一步。Crush 通过一组回调把每个中间态写进消息表并广播给 UI。

  5. 工具落地。 每个工具在真正动手前调 permissions.Request;只读的白名单命令直接放行, 其余阻塞等 UI 回答。用户点同意后才写文件/执行命令。

  6. 收尾。 一步结束时累计 token 与花费;上下文快满时触发自动摘要; 最后 flush 掉去抖的消息更新,发出这一 turn 唯一的终结事件 RunComplete


3. 阅读地图

建议按顺序读;每章都能单独读,但 01 是理解其它章的基础。

章节讲什么什么时候该读
01-agent-loop.md一次 prompt 的完整生命周期、并发状态机、自动摘要、循环检测想搞懂「agent 循环」到底怎么实现
02-tools-and-permissions.md工具集、权限闸门、bash 的命令闸门与硬禁用清单、edit 的容错匹配、hooks想抄工具层设计或安全边界
03-context-skills-mcp-lsp.mdsystem prompt 怎么拼、skills/MCP/LSP 怎么接、配置怎么分层关心上下文工程与可扩展性
04-client-server.md进程内 vs C/S 双形态、workspace 生命周期、SSE 与重连想做多客户端 / 远程 agent
05-insights-and-boundaries.md精华技术、边界、横向对比、总代码地图只想拿走「可借鉴的点」

一个先说清的口径:Crush 的安全边界是运行时权限闸门 + 静态命令黑名单, 它不做容器或沙箱隔离(详见 05 章 §3 的横向对比)。后文说 bash「三层收紧」, 指的是命令闸门,不是进程隔离。


4. 代码地图(先认门牌号)

主题文件路径符号名
程序入口main.gomaincmd.Execute
命令与形态选择internal/cmd/root.gosetupWorkspaceuseClientServer
前端门面接口internal/workspace/workspace.goWorkspace
一次 turn 的状态机internal/agent/agent.gosessionAgent.Run
模型/工具装配internal/agent/coordinator.goNewCoordinatorbuildTools
权限闸门internal/permission/permission.gopermissionService.Request
工具集合internal/agent/tools/NewBashToolNewEditToolNewViewTool
System promptinternal/agent/templates/coder.md.tpl(模板文件)
服务端internal/server/server.goServer.installHandler
多 workspace 管理internal/backend/backend.goBackend.CreateWorkspace
TUIinternal/ui/model/ui.goUI