官方说法与工程现实
Anthropic 的博客把 Claude Code 描述为一个简单的 while 循环:调用 LLM,如果返回工具调用则执行工具,否则结束。
这句话是对的。但这是 1% 的真相。
做过 Agent 的人大概都有过这种感受,让它能跑很容易,让它可靠很难。让它在 demo 里表现惊艳很容易,让它在第 50 轮对话、第 3 个并发工具、第 200K token 的场景下不崩溃,才是真正的工程挑战。
真正的难题在哪里
mindmap
root((Alice 工程难题))
["上下文管理"]
["如何不丢失关键信息"]
["如何控制 token 成本"]
["记忆系统"]
["短期 vs 长期"]
["跨会话一致性"]
["多 Agent 协作"]
["任务分配"]
["结果汇总"]
["权限安全"]
["自动化与安全的平衡"]
["用户信任等级"]
["可观测性"]
["行为可追溯"]
["成本透明"]
["活人感"]
["身份连续性"]
["主动生活能力"]
真正的 Agent 产品需要解决这些问题,但官方文档几乎从不提。更麻烦的是,一些常见的做法,Alice 也试过,最后发现走错了方向。
上下文窗口焦虑
用户的长对话迟早会接近模型的 token 上限。简单截断会让模型失去上下文;不处理会导致请求失败。怎么在记住重要的事和不超出限制之间找平衡?
一种直觉做法(Alice 也试过):很多团队直接用滑动窗口,保留最近 N 条消息,丢掉更早的。这在闲聊场景下勉强可用,但在 Agent 场景下几乎一定会出问题。因为 Agent 的关键信息往往在对话早期,用户描述需求的那段话、第一次工具调用返回的项目结构,这些信息的价值远高于中间的工具执行日志。滑动窗口不区分消息的价值密度,把最重要的上下文最先丢掉。
LangChain 早期版本的 ConversationBufferWindowMemory 就是这种思路,简单粗暴地保留最后 K 条消息。后来他们加了 ConversationSummaryMemory,把旧消息压缩成摘要,但压缩的时机和粒度仍然很粗糙,不区分用户消息、工具输出、系统消息的不同价值。
MemGPT 提出了一种更激进的方案,把上下文管理类比为操作系统的虚拟内存,让 LLM 自己决定什么时候把信息从主存换出到外部存储。这个想法在学术上很优雅,但在工程上有一个致命问题:LLM 自己判断信息重要性的准确率不够高。在实际测试中,模型经常把关键的项目配置换出去,同时保留了一些无关紧要的礼貌用语。
Alice 的选择是分层压缩:不同类型的消息用不同的策略处理,工具输出可以大幅压缩,用户原始消息尽量保留原文。代价是压缩系统本身的复杂度很高,需要维护多套策略和触发条件。
工具并发的竞态
当多个工具同时执行时,它们对共享状态的修改怎么合并?执行顺序怎么保证?某个工具失败了,之前的工具结果还算数吗?
一种直觉做法(Alice 也试过):把所有工具调用都串行执行。这样没有竞态问题,但代价是性能灾难。当模型同时要求读取三个文件时,串行执行意味着三倍的等待时间。在 Claude Code 的使用场景中,一次工具批处理经常包含 3 到 5 个并行读取,如果全部串行,用户感知到的延迟会从 1 秒膨胀到 5 秒。
另一个常见错误是无差别并行。Cursor 在早期就遇到过这个问题,当模型同时调用文件编辑和文件读取,且目标是同一个文件时,并行执行的结果取决于操作系统的调度顺序,这是不可接受的非确定性。
正确的做法是按安全性分类:在工具声明时标注自己是否支持并发执行,框架据此决定是否可以并行调度,其余串行。这需要每个工具自己声明并发安全性,增加了工具注册的复杂度,但消除了竞态隐患。
权限的精细化
不同危险级别的操作需要不同的处理:删文件要确认、读文件不需要、联网搜索视情况而定。如何设计一套既灵活又安全的权限体系?
一种直觉做法(Alice 也试过):二元权限模型,要么全部信任,要么每个操作都弹确认框。Claude Code 早期版本就是这样,Auto Mode 下几乎不拦截,Plan Mode 下几乎都拦截。后来他们引入了基于规则的权限配置文件,允许用户配置哪些工具自动放行、哪些需要确认,这是一个正确的方向。
LangChain 的 AgentExecutor 采用了一种不同的策略,通过 handle_tool_error 回调把错误传回模型,让模型自己决定怎么处理。这解决了部分问题,但把安全决策交给了 LLM 本身,而 LLM 的安全判断并不总是可靠的。
Alice 的设计是三层权限:工具元数据声明(只读、可破坏、网络访问)、全局规则配置、运行时动态判断。代价是权限系统本身成为了一个需要独立维护的子系统,配置项的组合爆炸也是一个持续需要管理的问题。
记忆的分层
有些信息只在这次对话里有用;有些需要跨会话保留;有些是用户的长期偏好;有些是项目的约定规则。这些记忆应该怎么组织、存在哪里、什么时候读取?
一种直觉做法(Alice 也试过):把所有记忆都塞进 System Prompt。这是最直觉的方案,也是最容易失控的方案。当记忆条目超过几十条,System Prompt 本身就会占用数千 token,侵蚀留给真正对话的空间。
Claude Code 的 CLAUDE.md 是一个精巧的折中:用纯文本文件存储项目级记忆,每次对话开始时注入 System Prompt,文件由用户手动维护。这解决了记忆持久化的问题,但回避了自动记忆提取和相关性检索的问题。用户需要自己判断什么信息值得写进 CLAUDE.md。
MemGPT 走到了另一个极端,让模型自己维护记忆的增删改查。这在理论上最灵活,但在实践中带来了一个令人头疼的问题:记忆操作本身消耗大量 token。每次对话开始时,模型需要先检索记忆,这个检索过程本身就是一次工具调用,产生额外的延迟和成本。
Alice 采用的是混合方案:项目级记忆用 ALICE.md 文件(类似 CLAUDE.md),会话级记忆在内存中管理,长期偏好通过后台异步提取并存储。代价是记忆系统的数据流比较复杂,需要仔细处理同步时序。
多 Agent 的信息流
当任务需要多个 Agent 并行工作时,它们之间怎么传递信息?怎么防止相互覆盖对方的工作?怎么保证整体结果是一致的?
一种直觉做法(Alice 也试过):共享上下文。让所有 Agent 读写同一个消息数组,这看起来是最简单的协作方式,但实际上是在制造混乱。当 Agent A 在消息数组中写入一条工具结果,而 Agent B 同时也在写入,最终的消息顺序取决于哪个先完成,这让调试变成噩梦。
CrewAI 和 AutoGen 采用了基于消息传递的方案,Agent 之间通过显式的消息发送协作。这比共享状态更干净,但引入了协调复杂度:谁先说话?说完之后谁接?如果两个 Agent 同时想给同一个 Agent 发消息怎么办?
Claude Code 的 SubAgent 模型很务实:父 Agent 创建子 Agent,给它一个隔离的上下文,子 Agent 完成后返回结果摘要,父 Agent 只看到摘要而不是完整的执行过程。这种信息隔离策略避免了状态冲突,代价是父 Agent 对子 Agent 的执行过程缺乏细粒度控制。
Alice 参考了 Claude Code 的隔离模型,同时增加了角色系统。每个子 Agent 有独立的人格定义和工具白名单,角色之间不共享上下文。代价是系统的总 token 消耗更高,因为每个 Agent 都需要独立的 System Prompt。
可观测性
Agent 做了一个不符合预期的操作,怎么追溯原因?是权限判断出了问题,还是上下文丢失了关键信息,还是工具描述误导了模型?
一种直觉做法(Alice 也试过):只记录最终结果,不记录中间决策过程。早期 Alice 的日志系统也只告诉你模型调用了什么工具、返回了什么结果,但不记录模型为什么做这个选择。当出了问题,只能猜测是工具描述写得不好、还是上下文里缺了关键信息、还是模型本身的推理错误。
LangSmith 是目前做得最好的 Agent 可观测性工具之一,它提供了完整的 trace 链路,从输入到每一步推理到最终输出都有记录。但 LangSmith 是一个外部服务,引入了额外的网络延迟和数据隐私问题。
Alice 的方案是内置结构化日志,每次关键决策都携带 reason 字段,每次 LLM 调用都记录完整的 prompt 和 response。代价是日志量很大,需要单独的存储和查询系统来支撑。
这本书的写法
每一章对应一个子系统,结构是:
- 这个问题是什么:不解决会怎样?
- 我们试过什么:探索过哪些方案,为什么放弃
- 设计决策:在多个方案中,为什么选这个,代价是什么
- 条件边界:这个方案在什么情况下不适用
- 可迁移的结论:不管你用什么技术栈,可以直接借用什么
书中不涉及任何具体代码,所有内容都是可以独立实现的方法论。
一个核心洞察
在写这本书之前,我们研究了多个生产级 Agent 系统的架构。有一个结论几乎所有系统都在验证:
Agent 产品的复杂度,主要在状态管理,其次才是模型能力。
这个结论听起来反直觉。Agent 的能力不只取决于模型有多聪明。实际经验反复证明,模型的智能只是必要条件,充分条件还需要正确的工程框架。
一个具体的例子:我们曾经在同一个 Agent 框架上分别测试了 GPT-4 和一个明显弱于 GPT-4 的开源模型。用 GPT-4 但不做上下文管理(消息列表无限增长直到超限报错),任务成功率大约 40%。用开源模型但配合精心设计的上下文压缩、记忆注入、工具结果截断,任务成功率反而达到了 65%。
这个实验不是说模型不重要,换一个更好的模型,65% 可以变成 85%。但它说明了一个关键的优先级判断:如果工程资源有限,投入到 harness(框架)的状态管理上,回报率远高于投入到模型选择和 prompt 调优上。
Claude Code 的成功也验证了这一点。它的 prompt 并不特别复杂,工具描述也很直白,但它的上下文管理、会话恢复、错误恢复机制设计得非常扎实。这就是状态管理的力量。
另一个角度的论证:模型能力在持续进步,今天 GPT-4 做不好的事,明年的模型可能就能做好。但无论模型多强,它仍然需要一个正确的上下文来工作,仍然需要工具执行结果被正确写回,仍然需要在 token 超限时被优雅地处理。这些状态管理的需求不会因为模型进步而消失。
这是 Alice 整个方法论的出发点。
另一个不常被提到的洞察:成本工程是 Agent 产品的道德命题
多数 Agent 框架的文档只关心能不能工作,很少关心花了多少钱。但在 Alice 的设计里,成本工程从第一天就是一个严肃的道德立场。
Alice 的运行成本 100% 由用户自己的 API Key 承担。这个选择从根本上塑造了整个产品的工程哲学:每一个设计决策,都必须对得起用户的钱包。
KV Cache:System Prompt 是宪法,不是便签纸
成本工程里最核心的一条是 KV Cache 哲学。大模型推理的一个关键机制是:System Prompt 保持不变时,模型服务商可以缓存其 Key/Value 矩阵,后续请求只需计算新增内容的 token 费用。但一旦 System Prompt 发生变化,哪怕只是时间戳从「15:00」变成「15:01」,缓存全部失效,每次对话都要重新计算完整的 prompt,费用可能翻倍甚至更多。
所以 Alice 有一条铁律:System Prompt 是宪法,只包含几乎不变的基础信息;时间、当前状态、动态上下文,一律追加到 User Prompt。
首次对话才改 System Prompt,后续动态内容追加到 User Prompt。这不只是省钱的技巧。稳定的 System Prompt 是整个 Agent 成本体系的基石,它让大量反复的交互变成廉价的增量计算,每次都从头开始的全量计算因此得以避免。
一个 Alice 用户完成第一次对话(可能花费约 3 毛钱)之后,后续每次对话的成本可能只有几分钱,正是因为 System Prompt 的 KV Cache 已经命中。
另一个常见错误是把所有记忆怼进 System Prompt。当记忆量级超过几十条时,System Prompt 本身会占用数千 token,侵蚀留给真正对话的空间,同时让每次缓存失效的代价更高。Alice 的做法是向量检索加按需召回:对话开始时,用语义检索找出最相关的记忆片段注入,全量注入因此被避免。
成本透明化:让用户理解,而不是制造焦虑
成本对普通用户是不可见的。用户不需要看到逐条 API 费用,被「这条消息花了 0.03 元」的提示反复打断会破坏沉浸感,让用户对每一次交互都产生焦虑式的算计。
但成本对用户也不应该是完全黑盒的。正确的分层是:普通用户感知「合理花费」,技术用户在 Debug 模式下能看到精确的 token 消耗和 KV Cache 命中率。更重要的是,用户要能理解「为什么这样花钱是合理的」。第一次对话稍贵,后面越来越便宜,因为 KV Cache;记忆系统按需加载而不是全量注入,所以不会越用越耗。这种理解是信任的来源。
成本透明化的终极目标:让用户理解为什么花这些钱是合理的、值得的。
这本书也会从成本工程的角度审视每一个设计决策。不是为了吝啬,而是因为对用户负责。
下一章:五大设计哲学