懂用户的 Agent System三层设计
参考资料
设计文 · 系统观点

构造一个更懂用户的 Agent System

把"懂用户"拆成三层:偏好(你是谁)、工作流(你怎么干)、对齐(这次你要什么)。这三层的时间尺度不同,但共享同一种工程取舍——读时灵活、写时延迟,让 KV cache 不被频繁打断。

时间尺度 · 跨会话 / 回合级 / 任务前 关键参考 · Claude Code · Hermes Agent · Anthropic Skills · Harness Design 状态 · 草稿 (temp/)
TL;DR
"懂用户"不是给 agent 加 RAG 或长窗口就够了,而要在 三个时间尺度上分别做对:跨会话的偏好、回合级的工作流、任务前的需求对齐。
每一层都需要"读时灵活、写时延迟"——session 内可以记,但要等下次 session 才生效,目的是不打断 LLM 的 prefix cache。这件事在 Claude Code 和 Hermes Agent 的设计里已经被两次确认。
▸ Overview · 视觉锚

三层"懂用户"架构

先用一张图把整套系统压成三行,后面三节分别拆解。结构上是从慢到快:偏好最稳,工作流次之,对齐发生在每一次任务开始的瞬间。

USER UNDERSTANDING · 3 LAYERS
由稳到变 · 由长到短 · 由静态到即时
LAYER 1
偏好
cross-session · static
你是谁、你怎么想?
用户的长期立场、风格、约束。一次会话里不能改,否则破坏 cache。
CLAUDE.md USER.md frozen snapshot auto memory
LAYER 2
工作流
turn-level · evolving
你怎么干这类活?
把重复出现的步骤沉淀成可复用 skill,每 N 回合让一个 evaluator 决定要不要写、要不要合并、要不要汰换。
Skill 自动迭代 LRU 汰换 每 N 回合触发 session 边界生效
LAYER 3
对齐
pre-task · interactive
这次你要的到底是什么?
任务一来就 fire-and-forget 是最大的浪费来源。轻量场景出选择题,复杂场景上 plan-eval loop。
clearify-task harness loop spec lock escalation
读这张图的姿势:由上到下是时间尺度从长到短,由静到变。三层的共同设计原则都是"读时灵活、写时延迟"——任何一层修改了用户视角的状态,都不应当立刻打断当前会话的 prefix cache,而是落盘后等下一个合适的边界(下次 session、下次任务)才注入 system prompt。
▸ Layer 1 · Preference

偏好层:你是谁,agent 怎么记得

偏好层管的是跨会话、低频更新的信息:编码风格、工作语言、组织约束、代码评审口味。这一层最容易被忽视,因为它"不变"——但正因为它不变,它才适合放进 system prompt 的最前面,吃满 KV cache 的 prefix 命中率。

业界这几年有两个有代表性的实现:Anthropic 的 Claude Code 和 Nous Research 的 Hermes Agent。两者的方案有 80% 的重合,但 20% 的差异恰好暴露了"偏好层应该怎么写"的工程口味。

Anthropic · Claude Code
CLAUDE.md · 分层继承
一个项目里同时存在组织级 / 项目级 / 用户级 / 本地级 四份偏好文件,按优先级合并。它假设"用户偏好"天然多层——公司规范、个人习惯、项目特殊约定需要分别归档,而不是糊在一个文件里。
4 层继承 auto memory /memory
Nous Research · Hermes Agent
USER.md + MEMORY.md · 极简加冻结
就两个文件,加起来不超过 1300 token。会话开始时整段塞进 system prompt 然后整段冻结——session 内的修改先落盘,必须等下次 session 才进入 prompt。冻结是为了保 KV cache prefix。
≤ 1300 token frozen snapshot memory_tool
▸ 把两者的设计逐项摆在一起
维度 Claude Code Hermes Agent
数据格式 CLAUDE.md · markdown,无强制结构 USER.md + MEMORY.md,分别记用户与 agent
分层结构 组织 / 项目 / 用户 / 本地,4 层继承 单层,所有内容都在一个文件里
字符上限 无强制上限(实际由 context window 约束) USER.md ≈ 500 token · MEMORY.md ≈ 800 token
写入路径 用户手写 + auto memory(agent 在每个回合末抽取) 仅通过 memory_tool 的 add / replace / remove
编辑入口 /memory 命令打开文件 · @path 跨文件 import 对话中靠工具调用 · 文件直接落盘
注入与冻结策略 会话开始注入;session 内修改的可见性未明确 会话开始整段注入并冻结,session 内改动落盘但下个 session 才生效
设计动机 分层归档:项目共享 + 个人偏好 + 团队规范同时存在 明确为保 prefix cache hit rate 而冻结
SHARED INSIGHT · 两者的共同选择
虽然 Claude Code 文档没有直接说"为了 cache hit rate 才不能 session 内变",但两者都选择把偏好放在 system prompt 的最前面,并把可变内容堆到 user/assistant 段尾部——本质上都默认偏好层应当是个稳定 prefix。
Hermes 把这件事说破了:"系统提示的注入只在 session 开始时捕获一次,整段会话不再变动。这是有意为之——为了保持 LLM 的 prefix cache 性能。"
偏好这一层的工程取舍其实只有一句话:把不变的东西放到 prefix,把会变的东西放到 suffix。CLAUDE.md / USER.md 是 prefix 的极致体现——它们假设用户的偏好在一次会话内不会反悔,于是用整个 KV cache 命中作为回报。
▸ 一个具体的 case · 偏好不是一次性能想清楚的

但偏好层有一个隐性问题:用户不会主动写 CLAUDE.md。"配置自己的偏好"对绝大多数用户来说是一项需要先想清楚再动手的活,可偏好本身又是在工作中浮现的——你不知道这个 LLM 会怎么犯错,怎么提前定义"我不喜欢的写法"?

一个典型例子是 Karpathy Guidelines。这套规则是 Karpathy 公开吐槽 LLM 编码踩坑后被社区整理出来的——每一条规则都对应一类没有明示就会反复出现的反模式。比如一堆 try / except 包着一段根本不会失败的代码,只是为了"看起来稳"。这种偏好是用户用了 N 次 agent 之后才发现"哦原来我不喜欢这样",而不是 day 0 就能写下来。

CASE · 偏好沉淀的实例
Karpathy Guidelines
原始来源

把"反复给 LLM 改同一类毛病"这件事变成可注入的偏好规则。这套规则不是 day 0 拍脑袋写的,是从大量 LLM 编码踩坑里逐渐沉淀出来的——它的每一条都对应一种"不说就会再犯"的反模式。

01 Think Before Coding
假设要明确说出来;多种解释要全部摆出来;不清楚就停下来问。不允许沉默地选择一种实现。
02 Simplicity First
不为不存在的可能错误加防御。不要 try/except 包一段不会出错的代码;不要为单次使用写抽象;不要加未要求的可配置性。
03 Surgical Changes
只动必须动的代码;不顺手"优化"周边格式与注释;不删别人的死代码,只清理自己改动产生的孤儿。每一行变更都要能回溯到用户请求。
04 Goal-Driven Execution
把"修复 bug"翻译成"先写一个能复现的测试,让它通过"。强 success criteria 让 agent 自己 loop,弱 criteria("让它 work")只能不停打扰用户。
这件事的工程结论:偏好层的写入路径不能只靠"用户手写 CLAUDE.md"。Claude Code 的 auto memory(在每个回合末从纠正中抽取偏好)、Hermes 的 memory_tool(agent 主动调用工具落盘)、Cursor 的 rules 文件——本质都是为了解决同一件事:让偏好可以被被动地、增量地积累。Karpathy Guidelines 这种"事后整理出来的反模式集合",正是它们的目标产物。
▸ Layer 2 · Workflow

工作流层:让 agent 自己沉淀 skill

偏好层只能装一次会话内不变的东西。但用户真正的工作流——比如训练完一个模型,要走 merge → 部署 → 测试 → 结果统计这一整条流水线——是 多次任务后才浮现、并且每次都有细微差异的模式。这种东西写在 CLAUDE.md 里既写不全也写不对,它们应该被 agent 自己发现、自己写、自己维护。

举个每天都在重复的场景——训练完一个新 checkpoint 后的标准流程:

STAGE 01
Merge权重合并
变体来源 LoRA / full · shard 数 · 合并策略
STAGE 02
Deploy推理部署
变体来源 TP / PP 切片 · 显存预算 · 引擎版本
STAGE 03
Eval跑评测套件
变体来源 benchmark · 子任务 ID · 采样策略
STAGE 04
Result汇总结果表
变体来源 列名 · 实验组归并 · 输出格式
骨架固定 · 4 阶段顺序不变,但每次每个阶段都有细微调整——换模型 / 换数据集 / 换实验组都会让某一格的具体配置改变。变体本身是任务驱动浮现的,不可能一次性写一个 cover 所有变体的脚本。

这正是 Skill 自动迭代 的舞台:把骨架沉淀成 skill,差异作为参数与示例追加进去,让 skill 随用随长。Anthropic 已经把"capability 打包"做成了 Skills——每个 skill 是一个 SKILL.md 加可选脚本,agent 按描述决定加载。但这套机制目前主要靠人工写。我想把它自动化——每隔 N 个回合启动一个 skill evaluator,扫描这段对话,判定是否出现了值得沉淀的工作流:

Skill 自动迭代闭环
EVERY N TURNS · ASYNC
对话进行中
主 agent 与用户正常协作。每个回合都被异步队列记录,但不影响主链路 cache。
main agent · turn N+M
每 N 回合
Skill Evaluator
独立 agent 读取近 N 回合,判定:无可抽取 / 命中已有 skill / 发现新 skill 三种之一。
judge · async
分支处理
写盘 / 合并 / 创建
命中已有 → 增量更新;新工作流 → 新建 SKILL.md;同时触发汰换检查。
skill pool · disk
关键约束
所有写盘操作都不进入当前 session 的 system prompt。新 skill 只在下一个 session 启动时被注入——这是把 Hermes 的 frozen snapshot 思路从"用户偏好"扩展到了"工作流模板",目的还是同一个:不打断当前会话的 prefix cache

第二个工程问题来自规模:随着用户使用时间变长,skill 会越攒越多,但 system prompt 装不下所有 skill 的描述(光是 SKILL.md 的 name + description 字段就占 token)。这时需要一套类似缓存的汰换策略。

简单可用的策略:LRU + 高价值锁定。统计每个 skill 在过去 K 个 session 中的命中次数,长期未命中的 skill 进入"冷池"——它的 SKILL.md 描述不再注入 system prompt(节省 token),但脚本与参考文件仍保留在磁盘。如果用户后来又触发了相关任务,evaluator 会把它从冷池捞回来。

Skill 池中的状态分布
示意 · 实际策略可换 LFU / TTL
merge-ckpt
38近 7 天
deploy-eval-pipeline
31近 7 天
result-table-merge
22近 7 天
tp-config-tuner
9近 7 天
karpathy-guidelines
7近 7 天
old-eval-suite-v1
230 天前
legacy-merge-script
140 天前
draft-deploy-poc
0已汰换
热池:描述始终注入 system prompt 温池:仅 metadata 在索引中,按需召回 冷/汰换:磁盘保留但不再 prompt 中露面
为什么不直接清掉冷 skill?因为 skill 的"价值"不只是命中次数。有些 skill 是为罕见但关键场景准备的——一年用一次的发布流程、跨季度的事故复盘模板。这类 skill 命中频次低但删除代价高。所以汰换策略要给"显式锁定"留口子,让用户/agent 自己标记 keep-forever。另一边,对反复失败的 skill 也要回写"这种条件下不能这么走",失败也是信号,不只是热度才是。
▸ Layer 3 · Alignment

对齐层:在动手之前先确认任务

前两层解决"长期的我",对齐层解决"此时此刻的我"——不同任务里总有无法从历史推断的实现细节,这一层只能靠

对齐机制按场景并存,不是替代关系:早期靠 /clearify-task 这类命令显式触发 agent 抛选择题;新模型(如 Opus 4.7)已经会自己主动追问实现细节,这件事被一定程度内化进了模型;但对不关注实现细节的用户,问 API 选哪个没意义,他们要的是产品需求层面的对齐——这只能靠 harness 风格的 plan-eval loop 来做。三种方案各有适用边界,下面把其中两种最成熟的代表放在一起看:

▸ 两种风格的对齐 · 各自的适用空间
轻量 · 命令式
clearify-task
在 Cursor / Claude Code 里挂一个 slash 命令,强制 agent 在动手前先识别需求里模棱两可、有 argue 空间的细节,再针对这些点抛多选题确认。重点不在问得多,在问对位置
  1. 用户给一段任务描述
  2. agent 识别其中模棱两可、有 argue 空间的需求细节(不是泛泛地问,而是聚焦 spec 漏洞)
  3. 针对这些点抛 2–4 个多选题(数据流?API?认证?UI?边界?仅作示例)
  4. 用户答完 → agent 用自己的话重述
  5. 用户确认 → 才允许进入 plan / 写代码
SUITS 中等复杂度的 IDE 内任务、单 agent 协作、几分钟到几十分钟的工作单元。重写代价低、用户在线。
复杂 · 流程式
harness loop
参考 Anthropic 的 harness for long-running apps:把对齐分成三层——产品顶层需求(feature list,只说做什么)、Sprint 契约(每个 feature 配 check + git 分支边界)、底层实现(在契约约束下写代码)。三层之间不许跨级——Plan 不写代码、写代码的不裁决质量。本质上是 state machine 的思路:只声明目标状态(check 通过),中间怎么走 agent 自由发挥。
  1. 顶层需求:plan agent 把含糊需求扩成产品级 feature list(只写做什么)
  2. Sprint 契约:每个 feature 单独锁定可验证的 check + git 分支 / commit 边界
  3. 用户审阅 feature list + 各自的 Sprint 契约,迭代到稳定
  4. 实现层:Generator 在契约约束下写代码,Evaluator 围绕 check 跑验收
  5. check 不过 → 回到当前 feature 修复;全过 → 合入主干,进入下一个 feature
SUITS 多 agent / 多日 / 多里程碑任务。feature 之间需要协同设计、重写代价高;用户只参与顶层需求 + Sprint 契约两层,实现层完全交给 agent。
对齐维度 clearify-task 风格 harness 风格
任务规模分钟级到小时级小时级到周级
对齐层级实现细节里模棱两可的点顶层需求 + Sprint 契约(feature × check × git)
用户参与频率开场一次开场审 feature list + 关键 milestone 审 Sprint 契约
对齐产物2–4 道针对性多选题feature list + 每个 feature 的 Sprint 契约(check + git 边界)
承载形式slash 命令 / prompt 模板子 agent / 工作流引擎
失败代价低(重跑一次任务)高(中间产物难复用)

这两种风格不是互斥关系。一个成熟的 agent 系统应当按任务复杂度自动选择:简单任务直接干、中等任务挂 clearify-task、复杂任务进 harness loop。判定逻辑可以放在主 agent 的开场 routing 里,也可以做成一个独立的 complexity classifier

更进一步,这两种风格的底层共性都是把"还没说清楚的需求"显式化——前者用选择题、后者用 spec 文档。两者都遵循同一条直觉:提问的 token 远比重做的 token 便宜

对齐层的产出不是"agent 答得多对",而是"agent 在多大程度上避免了答错的那些 token"。这个层次的 ROI 是用节省下来的废跑次数来衡量的。
▸ Closing · 三层回收

把三层放回一次真实对话里

把三层放在同一次对话的时序里——「在哪个位置插什么机制」就清楚了。读取(注入)发生在 session 开头,写入(落盘)发生在 session 结尾,下次 session 才生效。这就是读时灵活、写时延迟的全部含义。

REVISIT · 三层在一次对话中各自插在哪里
"懂用户" = 偏好 + 工作流 + 对齐,三层在 session 边界与对话内部各司其职。
▷ Session N · 开始system prompt 锁定
注入 · CLAUDE.md / USER.md 整段塞入 system prompt(frozen)
L1 · 偏好
注入 · 召回相关 skill 描述,挂入 system prompt(frozen)
L2 · 工作流
USER 帮我跑一遍新 ckpt 的训练-测试流水线
AGENT 识别歧义点 → 抛针对性多选题:LoRA 还是 full merge?eval 跑哪些 benchmark?
L3 · clearify-task
OR · 若任务是复杂产品需求(不只是工程细节),这一步换成 plan agent 输出 feature list + 每个 feature 的 Sprint 契约
L3 · harness loop
USER full merge / mmlu + gsm8k
AGENT 需求锁定,调用 deploy-eval-pipeline skill 开干
… 多轮工具调用 / 代码生成 / 验证 …
▷ Session N · 结束异步写盘,不打断本次 cache
异步写盘 · skill evaluator 抽取本次新工作流 → 更新 / 新建 SKILL.md
L2 · 自动迭代
异步写盘 · auto memory 抽取新偏好(如 try/except 风格) → 追加 CLAUDE.md
L1 · 增量
▷ Session N+1 · 开始读取上一次的写入
注入 · 新偏好 + 新 skill 一并 frozen 进 system prompt,从此次 session 起生效
L1+L2
注意图里所有实线圆点(用户/agent 消息)发生在 session 内,所有虚线圆点(注入/写盘)只发生在 session 边界——这就是"读时灵活、写时延迟"的物理形态。session 内 system prompt 始终是稳定的 prefix,任何对偏好或 skill 的更新都被推到下一次 session。"懂用户"和"算得快"由此可以同时做到。