从跑通到放弃:我的 Cloud Agent V1开发历程

从跑通到放弃:我的 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 -ikill -9。吃过几次亏之后就定了规矩:Agent 只负责生成代码,人负责启动服务和管理端口。代码和运行时分离,省掉一堆奇怪的运维问题,也省掉一些token。


V1 的故事到此为止。下一篇开始进入 V2。