从跑通到放弃:我的 Cloud Agent V1 开发历程
Vibe Coding,启动
2026年1月,我在各种SNS上看到越来越多的关于Vibe Coding的经验分享。上家公司我曾对接过一些AIGC的场景,也了解过cursor、copliot这些工具,起初并未在意。但当我看到有人说"并发开10个Agent------5个写代码、3个测试、1个工作汇总、1个写文档,下班回家睡一觉,第二天来公司代码就写好测完可上线"时,还是有点震惊,AI Coding的能力已经进化到这种地步了吗?处于尝鲜,我安装了Trae,让它接手了我业余时间编写的一个小应用,效果还不错,耳目一新。
等到年后开工,我尝试用Trae处理新分配的需求并编写代码。不过项目还没开始,公司就安排了一个调研任务------一个财务领域的 AI 助手要怎么实现?我也紧跟潮流安装了Claude Code,接入glm-5模型,开始编写代码。
Vibe Coding初体验
如何通过Vibe Coding从零构建一个Agent?对于LLM和Agent,我之前只是粗浅地了解过,没有相关的技术背景,但是既然SNS上把Vibe Coding吹的神乎其神,叠加此时的养龙虾热,我自然是信心满满。
开发流程很简单:
-
打开对标的产品,先体验其交互,然后告诉Claude Code:"我要有XXX功能,这个功能需要XXX"/"页面布局为XXXXX"等等等等。
-
等Claude Code写代码
-
我启动服务器,进行测试,发现有bug就让Claude Code继续改
-
如此循环。
不出意外,遇到了很多问题:
-
前端交互改起来很费时间,我毕竟是后端出身,对于前端没有那么了解
-
同一个bug,反复改很久才改好。有时还会出现:bug1修了1天修好了,开始修bug2,bug2修好了bug1又重现了。
-
大模型限流。虽然是公司提供的coding plan,但是用的太狠还是限流。
但说来也怪,尽管各种不顺,Vibe Coding 会让人上瘾。一旦开始写代码完全停不下来,有点像刚毕业那会儿大展拳脚的感觉------把想法丢给 AI,看着代码在面前长出来,那种即时反馈让人沉迷,甚至游戏都不香了。
V1:跑通了,跑不动了
经过一点一点探索和尝试,还真的把 Agent 写出来了,也就是 Cloud Agent V1。它用 Python FastAPI 搭建,6 周时间、170 个commit(多次squash,实际更多)、约 1.7 万行代码,纯 Vibe Coding,没有手写一行代码。V1 的核心是 LLM 输出带 XML 标签的 Python 脚本,正则解析后在沙箱环境执行。能对话、能操作文件、能执行脚本、能加载 Skill、能调用 MCP,跑通了完整链路。3 月份的各种产品演示都靠它撑了下来,但每次演示前心里都在打鼓------怕 LLM 输出跑偏,怕对话突然卡死。
能跑,但没有任何优势。CherryStudio、Openclaw 这类开箱即用的 Agent 同样能对话、能操作文件、能接 MCP,V1 没有差异化。更要命的是,V1 已经改不动了,继续加功能只会让它更不可控。下面说致命的部分。
V1 的致命伤
| 致命伤 | 一句话 | 直接后果 |
|---|---|---|
| XML 当协议 | LLM 生成任意 Python 脚本,正则提取执行 | 不可靠、不可调试、无法区分错误类型 |
| 上下文混乱 | 按轮数硬裁 + 提示词失控 + 优化全治错地方 | 失忆、token 烧光、越优化越慢 |
| God 类膨胀 | 无初始架构,Vibe Coding 堆功能越堆越大 | 改不动、测不了、牵一发动全身 |
致命伤一:XML 标签当协议
这是 V1 最根本的架构问题。系统不是通过 API 原生的 tool_use / function calling 机制执行操作,而是让 LLM 用 XML 标签包裹生成的Python代码,用正则匹配提取后在python沙箱执行。流程可以简化为两阶段:
用户输入
│
▼
┌─────────────┐
│ Judge 阶段 │ 轻量 LLM 调用 → 决定加载哪些 Skill、文件
└──────┬──────┘
▼
┌─────────────┐
│ 处理阶段 │
│ │
│ ◄── 循环 ──┤ LLM 生成 <script> → 正则提取
│ │ → 沙箱执行 → 结果回传 → 下一轮
│ │ 最多 50 轮
└─────────────┘
▼
返回结果
这带来了一连串问题:
- 自由代码无约束。 LLM 生成的是任意 Python 脚本,不是带 schema 的函数调用。参数是凭空捏的,函数名是幻觉出来的,文件路径是猜的。代码语法错误、import 不存在、变量未定义,全靠沙箱报错才知道。
- 一个脚本干所有事。 LLM 倾向于把多个操作塞进一个
<script>块:先读文件、再算逻辑、最后写结果。中间任何一步崩了,整个脚本失败,而且不知道崩在哪一步。 - 代码错误 vs 业务失败无法区分。 沙箱返回一个 traceback 或一个 None,系统分不清是"脚本写错了"还是"查询的数据确实为空"。两种场景下游处理完全不同,但 V1 都当成"再试一次"。
- 正则解析脆弱。 LLM 输出的
<script>标签稍有偏差------少个闭合、think 块里混入了类似标签,代码里碰巧包含</script>字符串,解析就断了。唯一的恢复手段是让 LLM 再试一次,但它不知道哪错了,只能反复生成代码、反复失败,直到运气好成功、轮数耗尽或用户放弃。 - 沙箱文件复制的开销。 每次执行脚本前,先把相关文件复制到隔离沙箱,安全但每轮多一次 I/O。多轮任务跑下来,大量时间耗在文件搬运上,实际计算反而占比不高。
致命伤二:上下文管理混乱
粗暴的轮数裁剪
历史消息不做 token 精算,唯一的措施是只保留最近 N 轮,更早的直接丢弃。简单粗暴,但结果是两个方向都崩:保留太少,聊几轮 LLM 就忘了开头说过什么,反复追问用户已经回答过的问题;保留太多,一轮交互轻松破 10 万 token。模型也就 200K 的上下文窗口,几轮就烧完了。要么触发 API 400 直接拒绝,要么 LLM 静默丢弃早期上下文,同样是失忆。根本不存在一个稳得住的平衡点。
核心循环最多跑 50 步,每步无条件追加 assistant 响应和脚本执行结果反馈到消息列表。加上聊天历史,全部按轮数硬裁,没有优先级、没有摘要压缩、没有关键信息标记。前面讨论的业务背景、确认过的文件路径,几轮之后就跟从来没出现过一样。
提示词失控
系统提示词约 200 行,硬编码在 Python 源码里。每次 LLM 调用动态拼接:基础提示词 + MCP 工具描述(含完整 JSON Schema)+ 项目文件预览(Excel 表名、列名、数据样本)+ Skill 指令全文。一次普通业务调用,提示词本身烧掉 1-2 万 token。而且每次从头构建,没有缓存复用。这意味着 Skill 指令也是跟着上下文一起膨胀:花大量时间和用户一轮轮调试优化 Skill 的触发条件和提示词,但优化完之后塞进已经臃肿的上下文里,LLM 到底有没有按指令走、触发是否准确,完全不可知。优化了一整天,效果全凭感觉。
治错地方的优化:两个失败的 token 节省策略
Judge Phase
开发和测试中,我意识到了上下文问题,设计了一个 Judge 阶段来解决它,这也是 V1 里我的原创设计。想法不复杂:每次执行任务前,先做一次轻量级 LLM 调用,判断用户要处理哪些文件、需要加载哪些 Skill,然后只把筛选后的内容注入完整上下文。
想法没问题,但忽略了一个基本账本:多一次 LLM 调用,用户就多等一轮。每次操作都要先等 Judge 返回,再等执行结果,响应速度直接被拖慢。如果能用轻量模型来做,消耗倒不至于太大。但当时根本没往这想。
而且 Judge Phase虽然削减的是文件预览和 Skill 内容,但上下文真正的大头是历史对话。前几轮生成的大段 Python 脚本、沙箱返回的几千字节执行结果、反复重试的失败记录。省掉的提示词跟历史对话这个黑洞比完全是杯水车薪。
File Preview
类似的还有文件预览机制。我观察到一个规律:LLM 处理文件时总是先瞄一眼:Excel 读第一个 Sheet 的前几行,PDF 看前几页文本,从不会一上来就做全量分析。于是加了一套文件预览:上传时解析一次,Excel 提取表名和样本数据,PDF 提取文本,Word 提取段落和表格,结果存为 preview_data。Judge 阶段只发精简版(文件名 + 维度),处理阶段发完整版(含样本行和文本摘录)。思路和 Judge 一样:用空间换 token。但问题也一样:根本没做缓存复用。同一份 Excel 预览数据,循环 50 步就在 50 次 LLM 调用里原样发了 50 遍,每次都是从头拼系统提示词。V1 的"优化"始终停在"传什么"层面,从没触及"怎么不反复传"。后者要等 V2 的 prompt caching 才真正解决。
致命三:God 类膨胀和架构失控
最根本的问题是,我一开始就没定架构。不是因为偷懒,而是此前从没做过类似的应用,根本不知道该长什么样。Vibe Coding 的方式是描述功能让 AI 写,不是先画蓝图再施工。结果是架构随功能有机生长,每多加一个模块,前面的设计债务就重一层。
executor.py,2000+ 行。这一个类干了多少事:判断阶段(Judge)、执行阶段(Execute)、脚本运行、MCP 过滤、重试逻辑、错误处理、上下文拼接。全部耦合在一个类里,没有清晰边界。
除了它,context.py 900+ 行,chat_service.py 600+ 行,每个都是同样的毛病。
开发过程中其实也让 AI 帮忙做过架构优化:分模块、拆文件、抽函数、建分层,这些 AI 都能执行。但有两个问题。第一,AI 只负责"当前这次修改",没人持续盯着全局结构。今天拆出去三个函数,明天为了修一个 bug 又塞回两个,后天加新功能再堆一层 if-else。第二,架构优化只在版本稳定时才能做:功能跑通了、演示没压力了,才敢动手。平时改需求、修 bug 时根本不敢大动结构,怕拆出新问题修不回来。结果优化窗口越来越窄,债越欠越多。
重写还是重构
这个决策我纠结了将近一周。不是因为看不清方向,而是沉没成本太重。
V1 毕竟是可以用的。3 月份好几场产品演示都靠它扛下来的,虽然每次演示前提心吊胆,但功能确实一项项跑通了。而且已经加了一个月的班,每天跟 AI 较劲到半夜,好不容易折腾出一个能跑起来的版本。调研项目有公司的时间节点,不是无限期探索。推倒重来意味着前面一个月的加班全部归零,新方案能不能按时跑出来,当时根本没底。
与此同时,用户提的 bug 越攒越多。修好一个冒出两个,越来越看不到头。
但继续在 V1 上修补,代价同样算得清。前文提的三个致命伤,每一个细看都不是重构能解决的。
继续修补 V1 推倒重写
──────────── ──────────
✓ 能跑、演示扛住了 ✓ 三个致命伤,重构解决不了
✓ 加班成果不归零 ✓ 参考其他开源Agent源码,但不知道选哪个
✗ 时间节点越来越不可预期 ✗ 一个月加班归零,风险未知
✗ bug 越修越多,看不到头 ✗ 1.7 万行代码作废
✗ 架构缺陷是骨架层的问题
XML 标签当协议是 V1 的根基。把 XML 解析换为标准 tool_use,意味着整个 agent loop 从 executor、parser、context 到 LLM 调用链路全部推倒。这不是"重构 executor.py 的一部分",而是删除它存在的理由。
即使有 Vibe Coding,重构的工作量也不会少。零测试兜底意味着每次改完只能靠人肉验证:告诉 AI "这里有问题,改成 X",跑一遍,发现崩溃了,再告诉 AI 修崩溃,再跑一遍。
还有一个语言层面的问题:V1 用 Python 只是因为当时熟悉 Python,并不意味着后端就应该用 Python。Claude Code 源码是 TypeScript 写的,要继续深入参考它的设计,用同一种语言显然比在 Python 和 TypeScript 之间做概念翻译更顺畅。继续守着 V1 的 Python 代码,意味着往后的每一轮优化都在隔着一层语言做映射。这个成本,重构省不掉。
最后一个砝码:V1 还处于内部小范围 demo 阶段,没有历史数据要兼容,没有线上用户要迁移。推倒重来最重的代价只是扔掉代码,不需要做数据迁移、协议兼容、灰度切换这些工作。V1 的价值在于验证方向、积累认知,不在于那 1.7 万行代码本身。
契机
当我在纠结要不要重写时,Claude Code 的源码泄露了。
消息出来那天我就把源码拉了下来,不过比起直接啃源码,真正帮我下定决心的是社区里陆续出现的解读文章。这些文章讨论的方向主要有几个:
-
Agent 循环设计
-
tool注册和调度
-
prompt caching 的分段策略
-
上下文压缩的分级机制
-
多 Agent 的协作模式
-
权限系统的分层拦截
当时我看得似懂非懂,很多概念还没建立起完整的认知。但我知道这就是我应该参考的方向。经过一个多月高强度使用 Claude Code,再看社区的评价,感受更确定了。大家反复提的几个点和我自己的体验完全重合:对代码库的理解不像关键词搜索,更像一个读完代码再动手的工程师;多文件重构时不会拆东墙补西墙,共享逻辑自然合并;每一步操作都附带自解释的上下文,像一个不需要文档的工具。
更棒的是,Claude Code并不是一个纯粹面向coding而是面向通用工作设计的Agent,即使直接将财务领域或其他业务领域的文件丢给它处理,它也能三下五除二地搞定,这对我来说足够了。一个让我每天用都不烦的设计,一定是值得跟着走的。
再次抉择:用 SDK 还是尽可能参考源码
参考方向有了,怎么落地?组里一直有声音建议直接用 Claude Code 的 SDK。毕竟官方封装好了 agent loop、工具调度、流式处理,上手最快。但经过前面一个多月的折腾,我很清楚这次不能只图快。如果只是用 SDK 拼业务逻辑,不过是换了一套 API,V1 的架构问题一个也解决不了。
动手重写前,我做了最后一次评估。
方案 A:基于 Claude Code SDK。 @anthropic-ai/claude-agent-sdk 封装了 agent loop、工具调度、流式处理,接入即用。优点是开发周期短,缺点是架构受 SDK 约束:权限模型、缓存粒度、中断机制、SSE 事件定义,能用但不能改。
方案 B:参考 Claude Code 源码自行实现。 研究 Claude Code 的架构设计、技术栈选择、模块划分,用自己的方式重写一遍。优点是每一层都可控,缺点是工作量大。
我选了方案 B。理由四个:
控制力。 SDK 封装了核心能力,使用方只能按它的接口接入。我需要的是自定义每一个关键环节:工具执行策略怎么设计、权限模型怎么隔离、缓存粒度怎么控制、中断机制怎么触发、SSE 事件怎么命名和传递。这些在 SDK 里是封闭的,在源码里是透明的。
集群部署。 SDK 的心智模型是单机运行,fork + process 隔离。项目一开始就规划了集群部署:多实例 + 会话亲和 + 状态外置 + SSE 跨节点转发。这要求 HTTP 层和流式传输层都是自主可控的。源码方式从 Hono server 到 SSE 流全是自己的,做分布式改造时不会有"SDK 封装的东西改不了"的尴尬。
技术栈对齐。 Claude Code 本身就是 TypeScript + Bun 构建的。V2 选同样的技术栈,模块结构、类型体系可以和参考实现保持一致,降低理解成本。
技术积累。 实际的开发方式不是"读完源码再动手写",而是让 Agent 写代码,并同步整理技术文档辅助理解。遇到拿不准的地方,回到 Claude Code 源码看对应的实现,然后在Claude Code 里复现同一个场景,观察它是怎么处理的。源码不是照着抄的模板,是遇到问题随时回去查的参考答案。
V2 的技术方向
┌──────────────────────────┬──────────────────────────┬───────────────────────────┐
│ 原则一 │ 原则二 │ 原则三 │
│ 技术栈靠拢 Claude Code │ 按模块推进,不照抄 │ 预留集群扩展空间 │
├──────────────────────────┼──────────────────────────┼───────────────────────────┤
│ TS + Bun + Zod 对齐 cc │ 提取核心设计思想 │ 会话外置 → SQLite / JSONL │
│ Hono 做 HTTP(原生 SSE) │ 适配需求做改写和简化 │ SSE → 预留跨节点转发接口 │
│ React + Vite(Web 前端) │ 先确保能跑起来 │ 文件 → 抽象 storage 层 │
│ SQLite 持久化 │ 卡住时回源码查参考答案 │ 不假设单机,杜绝"以后改不了" │
└──────────────────────────┴──────────────────────────┴───────────────────────────┘
动手之前,定了三条原则。
第一,技术栈向 Claude Code 靠拢。 理由很简单:Claude Code的源码是现成的参考答案,同一种语言翻起来最顺畅。具体选型以 Claude Code为基准,场景不同的地方做适配:
| 层面 | Claude Code | V2 | 说明 |
|---|---|---|---|
| 运行时 | Bun | Bun | 一致 |
| 语言 | TypeScript | TypeScript | 一致 |
| 校验 | Zod | Zod | 一致 |
| UI | Ink + React(终端) | React + Vite(浏览器) | Claude Code是 CLI 工具,V2 面向 Web 用户 |
| HTTP 层 | --- | Hono | cc 无需 HTTP server,V2 需要;选 Hono 是因为轻量、原生支持 SSE 流 |
| 存储 | --- | SQLite | cc 主要操作本地文件系统;V2 需要持久化会话、用户、配置数据 |
第二,按功能模块推进,不搞像素级拷贝。 Claude Code是通用 CLI 工具,V2 是面向业务的 Web 应用,场景不同决定了不能全抄。开发策略是:从源码提取核心设计思想,适配自己的需求做改写和简化,在和Claude Code的流程一致的前提下,先确保能跑起来。当简化版确实满足不了需求时,再回头参考源码,看 Claude Code 在同类场景下是怎么处理的。
第三,预留集群部署的扩展空间。 项目规划中就有多实例部署的需求。这意味着从第一行代码开始就要考虑:状态不能长在进程内(会话数据外置到 SQLite/JSONL),SSE 连接不能假设单机(跨节点转发预留接口),文件存储不能依赖本地磁盘(抽象 storage 层)。不一定一开始就把集群搭起来,但架构上不能有"以后改不了"的单机假设。
V1 的代码被全部丢弃,但 V1 验证的方向:Skill 系统、MCP 集成、产品形态,全部保留。
Vibe Coding 实战心得
除了V1本身的经验教训外,我还总结了一些通用的Vibe Coding经验。
善用 git 做小步提交。 每次 commit 只做一件事。commit 粒度太大,前端改版 + 后端调整 + 提示词修改全塞一起,出了 bug 根本定位不到哪次改动引入的。更常见的是 bug A 修好了,下一个改动又把它带回来,因为提交太乱,AI 理不清哪些代码是修 bug 的、哪些是新功能的。小步提交、每个 commit 可独立回滚,省掉大量排查时间。
控制上下文窗口。 不论是 Vibe Coding 还是设计 Agent,上下文管理都是 AI 时代程序员的核心能力。对话长了 AI 会忘事,前面说过的规则、约定、已经修好的 bug,一旦被压缩出去就不在了。压缩本质是丢信息,没有两全方案。所以实际开发中要刻意保持对话聚焦:一个需求通路走到底就开新会话,不要在一条里反复横跳。把 AI 的记忆当成一种需要主动管理的稀缺资源,而不是默认它什么都能记住。
自己管运行环境,别让 Agent 插手。 可能是我用的LLM能力不足,Agent 启动的服务有时候 kill 不掉,端口被占着回收不了,只能手动 lsof -i 再 kill -9。吃过几次亏之后就定了规矩:Agent 只负责生成代码,人负责启动服务和管理端口。代码和运行时分离,省掉一堆奇怪的运维问题,也省掉一些token。
V1 的故事到此为止。下一篇开始进入 V2。