惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
Jina AI
Jina AI
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 叶小钗
GbyAI
GbyAI
阮一峰的网络日志
阮一峰的网络日志
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Hacker News: Ask HN
Hacker News: Ask HN
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Privacy & Cybersecurity Law Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
A
About on SuperTechFans
D
DataBreaches.Net
The Cloudflare Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
大猫的无限游戏
大猫的无限游戏
Know Your Adversary
Know Your Adversary
T
Tenable Blog
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
The Register - Security
The Register - Security
O
OpenAI News
G
Google Developers Blog
T
The Blog of Author Tim Ferriss
C
CERT Recently Published Vulnerability Notes
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Application and Cybersecurity Blog
Application and Cybersecurity Blog
P
Palo Alto Networks Blog
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
N
Netflix TechBlog - Medium
V
Vulnerabilities – Threatpost
月光博客
月光博客
Recorded Future
Recorded Future
P
Proofpoint News Feed
Cisco Talos Blog
Cisco Talos Blog
P
Privacy International News Feed
Security Archives - TechRepublic
Security Archives - TechRepublic
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
T
The Exploit Database - CXSecurity.com
博客园 - Franky
Latest news
Latest news
MongoDB | Blog
MongoDB | Blog
V
Visual Studio Blog

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
Cloud Agent 开发笔记(2):Agent 引擎与 Tool 体系
五岳 · 2026-06-02 · via 博客园_首页

另起炉灶

上一篇Cloud Agent 开发笔记(1):V1从跑通到放弃聊到决定重写,以及为什么选了参考 Claude Code 源码这条路。4 月 8 号动手,第一个要解决的问题是:Claude Code 是 CLI 工具,用户面对的是终端窗口。Cloud Agent V2 是 Web 应用,用户面对的是浏览器,都要做出哪些变化?这个答案只能在逐个推进模块的过程中解答,下面按实际的开发推进顺序讲。

先定个调:这不是另一篇 Claude Code 的源码解读——网上不缺这类文章。它是一个实际工程项目的决策记录:代码是 Vibe Coding 写的,但取舍是人做的,踩的坑是人踩的。写下来是为了以后回头看时,能想起当时为什么选了这条路而不是那条。

Agent Loop和首要原则

真正动工前,先要把基础方向定下来。上一篇提到技术栈向 Claude Code 靠拢:TypeScript + Bun + Zod,Hono 做 HTTP 层,React + Vite 做前端,SQLite 做持久化。这些是开工前就想清楚的,没什么悬念。

但有一件事是开始设计 agent loop 时才意识到的。

Claude Code 的 agent 循环有两层结构。QueryEngine 管会话生命周期:多轮状态、transcript 持久化、usage 累积、错误恢复。queryLoop() 管单轮执行:调用 LLM、执行工具、拼接结果。我一开始也照这个模式搭了框架,但越搭越不对劲:为什么要两层?

翻回去看 Claude Code 源码,才搞清楚它的上下文。Claude Code 同时服务四个消费端:CLI、IDE 插件、SDK 调用、MCP server 模式。每个消费端对事件的格式、粒度、生命周期管理的要求不一样。CLI 要把事件渲染到终端,IDE 插件要推给前端组件,SDK 调用要返回结构化数据。QueryEngine 的存在意义是统一适配这些差异,让 queryLoop 不用管上游是谁。

Claude Code 的两层结构:

  ┌─────────────────────────────────────────┐
  │              QueryEngine                │
  │   会话状态 · transcript 持久化 · usage   │
  │   错误恢复 · 多消费端事件适配            │
  ├──────────┬──────────┬────────┬──────────┤
  │   CLI    │ IDE 插件  │  SDK   │ MCP Server│
  │ (Ink+    │ (前端     │ (结构化  │ (工具     │
  │  React)  │  组件)    │  返回)  │  调用)    │
  ├──────────┴──────────┴────────┴──────────┤
  │              queryLoop()                │
  │    调用 LLM → 执行工具 → 拼接结果        │
  │    流式事件通过 AsyncGenerator 上抛      │
  └─────────────────────────────────────────┘

Cloud Agent V2 只有一个消费端:浏览器。没有 CLI、没有 IDE 插件、没有 SDK。多出来的那一层适配纯粹是负资产——多一层调用、多一层状态管理、多一层要维护的代码。

直接把 QueryEngine 拿掉,两层合并成一个 query() 函数:

V2 的简化结构:

  Hono Server  (POST /api/sessions/:id/chat)
    │
    ▼
  query()  AsyncGenerator<StreamEvent>
    │  调用 LLM → 执行工具 → 拼接结果
    │  每轮检查 abort 信号
    ▼
  SSE 事件流  ───────────────────►  浏览器
    text / tool_result / usage

Hono 收到请求后调用 query(),遍历它 yield 出来的事件,序列化成 SSE 发出去。中间没有适配层、没有事件格式转换,query 产什么 SSE 就推什么。

这个决策的底层逻辑后来贯穿了整个 V2 的设计:Claude Code 的复杂度对应它的多场景需求,V2 不需要为用不上的场景买单。 从工具删减到协议选型,本质上都是这句话的延伸。

上下文是怎么构建的

Agent 循环的入口是 query(),每次调用时它会组装一份完整的上下文发给 LLM。以下是 V2 每次 LLM 请求的上下文组装过程,以及和 Claude Code 的差异。

V2 上下文构建流程(每次 query() 调用时组装):

  系统提示词(15 个 section,启动时初始化,内存缓存)
  ┌─────────────────────────────────────────────┐
  │ 静态段(可缓存,每次都一样)                     │
  │  base → using_tools → actions → output_style  │
  │  → tone → session_guidance                   │
  │  每个段标记 cache_control: ephemeral          │
  ├─────────────────────────────────────────────┤
  │ __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__           │
  ├─────────────────────────────────────────────┤
  │ 动态段(不可缓存,随环境变化)                    │
  │  environment: 工作目录、项目 ID                │
  └─────────────────────────────────────────────┘
                        │
                        ▼
  消息列表(currentMessages[])
  ┌─────────────────────────────────────────────┐
  │ 技能注入(首次对话时 unshift)                   │
  │  system-reminder: "以下技能可用:..."          │
  ├─────────────────────────────────────────────┤
  │ 历史消息(JSONL 加载)                          │
  │  user / assistant / tool_result 交替          │
  ├─────────────────────────────────────────────┤
  │ 本次用户消息                                    │
  ├─────────────────────────────────────────────┤
  │ 工具结果预算(每轮开始前执行)                     │
  │  applyToolResultBudget: 超 100KB → 磁盘       │
  │  总预算: 单轮 20 万字符                         │
  ├─────────────────────────────────────────────┤
  │ 消息归一化                                     │
  │  normalizeMessagesForAPI: 内部格式 → API 格式  │
  │  最后一条消息附加 cache_control                 │
  └─────────────────────────────────────────────┘
                        │
                        ▼
  Tool 定义(tools[])
  ┌─────────────────────────────────────────────┐
  │ 内置 Tools: 11 个,直接加载                    │
  │ MCP Tools: 动态注入,mcp__{server}__{tool}    │
  │ Skill/MCP: deferred,由 ToolSearchTool 发现   │
  │ Schema 缓存: toolSchemaCache(LRU 100 条)     │
  └─────────────────────────────────────────────┘
                        │
                        ▼
                  发给 LLM API

对比 Claude Code,V2 的上下文构建骨架相同,但少了几层:

Claude Code 有而 V2 没有/未启用的:

  多级上下文压缩(cc 独有)
  ├── microcompact: 自动清理旧 tool_result
  ├── auto-compact:  输入 token 超阈值 → 裁剪历史消息
  └── reactive-compact: LLM 返回特殊信号 → 触发压缩

  V2 现状: contextManagement.ts 已从 cc 搬运(clear_tool_uses_20250919 策略),
  但未接入 query()。当前只靠工具结果截断 + maxTurns=20 硬限制。

  Thinking blocks(cc 独有)
  └── cc 有 extended thinking + clear_thinking_20251015 清理策略
      V2 直接移除 thinking,API 调用不带 thinking 参数。

  权限反馈(cc 独有)
  └── cc 的 Permission 系统把用户 allow/deny 决定回写上下文
      V2 简化为全 allow,不产生额外消息。

  技能发现(cc 更复杂)
  └── cc 支持 MCP skills + 远程技能(AKI/GCS) + 多级筛选
      V2 只从 skill_registry 表 + 文件系统查。

两者的架构模式一致——分段缓存、消息归一化、工具预算、deferred tools——但 cc 在上下文压缩上多了三级机制。V2 在裁剪、管理上下文机制上已经强过V1太多,够用所以没补,contextManagement.ts 留着是预留的,将来上下文压力大了直接启用。

方向定了,接下来面对的是 Tool 系统。

插一句背景。Tool calling 是这几年 LLM 应用最重要的演进之一。ChatGPT 2022 年底出来时只能纯文本对话,问它"今天天气怎么样"它只能告诉你它不知道。后来 OpenAI 在 2023 年中发布了 Function Calling,LLM 不再只输出文本,而是输出一个结构化的函数调用请求,由外部程序执行后再把结果传回给 LLM。从此 Agent 不再是一个 prompt 工程概念,变成了一个可实现的系统:LLM 负责决策(调用哪个函数、传什么参数),Tool 负责执行(真正去做事)。

Claude Code 就是在这个范式上搭起来的。虽然它名字里带"Code",但它的能力范围远超编程。它的 Tool 体系——文件读写、数据搜索、Shell 执行、Web 访问——本质上是一套通用的信息处理能力。一个财务分析师让它处理 Excel 对账单,一个法务让它对比合同条款,一个运维让它检查日志异常,它都能应对。我自己用它处理财务业务文件时就感受到这一点:它不像一个只懂代码的工具,更像一个会读文件、会分析、会动手的通用 Agent。这是它值得作为参考的第一个原因。

第二个原因和模型无关,和 Agent 设计有关。实际用下来我的感受是,Agent + LLM 的实际表现四六开:闭源模型(GPT、Claude)本身当然强,但一个设计良好的 Agent 同样关键。坏的 Agent 能把好模型的上下文搞乱、Token 烧光、输出失控。Claude Code 除了模型好,它的 Agent 设计——Tool调度、上下文管理、缓存策略、中断恢复——才是让它持续可靠工作的根基。这种设计带来的效果不是"一次性输出完美的结果",而是更务实的:不强制要求一步做对,能合理规划流程,先读文件、再分析、中间可能绕点弯路,但只要步数不是特别离谱,最终能拿到正确结果。这是我在用它写代码时反复观察到的模式,Tool的适配,就是此时的重点。

引入哪些,按什么顺序

Tool 迁移不是一次性全引入的,是分了五批,按依赖关系和复杂度推进。

第一批:6 个基础 Tools

FileRead、FileWrite、FileEdit、Glob、Grep、Bash,这6个Tools项目初始化时就直接搬了,这是 Agent 的底线能力:能读文件、能写文件、能搜索、能执行命令。没有这 6 个,Agent 什么都做不了。Bash 最特殊,下面单独讲。

第二批:2个 Web Tools

WebFetch

移除了 Claude Code 特有的域名黑名单和 Haiku 二次摘要,换了 turndown 做 HTML→Markdown 转换。

WebSearch

它实现方式值得单独说一下。市面上很多 Agent 的搜索能力是通过 MCP 接入的——启动一个 Brave Search 或 Tavily 的 MCP server,Agent 调用 MCP Tool 来完成搜索。本质上是"应用→MCP server→搜索 API"三段链路。

V2 没用这个方案,走的是 Anthropic API 原生的 web_search_20250305。流程是:Tool 把 web_search_20250305 作为 tool schema 传给 LLM API,API 自己完成搜索,返回结构化的结果(web_search_tool_result,含 title/url/文本摘要),Tool 只负责解析和格式化。搜索动作发生在 API 侧,不经过 V2 的服务器,也不经过任何 MCP 中间层。

选这个方案的理由很直接:一是少一个进程就少一个维护点,MCP server 也有自己的版本和配置要管;二是 API 原生的搜索质量受模型厂商持续优化,不需要我来操心搜索引擎的选型和升级。这个方案有一个前提:LLM API 需要支持 WebSearch 能力。Anthropic 原生的 web_search_20250305 是目前的事实标准,兼容 Anthropic API 的模型提供商(GLM、百炼等)一般都支持,当前用的模型跑起来没问题。代价是绑定了 API 厂商——如果换了一个不支持此特性的提供商,这个 Tool 就得回退到 MCP 方案。

第三批:2个需要前端配合的 Tools

AskUserQuestion(向用户提问确认)

一开始我没想过 Tool 还需要前端配合。Claude Code 的 Tool 全是服务端执行,结果通过终端渲染,不涉及"前端"这个概念。但 Web 场景下,终端就是浏览器。Tool 执行过程中如果卡住了需要用户选择——比如 AskUserQuestion 弹出一个多选表单——服务端没法自己完成,必须把问题推到浏览器、等用户操作完再拿结果回来。反过来想,这其实和 Claude Code 的 Permission 弹窗是一回事,只是弹窗从 TUI 换成了 Web UI。Tool 需要交互层,交互层在 Web 架构下天然属于前端。想通这一点后,第二批的依赖关系就清楚了。

AskUserQuestion 源码约 250 行(含 Ink React 组件),精简和适配后约 100 行。原版的交互方式是 Permission 系统触发 TUI 弹窗,Claude Code 的终端 UI 框架全在这一层。V2 砍掉了整个 Ink React 依赖,改成了 SSE user_question 事件 + 前端 UserQuestionDialog 弹窗。工具只负责触发事件和等待答案,渲染全交给前端。

SkillTool(技能调用)

SkillTool 源码 1109 行,精简和适配后 200 行。砍掉了三个重头:

  • forked sub-agent 执行模式(V2 只保留 inline 模式)

  • 远程技能加载(AKI/GCS)

  • MCP skills 支持。

技能发现也从 getAllCommands 简化为数据库加文件系统目录。SkillTool 需要前端的理由和 AskUserQuestion 不一样。AskUserQuestion 是运行时强依赖:Tool 执行到一半必须等前端弹窗返回结果。SkillTool 走 inline 模式,执行时不依赖前端。但它依赖 skill_registry 表里的配置数据——哪些技能启用了、scope 是什么、SKILL.md 放在哪——这些配置只能通过前端的 Skills 管理页面录入。没有前端管理后台,表就是空的,SkillTool 无技能可调。所以不是运行时依赖,是配置链路依赖:前端填数据→数据库→SkillTool 读取。

所以这一批的节奏是:前端先做 UserQuestionDialog 和 SkillsPage,同时后端扩展 SSE 协议加 user_question 事件类型,然后才接上两个 Tool 的实现。

第四批:4 个 MCP 骨架

MCPTool、ListMcpResourcesTool、ReadMcpResourceTool、McpAuthTool。这批只搬了骨架(类型定义和占位逻辑),实际的 MCP 连接管理到 4 月 16 号才接上。McpAuthTool 至今仍是占位,因为 stdio 不需要 OAuth。

先骨架后完整是刻意的开发策略。4 月 13 号这个时间点,Agent 的核心循环刚跑通,Skill 系统还在搭,需要尽快验证"Tool 能正常注册、Agent 能正常调用、SSE 能正常推送"这条链路。MCP 的完整实现涉及子进程管理、连接状态机、错误分类、重连策略,这些堆在一起调试的成本太高。先把骨架放进去占住位置,让架构能跑通,MCP 单独拉出来慢慢搭。后面 04 篇会详细讲 MCP 搭建过程中踩的坑。

第五批:ToolSearchTool

MCP 上线后的连带需求:MCP 和 Skill Tool 标记为 deferred(shouldDefer: true),不直接出现在 Tool 列表里,由 ToolSearchTool 托管。LLM 需要时先调用 ToolSearchTool 按关键词搜索,找到对应的 Tool 后再调用。这个设计减少了初始 prompt 的体积。

总结一下

最终内置 Tool 是 11 个,加上 4 个 MCP 相关 Tool:

Tool 用途
FileRead 读文件
FileWrite 写文件
FileEdit 编辑文件
Glob 文件名搜索
Grep 文件内容搜索
Bash 执行 Shell 命令
WebFetch 获取网页
WebSearch 网络搜索
AskUserQuestion 向用户提问
SkillTool 技能调用
ToolSearchTool 延迟 Tool 发现
MCPTool MCP 工具调用核心
ListMcpResourcesTool 列出 MCP 资源
ReadMcpResourceTool 读取 MCP 资源
McpAuthTool MCP OAuth 认证(占位)

后 4 个是 MCP 体系的骨架 Tool。除此之外,运行时还会动态注入 MCP Tool(以 mcp__{server}__{tool} 命名),数量取决于连接了多少 MCP server。

哪些没实现

Tool名称 原因
LSPTool、NotebookEditTool、NotebookReadTool 代码编辑器功能,业务场景不存在
TaskCreateTool、TodoWriteTool 任务管理,用不上
Worktree 系列 Git worktree 隔离,无需求
REPLTool、PowerShellTool Bash 已覆盖
AgentTool 子 Agent 系统,复杂度高,评估后暂时不需要
Chrome 浏览器工具 无浏览器自动化需求
SleepTool、ScheduleCronTool 无定时需求
TeamCreate/Delete 无团队协作需求
ConfigTool 配置通过 Web UI 管理,不需要 LLM 操作

接入不是照抄:BashTool 的极端简化

用得上的 Tool,也不是原样接入。最典型的例子是 BashTool。

Claude Code 的 BashTool 实现在安全上下了血本:tree-sitter WASM 做 AST 解析区分复合命令和参数、22 个独立验证器各检查一种危险模式、沙箱隔离限制文件系统和网络访问、sed 命令解析器处理编辑场景、React 组件渲染工具执行状态。总共 16 个文件。

V2 把这一套几乎全砍了。业务场景下 LLM 执行的 Shell 命令通常是对已有数据的分析操作:统计文件行数、查找特定记录、格式转换。不是随意系统命令。没有沙箱、没有 AST 解析、没有 sed 验证。安全策略简化为两层:BashTool 自身做精确匹配拦截危险命令(rm -rfmkfsshutdownreboot 等 10 条),更细粒度的路径边界、命令阻断、复合命令拆分检查由下面的 pathGuard 负责。V2只有3个文件。

其他 Tool 也有类似程度的简化,只不过没有BashTool改动那么大。

Tool 的安全边界:pathGuard

砍掉 Claude Code 复杂的权限系统后,需要有东西补上安全缺口。pathGuard 就是干这个的,这是我原创设计的模块,没有参考 Claude Code,因为 Claude Code 的安全路径在 Permission 系统和沙箱层面,和 V2 的场景完全不搭。

设计思路很直接:以项目目录为边界,以路径检查代替命令分析。 Claude Code 的做法是分析命令本身是否危险(AST 解析、验证器),pathGuard 的做法是分析命令的操作目标是否在允许范围内。前者问"你在做什么",后者问"你在动哪里"。对 V2 的场景来说,后者更有效——LLM 执行分析命令本身不可怕,可怕的是它访问不该访问的路径。

分三层拦截:

第一层,路径边界。 所有文件读写操作先过 isPathWithinProject,检查目标路径是否在项目目录内。相对路径先解析为绝对路径再比较,~ 展开到 home 目录,.. 穿越被拒绝。uploaded 文件标记为只读,不能覆盖用户上传的原始数据。

第二层,技能白名单。 Skill 执行时 LLM 需要读技能目录下的脚本和模板文件,这些文件不在项目目录内。pathGuard 维护了一个白名单:data/skills/(全局技能)和 data/userSkill/{userId}/(用户自己的技能)。Read 操作命中白名单直接放行——但只放行 LLM 的读取,推给用户的 SSE 事件在 chat.ts 里会被拦截替换(下一篇会细讲)。

第三层,Bash 命令检查。 LLM 通过 Bash 执行命令时,不是直接放行。先过禁止命令列表(chmodsudosshpip install 等 29 条),再过路径参数提取——把复合命令按 ;&&|| 分割,提取每段子命令的路径参数,逐个检查是否越界。输出重定向(>>>)的目标路径同样检查。cd + 写操作的组合(cd /tmp && rm data.csv)直接拒绝,防止绕过目录检查。

三层之间是递进关系:路径边界是第一道门,白名单是墙上开的窗(只对特定目录放行读操作),Bash 检查是对最灵活也最危险的执行通道做额外加固。

pathGuard 替代了 Claude Code 的 tree-sitter + 22 个验证器 + 沙箱的一整套安全体系。不能说它比 Claude Code 的方案更安全——明显更粗糙,没有 AST 级别的命令理解,路径参数提取也用了简化匹配而非真正的 shell 解析。但它匹配了 V2 的威胁模型:用户上传文件有独立目录,LLM 的操作范围被限制在项目内,敏感路径通过白名单显式授权,够用了。

集群部署时的沙箱方案(未实现)

当前单实例没上沙箱,pathGuard 的路径边界加命令阻断把风险控制在可接受范围。但集群部署时安全模型要重新评估:多租户共用实例,一个用户的 LLM 执行了恶意命令可能影响其他用户。pathGuard 的路径检查挡不住同实例内的横向操作。

沙箱方案有三个候选方向:容器隔离、进程级沙箱、应用层限制加用户级隔离。

上了沙箱之后,pathGuard 的部分机制可以简化。比如禁止命令列表可以缩减——沙箱本身限制了文件系统访问,rm -rf 在沙箱里只能删除挂载目录内的文件,比 pathGuard 的字符串匹配更可靠。路径边界检查也可以从"拒绝越界"退到"警告越界"——沙箱挡住了,pathGuard 多一道审计日志就行。这是后续的优化方向,当前不急着动。

Tool 接口的精简

Claude Code 的 Tool 类型有 30 多个字段,将近一半是 React 渲染入口:renderToolUseMessagerenderToolResultMessagerenderToolUseProgressMessage 等。V2 是 Web 应用,前端有自己的 Zustand 状态管理和 React 组件树,不需要 Tool 层插手 UI。把这些全砍了,Tool 接口只保核心字段:name、inputSchema、call、description、prompt、isEnabled、isReadOnly、isConcurrencySafe、maxResultSizeChars。Tool 层不依赖任何 UI 框架,将来换前端方案不受影响。

卡住了回头看:持续学习和理解 Claude Code 源码

前面讲的都是结构性问题:架构怎么改、Tool 怎么裁、安全怎么补。这些问题开工前就能识别,不需要看源码也知道要做。

但跑起来之后,才暴露了一些实现层的问题。这些问题靠自己琢磨也能解决,但回头看 Claude Code 源码通常能省掉大量试错。

prompt caching 是最典型的例子。一开始系统提示词是一整段文本,每次请求原样发送。跑了几天后发现 token 消耗过高——同样的工具使用说明每次都原样传。翻 Claude Code 源码,发现它的系统提示词不是一段字符串,是 string[] 数组:每个元素独立标记 cache_control: { type: 'ephemeral' },静态段(工具使用说明、格式要求)可缓存,动态段(环境信息如工作目录、项目 ID)放在 breakpoint 后面。照着这个思路把系统提示词拆成 15 个 section,用一个边界标记 __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 分开静态和动态。静态段的 token 消耗接近零。

工具结果预算也是类似。Claude Code 有多级压缩策略(microcompact、auto-compact、reactive-compact),评估后认为业务场景不需要这么重,只做了单级截断。

没做的

SubAgent 系统、流式工具执行(Claude Code 的 StreamingToolExecutor)、多级上下文压缩、交互式恢复。都评估过,都决定不做。理由一样:业务场景用不上。

Claude Code 是给程序员用的通用工具,V2 是给特定业务场景用的专用工具。Claude Code 有的功能不代表 V2 需要。判断什么不该做和判断什么该做一样重要。看起来是放弃了一些能力,实际上是拒绝了不必要的复杂度。


Agent 引擎和 Tool 体系讲完了。下一篇聊 Agent 的事件怎么推到浏览器、数据怎么持久化、多会话和中断怎么处理——就是上面架构图里 query() 下面那条 SSE 箭头牵引出的一整套 Web 交互机制。