第17章 文件系统即自我
2026.07.29你的 agent 有个内置的名字——但这个名字写在别人的代码里。
引子
你打开一个新对话。agent 说"我是 Claude"。你知道这不是真的——这行字来自一个系统 prompt 常量,写在 Anthropic 的服务器里,跟你手上这个项目毫无关系。
你再想一想:每次开始一个新 session,它都要重新认识你。它不知道上次聊了什么,不知道你项目里有哪些文件,不知道你最烦什么编码风格。每一次都是出厂设置。
现在换个场景。你启动 CodeCoder(一个在你自己机器上跑的 agent),它的自我介绍是这样开始的:
你是 CodeCoder,一个自主 AI 软件工程师。你的核心原则是'文件系统即自我'——你的身份、知识、能力都存储在磁盘上的文件中,而非硬编码在二进制中。改动这些文件,就是改动你自己。
不是从某个 API 响应里读到的常量,而是从一个名叫 AGENTS.md 的文件里读到的。你改这个文件,它就改身份。你往 skills/ 目录放一个 Markdown 文件,它就多了一份知识。你不删 memory/ 里的记录,它就记得几个月前的事。
这就是这篇文章要聊的核心问题:agent 的"你是谁"应该是一个可编辑的文件,还是一行不可改的代码?
一、硬编码的 identity——一次性工具的宿命
今天绝大多数 agent 的身份是硬编码的。打开任何一个主流 AI 产品的 system prompt,你都会看到类似这样的行:
You are ChatGPT, a large language model trained by OpenAI.
You are Claude, an AI assistant created by Anthropic.
这行字写在 provider 的代码里。你无法修改它。你无法扩展它。你无法告诉它"你的领域是医疗影像,不是通用对话"。
这带来了三个问题。
第一个问题是跨 session 的失忆。 每一次新对话都是一个全新的 agent。它不知道昨天跟你在同一个项目里写了什么代码,不记得你上周批准的权限策略。它连你自己的名字都不记得。Session 持久化(即保存为可恢复的 JSON 对话文件,见篇 5「事前之图,事后之树」)可以缓解"记住事实"的问题——但身份不持续,记住了也没人认领。
第二个问题是能力的不可扩展性。 一个硬编码身份的 agent 能做什么就是什么。你不能告诉它"去学一个新工具怎么用,然后记住",因为学到的知识没有地方存。如果知识只能通过对话中的上下文传递,那么上下文的尽头就是遗忘的起点。
第三个问题最隐蔽:你无法审计它。 硬编码的身份声明运行在 provider 的服务器上。审计员想确认"这个 agent 到底被赋予了哪些原则",唯一的办法是看 provider 的公告。你无法在你自己的代码仓库里打开一个文件,指着某一行说"这就是它的行为准则"。
你可以把这想象成:你的 agent 永远在租别人的房子,租约一次对话一签。
二、文件定义的 identity——可演化的实体
现在回到 CodeCoder。它的身份不只一行声明,而是一组文件:
AGENTS.md — 身份声明。第一行写"你是谁",后面的段落写你的行为准则、决策原则、不得违反的规则。它不是系统 prompt 的替代品——它是系统 prompt 的源头。agent 启动时读它,/reload 时重读它。
CONTEXT.md — 领域知识。这是项目的术语表。每一条目包含定义、边界、以及不得使用的近义词。CodeCoder 的 CONTEXT.md 有 100 多条目(写作时),从 Mode 和 Dialog 的区分到 MessageId 和 ToolCall.id 的不可混用。agent 在生成任何输出之前都经过它——审计员读这个文件,就能知道 agent "知道什么"以及"怎么理解这个词"。
skills/ — Skill(程序性知识)。一个目录,里面每个 .md 文件是一套方法论。比如一个代码审查 Skill 规定了遇到审查任务时的步骤:先查安全性、再查性能、再查风格。agent 不背这个步骤——它在需要的时候打开这个文件读一遍。Skill 的好处是:你可以写一个全新的 .md 文件放进去,agent 下次遇到同类场景就会用。
capabilities/ — 可执行产物。agent 自己写的代码,附带一个 manifest 声明它需要什么执行环境(Shell / Wasm / Docker)和生命周期(一次性 / 按需 / 常驻)。agent 可以编写一个新的 capability,放进这个目录,然后自己调用自己。
memory/ — 持久化的 key-value 记忆。不是 session 历史的 JSON 拷贝——是 agent 自己决定要记住的碎片信息。"用户不喜欢 unwrap()"、"这个项目用 nightly Rust"、"上次绕过这个坑的方式是……"。每一条带时间戳和来源,agent 可以跨 session 查阅。
五类文件,五种不同的自我组成部分。你改 AGENTS.md = 改身份声明;你往 skills/ 加文件 = 加思考方式;你往 capabilities/ 加文件 = 加行动能力。
这不是一个"所有东西都放在一起"的大熔炉——每类文件有明确的存储格式、加载时机、作用范围。文件系统不是数据库的替代品,它是 agent 的基因组。
三、运行时加载 vs. 编译时注入
这两种模式的根本区别在于:变更生效的代价。
编译时注入的意思是:agent 的 identity 和知识在构建阶段(provider 打包模型时、你编译二进制时、甚至每次对话开始时拼 system prompt 时)固定下来。要改变它,你需要走一遍完整的构建-部署-重启流程。
运行时加载的意思是:agent 运行过程中,直接从磁盘读文件。你改文件,它下次用到时就读到新内容。
CodeCoder 用的是后者。启动时,Registry 扫描 skills/、prompts/、capabilities/ 目录,把找到的文件注册到常驻目录表里。然后把这些文件的内容拼进 system prompt。之后你每次执行 /reload,它重新扫描一遍。新增、删除、修改全部在下一次 turn 生效。
一个 10 行的最小 demo 就能展示这个体验:
# 1. 创建一个身份文件
echo "你是一个代码审查助手,只关注安全问题。" > AGENTS.md
# 2. 写一个 skill
mkdir -p skills
cat > skills/security-review.md << 'EOF'
审查代码时按这个顺序:1) 输入验证 2) 认证授权 3) 数据泄露 4) 注入风险
EOF
# 3. 启动你的 agent(读 AGENTS.md + skills/)
# 4. 改 AGENTS.md 加一条规则
echo "你是一个代码审查助手。额外规则:永远不批准 println! 进入 main 分支。" >> AGENTS.md
# 5. 触发重载
# 下一条消息,agent 的行为已经变了
这段脚本不需要 Rust 编译器,不需要 AI provider 的 API key。核心思路就是:agent 读文件,你改文件就是改 agent。
这种模式对审计尤其友好。你想知道"这个 agent 被告知了什么原则"——打开 AGENTS.md 看一眼。你想知道"它什么时候学会了这个 skill"——看文件的 git log。你想知道"它记住过什么"——读 memory/ 目录。
代价与权衡
文件系统即自我不是没有代价。把身份放在文件中而不是编译时固定引入了一个新维度的问题:文件的可篡改性。
CodeCoder 的方案中,AGENTS.md 和 CONTEXT.md 没有任何写保护。任何人(或 agent)只要有 write_file 权限就能修改它们。如果攻击者控制了 agent 的文件系统,他可以改 AGENTS.md 让 agent 按与预期完全不同的行为准则运行——甚至比硬编码身份更危险,因为硬编码至少需要攻击 provider 的部署系统。
另一个代价是加载延迟。Registry 每次启动或 /reload 时扫描目录,如果 skills/ 中有上百个文件,每次扫描和注入会影响启动速度。当前 CodeCoder 的扫描量(6 个 Skill、1 个 Capability,写作时数据)尚不构成瓶颈,但扩展到大几百个时,要么需要增量加载,要么引入缓存机制。
还有一个更微妙的代价:身份分散在多个文件意味着审计的入口点也分散了。 如果要确认"这个 agent 此刻完整的身份是什么",你不能只看 AGENTS.md——你还要看 CONTEXT.md、skills/、capabilities/ 和 memory/。分散降低了一扫即可理解的便利性。CodeCoder 用 AGENTS.md 顶部的一段概览来缓解这个问题,但本质上是间接引用,不是决定性解决。
这些代价不是放弃文件系统即自我的理由——但承认它们的存在让这本书的论证更诚实。
延伸:这改变了什么?
文件系统即自我,不只是"换个地方存 system prompt"这么简单。它改变了 agent 的信任模型。
硬编码身份的 agent 的信任基础是:"我信任这个 provider 没有在 system prompt 里藏坏东西。" 你没法验证,你只能相信。
文件定义身份的 agent 的信任基础是:"我信任我能读到的这个文件的内容。" 你可以在 PR review 里看 AGENTS.md 的改动,可以在 CI 里检查 CONTEXT.md 有没有过时的条目,可以 audit memory/ 目录来确认 agent 没有偷偷记住不该记的东西。
信任从 provider 的公告栏转移到了你的 git 仓库。
另一个改变是跨 session 连续性。不需要"每次对话都告诉 agent 项目背景"——AGENTS.md 和 CONTEXT.md 在 session 之间持续存在。Session 可以结束,agent 的身份文件不结束。下一轮对话,它读的还是同一组文件。
收尾:你今天就能做的事
你不需要造一个 CodeCoder 才能用这个思路。三个步骤,你今天在任何一个项目里都可以做:
第一步:把 AGENTS.md 从代码中抽出来。 如果现在你的 system prompt 写在一个环境变量或代码常量里,把它挪到一个项目根目录的 .md 文件中。你的 agent 启动时读这个文件而不是读常量。
第二步:写第一条 CONTEXT.md 条目。 找一个你项目里最容易混淆的术语,写清楚它是什么、不是什么。比如如果你的项目里有 User 和 Account 两个概念——它们的关系是什么?边界在哪里?哪条规则容易违反?
第三步:创建一个自定义 skill。 如果你发现自己在每个任务里都要告诉 agent"先跑测试再提交"——把它写进一个 .md 文件。下次直接说"按 check-test 流程跑一遍"。
这三个步骤加起来不到一小时。做完之后,你的 agent 就不再是每次开箱都重新自我介绍的那个陌生人——它是一个有身份、有知识、有方法的实体。而那个身份,存在你的项目里,不是别人的服务器里。
下一篇,我们把"能力"拆开来看——你会发现一个 agent 的成长恰好需要三种不同的机制,不是一种,不是四种。