1. 科研场景下 AI Agent 的真实困境
1.1 从“能跑通”到“能复现”之间的鸿沟
我接触 AI Agent 辅助科研这件事,最早是从跑通一个文献综述的小流程开始的。当时觉得挺爽:把几篇 PDF 丢进去,Agent 自动抽取方法、数据集、结论,生成一张对比表。但真正把这种东西用到自己的课题里,问题就来了。隔了两周再打开那个项目,我完全想不起来当时为什么选了某个分块策略,为什么把温度参数设成 0.2 而不是 0,为什么某个字段要单独做一次归一化。代码还在,但决策上下文丢了。
这不是我一个人的问题。科研和普通写代码最大的区别在于:科研的“正确性”不是跑通就算数,而是别人(包括三个月后的你自己)能不能沿着你的思路复现出同样的结论。AI Agent 参与进来之后,这个复现链条变得更脆——因为 Agent 的行为高度依赖提示词、上下文组织方式、工具调用顺序,这些东西如果只散落在聊天记录和临时脚本里,基本等于没记录。
我后来意识到,缺的不是更聪明的模型,而是一份项目文档。具体来说,是一份放在项目根目录、Agent 每次开工前都会读、人类每次回来也能看懂的PROJECT.md。它和AGENTS.md是同一类东西,只是叫法不同:AGENTS.md更偏“给 Agent 的操作手册”,PROJECT.md更偏“项目本身的说明书”,实际用的时候我经常把两者合并成一个文件,或者让PROJECT.md承担主文档、AGENTS.md只放 Agent 专属的硬约束。
1.2 为什么是 PROJECT.md,而不是散落的 README 和注释
有人会问,README 不也是项目文档吗?注释不也记录了吗?我试过,不行。README 是写给“人”看的,默认读者有耐心从头读到尾;而 Agent 读文档的方式是按需检索,它会在一次任务里反复回看某几个片段。注释的问题是太碎,Agent 要拼半天才能拼出全局意图,而且注释往往只解释“这行在干嘛”,不解释“这个项目整体在追求什么”。
PROJECT.md的价值在于它是一个单一事实来源:项目目标、数据来源、目录约定、运行命令、已知坑、当前进度,全在一个文件里。Agent 读一遍就能建立全局观,人类扫一眼就能接上进度。我实测下来,同一个课题,有PROJECT.md和没有,Agent 第一次给出可用结果的概率差了一大截——没有的时候它经常自作主张换掉我精心设计的评估指标,有了之后它会先确认“你这里写的是用 macro-F1,我保持不动”。
1.3 适合谁来用这套方法
这套东西不是给“只想让 AI 帮我写个周报”的人准备的。它适合三类人:一是做实证研究、需要反复迭代实验的研究生和博后;二是带学生做课题、需要统一协作规范的老师;三是任何用 Codex、Claude Code 这类命令行 Agent 做长期项目的人。如果你只是偶尔问一句答一句,那确实用不上;但只要你有一个持续超过一周、涉及多轮实验的项目,PROJECT.md带来的收益会非常明显。
2. PROJECT.md 到底该写什么:结构设计与取舍逻辑
2.1 一份能用的 PROJECT.md 的最小结构
我前后改过七八版,最后稳定下来的结构大概是这样的。注意,这不是模板,是我自己项目里真实在用的骨架,你可以按课题性质增删。
# PROJECT.md ## 项目目标 一句话说清这个项目要回答什么问题,以及成功的判据是什么。 ## 数据与来源 数据在哪、怎么获取、版本号、预处理到哪一步。 ## 目录约定 每个文件夹放什么,命名规则是什么。 ## 运行方式 从零到出结果的最短命令序列。 ## 评估标准 用什么指标、为什么用这个指标、基线是多少。 ## 已知问题与坑 踩过的坑、绕过的路、暂时不打算修的东西。 ## 当前进度 做到哪了、下一步做什么、卡在哪。看起来平平无奇,但每一节都有它存在的理由。我逐个说。
2.2 为什么“项目目标”必须放在最前面
Agent 和人一样,读文档有注意力衰减。放在最前面的内容,它每次都会重新读到。项目目标这一节我要求自己用一句话写完,比如“验证在低资源条件下,加入领域词典能否提升分词对下游分类的贡献”。这句话里包含了自变量、因变量和边界条件。Agent 在后续做任何决策时,都会拿这句话当锚点。
我踩过的坑是:早期我把目标写得很长,分了三段,结果 Agent 经常只抓住最后一段的细节,忘了整体要验证什么。后来强制自己压缩到一句话,问题就没了。这不是玄学,是因为长文本里 Agent 的检索权重会被稀释,短而硬的句子更容易被稳定引用。
2.3 数据与来源:版本号比路径更重要
很多人写数据这一节只写路径,比如./data/raw/。这不够。Agent 不知道这份数据是什么时候的、有没有被清洗过、和上次跑的是不是同一份。我的做法是强制写三样东西:路径、版本标识、预处理状态。
版本标识可以是日期,可以是哈希,可以是“v3-去重后”。预处理状态要写清楚“原始”“已去重”“已分词”这种粒度。为什么这么较真?因为科研里最怕的就是“我明明改了数据但结果没变”或者“结果变了但不知道是不是数据变了”。Agent 在跑实验时如果看到数据状态和上次不一致,它会主动提醒你,这比你自己事后排查省太多事。
2.4 目录约定:给 Agent 划出活动边界
这一节是我认为最被低估的部分。Agent 默认是“哪里都能碰”的,如果你不告诉它哪些目录是只读的、哪些是它生成的、哪些是绝对不能动的,它真的会去改你的原始数据。我在PROJECT.md里会明确写:
data/raw/只读,任何情况下不得写入data/processed/由脚本生成,可删除重建results/存放实验输出,按日期分子目录notes/人类手写笔记,Agent 只读不写
这几条写清楚之后,Agent 的行为边界就明确了。有一次它想“顺手”把中间结果覆盖到 raw 目录,读到这条约束后停下来了,改成写到 processed。这种拦截在长期项目里能救命。
2.5 运行方式:最短命令序列的价值
这一节我要求自己写出“从零到出结果”的最短命令序列,通常不超过五行。比如:
python -m src.preprocess --config configs/base.yaml python -m src.train --config configs/base.yaml python -m src.eval --ckpt results/latest/best.pt为什么强调“最短”?因为 Agent 在规划任务时,会优先复用文档里已有的命令,而不是自己现编。你给的命令越短越明确,它越不容易跑偏。我见过太多项目,文档里写的是“先运行预处理脚本”,Agent 就自己去猜脚本叫什么名字,猜错了就报错,报错了就瞎改。把命令写死,这类问题基本消失。
2.6 评估标准:把“为什么用这个指标”写进去
这一节是科研项目的灵魂。普通工程文档写“用 accuracy”,科研文档必须写“用 macro-F1,因为类别不平衡且我们更关心少数类的召回”。Agent 读到这个理由之后,在做模型选择、阈值调整时会有意识地保护少数类,而不是无脑优化整体准确率。
我实测过一个对比:同一份任务,PROJECT.md里只写“评估用 macro-F1”和写“评估用 macro-F1,因为类别不平衡”,Agent 给出的超参搜索空间明显不同。后者会主动尝试 class weight 和 focal loss,前者只会调学习率。这就是“为什么”带来的差异。
3. 把 PROJECT.md 接入 Agent 工作流:实操过程
3.1 在 Codex 和 Claude Code 里怎么让它自动读
文档写好了,接下来要解决“Agent 怎么知道去读它”的问题。不同工具机制不一样,我分别说。
Codex 这类命令行 Agent,通常会在项目根目录寻找约定文件。我的做法是在根目录同时放PROJECT.md和AGENTS.md,其中AGENTS.md只写一句话:“开始任何任务前,先完整阅读 PROJECT.md,并遵守其中的目录约定和评估标准。”这样无论工具默认找哪个文件,都能被引导到主文档。
Claude Code 的机制类似,它会在会话开始时加载项目上下文。我习惯在第一次交互时手动说一句“先读 PROJECT.md”,之后它就会把内容纳入上下文。如果项目很大,我还会在PROJECT.md顶部加一个“快速索引”,列出各节标题,方便 Agent 按需跳读。
注意:不要指望 Agent 每次都自动读。我的经验是,在长会话里它可能中途“忘记”,所以关键节点(比如开始新一轮实验前)我会再提醒一次“回顾 PROJECT.md 的评估标准”。
3.2 一次完整的实验迭代:从指令到结果
我拿一个真实的小实验举例。任务是“比较两种文本分块策略对检索质量的影响”。流程是这样的:
第一步,我在PROJECT.md的“当前进度”里写清楚:已完成基线分块,待比较语义分块,评估指标是 recall@10。第二步,我对 Agent 说:“按 PROJECT.md 的评估标准,跑语义分块这一组,结果写到 results/ 下按日期命名。”第三步,Agent 读文档,确认数据路径、运行命令、评估指标,然后执行。第四步,它把结果写到指定目录,并回来更新“当前进度”。
整个过程我几乎不用重复解释背景。这就是PROJECT.md的核心收益:把重复的上下文交代变成一次性的文档投入。第一次写文档花了我大概四十分钟,但后面每一轮实验都省下至少十分钟的解释时间,跑十轮就回本了。
3.3 参数选择与记录:让 Agent 帮你算
科研里经常要算一些参数,比如分块大小、学习率预热步数、交叉验证折数。这些计算如果写在文档里,Agent 可以直接复用。我举一个分块大小的例子。
假设平均文档长度是 8000 词,检索窗口是 512 词,重叠率我设 20%。那么分块数量大约是:
块数 ≈ 文档长度 / (窗口大小 × (1 - 重叠率)) = 8000 / (512 × 0.8) ≈ 19.5这个计算过程我会写进PROJECT.md的“已知问题与坑”里,注明“重叠率 20% 是经验值,低于 10% 会丢上下文,高于 30% 冗余太多”。Agent 读到之后,如果它想调整窗口大小,会自己按这个公式重算,而不是拍脑袋。这种“把计算逻辑文档化”的做法,在需要反复调参的课题里特别省心。
3.4 版本管理与协作:文档也要进 Git
PROJECT.md必须进版本控制,和代码一起提交。我见过有人把它放在本地不提交,结果换台机器就丢了。进 Git 之后有两个好处:一是每次实验对应的文档状态可追溯,二是多人协作时冲突可见。
我的提交习惯是:每次实验有结论,就同时提交代码和PROJECT.md的“当前进度”更新。提交信息写清楚“完成语义分块对比,recall@10 提升 3 个点”。这样回头看历史,能直接看出哪次改动带来了什么效果。Agent 在读取 Git 历史时,也能据此判断哪些方向值得继续。
4. 常见问题与排查技巧实录
4.1 Agent 不读文档或读了不用,怎么办
这是最高频的问题。表现是:你明明写了评估标准,Agent 还是按自己的默认逻辑来。我的排查顺序是这样的。
先确认文档是否在 Agent 的可见范围内。有些工具只读特定文件名,你把内容写在PROJECT.md但它只认AGENTS.md,那就白写。解决办法是让两个文件互相引用,或者干脆合并。
再确认文档是否太长。超过一定长度后,Agent 的检索会变得不稳定。我的经验是把PROJECT.md控制在两千字以内,超出的细节拆到子文档,主文档只留索引和硬约束。
最后确认指令是否明确。如果你说“按项目规范来”,Agent 可能不知道指哪条;说“按 PROJECT.md 第 5 节的评估标准”,命中率会高很多。把引用写具体,是个低成本高回报的习惯。
4.2 文档和代码不一致导致的“幽灵 bug”
这个坑我踩过好几次。代码里评估指标已经改成 micro-F1,但PROJECT.md还写着 macro-F1,Agent 读文档后按 macro 跑,结果和预期对不上,排查半天才发现是文档过期。
解决办法是建立“改代码必改文档”的纪律,并且在PROJECT.md顶部写一行“最后同步时间”。Agent 如果发现文档时间和代码提交时间差距过大,会主动提醒你核对。我还会在关键节加一句“如与代码冲突,以代码为准,并立即更新本节”,给 Agent 一个明确的优先级规则。
4.3 多 Agent 或多工具切换时的上下文断裂
有时候一个项目里会同时用 Codex 和 Claude Code,或者多个 Agent 分工。这时候PROJECT.md就成了共享的“交接班记录”。我的做法是要求每个 Agent 在完成任务后,把“当前进度”更新到文档里,写清楚做了什么、结果如何、下一步建议。下一个 Agent 接手时先读这一节,就能接上。
这里有个细节:不同 Agent 的写作风格不一样,有的喜欢写长句,有的喜欢列点。我会在文档里规定“当前进度”用固定格式:日期、任务、结果、下一步。格式统一之后,交接效率明显提升。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查动作 |
|---|---|---|
| Agent 忽略评估标准 | 文档未被读取或引用不具体 | 确认文件名匹配,指令中写明节号 |
| 结果与文档描述不符 | 文档过期,代码已改 | 核对最后同步时间,以代码为准并更新 |
| Agent 改动只读目录 | 目录约定未写或未强调 | 在文档中明确只读目录并加粗 |
| 长会话中 Agent 遗忘约束 | 上下文被稀释 | 关键节点重新提醒回顾文档 |
| 多工具切换后进度丢失 | 未更新当前进度 | 强制每次任务后更新进度节 |
4.5 几个我压箱底的小技巧
第一个技巧:在PROJECT.md里放一个“禁止事项”清单,比如“禁止修改 raw 数据”“禁止在未确认的情况下更换评估指标”“禁止删除 results 下的历史目录”。Agent 对否定指令的遵守度比肯定指令高,这个清单能挡掉大部分误操作。
第二个技巧:给关键参数加“为什么”注释。比如“温度 0.2,因为需要一定多样性但不能太发散”。Agent 在调整时会参考这个理由,而不是随便改。
第三个技巧:定期让 Agent 自己审查PROJECT.md和代码的一致性,让它列出“文档说 A、代码做 B”的地方。这个自检动作我每个月做一次,能提前发现不少隐患。
5. 从 PROJECT.md 到 AGENTS.md:文档体系的扩展
5.1 什么时候需要拆出 AGENTS.md
项目小的时候,一个PROJECT.md就够了。但当项目变大,尤其是多个 Agent 承担不同角色时,我会拆出AGENTS.md。分工是这样的:PROJECT.md管“项目是什么”,AGENTS.md管“Agent 该怎么干”。前者相对稳定,后者随工具和流程变化。
AGENTS.md里我通常写:各 Agent 的职责边界、工具调用规范、输出格式要求、失败重试策略。比如“数据清洗 Agent 只负责清洗,不做建模”“所有 Agent 输出必须带时间戳和输入哈希”。这些是操作层面的约束,放在PROJECT.md里会显得杂乱,拆出来更清晰。
5.2 文档体系的长期维护成本
说实话,维护文档是有成本的。我一开始也担心“写文档的时间比做实验还多”。但实测下来,只要控制好粒度,成本是可控的。我的原则是:只写会变的东西和会忘的东西。不会变的(比如领域常识)不写,不会忘的(比如显而易见的命令)不写。这样文档始终保持在“够用”的状态,不会膨胀成没人看的摆设。
另一个降低维护成本的办法是让 Agent 帮你写初稿。比如做完一轮实验,让 Agent 根据代码和结果生成“当前进度”的草稿,你再改。它写得不一定好,但能省掉从零组织语言的时间。
5.3 这套方法在更广场景下的延展
PROJECT.md这套思路不只适用于科研。我带过一个做数据产品的朋友,他把同样的结构用在需求文档上,Agent 读完之后能自动生成测试用例和验收清单。还有人用在写作项目上,把人物设定、时间线、已写章节放进PROJECT.md,Agent 续写时就不会跑偏。
核心逻辑是一样的:把隐性的上下文变成显性的文档,让 Agent 和人都能稳定复用。工具会换,模型会升级,但这个逻辑不会过时。我现在开任何新项目,第一件事就是建PROJECT.md,哪怕只有三行,也比没有强。
最后分享一个我自己的习惯:每次项目告一段落,我会让 Agent 把PROJECT.md压缩成一页“复盘摘要”,只留目标、结论和最大的坑。这份摘要下次开类似项目时直接拿来当起点,省掉大量重新摸索的时间。踩过的坑不重复踩,这大概就是文档化最实在的回报。