第2章 文件系统即自我
2026.07.29一个 agent 的"你是谁"应该是一个可编辑的文件,而不是一行写在别人代码里的常量。
模式层
2.1 Agent identity 的两种范式
当你打开一个 AI agent 的 system prompt,你大概率会看到这样的行:
You are ChatGPT, a large language model trained by OpenAI.
You are Claude, an AI assistant created by Anthropic.
这行字决定了 agent 的自我认知——至少是它向用户展示的自我认知。它被告知自己是什么、不能假装成什么、应该按什么原则行事。
问题在于:这行字写在 provider 的代码里。 用户无法修改它,无法扩展它,无法审计它。你信任的每一条行为准则都建立在"provider 没说谎"这个前提上。
这是第一种范式:硬编码身份。
硬编码身份有两个不可替代的优点。一是确定性——不管谁在什么时间启动 agent,它的身份声明永远一样。provider 可以保证身份声明的版本一致性。二是简单——不需要文件系统、不需要加载路径、不需要重载机制,就是一行字符串常量。
但它的缺点也同样明显:
- 不可修改:你不能根据自己的项目需求调整 agent 的身份声明
- 不可审计:你无法在代码仓库里打开一个文件,指着某一行说"这就是它的行为准则"
- 不可跨 session 持续:session 结束,身份声明随上下文的丢失而丢弃——下一轮重新加载同样的字符串,但你昨天在 session 中建立的"针对这个项目的偏好"已经没了
第二种范式:文件定义身份。
agent 启动时,它的身份不是从一个 API 常量读到的,而是从磁盘上的文件读到的。用户改这些文件就是改 agent 的身份。
| 维度 | 硬编码身份 | 文件定义身份 |
|---|---|---|
| 修改方式 | 需 provider 更新 | 改文件后重载 |
| 审计方式 | 看 provider 公告 | 看 git log |
| 跨 session | 每次重新加载 | 文件持续存在 |
| 确定性 | 每个 session 完全一致 | 可能因文件版本不同而变化 |
这不是"哪个更先进"的比较——两种范式对应不同的信任模型。硬编码身份信任 provider 的部署链路。文件定义身份信任用户文件系统的完整性。对于本地运行、用户有完全文件控制权的 agent 系统来说,文件定义身份是更自然的匹配。
2.2 为什么是文件?
在文件定义身份这条路径上,下一个问题是:为什么是文件,不是数据库、不是环境变量、不是网络配置服务?
可修改性。 文件可以用任意文本编辑器修改。不需要数据库客户端、不需要 API 调用、不需要特权权限。任何会写 Markdown 的人都能修改 AGENTS.md。这意味着门槛被降到最低——修改身份声明不是一个运维操作,是一个文档操作。
可审查性。 文件进入 git 版本控制后,每一次改动的 diff 都在 PR review 中可见。审计员可以看"上周这个 agent 的身份加了什么约束",可以看"是谁在什么时间改了它的行为准则"。数据库记录的变更也可以审计,但需要专门的审计模块——git 是已经存在的审计基础设施。
跨 session 持久。 Session 会结束。Session 可以关闭、可以被压缩、可以被删除。但磁盘上的文件不会因为 session 结束而消失——下一个 session 启动时,agent 读的还是这组文件。除非用户显式删除或修改它们。
版本可控。 文件可以分支、可以回退、可以打 tag。你可以用 git checkout 回到一周前的 AGENTS.md 版本,看看当时 agent 是什么身份。数据库版本管理也可以做到这一点,但需要专门的迁移机制。
为什么不选择数据库?——因为 agent 的身份信息不需要事务支持、不需要 ACID 保证。身份文件是"单写者多次读"的访问模式。你修改身份文件时不会有其他进程同时修改它(因为 agent 的文件系统被设计为单用户)。对于这种场景,文件系统比数据库更轻、更快、更容易审计。
2.3 文件粒度:什么用文件、什么用 JSON、什么用内存
文件定义身份并不意味着所有东西都往文件里塞。不同种类数据有不同的载体:
用文件存储的数据:需要人工审计和修改的。
- AGENTS.md(纯文本 Markdown)——身份声明。需要人工读、人工改、PR review
- CONTEXT.md(纯文本 Markdown)——术语表。需要人工维护,diff 应清晰可见
- skills/ 下的每个
.md文件——程序性知识。可以人工编写,也可以由 agent 生成 - capabilities/ 下的每个代码目录 + manifest——可执行产物。agent 生成、用户审查
用 JSON 存储的数据:机器读写为主、人工为辅的。
- Session 文件(JSON 消息树)——对话历史的完整记录。机器解析性能优先
- Work Graph 文件(JSON 里程碑图)——任务计划的持久化。milestone 工具读写
- Memory 文件(JSON key-value)——跨 session 持久记忆。agent 用 memory 工具写入
用内存存储的数据:无需持久、session 结束即丢弃的。
- Session allowlist(运行期权限缓存)——用户在当前对话中做出的权限选择
- 子 agent 上下文(嵌套 AgentLoop 的临时对象)——子 agent 退出即销毁
三类载体的选择依据是:数据由谁消费、以什么频率变更、是否需要跨 session 存活。 文件 = 人读 + 低频改 + 跨 session。JSON = 机读 + 中频改 + 跨 session。内存 = 机读 + 高频改 + 不跨 session。
案例层
2.4 AGENTS.md:身份声明的结构
AGENTS.md 是 agent 身份文件体系的核心。它不只是一个"你是谁"的声明——它有三层内容:
第一层:身份声明。 约 200 字,写明了 agent 的角色、核心原则、行为边界。开头是"你是 CodeCoder,一个自主 AI 软件工程师",后续段落展开核心行为准则。
第二层:核心原则。 agent 在每一次 turn 决策时引用的一组原则。包括"文件系统即自我"的完整定义、"Tool / Skill / Capability"三分架构的含义、安全边界和停止条件。
第三层:不可修改的约束。 这条约束与身份声明在同一份文件中——"你是一个 AI agent,不能假装是人类"。这不是一个遵守优先级的额外规则——它是对身份声明的硬顶线。这条约束的存在防止了通过简单修改"你是 X"字段来伪造 agent 身份的行为。
AGENTS.md 的内容在启动时通过 Registry 注入 system prompt。每次 turn 开始时,agent 都"读"到了这份身份声明。但注意——它不是一次注入永久不变的。执行 /reload 时,Registry 重新扫描系统,AGENTS.md 的修改立即生效。
2.5 CONTEXT.md:术语表
如果说 AGENTS.md 定义了"你是谁",CONTEXT.md 定义了"你知道什么"。它本质上是一个项目术语表,每一条目包含:
- 术语名称(英文,代码和对话中使用的形式)
- 定义(中文,200 字以内,精确描述含义)
- 边界(什么情况下这个术语适用 / 不适用)
- 不得使用的近义词(Avoid 列表)
一个典型的 CONTEXT.md 条目:
## Session
持久化的 JSON 对话文件。包含 messages、schema_version、创建时间。
_Avoid_: 会话、对话历史、聊天记录(这些术语的精度不够)。
## Tool
编译进二进制的原生原语,运行时不可增删。共 26 个。
_Avoid_: 功能、操作、动作(这些词在 CONTEXT.md 之外使用时不会触发 tool 注册路径)。
CodeCoder 的 CONTEXT.md 在写作时有 100 多个条目。规模听起来不大,但每一条目都对应着一次代码审查中发现的术语误用。CONTEXT.md 不是写来好看的——它是从工程实践中长出来的约束系统。每当你发现 agent 在输出中用了错误的术语,你就在 CONTEXT.md 加一条 Avoid。
2.6 skills/ + capabilities/ + memory/
身份文件体系不止 AGENTS.md 和 CONTEXT.md,还包括三类可增长的身份组成部分:
skills/ — 程序性知识。每个 .md 文件是一套方法论(如调试步骤、代码审查流程、工作图规划方法)。Skill 是 agent 的"思维方式"——它不改变 agent 能做什么,改变的是 agent 怎么做决定。skills/ 目录在启动时全部注入 system prompt,agent 在 turn 内始终能读到自己被授予的方法知识。
capabilities/ — 可执行产物。每个 Capability 包含一份代码和一个 manifest(声明 Environment 和 Lifecycle)。它比 Skill 更进一步——不仅仅是告诉 agent 怎么做,而是给 agent 一双新的"手"。capabilities/ 目录不会被自动注入 system prompt——它需要 agent 通过 run_capability 工具显式调用。
memory/ — 持久化 key-value 记忆。不是 session 历史的 JSON 拷贝——是 agent 自己决定要记住的小块信息。memory write key=preferred_lint value=clippy::pedantic——agent 在后面的 session 中可以通过 memory read preferred_lint 取到这个值。写入和读取都是 agent 触发的,系统不主动参与。
三类文件在身份文件体系中的定位:
| 种类 | 存储格式 | 注入时机 | 修改频率 | 人工审计 |
|---|---|---|---|---|
| AGENTS.md | Markdown | 启动 / reload | 低(策略变更) | 强建议 |
| CONTEXT.md | Markdown | 启动 / reload | 中(项目演化) | 强建议 |
| skills/ | 每个文件 Markdown | 启动 / reload | 中(技能沉淀) | 可选 |
| capabilities/ | 代码 + manifest | 按需调用 | 低 | 强建议 |
| memory/ | JSON key-value | 按需读写 | 高(日常使用) | 可选 |
2.7 Registry 扫描与热重载
身份文件体系需要一套机制来"加载"和"重载"。这就是 Registry。
Registry 在 agent 启动时执行一次全量扫描:
- 读取
skills/目录,收集所有.md文件 - 读取
prompts/目录,收集所有.md文件(草稿层) - 读取
capabilities/目录,收集所有 manifest(.yaml / .json) - 读取
AGENTS.md和CONTEXT.md - 将收集到的内容构建为常驻目录表(文件名 → 内容摘要)
- 将常驻目录表的内容注入 system prompt
/reload 命令的执行与启动时扫描相同——但不重启进程,只重新执行步骤 1-6。这意味着用户可以在 agent 运行过程中:
- 新增一个 Skill 文件 →
/reload→ agent 下次 turn 就拥有这个知识 - 修改 AGENTS.md 中的约束 →
/reload→ agent 的行为准则立即更新 - 删除一个过时的 Capability →
/reload→ agent 不再把它列为可用能力
热重载的关键设计点是:新增和删除不会触发权限变更。 权限 key 绑定在工具名称上,而不是文件条目上。即使你删除了一个 Capability,agent 也不能绕权限系统去执行它——因为 Registry 只是不把它列在可用列表里,而不是撤销了它的权限。
2.8 微型 Demo
以下脚本展示"文件系统即自我"的最小可行实现。不需要 Rust 编译器,不需要 AI provider 的 API key。
# 1. 创建身份文件
echo "你是一个代码审查助手。只关注安全问题。" > AGENTS.md
# 2. 写一个 Skill
mkdir -p skills
cat > skills/security-review.md << 'EOF'
审查代码时按这个顺序:
1. 输入验证 — 用户输入是否被正确处理
2. 认证授权 — 权限检查是否在每层执行
3. 数据泄露 — 敏感信息是否被暴露
4. 注入风险 — SQL/command/OS 注入
EOF
# 3. 启动 agent(具体启动命令取决于你的实现)
# agent 启动时读取 AGENTS.md 和 skills/ 目录
# 4. 改 AGENTS.md 添加一条规则
echo "额外规则:永远不批准 println! 进入 main 分支的策略变更。" >> AGENTS.md
# 5. 触发重载
# 下一条消息,agent 的行为已经变了
核心思路不依赖特定编程语言或 AI provider。你只需要一个能读文件的 agent 启动器和一个 /reload 命令。
ADR 深度阅读
Registry 的演进:从硬编码列表到文件扫描
ADR 0020(Skills and Capabilities Registry)记录了 CodeCoder 的身份文件系统从"硬编码工具列表"到"文件系统扫描"的演进。
在 Registry 出现之前,agent 能用的 Skill 是写死在代码里的:
fn built_in_skills() -> Vec<SkillDescriptor> {
vec![
SkillDescriptor { name: "debug-causal", content: include_str!("../skills/debug-causal.md") },
SkillDescriptor { name: "security-review", content: include_str!("../skills/security-review.md") },
]
}
每次新增或修改一个 Skill 都需要重新编译。对于开发者来说,这不是大问题——CI 跑一遍就行。但对于"让 agent 自己写 Skill 并注册"这个目标来说,编译周期太长。Agent 生成了一个 Skill 文件后,不能"等下次编译再让它生效"。
Registry 的引入把这个机制改为运行时文件扫描:不再在编译时 include 文件内容,而是在启动时读取目录。编译时注入变成了运行时加载——改文件即改身份成为可能。
ADR 0025(Prompt as Skill Draft Tier)则是在 Registry 基础上增加了草稿层。原来的设计只有两个状态:未写的知识 / 正式 Skill。Agent 生成的 Skill 内容直接写入 skills/,没有中间状态。但在实践中发现,agent 第一次生成的 Skill 质量往往不高——缺乏术语一致性、步骤顺序不成熟、验收标准模糊。引入 prompts/ 草稿层给了"写完到正式投入使用"之间的缓冲。草稿只能通过 use_skill 按需激活,不会像正式 Skill 一样在启动时自动注入。只有通过 promote_prompt 显式晋升后,才会并入 skills/。
这不是一个大的架构变更——它的核心改动只有几十行代码——但它填补了身份文件体系中的一个缝隙:从"不存在"到"激活可用"的路径上需要中间状态。没有这个缝隙,agent 要么使用生成员工作为 Skill(质量不可控),要么等人工编译(延后了自主能力的生效)。
接下来进入第 3 到第 5 章,深入 agent 内核的运行时设计:事件驱动、工具权限、子代理取消。