跳到主要内容

第 5 章 · 巧妙之处 / 边界 / 对比 / 代码地图

5.1 巧妙之处(可借鉴的技术)

① 终结事件的「恰好一次」契约。 Crush 把「一个提交恰好收到一个 RunComplete」当成一等公民契约,所有退出分支收敛到单一 publishRunComplete,并用 PublishMustDeliver 的有界阻塞防止订阅者满了丢事件(internal/agent/agent.go:523)。任何要支持非交互/脚本客户端的 agent 服务都该有这层。妙在它显式枚举了每一个出口(取消-on-entry、队列丢弃、正常 defer、跨 RunID 交接、重试合并),而不是靠「希望只发一次」。

② 孤儿工具调用的加载时自愈。 把「tool_use 必须配对 tool_result」这条 API 硬约束的修复,放在组装历史的那一刻(preparePrompt,internal/agent/agent.go:1470),而不是寄望于写入时永不出错。两个方向都补(丢孤儿结果、给孤儿调用合成占位结果),让会话无论怎么被打断都不会被永久锁死。这是生产级 agent 的必备护栏。

③ 用单调序号 + 高水位实现「精确取消」。 不用布尔 canceled 标记(会误伤 cancel 之后才来的 prompt),而用 accept 序号 + cancelMark 高水位:seq ≤ mark 才算被覆盖(canceledBySeq,internal/agent/agent.go:477)。这把「取消现在在飞的所有 prompt、但不碰未来的」表达得既精确又幂等。

④ LSP 诊断回灌成闭环。 改完文件立刻跑 LSP、把报错拼进工具结果(internal/agent/tools/edit.go:101getDiagnostics)。模型当场看到自己引入的 error,自我修正,不用等用户编译。把「编辑器的红波浪线」变成了 agent 的即时反馈信号。

⑤ 只读子 agent 当「上下文压缩器」。 task 子 agent 只给只读工具(resolveReadOnlyTools,internal/config/config.go:760),跑在独立子会话,只把最终结论回吐给主 agent。几十次搜索/阅读的噪音不污染主上下文——委派的本质是上下文隔离,而非只是并行。

⑥ bash 双名单 + 串联检测。 黑名单堵危险命令(并借禁 curl/wget 把联网逼回有审计的 fetch 工具),只读白名单免弹窗放行 ls/git log,同时要求白名单命令不含 ;/&& 串联防幌子(internal/agent/tools/bash.go:207safe.go)。

⑦ 流式落库用两套 context。 生成用可取消的 genCtx,但「把已发生的事实落库」用不可取消的父 ctx + 脱离取消的超时 context(context.WithoutCancel)。这保证用户中途取消时,已跑完的工具结果和最终状态仍能写盘(OnToolResult 注释,internal/agent/agent.go:950;flush defer,internal/agent/agent.go:743)。

5.2 边界与局限(诚实)

  • 核心循环不是 Crush 自己的。 多步工具循环、各 provider 的流式协议解析在 charm.land/fantasy 库里,模型元数据在 charm.land/catwalk 里。Crush 是「在 fantasy 循环上挂回调 + 管会话生命周期」——本系列文档讲的是 Crush 这层,fantasy 内部未展开(它不在本克隆内)。
  • 死循环检测是启发式的。 hasRepeatedToolCalls 靠「最近 10 步同签名 > 5 次」(internal/agent/loop_detection.go)。模型若每次微调输入做无意义空转,签名各异就检测不到;阈值也是固定常量,非自适应。
  • 自动摘要是有损的。 触发即把整段历史压成一条摘要消息,细节必然丢失;续跑靠摘要 + 重述的原始请求,复杂任务可能丢上下文。
  • mod-time 乐观锁精度到秒。 editModTime().Truncate(time.Second) 比对(internal/agent/tools/edit.go:363),同一秒内的外部改动理论上可能漏检。
  • 多 agent 仍是「写死的两个」。 代码里多处 TODO: when we support multiple agents(如 internal/agent/coordinator.go:725),目前只有 coder + task 两个固定 agent,动态多 agent 未实装。
  • 权限是会话级 + 路径级记忆,不是细粒度策略引擎。 记忆键是 (会话,工具,动作,路径)(PermissionKey),没有更复杂的规则/通配策略。

5.3 横向对比(同 shelf 兄弟项目)

维度Crush典型对照
语言 / 形态Go,单二进制,TUI + HTTP(Unix socket)多为 TS/Python
会话存储SQLite 消息流为唯一事实源常见内存 + 落盘快照
并发模型重点投入:同会话 accept/queue/cancel 三态握手 + RunComplete 唯一性多数 agent 假设「一会话一次跑」,并发处理较弱
编辑安全先读后改 + mod-time 乐观锁 + 精确唯一匹配思路相近(read-before-edit 是行业共识)
反馈闭环LSP 诊断回灌进工具结果部分项目靠模型自己跑编译/测试
子 agent只读 task 子 agent 作上下文压缩类似「sub-agent / spawn」模式普遍
模型抽象fantasy 统一多 provider + catwalk 元数据各家自建 provider 适配层

Crush 最有辨识度的取舍是把「同一会话的并发生命周期」当成头等工程问题——这在多数把 agent 当「一问一答循环」的实现里是缺失的,代价是 agent.go 里那些极长的并发注释。

5.4 代码地图(导航索引)

主题文件路径关键符号
入口main.gomain,cmd.Execute
核心循环internal/agent/agent.gosessionAgent.Run,agent.Stream 调用块(:790)
三态握手internal/agent/agent.goBeginAccepted,canceledBySeq,enqueueCall,drainQueueForStep
取消internal/agent/agent.gosessionAgent.Cancel,cancelMark,AcceptedRun
终结事件internal/agent/agent.gopublishRunComplete,publishCanceledQueueDrops
摘要internal/agent/agent.goSummarize,getSessionMessages,buildSummaryPrompt
孤儿修复internal/agent/agent.gopreparePrompt,filterOrphanedToolResults,syntheticToolResultsForOrphanedCalls
缓存/媒体internal/agent/agent.gogetCacheControlOptions,workaroundProviderMediaLimitations
死循环检测internal/agent/loop_detection.gohasRepeatedToolCalls,getToolInteractionSignature
协调器internal/agent/coordinator.gocoordinator.run,buildTools,getProviderOptions,runWithUnauthorizedRetry
子 agentinternal/agent/agent_tool.go,coordinator.gocoordinator.agentTool,runSubAgent,AgentToolName
工具基建internal/agent/tools/tools.goGetSessionFromContext,NewPermissionDeniedResponse
编辑工具internal/agent/tools/edit.goNewEditTool,replaceContent,createNewFile
bash 工具internal/agent/tools/bash.go,safe.goNewBashTool,bannedCommands,blockFuncs,safeCommands
LSP 诊断internal/agent/tools/diagnostics.gonotifyLSPs,getDiagnostics
钩子包裹internal/agent/hooked_tool.gohookedTool.Run,wrapToolsWithHooks
权限服务internal/permission/permission.gopermissionService.Request,resolve,GrantPersistent
prompt 组装internal/agent/prompt/prompt.goPrompt.Build,promptData,getGitStatus
系统提示internal/agent/templates/coder.md.tpl<critical_rules>
默认 agentinternal/config/config.goConfig.SetupAgents,resolveReadOnlyTools
LSP 管理internal/lsp/manager.goManager,NewManager
应用装配internal/app/app.goNew,agent.NewCoordinator 调用

回到 index.md · 上一章 04-context-management.md