Claude Code Agent Harness工程拆解:Skills、Session与记忆系统的实现骨架

这篇不再讲“agent harness 是什么”,而是直接讲:一个 agent 系统里,skillssessionmemory 在工程上分别落在哪里,又是怎么彼此串起来的。

先说明一下边界。

本文基于一份公开暴露出来、被认为和 Claude Code 相关的仓库做分析。它并不是我能确认的“官方完整源码”,而更像一份暴露了大量表面结构的研究样本。因此下面的结论分两类:

  • 可以直接从代码确认的事实
  • 基于文件名、模块边界和命令设计做出的工程推断

我会尽量把两者分开说。


零、先给结论:如果只看一张图,Claude 的 harness 更像什么

如果你希望看完这篇以后,对 RAG / skill / memory / session / tool 的关系一眼就有图感,那我先给结论:

Claude Code 这类 harness,本质上不是“聊天 + 工具”,而是一个围绕 任务执行 组织起来的运行时系统。
它的核心不是某条 prompt,而是五条咬合在一起的链路:路由、检索、执行、记忆、压缩

1. 总体架构图

这张图里最关键的不是方块多,而是你能看到:

  • skill 不是跟 tool 并列的“另一个功能”
  • memory 不是一个孤立黑盒
  • RAG 也不是一个单独向量库

它们实际上都在服务同一件事:让 query engine 在每一轮都拿到“这次该怎么做、该用什么、该记住什么”。

2. 一次任务的执行流程图

如果你把这条流程看懂了,后面再看 skill、memory、RAG 就不会散。


一、先看系统入口:它到底把什么当成一等公民

一个仓库的入口,往往最能暴露作者眼里的系统边界。

Python 侧的 src/main.py 暴露了这些命令:

  • summary
  • manifest
  • commands
  • tools
  • route
  • bootstrap
  • turn-loop
  • flush-transcript
  • load-session
  • remote-mode
  • ssh-mode
  • teleport-mode
  • direct-connect-mode
  • deep-link-mode

这说明在这个系统里,至少有几块被视作一等公民:

  1. 命令表面
  2. 工具表面
  3. 路由
  4. bootstrap
  5. 多轮 turn loop
  6. transcript / session
  7. remote / direct / deep link 等运行模式

也就是说,它并没有把 agent 理解成“拿到 prompt 然后直接答”,而是理解成一个会启动、会路由、会持久化、会切换模式的 runtime

这就是工程味。


二、Skills:在这个仓库里,它更像“工作流层”

1. 代码里能直接确认的事实

Python 侧有一个 skills 子包,但它本身只是 placeholder。也就是说,当前 Python 实现并没有真正把 skill runtime 完整搬过来。

不过,src/reference_data/subsystems/skills.json 保存了暴露出来的技能子系统快照,其中能看到这些样例文件:

1
2
3
4
5
6
7
skills/bundled/batch.ts
skills/bundled/remember.ts
skills/bundled/stuck.ts
skills/bundled/verify.ts
skills/bundled/skillify.ts
skills/loadSkillsDir.ts
skills/mcpSkillBuilders.ts

同时,命令快照里还存在 commands/skills/index.tscommands/skills/skills.tsx 这样的条目。

另外,src/command_graph.py 还专门把 command 分成了三类:

  • builtins
  • plugin_like
  • skill_like

这说明在原始设计里,skill 并不是完全附属物,而是一个足够独立、足够成体系的命令来源。

2. 我对这套 skill 机制的工程推断

只看这些表面,已经能推断出几点很关键的设计思想。

skill 是“按任务类型组织”的

rememberverifystuck 这种名字看,skill 不是按模型能力分的,而是按任务阶段与执行语义分的。

这跟很多人做 agent 的方式很不一样。

很多人会这样组织:

  • 一个技能负责画图
  • 一个技能负责写代码
  • 一个技能负责搜索

而这里暴露出来的命名方式更像:

  • 当 agent 需要记住某些东西时怎么办
  • 当 agent 做完事情后怎么验证
  • 当 agent 卡住时如何脱困

这是一种更高层的抽象。

skill 很可能支持“目录加载 + 内置捆绑”

bundledSkills.tsloadSkillsDir.ts 的组合很像一套双层机制:

  • 系统一开始自带一批 bundled skills
  • 同时允许从某个 skills 目录动态加载额外技能

如果这个推断成立,那它的价值非常大,因为这意味着 skill 系统不是写死在二进制里的,而是支持扩展。

skill 可能和工具生态深度耦合

mcpSkillBuilders.ts 这个名字特别有意思。

它暗示 skill 不只是 prompt 文本,还可能和 MCP tool schema、tool registry 甚至自动生成逻辑有关。

换句话说,skill 很可能是“任务方法 + 工具约束 + 执行偏好”的组合体。

这也是很多 agent 工程后面会自然走到的一步:单纯写提示词不够了,必须把工具调用规则也一起编进任务模板里。


三、Session:真正的会话系统,不是简单聊天记录

这部分是这份仓库里最能直接学的地方。

1. Python 侧:最小闭环版本

Python 的会话设计很朴素,但很适合拿来当教学样本。

TranscriptStore

src/transcript.py 干的事情非常克制:

  • append(entry)
  • compact(keep_last)
  • replay()
  • flush()

它没有做花哨的 event sourcing,也没有做复杂索引,就是一个轻量的会话缓存层。

但它已经告诉你一个重要的工程事实:

transcript 和 session 不一定是一回事。

transcript 更像当前运行期暂存的消息轨迹,而 session 则是最终需要落盘和恢复的状态对象。

SessionStore

src/session_store.py 则把会话持久化成 JSON:

1
2
3
4
5
6
{
"session_id": "...",
"messages": [...],
"input_tokens": 123,
"output_tokens": 456
}

这个结构有两个值得学的点。

第一,它把消息历史usage 统计放在一起。

第二,它默认把 session 存在 .port_sessions/ 下。

这说明在作者眼里,会话不是临时副产品,而是可恢复、可分析、可追踪的工件。

2. QueryEngine:会话状态如何流动

src/query_engine.py 把这套 session 骨架真正串了起来。

核心逻辑非常清楚:

  1. 每次 submit_message(prompt, matched_commands, matched_tools, denied_tools)
  2. 生成一次当前 turn 的摘要输出
  3. 更新 token usage
  4. 把 prompt 记入 mutable_messages
  5. 把 prompt 追加到 TranscriptStore
  6. 必要时执行 compact
  7. 最后支持 persist_session()

这一层的作用,不是生成“聪明答案”,而是管理多轮状态机。

对 harness 来说,这是关键分工:

  • 模型负责生成内容
  • query engine 负责管理回合

如果这层没分出来,系统后面会非常难维护。


四、Rust Session:为什么我说它更接近真实 harness

Rust 里的 Session 模型,明显已经进入“可运行 runtime”的阶段。

它不是简单保存一串字符串,而是:

1. 显式区分消息角色

MessageRole 包括:

  • System
  • User
  • Assistant
  • Tool

这是 agent 系统和普通聊天系统最大的分水岭之一。

一旦有工具调用,消息流就不再只是:

1
用户 -> 模型 -> 用户 -> 模型

而更像:

1
用户 -> assistant 推理 -> tool_use -> tool_result -> assistant 继续推理

如果会话模型不能表达这条链,后面所有的 replay、resume、debug、audit 都会变得很弱。

2. ContentBlock 不是纯文本

会话里的 block 分三类:

  • Text
  • ToolUse
  • ToolResult

这意味着 session 记录的是结构化执行轨迹,而不是单纯的人类可读 transcript。

为什么这点重要?

因为当你要恢复 session 时,真正有价值的不只是用户说过什么,而是:

  • 模型在第几步决定调用了什么工具
  • 传了什么参数
  • 工具有没有报错
  • 后续回答是否依赖这次工具结果

这些都必须写进 session schema。

3. slash command 直接把 session 当操作对象

Rust 的 /compact/resume/session/export 命令都不是围绕“当前聊天框”设计的,而是围绕 Session 这个数据结构设计的。

比如:

  • /compact:压缩当前 session
  • /resume:加载某个 session 文件
  • /session list:列出当前管理的本地 session
  • /export:导出当前 session transcript

这说明在作者眼里,会话不是 UI 附属品,而是 runtime 的核心对象。


五、Memory:这里最值得学的是“文件化记忆”

很多人做 agent,第一反应就是往向量数据库上想。

但这份仓库对我最大的启发反而是:

在工程实践里,最先落地、也最稳的 memory 往往不是 embedding,而是 instruction files。

1. Prompt 构建器会主动发现 instruction files

Rust 的 prompt.rs 会从当前工作目录向上扫描,并尝试读取:

  • CLAUDE.md
  • CLAUDE.local.md
  • .claude/CLAUDE.md
  • .claude/instructions.md

然后把这些文件内容放进 system prompt 里。

这套机制有几个很强的工程特征。

可组合

目录层级越深,规则可以越具体。

比如:

  • 仓库根目录有全局开发规范
  • 某个子目录有该模块的局部规则
  • 本地 .claude 目录再补充个人环境约束

这就像配置继承,而不是一次性写死。

可审计

memory 变成了文件,就能:

  • 看 diff
  • 做 review
  • 做版本管理
  • 知道到底是什么规则在影响模型

可显式暴露

/memory 命令会把当前发现的 instruction files 列出来,包括路径、行数和预览内容。

也就是说,memory 不是神秘黑盒,而是可检查的上下文输入

2. memdir 快照暴露了另一层 memory 能力

除了 instruction files,这份仓库还暴露出一个 memdir 子系统,样例文件包括:

1
2
3
4
5
6
7
memdir/findRelevantMemories.ts
memdir/memdir.ts
memdir/memoryAge.ts
memdir/memoryScan.ts
memdir/memoryTypes.ts
memdir/teamMemPaths.ts
memdir/teamMemPrompts.ts

这非常像一套“目录化记忆检索系统”。

我对它的推断是:

  • memoryScan:扫描可用记忆
  • findRelevantMemories:按当前任务检索相关记忆
  • memoryAge:考虑记忆新鲜度
  • memoryTypes:给记忆分类型
  • teamMem*:支持团队级共享记忆

如果这个推断是对的,那么它和前面的 instruction files 很可能形成了一个双层 memory 体系:

第一层:静态长期规则

就是 CLAUDE.md 这类明确文件,稳定、可读、可版本化。

第二层:动态相关记忆

就是根据当前任务,从 memdir 里找最相关的内容补进来。

这个组合很合理:

  • 静态规则负责稳定约束
  • 动态记忆负责任务适配

六、Compaction:session 和 memory 之间的胶水层

compact.rs 是这份仓库里一个很有“工程启发”的模块。

它做的事情不是简单删除旧消息,而是:

  1. 先估算当前 session token 数
  2. 如果超过阈值,就保留最近若干条消息
  3. 把更早的消息做 summary
  4. 再把这段 summary 作为新的 System message 放回去

从结构上看,这一步几乎就是把“短期对话历史”重新编码成“中期可恢复记忆”。

所以我会把 compact 看成连接 session 和 memory 的一层胶水:

  • session 太长了,不能全留
  • 但也不能直接删
  • 所以用 compact 把旧 session 转成 summary memory

这非常像一个成熟 agent 系统必须拥有的能力。


七、这套骨架怎么串起来

如果把这个仓库里暴露出来的设计压成一张流程图,大概会是这样:

1
2
3
4
5
6
7
8
9
10
用户输入
-> route / command graph
-> 命中 builtin / plugin / skill-like command
-> 进入 runtime / query engine
-> 读取长期 memory(CLAUDE.md / .claude/instructions)
-> 调用工具
-> 结构化写入 session
-> transcript 暂存
-> 必要时 compact
-> 持久化 session

从这里你会看到一个很关键的点:

skill、session、memory 不是三块孤立能力,而是三条互相咬合的基础设施链路。

  • skills 负责“怎么走”
  • session 负责“走到哪了”
  • memory 负责“走的时候要遵守什么长期规则”
  • compact 负责“路太长时怎么压缩”

这就是 harness 的真正骨架。


八、看完源码后,我对 RAG 的理解是什么

这是你这次最想要补清楚的地方。

很多人一说 RAG,脑子里立刻只剩下:

  • chunk
  • embedding
  • vector db
  • top-k recall

但看完 Claude 这类 harness 的源码以后,我更愿意把 RAG 理解成:

在任务执行前,把“当前这轮真正需要的上下文”从各种候选知识源里检出来,再装配进模型上下文。

这个定义比“向量检索”更大,也更接近工程现实。

1. 在 harness 里,RAG 其实至少有四层

来源 作用 典型实现感觉
静态规则检索 CLAUDE.md / instructions 提供长期约束 文件扫描 + 注入 system prompt
skill 检索 loadSkillsDir / skillSearch 找到该走哪套 workflow frontmatter + 目录索引 + 搜索
memory 检索 memdir/findRelevantMemories 找到与当前任务真正相关的经验或提醒 扫描 memory header,再让模型做选择
session 检索 SessionMemory / compact summary 找回当前会话已经形成的阶段性结论 会话摘要 markdown / compact result

也就是说,RAG 在这里不是“外接知识库模块”,而是整个 harness 的上下文装配机制。

2. 这份源码里最有启发的一点:它并不迷信向量库

findRelevantMemories.ts 的思路非常值得你注意。

它不是一上来就 embedding 全量召回,而是:

  1. 先扫描 memory 文件头部和描述
  2. 把候选 memory manifest 组织出来
  3. 再让一个侧路模型去判断“哪些 memory 明确有用”

这说明一个很重要的工程判断:

很多内部知识检索问题,先做结构化候选缩小,再做模型选择,已经足够好。

这对你后面在公司里做提效非常关键,因为它意味着:

  • 你不一定要先上向量数据库
  • 你可以先把知识组织成可枚举、可筛选、可解释的文件或卡片
  • 然后再用模型做轻量 rerank

这条路线往往更容易落地。

3. 所以我现在对 RAG 的实际理解是

对 agent 系统来说,RAG 不是单独的一层,而是下面这套动作的统称:

  • 识别这轮任务需要什么上下文
  • 从多个来源里检索候选信息
  • 过滤噪音和重复信息
  • 把最相关的内容注入当前回合
  • 在回合结束后决定是否沉淀成新的 memory

这才是你后面做公司级 agent 时真正该做的 RAG。


九、看完源码后,我对 skill 的理解是什么

看完 loadSkillsDir.ts 之后,我觉得 skill 最不该被理解成“一段提示词模板”。

更准确一点说:

skill 是 workflow + policy + tool contract 的组合。

为什么这么说?

因为从这份源码暴露出来的字段看,一个 skill 不只是正文说明,它还包含:

  • description
  • when_to_use
  • allowed-tools
  • arguments
  • hooks
  • model
  • effort
  • paths
  • executionContext

这意味着 skill 的本质不是“多写几句提示词”,而是把下面这些东西一起打包:

  1. 什么时候触发
  2. 允许用什么工具
  3. 约束在哪些路径生效
  4. 用什么执行强度
  5. 卡住时有没有 hooks 或补救动作

1. 这对你做公司 skill 的直接启发

如果你后面在公司里做 skill,别把它写成知识点百科。
更好的写法应该是:

  • 触发场景是什么
  • 这个场景要先查什么
  • 哪些工具必须优先用
  • 哪些输出格式是标准产物
  • 成功标准是什么
  • 遇到失败时要怎么降级

比如一个“线上问题排查 skill”,就不该只是:

请分析日志并定位问题。

而应该更像:

  1. 先拿 traceId / issue key
  2. 查询日志 / 调用链 / 配置
  3. 识别异常归属模块
  4. 输出结论、证据、影响面、下一步建议
  5. 如果证据不足,再补查哪类信息

这就是从“提示词”升级到“workflow skill”。

2. skill 和 agent 的边界

这是很多团队容易混淆的地方。

  • skill 更像“做事的方法包”
  • agent 更像“执行这个方法的人”

一个 agent 可以调用多个 skill;
一个 skill 也可以被多个 agent 复用。

所以如果你在公司里要做体系,建议不要把 skill 和 agent 绑死。

更合理的分层是:

  • skill 负责沉淀流程
  • agent 负责调度执行
  • tool 负责具体动作

十、看完源码后,我对 memory 的理解是什么

这块我现在的理解会比以前更清晰:

memory 不是“记住你说过的话”这么简单,而是让系统在多轮和跨轮执行中保持稳定工作方式的上下文层。

而且从 Claude 这类 harness 的实现来看,memory 不是一层,而是分层的。

1. 第一层:静态规则型 memory

也就是 CLAUDE.md.claude/instructions.md 这种文件。

它的作用不是记录临时事实,而是保存长期有效的约束,比如:

  • 项目规范
  • 工具偏好
  • 提交方式
  • 测试命令
  • 安全边界

这一层最大的优点是:

  • 可见
  • 可 review
  • 可 diff
  • 可协作

对企业环境来说,这一层比“神秘长期记忆”更重要。

2. 第二层:会话摘要型 memory

SessionMemory/sessionMemory.ts 很有意思。

它不是让主线程每次都自己总结,而是:

  • 在后台周期性提取当前会话中的关键结论
  • 维护成 markdown 文件
  • 作为后续上下文的一部分继续供系统使用

这意味着 session memory 的本质是:

把高噪声的多轮对话,提纯成可复用的阶段性状态。

这一步对企业 agent 特别重要,因为公司里的任务往往不是一轮完成,而是:

  • 需求澄清
  • 查代码
  • 查日志
  • 修改
  • 测试
  • 复盘

如果没有这层摘要型 memory,系统很容易在长链路里越跑越散。

3. 第三层:动态相关记忆

也就是 memdir/findRelevantMemories.ts 这一层。

它不是把所有 memory 全塞进去,而是只挑当前 query 真有帮助的那几个。

这说明动态 memory 的重点不是“存更多”,而是“选更准”。

4. 所以 memory 最终应该怎么设计

如果让我压成一句话:

  • 静态规则 memory 负责稳定约束
  • 会话摘要 memory 负责阶段状态
  • 动态相关 memory 负责当前命中

这三层加在一起,才是一个 agent 真正可用的 memory 系统。


十一、对你后面做公司提效、做 skill 或 agent,有哪些可借鉴的地方

这一段我尽量只讲可落地的。

1. 不要一开始就做“大而全平台”,先做最小闭环

看完这种源码后,一个很容易犯的错是:
想一步到位做出一个“公司版 Claude Code”。

但更现实的做法是先跑通一个高价值闭环,比如:

  • Jira / 需求单 -> 技术方案草稿
  • Trace / 日志 -> 问题分析报告
  • 代码变更 -> 风险点和测试建议
  • 发布单 -> 发布前核对清单

先把一个链路打深,比做十个半成品 agent 有价值。

2. skill 优先沉淀高频、稳定、可复用的流程

适合先 skill 化的事情,通常有三个特征:

  • 高频发生
  • 步骤相对稳定
  • 输出格式容易标准化

比如你公司里很适合先做的,往往是:

  • 问题排查 skill
  • 技术方案生成 skill
  • CR / review skill
  • 集成测试设计 skill
  • 发布校验 skill

3. 先做文件化 memory,不要急着上复杂“企业记忆”

对公司提效来说,第一批最值钱的 memory 很可能不是知识图谱,而是这些:

  • 项目规范文件
  • 常见故障处理手册
  • 领域术语定义
  • 默认链路和边界说明
  • 组件/模块 owner 约定

这些内容先文件化、结构化、可搜索,收益就已经很高。

4. 先做“检索装配”而不是“万能问答”

很多内部 agent 做不起来,不是因为模型不够强,而是因为输入上下文太乱。

你真正该做的,不是让 agent 什么都懂,而是让它每次执行前能拿到:

  • 当前任务所属域
  • 相关技能
  • 相关 memory
  • 相关文档或日志
  • 允许使用的工具

这就是 harness 思维。

5. 把 session 当资产,而不是临时聊天记录

如果你后面要在公司里做 agent,session 一定要可:

  • 回放
  • 审计
  • 恢复
  • 摘要
  • 导出

因为这会直接决定它能不能进入真实业务环境。


十二、如果你现在就要做一个公司内提效 agent,我建议的最小架构

你完全可以先做成下面这个版本:

这个架构里最应该先补的不是“更强模型”,而是:

  1. 分类器
  2. skill 选择
  3. 静态规则装配
  4. 动态记忆检索
  5. session summary

只要这五件事顺起来,你的 agent 就会从“偶尔有用”变成“逐渐稳定”。


十三、如果你自己做 agent,我建议直接借鉴什么

1. 先把 session schema 做对

至少要有:

  • role
  • content blocks
  • tool_use
  • tool_result
  • usage

如果这层做错,后面的 resume、audit、export 都会很难看。

2. 把 skill 看成 workflow template

不要把 skill 做成一堆漂亮话,而要做成:

  • 触发条件
  • 任务步骤
  • 工具偏好
  • 完成标准
  • 失败或卡住时的回退路径

3. 先用文件化 memory,再考虑复杂检索

CLAUDE.md 这种模式特别适合落地,因为它:

  • 简单
  • 可解释
  • 便于团队协作
  • 易于与代码仓库一起管理

很多时候,这比你一上来就搞复杂长期记忆系统更靠谱。

4. 把 compact 当正式能力来设计

context window 再大,也会有上限。

真正长期运行的 agent,迟早要面对:

  • 如何保留最近上下文
  • 如何压缩历史
  • 如何让压缩后仍然可恢复工作状态

这不是锦上添花,而是 runtime 核心能力。


十四、我的一句判断

如果只从工程角度评价,这份仓库最值得学的地方,不是某个模型调用细节,而是它暴露出了一套很像样的基础设施分层:

  • 命令层
  • 工具层
  • 技能层
  • prompt / memory 装配层
  • session 持久化层
  • compaction 层
  • resume / export / inspect 层

这正是一个 agent harness 从“好玩 demo”走向“可持续运行系统”时必须长出来的骨架。

下一篇我会把这份仓库当成研究对象,不再只讲实现,而是尝试回答一个更有意思的问题:

仅凭这份“暴露出来的仓库表面”,我们到底能反推出 Claude Code 怎样的设计哲学?

延伸阅读