Agent
Agent 是什么?
我认为 Agent 就是自主工具循环。
脱离具体范式讨论 Agent 是什么意义不大。最基本的 Agent 范式是 ReAct,本质上就是一个 while True 循环:
停止条件由具体定义和框架决定。
Agent 的最小基础定义为自主工具循环;记忆和规划很重要,但不是 Agent 成立的必要条件。
- 规划:事实上即使没有显式的规划,agent 或者说 LLM 也能通过整个上下文大致知道下一步要做什么(比如调用什么工具)
- 记忆:对于最基本的 agent,上下文就是记忆,或者你叫它是短期记忆也行;但是长期记忆真的不是基本架构之一。
Tool、workflow
Tool 是被 Agent 使用的,Agent 是 Workflow 中的一个基本的执行单元,而 Workflow 是组织多个执行单元的方式。
我不想把 Tool、LLM、Agent 分得太玄乎。Tool 本质上就是一段业务代码,只是为了让 LLM 能调用,额外包了一层 schema 和描述。LLM 也可以被看作一个最小执行单元,甚至可以理解成一个退化版 Agent:只做一次模型调用,不进入工具循环。
既然 Workflow 是“怎么组织”,那它当然可以有不同形态。
一种是确定性 Agentic Workflow:用 Python 代码、DAG、条件分支来决定下一步。即使节点里包含 Agent,控制流仍然由代码掌握,所以相对稳定、可复现、可调试。
另一种是动态 Agentic Workflow:引入高层次的 Agent 组织者,由它判断下一步应该调用哪个低层 Agent、Tool 或流程。只有当下一步判断无法用明确代码规则解决时,才有必要引入这种上层 Agent。
Agent 设计范式
我确实知道有基本的 ReAct,以及 Plan-and-Execute,还有 Reflection。
- ReAct 就是 LLM 调用工具,得到结果,LLM 分析并进行下一轮工具调用。
- Plan-and-Execute 是先规划后执行。
- Reflection:事后检讨。
我基本没见过成熟产品把 Reflection 当成核心 Agent 范式来用。工程上更常见的基础骨架还是 ReAct。
ReAct
ReAct 是设计范式,不是模型内置运行时;既然是范式,就当然需要代码或框架实现循环。
LangChain 这类框架的作用,本来就是封装 ReAct loop:调用 LLM、解析 action、执行 tool、写回 observation、判断停止条件。
重点不是“模型会不会自己循环”,而是 LLM 每轮只负责决策,循环由宿主程序驱动。
Plan-and-Execute
简单来说就是先规划后分步执行。但是这实在是太浅薄了。
动态 Replan:比如执行过程中是否需要调整计划?多久检查一次?
以及既然有了规划,有了任务拆分,那么任务之间是否有先后关系?有哪些是可以并行的?
你能很明显的感觉到这种模式是依附于 orchestration - worker 模式的,比如调度一个 planner 之类的。
Reflection
也许可以理解为独立的检查 agent。说独立是因为一般一个 agent 对自己的输出是有自信的。
重点是要限制轮次。
记忆机制
概念
- 短期记忆:context
- 长期记忆:存储在外部
长期记忆的要务就是:存什么、怎么存、什么时候用、用哪些。
存什么
再借用认知科学的一些词(情节、语义、程序)的词汇,长期记忆可以分出这些。
第一种是「情节记忆」(Episodic Memory),存的是具体的事件经历。比如「上周二用户让我写了一个 Python 爬虫,中间遇到了反爬问题,最后用 Selenium 解决了」,这是一段完整的任务经历,包含了时间、场景、过程和结果。情节记忆的价值在于,当 Agent 遇到类似的新任务时,可以检索出历史上的相似经历,参考上次是怎么解决的,避免重复踩坑。
第二种是「语义记忆」(Semantic Memory),存的是从多次经历中提炼出来的通用知识和规律。比如经历了好几次反爬问题之后,Agent 沉淀出一条规律:「当目标网站有 JavaScript 动态渲染时,requests 库抓不到内容,应该优先考虑 Selenium 或 Playwright」。这不再是某一次具体的事件记录,而是跨多次经验总结出来的抽象知识。语义记忆的信息密度更高,检索时也更容易命中,因为它直接存储的就是结论而不是过程。
第三种是「程序记忆」(Procedural Memory),存的是怎么做某件事的操作流程。比如「部署一个 Flask 应用的标准步骤:创建虚拟环境 -> 安装依赖 -> 配置 gunicorn -> 设置 nginx 反向代理 -> 启动服务」,这是一套可以直接复用的操作 SOP。程序记忆在处理重复性任务时特别有用,Agent 不需要每次都从头推理,直接调出对应的 SOP 执行就行,既快又稳。
三种子类型各有侧重,实际项目中通常会混合使用。情节记忆提供具体的参考案例,语义记忆提供抽象的知识规律,程序记忆提供可复用的操作流程,三者配合起来才能让 Agent 的长期记忆真正好用。
怎么存
根据信息的类型选合适的存储介质,混合存储是主流做法:结构化的偏好字段用关系数据库精确查,非结构化的知识和历史用向量数据库语义检索,两者配合使用。
什么时候用、用哪些
开始时可以做一次主动检索,把和当前任务稳定相关的规则、背景或经验加载进上下文;任务执行过程中,遇到需要专业知识、历史结论或外部事实的步骤,再让 Agent 按需检索。具体加载什么要看产品形态:个人助手可能需要用户偏好,安全审计系统更需要经过审查的经验和证据规则。
记忆整理
去重、消解冲突
第一个环节是去重。把语义相近的多条记忆合并成一条更完整的版本。比如你存了「用户喜欢简洁的代码」「用户说过代码要精简」「用户要求不要冗余代码」三条,其实表达的是同一个意思,合并成一条「用户偏好简洁精练的代码风格,反对冗余」就够了。
第二个环节是冲突消解。当两条记忆互相矛盾时(比如「用户偏好 Python」和后来说的「最近转用 Go 了」),保留时间更新的那条,标记旧的为过期。这里时间戳就非常关键了,没有时间戳就无法判断哪条是最新的。
Claude Code 的做法
Claude Code 的记忆机制很值得参考,因为它没有把长期记忆默认做成复杂数据库,而是更接近「本地文件 + 小索引 + 按需读取」。这和 skill 的形态很像:都是外部 Markdown 材料,都是在需要时进入上下文。但两者的语义不同:skill 更像预装能力,回答「这类事一般怎么做」;memory 更像后天经验,回答「这个用户、这个项目、这段历史有什么特殊之处」。
核心结构
它的核心结构大致可以这样分:
CLAUDE.md:人工维护的项目规则和工作约定,比如测试命令、代码风格、架构偏好。这更像稳定的项目说明,而不是自动学习出来的记忆。MEMORY.md:自动记忆的索引文件。官方文档明确说它是 memory directory 的入口和 index;启动时只加载前 200 行或前 25KB,而不是把所有历史经验都塞进 context。- topic files:具体主题的详细记忆,比如调试经验、用户偏好、项目决策背景。只有任务需要时再读取。
MEMORY.md 的角色不是大笔记本,而是路由表。它应该短,里面放的是「有哪些主题、每个主题大概是什么、细节去哪个文件看」。启动时 Agent 先看到这个索引,再决定是否读取某个 topic file。也就是说,长期记忆不是无限追加到上下文里,而是先常驻一个小目录,需要细节时再打开具体文件。
Transcripts 的位置
transcripts 要单独看。它们很重要,但本质上不是记忆,而是历史会话日志,是记忆的来源材料。Claude Code 会把 session 保存成本地 JSONL,这个形式很朴素但合理:会话本来就是 append-only 的事件流,一行一条消息、工具调用或元数据,方便追加、恢复、grep 和局部处理。公开分析里提到的 Auto Dream 会从 transcripts 里寻找值得沉淀的信号,但这一步发生之后,真正进入长期记忆的应该是被筛选、压缩、归类后的内容,而不是 transcript 本身。
所以更准确的边界是:transcripts 是 history/log/source material,MEMORY.md 和 topic files 才是整理后的 memory。把 transcript 直接当记忆,会把原始过程和可复用经验混在一起,最后反而让上下文变脏。
写入方式
从写入方式看,Claude Code 的官方 auto memory 更像 live-write。当前会话里的 Claude 在工作中判断某条信息未来还有用,就可以直接写入 memory directory;官方文档也说,当界面显示 “Writing memory” 或 “Recalled memory” 时,Claude 正在读写 ~/.claude/projects/<project>/memory/。用户明确说「remember that ...」时,Claude 也会把它保存到 auto memory。也就是说,在 Claude Code 的事实机制里,主 Agent 不只是产出候选,它可以直接写长期记忆。
这个设计的前提是用户可审计、可回滚、可关闭。auto memory 是本地普通 Markdown 文件,可以通过 /memory 打开、编辑、删除;也可以关闭 auto memory。它不是模型权重的学习,而是 agent 在本地文件系统里给自己写笔记。这个形态很朴素,也很强,但确实有风险:主 Agent 可能把临时事实写成长期事实,把噪声写进索引,或者把后来已经过期的判断留在 memory 里。
我们可以怎么做
所以如果我们自己做这个安全审计系统,我会更偏向 experience capture,而不是通用 user memory。这里不一定需要 user-preferences.md 这类个人助手式记忆;更重要的是从真实 run 里沉淀「经验」:什么证据要求有效,什么误报模式反复出现,什么 verifier discipline 能抓住补丁遗漏,什么 prompt 或 skill 缺口导致了失败。
更稳的路径是:
- 主 Agent:执行当前审计任务,不直接消费未经审查的跨 run memory;最多在 artifacts 里留下结构化 observation 或失败原因。
- Experience extractor:离线读取 transcripts、stage artifacts、最终 verdict、patch 结果和测试/评估记录,产出 experience candidates。
- 记忆整理 Agent:不参与当前任务决策,专门审查、聚类、合并这些 candidates,把可靠经验提升到更稳定的位置。
- promoted experience:真正会影响未来 agent 行为的材料,优先进入 prompts、skills、docs、tests、probes 或 curated datasets,而不是一开始就做 runtime memory 检索。
这样可以把「当前安全判断」和「长期学习」分开。当前 run 的 finding 必须来自当前仓库证据、stage artifacts 和明确输入,不能来自未经审查的隐藏记忆;长期学习则可以从 transcripts 和 artifacts 中提炼经验。公开分析里提到的 Auto Dream,就更像后台整理角色:它不负责完成当前用户任务,而是在空闲或特定时机审视已有记忆和历史日志,把重复的合并,把太长的压短,把过时的标记掉,把能抽象的情节记忆提升成语义记忆或程序记忆。这里要注意事实层级:MEMORY.md 索引、topic files 的加载方式、会话中读写 auto memory 是官方文档确认的;Auto Dream 的触发、整理流程、如何使用 transcripts 等细节更多来自公开技术分析,不能当成官方完整承诺。
从实现上说,Experience extractor 和记忆整理 Agent 可以是同一个模型在不同 prompt、不同工具权限下扮演不同角色,也可以是两个不同的 agent。关键不是「是不是同一个 LLM」,而是职责要分开。如果让主 Agent 在任务中同时整理历史经验,容易污染当前任务上下文,也会让工具循环变慢。更好的产品形态是:主 Agent 只留下可追溯运行材料;后台 extractor 读取 transcripts 和 artifacts,产出候选经验;再由整理/人工 review 把它提升为正式项目材料。
存储和规则边界
这也说明记忆未必需要向量数据库。向量检索可以作为补充,尤其适合跨项目、跨团队、海量非结构化历史和相似案例检索;但如果一个个人项目的记忆多到必须依赖向量库才能捞出来,往往说明整理机制已经不够好了。好的长期记忆应该是少量、高密度、可审计、可更新、可废弃的。
关系数据库也不一定要叫记忆。比如订单、issue、CI 记录、用户配置这些外部事实,更像是 Agent 通过工具查询的事实源。它们可以被放进上下文参与推理,但不等于 Agent 自己沉淀出的经验。更像记忆的是用户偏好、历史纠正、项目约定、调试结论、决策背景这些东西。
进一步说,因为 Claude Code 的 system prompt 不是用户可以长期直接改写的东西,用户真正可控的持久行为层其实分散在几个地方:稳定规则放进 CLAUDE.md 或 rules;必须强制执行的规则放进 settings、permissions 或 hooks;可复用的个人工作法做成 skills;项目和用户的后天经验放进 memory。可是对我们自己的 agent 产品来说,关键规则应该进入产品自己的 system/developer prompt、工具权限、skills、tests 和 workflow 编排。memory 不是唯一答案;在安全审计系统里,未经审查的 runtime memory 甚至可能是坏答案。更合适的起点是 experience:从 transcripts 和 artifacts 里提炼经验,但先不让它直接支配下一次安全判断。
启发
所以 Claude Code 给我的启发是:存储形式不是长期记忆的关键,后台整理才是。记忆写入很容易,难的是后续的 merge、promote、deprecate、delete 和 verify。
- merge:把重复或相近的记忆合并。
- promote:从具体情节中提炼出更高密度的语义记忆或程序记忆。
- deprecate:当新记忆和旧记忆冲突时,标记旧记忆过期。
- delete:删除错误、过细、已经失效的记忆。
- verify:重要记忆在使用前回源确认,避免错误记忆污染判断。
从这个角度看,记忆系统真正像人的地方,不是「什么都记住」,而是会遗忘、会整理、会把经验压缩成更可靠的判断。
完整流程
第一阶段:任务开始前,先「读」记忆 第二阶段:任务执行中,持续「用」记忆 第三阶段:任务结束后,主动「写」记忆
压缩机制
压缩/裁剪的目的都是因为 context window 长度有限。这个问题在实际项目中非常常见,解决方案有好几种思路,从简单到复杂都有。
最简单的是「滑动窗口」,只保留最近 N 轮对话,更早的历史直接丢弃。好处是实现简单,代价是早期的重要信息可能被丢掉。比如用户在第一轮就说了「所有代码用 TypeScript」,到了第十轮这条信息被滑出窗口了,Agent 又开始写 JavaScript,用户就会很崩溃。
进阶一点的做法是「摘要压缩」。当历史长度接近上限时,用 LLM 把早期的对话历史压缩成一段摘要,替换掉原始的冗长历史。比如把前面十轮的详细对话压缩成「用户要求用 TypeScript 编写一个 REST API,已完成数据库设计和路由定义,当前正在实现用户认证模块」,一段话就把关键信息保留了,token 占用从几千降到几百。代价是压缩过程本身会丢失细节,而且需要额外的 LLM 调用来做摘要。
还有一种做法是把长结果从 context 里卸载到外部存储。比如工具调用返回了一大段日志、网页正文、代码搜索结果或中间分析产物,当前上下文里不一定需要完整保留,就可以把原文写入数据库、文件或对象存储,只在 context 中留下一个简短摘要和引用 ID。
后续如果 Agent 需要查看细节,再通过专门的读取工具按引用 ID 取回原文。这样 context window 里保留的是“索引 + 摘要”,而不是完整材料,既能减少 token 占用,又不会真正丢失信息。
这更像是给 Agent 配一个外部工作区:桌面上只放便签和索引,完整资料放在抽屉里,需要时再按编号取出来。
multi-agent
说到 multi-agent,一般是认为某一个复杂任务无法由一个 agent 完成。
一般不只是上下文长度的问题,而是涉及到分工、人格的问题:让一个 Agent 既搜信息、又写代码、又做测试、又写文档,它在每一件事上都得兼顾,精力是分散的,就像一个人同时担任产品经理、程序员、测试工程师和文档工程师,每个角色都做得不够专注,互相干扰。
就目前来说,multi-agent 有很多方式,比如:
- 有最固定的流水线,流水线上的每一步都是一个 agent,但是就工作流来说没有非确定性的部分
- 假设 1 不足够,有些流程上的判断无法仅有一些确定性逻辑完成,那么就可以引入一些 LLM 判断节点
- 再进一步的,如果任务无法预先知道细节,那么就可以引入 orchestration - worker 模式,也即最上层的工作流编排也由 agent 掌握。不过这仍然是中心化方能。
- 传说中的去中心化方案,也就是多个 Agent 通过共享的消息队列或状态空间自行协商、直接通信。这很难,更多停留在学术研究里探索生产环境里,几乎所有正经项目都选 Orchestrator 模式。
就 anthropic 的 blog 来说:很多时候你总要先尝试 single-agent,然后观察是否能把任务完成得比较好,如果不行才需要使用更复杂的架构。
「手搓」Agent,而不是直接用成熟框架?
我只能说目前我使用的是 langchain。
要分 2 层回答问题:
框架适合的时机:POC 阶段快速验证 idea,目标是跑通而不是优化;团队刚接触 Agent 开发,用框架能少踩一些基础性的坑;周边工具(文档解析、向量检索)依赖框架的生态,核心逻辑本身复杂度不高。这些场景里,框架带来的速度优势是真实的,值得用。
手搓的时机:准备上生产,稳定性成为核心关切;流量开始上来,性能和成本变得敏感;业务逻辑高度定制,和框架的通用设计偏差很大,改起来反而麻烦;团队需要高可观测性,链路要能随时监控和回溯。
可是就我的经验来看,我只能说我是边用然后边自己打补丁,比如有框架暂时没有解决的问题我会自己补上。
然后就是所谓的抽象过多——其实我认为在现在有 ai 可以追查库代码的情况下很难说是非常大的问题。
我对手搓轮子还是有一定警惕,因为成本太高,所以要和你的需求结合。
CoT(Chain of Thought)
CoT(Chain of Thought)就是让 LLM 在回答前先生成中间推理步骤,而不是直接生成最终答案。
它本质上是 LLM 层面的生成/推理方法,不是 Agent 技术。
因为 LLM 是自回归生成模型,前面生成的推理步骤会进入上下文,影响后续 token 的生成。所以 CoT 相当于让模型“打草稿”,用显式的中间步骤帮助后续生成。
所以如果 prompt 诱导模型显式写出推导过程(比如“请一步步思考”,或者 给模型几个带推理步骤的示例,让它模仿这种格式),前面生成的推导步骤就会进入上下文,约束后面的生成结果。模型不再是直接从问题跳到答案,而是先生成中间步骤,再基于这些中间步骤继续生成。
这带来的好处是:复杂问题被展开成了更细的 token 序列,模型有更多中间状态可以依赖,后续生成也更容易沿着一个相对连贯的路径推进。直观上看,CoT 相当于把“隐式心算”变成“写草稿”。
Reasoning model 只是把这种“生成中间推理轨迹”的能力进一步强化了,所以在需要多步展开的问题上更稳定。