1. 科研场景下 AI Agent 的真实困境
1.1 从一次失败的文献复现说起
去年冬天,我在复现一篇材料计算方向的论文时,第一次认真尝试让 AI Agent 深度参与科研流程。当时的想法很朴素:既然 Claude Code 和 Codex 这类工具已经能读懂整个代码仓库、能跨文件重构、能根据自然语言指令执行多步操作,那让它们帮我处理科研中那些重复性极高的环节,应该顺理成章。
结果第一周就翻车了。
我让 Agent 帮我重构一个用于处理光谱数据的 Python 脚本。它确实改对了代码逻辑,但顺手把我三个月前标注的一组实验参数给“优化”掉了——那组参数是我根据特定样品批次手动校准的,代码里没有任何注释说明它的特殊性。Agent 看到的是一个“看起来可以简化”的常量列表,于是它简化了。等我发现的时候,已经用错误参数跑完了两轮模拟。
这件事让我意识到一个被大多数人忽略的问题:AI Agent 在科研场景下的最大风险,不是它不够聪明,而是它太聪明却缺乏领域上下文。它能读懂代码语法,但读不懂代码背后那些“只可意会”的实验约定、参数来源、数据边界条件。这些东西在正式论文里可能一笔带过,在代码里可能就是一个裸奔的数字,但它们恰恰是科研工作可复现性的命脉。
后来我花了大概两周时间,逐步摸索出一套用PROJECT.md作为 Agent 上下文锚点的做法。核心思路很简单:在项目根目录放一个结构化的 Markdown 文件,把那些 Agent 无法从代码本身推断出来的关键信息显式写清楚。这个文件不参与编译、不进入版本发布产物,但它决定了 Agent 每一次操作的质量上限。
1.2 为什么是 PROJECT.md 而不是其他方案
市面上关于 Agent 上下文管理的方案不少,AGENTS.md、context.md、.cursorrules各有拥趸。我最终选择PROJECT.md作为主入口,有几个很实际的考量。
第一,命名即契约。PROJECT.md这个名字足够直白,任何 Agent 在扫描项目根目录时都会优先注意到它。相比之下,AGENTS.md更偏向工具配置,context.md太泛化,容易被其他上下文文件淹没。我在多个项目中实测下来,Agent 对PROJECT.md的读取优先级和内容采纳率明显更高。
第二,与科研工作流的天然契合。科研项目天然需要一个“项目说明”文档——记录数据来源、实验条件、参数选择依据、已知问题。这些内容本来就该写,只是以前写给人看,现在多了一个读者:AI Agent。把这份文档命名为PROJECT.md,既符合科研文档习惯,又恰好落在 Agent 的默认扫描路径上。
第三,避免工具锁定。我用 Claude Code 做代码重构和文献梳理,用 Codex 做快速原型验证,偶尔也会换其他 Agent 工具。如果上下文写在某个工具专属的配置文件里,换工具就得重写。PROJECT.md是纯 Markdown,任何能读文件的 Agent 都能用,迁移成本几乎为零。
提示:不要试图用一个文件解决所有问题。
PROJECT.md负责“项目级上下文”,具体到某个模块的细节,可以在子目录放MODULE.md,Agent 会按需读取。层级清晰比内容堆砌重要得多。
2. PROJECT.md 的核心结构与编写逻辑
2.1 最小可用模板:五个必填区块
经过十几个项目的迭代,我固化下来一个最小可用模板。它不追求大而全,但覆盖了 Agent 最容易踩坑的五个方面。
# PROJECT.md ## 项目概述 一句话说明项目目标、核心方法、当前阶段。 ## 数据与参数 - 数据来源、预处理步骤、关键参数及其选择依据 - 标注哪些参数是“硬约束”,不可被 Agent 修改 ## 代码结构约定 - 目录职责划分 - 命名规范、导入顺序、禁止事项 ## 当前任务与边界 - 本次 Agent 会话的目标 - 明确禁止的操作(如“不要修改 data/raw/ 下任何文件”) ## 已知问题与陷阱 - 历史踩坑记录 - 容易误判的代码段说明这个模板看起来简单,但每一条都是血泪教训换来的。比如“数据与参数”区块里那句“标注哪些参数是硬约束”,直接解决了我开头提到的参数被误改问题。Agent 读到“此参数由实验校准确定,禁止修改”这样的明确指令后,基本不会再自作主张。
2.2 为什么这些区块缺一不可
项目概述的作用是给 Agent 一个“全局视角”。没有这个,Agent 容易陷入局部最优——比如为了优化某个函数的性能,破坏了整个流程的数据流。我试过在概述里写“本项目核心目标是保证数值稳定性,性能优化是次要目标”,之后 Agent 的所有修改都会优先考虑数值精度,而不是盲目追求速度。
数据与参数是科研项目的灵魂。Agent 可以帮你写数据处理代码,但它不知道你的数据是怎么来的。是实验测量还是模拟生成?有没有缺失值?异常值处理策略是什么?这些信息不写清楚,Agent 生成的代码可能逻辑正确但完全不可用。我习惯在这里用表格列出关键参数,并标注“来源”和“约束”两列。
代码结构约定解决的是“风格一致性”问题。Agent 默认会按照它自己的审美来组织代码,但科研项目往往有历史遗留的目录结构和命名习惯。提前写清楚“所有绘图函数放在viz/目录下,使用plot_前缀”,Agent 就会遵守。不写的话,它可能把绘图代码塞进utils/,三个月后你自己都找不到。
当前任务与边界是最容易被忽略但最重要的区块。每次启动 Agent 会话前,花两分钟更新这个区块,明确告诉它“这次只改preprocess.py,不要动其他文件”。实测下来,这能减少 80% 以上的意外修改。边界越清晰,Agent 的表现越稳定。
已知问题与陷阱是项目的“错题本”。比如“legacy/目录下的代码是旧版本,不要参考其实现方式”,或者“config.yaml中的batch_size在 GPU 显存小于 8G 时必须设为 1”。这些信息写进去,Agent 就不会重复踩坑。
2.3 一个真实的 PROJECT.md 片段
下面是我在一个分子动力学模拟项目中的实际片段,脱敏后分享出来:
## 数据与参数 | 参数 | 值 | 来源 | 约束 | |------|-----|------|------| | cutoff | 10.0 Å | 文献[3] | 硬约束,禁止修改 | | timestep | 1.0 fs | 体系振动频率计算 | 硬约束 | | temperature | 300 K | 实验条件 | 可调,但需记录变更 | | random_seed | 42 | 复现性要求 | 硬约束 | > 注意:`random_seed` 必须固定,任何引入随机性的操作都要显式设置种子。这个表格让 Agent 一目了然地知道哪些能动、哪些不能动。特别是“约束”那一列,用“硬约束”和“可调”区分,比写一大段说明文字有效得多。
3. 实操:从零搭建 Agent 友好的科研项目
3.1 初始化流程与目录布局
假设你刚拿到一个科研课题,准备用 AI Agent 辅助开发。我建议的初始化顺序是这样的:
第一步,创建项目骨架。不要一上来就写代码,先把目录结构定下来。我的习惯是:
project/ ├── PROJECT.md # Agent 主上下文 ├── data/ │ ├── raw/ # 原始数据,只读 │ ├── interim/ # 中间处理结果 │ └── processed/ # 最终用于分析的数据 ├── src/ │ ├── preprocess/ # 数据预处理 │ ├── analysis/ # 核心分析逻辑 │ └── viz/ # 可视化 ├── experiments/ # 实验记录与配置 ├── notebooks/ # 探索性分析 └── tests/ # 测试用例这个布局的关键在于data/raw/的只读约定。在PROJECT.md里明确写“data/raw/下的文件是原始数据,任何情况下不得修改或删除”,Agent 就会在生成代码时自动加上只读检查,或者至少不会写出覆盖原始数据的逻辑。
第二步,写PROJECT.md的初版。不用追求完美,先把“项目概述”和“数据与参数”写清楚。这两块是 Agent 最需要的。代码结构约定可以等目录稳定后再补。
第三步,用一个小任务测试 Agent 的理解程度。比如让它“读取data/raw/下的 CSV 文件,输出前五行和列名”。观察它是否遵守了只读约定、是否按照你期望的方式组织代码。根据结果调整PROJECT.md。
3.2 与 Claude Code 和 Codex 的配合技巧
Claude Code 和 Codex 在读取PROJECT.md时的行为略有差异,需要针对性调整。
Claude Code 对 Markdown 结构的敏感度更高。它倾向于把##二级标题当作“章节”来理解,把表格和引用块当作“重点信息”。所以我在给 Claude Code 用的PROJECT.md里,会更多使用表格和>引用块来标注关键约束。实测下来,它对引用块里的“禁止”“必须”这类词响应非常准确。
Codex 则更偏向“扁平化”理解。它会把整个文件当作一段连续上下文,对层级结构的依赖没那么强。给 Codex 用的版本,我会把最重要的约束放在文件最前面,用加粗和列表强化。另外 Codex 对代码块里的内容理解更好,所以我会把一些配置示例直接写成代码块。
一个实用的技巧是:在PROJECT.md里同时保留人类可读的说明和 Agent 可执行的指令。比如:
## 代码结构约定 - 所有数据加载函数必须处理缺失值,默认策略为删除。 - 绘图统一使用 `matplotlib`,禁止引入 `seaborn`(历史依赖冲突)。 > Agent 注意:修改 `src/analysis/` 下任何文件前,先检查是否影响 `tests/test_analysis.py` 中的断言。这样既方便你自己回顾,也让 Agent 有明确的行动指南。
3.3 版本控制与变更记录
PROJECT.md本身也需要版本管理。我习惯在文件末尾加一个“变更记录”区块,每次修改都记一笔:
## 变更记录 - 2025-01-15: 新增 `cutoff` 参数硬约束说明。 - 2025-01-20: 更新当前任务为“重构预处理流程”。 - 2025-02-01: 补充 `legacy/` 目录的陷阱说明。这个记录有两个作用。一是你自己能追溯上下文变化,二是 Agent 读到变更记录后,能感知到项目的“动态”——它会知道哪些部分是最近调整过的,从而更谨慎地处理。
提示:不要把
PROJECT.md写得太长。我见过有人写了三千字,结果 Agent 读取时反而抓不住重点。控制在 500-800 字为宜,超出部分拆到子目录的MODULE.md里。
4. 常见问题与排查实录
4.1 Agent 不读 PROJECT.md 怎么办
这是最常见的问题。你辛辛苦苦写了半天,Agent 却像没看见一样。排查思路如下:
首先确认文件位置。PROJECT.md必须在项目根目录,也就是你启动 Agent 时的工作目录。如果你在子目录启动,Agent 可能扫描不到。我习惯在项目根目录启动所有 Agent 会话。
其次检查文件名大小写。有些系统对大小写敏感,project.md和PROJECT.md可能被区别对待。统一用大写。
如果位置和名称都没问题,那可能是 Agent 的上下文窗口被其他文件占满了。比如项目里有个巨大的data.csv,Agent 优先读取了它,导致PROJECT.md被挤出窗口。解决办法是在PROJECT.md里明确写“本文件是项目主上下文,请优先读取”,或者在 Agent 启动指令里直接指定“先读PROJECT.md”。
还有一个隐蔽的原因:PROJECT.md内容太泛,Agent 判断“没有具体可执行信息”就跳过了。这时候需要把内容改得更具体,比如把“注意数据质量”改成“data/raw/sensor_01.csv第 3 列存在 5% 的缺失值,预处理时需插值”。
4.2 Agent 修改了不该修改的文件
这个问题我在开头已经提过,解决方案的核心是显式声明边界。除了在PROJECT.md里写“禁止修改data/raw/”,还可以利用 Agent 工具本身的权限控制。
Claude Code 支持在配置里设置“只读目录”,Codex 可以通过指令限制操作范围。但工具层面的控制不如文档层面的约定灵活。我的做法是双管齐下:PROJECT.md里写清楚边界,同时用 Git 做版本控制,每次 Agent 操作后git diff检查一遍。如果发现越界修改,立即回滚,并把这次教训写进“已知问题与陷阱”。
另外,养成一个习惯:在启动 Agent 前提交一次 Git。这样任何意外修改都能一键恢复。我现在的流程是git add -A && git commit -m "pre-agent checkpoint",然后才让 Agent 开始工作。
4.3 参数被“优化”掉的预防措施
回到开头那个参数被改的问题。除了在PROJECT.md里标注硬约束,还有一个更保险的做法:把关键参数从代码中抽离到配置文件,并在PROJECT.md里说明配置文件的权威性。
比如,不要把cutoff = 10.0直接写在 Python 脚本里,而是放在config/params.yaml:
# config/params.yaml # 此文件中的参数由实验校准确定,修改需记录到 experiments/log.md cutoff: 10.0 timestep: 1.0 random_seed: 42然后在PROJECT.md里写:“config/params.yaml是参数唯一权威来源,代码中不得硬编码任何物理参数。”这样 Agent 在重构代码时,会倾向于从配置文件读取,而不是直接改数值。
如果 Agent 确实需要调整参数,它应该修改配置文件,并且按照约定在experiments/log.md里记录变更原因。这个流程写进PROJECT.md后,Agent 基本能遵守。
4.4 多 Agent 协作时的上下文同步
当你同时用 Claude Code 做重构、用 Codex 做原型验证时,两个 Agent 可能对项目状态的理解不一致。比如 Claude Code 刚把某个函数改名了,Codex 还在用旧名字生成代码。
解决这个问题的关键是把PROJECT.md当作唯一事实来源。每次切换 Agent 工具前,更新PROJECT.md里的“当前任务与边界”和“代码结构约定”,确保它反映最新状态。然后在新 Agent 启动时,第一句话就是“请先读取PROJECT.md,了解项目当前状态”。
我还会在PROJECT.md里加一个“最近变更”区块,记录最近一次 Agent 操作做了什么。这样切换工具时,新 Agent 能快速对齐。
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| Agent 不读 PROJECT.md | 文件位置错误或内容太泛 | 确认根目录、检查文件名大小写 | 移到根目录,内容具体化 |
| 修改了只读文件 | 边界未声明 | 检查 PROJECT.md 是否有禁止指令 | 显式写“禁止修改”并加 Git 检查 |
| 参数被误改 | 硬约束未标注 | 搜索代码中的裸参数 | 抽离到配置文件并标注权威性 |
| 多 Agent 状态不一致 | 上下文未同步 | 对比各 Agent 的操作记录 | 更新 PROJECT.md 后重启会话 |
5. 进阶:让 PROJECT.md 成为科研协作的枢纽
5.1 与实验记录系统的联动
科研项目离不开实验记录。我现在的做法是让PROJECT.md与experiments/目录形成联动。每次跑实验前,在experiments/下创建一个带时间戳的 Markdown 文件,记录实验目的、参数配置、预期结果。然后在PROJECT.md的“当前任务”里引用这个文件。
Agent 读到引用后,会主动去读取实验记录,从而理解当前任务的背景。比如PROJECT.md里写“当前任务:根据experiments/2025-02-01_baseline.md的配置,重构预处理流程”,Agent 就会先读那个实验记录,再动手改代码。这样它改出来的代码更贴合实验需求。
5.2 自动生成 PROJECT.md 的尝试
手动维护PROJECT.md确实有成本。我尝试过用脚本自动生成部分内容。比如扫描config/目录下的 YAML 文件,自动提取参数表格;扫描src/目录,自动生成代码结构说明。但完全自动化的效果并不好,因为PROJECT.md的核心价值在于那些“无法从代码推断”的信息——参数选择依据、历史踩坑、边界条件。这些必须人工写。
我目前的折中方案是:用脚本生成“数据与参数”表格的初稿,然后手动补充“约束”列和“来源”列。代码结构约定也半自动生成,但“禁止事项”和“已知陷阱”完全手写。这样既省力,又保证了关键信息的质量。
5.3 团队协作中的 PROJECT.md 规范
如果你在团队中使用这套方法,需要统一PROJECT.md的格式。我们实验室的做法是定义一个最小模板,每个人根据自己的项目填充。模板规定了必须包含的区块和推荐的字数范围。新成员加入时,先读三个现有项目的PROJECT.md,理解写法后再写自己的。
团队协作还有一个好处:踩坑记录可以共享。我们把常见的 Agent 误操作案例整理成一个公共的KNOWN_ISSUES.md,各项目的PROJECT.md可以引用它。比如“关于参数误改的预防,参见KNOWN_ISSUES.md#参数保护”。这样避免每个项目重复写同样的警告。
提示:团队使用时,建议把
PROJECT.md纳入代码评审流程。每次修改都像改代码一样经过 review,确保信息准确、边界清晰。
6. 我踩过的三个坑与对应心得
6.1 坑一:把 PROJECT.md 写成了 README
刚开始的时候,我把PROJECT.md写成了项目介绍文档,大段描述研究背景和意义。结果 Agent 读完一头雾水,因为它需要的是“可执行的约束”,而不是“学术背景”。后来我砍掉了所有背景描述,只保留 Agent 能直接用的信息。背景介绍放在README.md里,给人类读者看。
心得:PROJECT.md 是给 Agent 的“操作手册”,不是给人类的“项目介绍”。每一句话都要能转化为 Agent 的具体行动或约束。
6.2 坑二:约束写得太模糊
“注意数据质量”这种话,Agent 完全无法执行。什么叫“注意”?怎么注意?后来我改成“data/raw/下的 CSV 文件第 1 行是列名,第 2-5 行是单位说明,实际数据从第 6 行开始”。这样 Agent 写读取代码时就知道跳过前 5 行。
心得:约束必须具体到可验证。写完之后问自己:Agent 读到这句话,能做出一个明确的判断吗?如果不能,就继续细化。
6.3 坑三:忘了更新 PROJECT.md
有一次我让 Agent 重构了一个模块,改完后忘了更新PROJECT.md里的代码结构约定。第二天换 Codex 继续开发,它按照旧的约定生成了代码,导致两个模块的接口对不上。排查了半天才发现是PROJECT.md过时了。
心得:把更新 PROJECT.md 作为 Agent 会话的收尾动作。就像写完代码要提交 Git 一样,Agent 操作结束后,花一分钟检查PROJECT.md是否需要同步更新。这个习惯能省掉大量后续排查时间。
7. 不同 Agent 工具下的 PROJECT.md 适配要点
7.1 Claude Code 的读取偏好
Claude Code 对 Markdown 的层级结构非常敏感。它会把##标题当作“章节边界”,在回答时倾向于按章节组织信息。所以给 Claude Code 用的PROJECT.md,我建议:
- 用
##划分大区块,###划分小区块,层级不超过三级。 - 关键约束用
>引用块,Claude Code 对引用块的注意力权重更高。 - 表格用于参数列表,Claude Code 解析表格的能力很强。
- 避免在
PROJECT.md里放太长的代码块,它会尝试执行或深度分析,可能偏离重点。
7.2 Codex 的读取偏好
Codex 更偏向“扁平化”理解,对层级结构的依赖较弱,但对文件开头的注意力更集中。给 Codex 用的版本:
- 把最重要的约束放在文件最前面,用加粗和列表强化。
- 代码块可以多一些,Codex 对代码块的理解更准确。
- 避免使用太深的嵌套列表,Codex 可能解析混乱。
- 可以在文件末尾加一个“快速检查清单”,Codex 倾向于逐条核对。
7.3 通用原则
不管用什么 Agent 工具,有几条通用原则:
- 文件编码用 UTF-8,避免中文乱码。
- 避免特殊字符,比如 emoji、数学符号,有些 Agent 解析会出错。
- 定期验证,每隔一段时间让 Agent 复述
PROJECT.md的关键约束,检查它是否理解正确。 - 保持更新,项目状态变了就更新,过时的上下文比没有上下文更危险。
我在实际使用中发现,PROJECT.md的效果不是线性的——写得好,Agent 的表现提升非常明显;写得敷衍,反而可能误导 Agent。所以要么认真写,要么干脆不写,让 Agent 从代码本身去推断。半吊子的上下文文件是最糟糕的情况。
最后分享一个小技巧:如果你不确定PROJECT.md写得够不够清楚,可以把它发给一个不了解项目的同事,让他根据文件内容复述项目状态。如果他能准确复述,Agent 大概率也能理解。这个“人类测试”比任何自动化检查都有效。