# AGENTS.md 本文件是给所有编码 Agent(Claude Code、Codex 等)的工作说明。`CLAUDE.md` 只引用本文件,内容以这里为准。 ## 项目是什么 CADx-4 研究胰腺囊性肿瘤的鉴别诊断:给同一个病人的三路资料(临床信息、CEUS 超声造影视频与超声报告、MR 报告)各配一个只看自己那一路的 AI 专家,模拟多学科会诊,判断病灶属于四类胰腺囊性肿瘤中的哪一类:IPMN(导管内乳头状黏液性肿瘤)、SPN(实性假乳头状肿瘤)、MCN(黏液性囊性肿瘤)、SCN(浆液性囊性肿瘤)。前三类有恶变可能或本身是低度恶性,通常要手术或密切随访;SCN 几乎都是良性,多数不用手术。分错直接决定病人是手术、随访还是不用管。金标准是术后或穿刺取得的病理结论。最终产出是一篇医学 AI 交叉论文,重点在临床问题、与人类医生的对比和可解释性,不在方法复杂度。 几个词先解释:**MR** 是磁共振检查,本项目只有它的文字报告;**CEUS** 是往静脉打造影剂后连续录下的超声视频;**ROI**(感兴趣区)是视频里圈出的病灶区域;**TIC**(时间-强度曲线)是 ROI 平均亮度随时间的变化,造影剂进来变亮、洗出变暗,曲线形状就是灌注的量化。本仓库的术语表随模块迁移逐步建在 `research-wiki/reference/术语表.md`;建成前查旧项目的 `reference/CADx-4-CAD/research-wiki/reference/glossary.md`。 本仓库是对旧项目的**重构**。旧项目在远程服务器上按工程产品搭建(HTTP 服务、数据库、任务队列),后来发现目前不需要做成产品,于是在这里按研究原型重写,只保留有研究价值的算法和知识。旧项目的完整副本在 `reference/CADx-4-CAD/`(只读,见下文)。重构的来由和取舍见 `research-wiki/design/0001-从产品重构为研究原型.md`。 和临床合作方之间的议题、会议纪要、交付清单和临床口径在 Linear(团队用的议题跟踪系统,临床方也在上面)上,不在本仓库。 ## 新会话先读什么 1. `WORK_LOG.md`:当前目标、做到哪一步、下一步、运行中的任务。 2. `research-wiki/README.md`:文档体系与各目录的用途。 3. `research-wiki/explanation/结构.md`,以及与当前任务相关的其他 `explanation/` 文档。 ## 事实来源 文档之间有冲突时按下表定谁说了算,找负责人确认,不自行折中成第三种说法。 | 问题 | 看哪里 | | --- | --- | | 当前进度、下一步 | `WORK_LOG.md` | | 代码分哪些模块、谁能依赖谁、各模块预期规模 | `research-wiki/explanation/结构.md` | | 各环节在做什么、输入输出 | `research-wiki/explanation/` 的其他文档 | | 还没定的问题 | `research-wiki/待定问题/`(定了就删,结论进 `design/`) | | 某个决策为什么这样定 | `research-wiki/design/NNNN-*.md`(写完冻结) | | 命令怎么敲、环境怎么配 | `research-wiki/guides/` | | 医学词、参数的原始口径、临床方给的字段定义 | `research-wiki/reference/`(每条自带出处) | | 交付资料的格式和口径(列名、编码、时间的含义) | 最新的交付包,目前是 `data/交付包_20260908_40例/`。旧样本(如 `data/1.2-TEST/`)和旧程序的口径只当线索。来源:负责人 2026-10-16「数据改过很多版,所以你们需要对齐“40例交付包”那个」 | | 配置值、字段定义 | 代码里的定义与校验;文档只解释含义,不抄数值 | | 临床口径、双方约定、待交付清单 | Linear(临床方维护的那几份 document 是权威,仓库里的抄本以它为准) | | 旧项目做了什么、踩过什么坑 | `reference/CADx-4-CAD/`(只当线索,见下文) | ## reference/ 怎么用 `reference/CADx-4-CAD/` 是旧项目的副本,含完整 git 历史(366 个提交)和当时没进 git 的 `debugs/` 脚本。 - 只读。不在里面改文件、不在里面跑会写盘的命令。 - **算法代码可以迁移**:`app/domain/ceus_roi/`(除 `service.py`)、`app/providers/video_decoder.py` 和 `debugs/` 里的三个 `.py` 脚本是负责人自己的成果,迁移时去掉数据库、HTTP、任务队列等产品层依赖,按本文件的标准改写,并补上测试。读交付包的逻辑(`app/providers/` 里的列白名单、病例号规范化、剔除报告结论段)只借规则,按普通函数重写。产品层代码(`app/api/`、`persistence/`、`workers/`、`migrations/` 等)不搬。 - 旧文档和报告里的数字(40 例跑通 34 例、参数取值等)只是线索。要写进本仓库的文档或结论,先在原始产物或本仓库的真实运行里核到。 - 旧 `research-wiki/design/0001–0049` 讲的是当时为什么那样定。其中与研究有关的结论(优先读哪几份见本仓库 `research-wiki/design/0001` 第 8 条的表)在本仓库重新写成 explanation 或 design。可以注明「出自旧 design 00NN」交代来历,但现行规则以本仓库的文档为准。 `reference-pdf/` 放文献原文,命名 `年份-第一作者-短名.pdf`,索引在 `reference-pdf/INDEX.md`。两者都不进 git。 ## 硬性约束 1. **答案不进 AI 专家能看到的任何东西。** 病理金标准、工作簿里的诊断列、按规则算出诊断倾向的公式列、医生的结构化判读列(**标注工作簿**是临床方另交的一份表,医生在里面逐例标了病灶位置、大小、囊壁、增强模式等三十来列,它只用于评价专家抽取得准不准)、报告的「印象 / 结论」段、真实病例号、Excel 行序,一律不能进入提示词、工具返回或专家可读的文件。**行序也算**:原始表格按诊断分组录入,行号本身就带着答案。这一条错了不会报错,只会让所有「系统诊断得准不准」的结论变成「系统能不能读到答案」。任何会削弱这条的改动,先停下来问。来源:旧项目 CLAUDE.md §1.2、旧 design 0026。 2. **影像专家先独立判断,再听临床信息。** 资料库里每一路只放自己那一路资料:性别和年龄只在临床那一路,超声和 MR 两路的资料里没有,即使医院把这几列也复制进了报告工作簿。会诊时,超声和 MR 两位专家先只凭本路资料各自给出判断;第一轮之后,它们需要时可以向临床专家提问,临床专家按问作答,可以答性别、年龄。先独立这一步不能省:判断有没有分歧要靠两路影像证据本身,先听到同一份人口学信息会把两位专家往同一个方向拉,分歧就看不出来了。各路能看见什么见 `research-wiki/explanation/结构.md` 第四部分。来源:旧项目 CLAUDE.md §1.2、旧 design 0026;临床专家只提供信息出自旧项目 HAI-62 的 2026-08-02 会议纪要;「第一轮之后、影像专家提出需要才给临床信息」与本条改写是负责人 2026-10-16 定的。 3. **患者资料不进 git、文档和提交说明。** 原始资料只在 `data/`,运行产物只在 `runs/`,两者都不进 git。文档里可以写结构和规模,不写报告原文、姓名、病例号和诊断。把病例资料发给外部模型服务之前,先确认负责人已同意该服务。超声帧上烧录的信息栏(拼音姓名、检查号、性别年龄、医院与设备参数)不做遮蔽:临床方判定拼音和不唯一的检查号不构成身份泄露(出自旧 design 0041)。 4. **密钥不进 git、文档、日志和运行快照。** 只放环境变量或本地 `.env`;仓库只提交不含真实值的 `.env.example`。 5. **写进论文和结论的数字能追到一次真跑的原始记录**(`runs/` 下的运行目录)。不把预期、估算、旧项目报告或别人论文里的数字当成本仓库的结果;没有就写「没有」。文献里的数字核到 `reference-pdf/` 的原文。 6. **错误不静默吞掉。** 不吞异常,不用默认值把问题盖过去,缺关键配置直接报错。细则见「写代码的标准」。 7. **花钱的批量运行先说预估成本。** 调模型 API 跑全部病例这类动作,开跑前在回复里给出预估;量级明显超过以往的,先问。 ## 怎么协作 - **用简体中文回复,先给一句话结论。** - **先讲后写。** 新环节或新设计先在终端讲:旧项目怎么做的、问题在哪、打算怎么做、为什么。负责人认可后再写文件或代码。需要负责人决定时,一次列出全部问题、选项和你的倾向。 - **能自己定的直接定**,按设计判断或文献先例给方案,不把问题抛回去。 - **范围要窄,改得要深。** 只动这次要改的那一块;范围内要改到读起来像一开始就这么设计的,被取代的旧内容同一次删掉。范围外看到问题,记下来告诉负责人。 - **讨论会话与执行会话分开。** 和负责人讨论方向的会话写任务书(`research-wiki/任务书/NN-*.md`),由负责人另开会话执行、讨论会话验收。小改动不必写任务书。 - **子 Agent 交证据不交判断**:带 `文件:行号`、命令原始输出或产物路径;prompt 里不带邮箱等个人信息和患者资料;要用浏览器只能用无头模式。收回的报告先抽查两三条原文,其中一条挑带评价性修饰的句子;子 Agent 说「通过」不算数,要自己核对实际改动和测试产物。 - **审查**:改动完成后派一个新鲜上下文的子 Agent,只给 diff、验收标准和相关文档,不给实现时的推理。下文「账本级」的代码另派一个全新上下文的子 Agent 再审一轮;Codex 可用时再加一轮对抗审查(`/codex:rescue --fresh --wait`)。审查意见只采纳影响正确性或明确需求的项,采纳前问:日常使用中会不会真撞上。 - **汇报**时分开写已验证的事实、推断和未验证的部分。 - **新加的规则注明来源**:负责人哪句话、哪份 design 文档,还是 Agent 自己提的。本文件的改动来源写在提交说明里。 - 推送、建 PR、往 Linear 写东西这类外部可见的动作先问;Linear 上有临床方在看。 ## 写代码的标准 ### 两档标准 - **账本级**(像记账一样只追加、可核对;出错会悄悄污染结论的部分):答案隔离(各路资料怎么读、怎么分给专家)、金标准的读取、评分与统计、写进论文的定量结果。要求: - 运行产物只追加,已有的运行目录不删、不覆盖,要删先问; - 每条规则有独立预期值的测试,预期值来自手算、独立脚本或人工核对过的样例,不从实现里抄公式。 - **其余部分**:视频解码、ROI 追踪、绘图、提示词拼装、脚本。写错了马上能看出来,按研究原型的标准写:用最直接的写法,遇到罕见情况就停下来把原因写清楚。 ### 可复现 随机种子、模型的精确版本号(以服务商返回为准)、提示词版本、git 提交号和全部配置,写进每次运行的快照。复现不了的结果等于没有结果。每次运行一个新目录,同一个输出目录同时只允许一个进程写。 ### 不设默认值 配置的每个字段都必填,函数参数不写默认值;可以没有的值显式写 null,不靠省略。缺了就报错。默认值会掩盖错误:字段名拼错、文档改了代码没跟上,流水线照样跑完,没人知道用的是哪个值。路径、模型名、服务地址同样不写死在代码里。 ### 罕见情况怎么办 「可能会出现」不是写分支的理由。按顺序选: 1. 改数据的表示或格式要求,让它不可能出现。 2. 做不到,就在系统边界检查,不满足就报错停下。边界指交付的资料、模型的输出、负责人的配置。模块之间的调用不算边界,不互相防御。交付资料里只落在某一个病例上的问题是例外,见「错误怎么处理」。 一个抽象在出现第二个真实调用点之前不引入。不考虑向后兼容:不写兼容层,读到旧格式直接报错;发现更好的设计就按它重写,同步改文档和测试。 ### 遇到问题先找原因 出了问题先查清为什么,再到原因所在的地方改。重试、跳过、补默认值、放宽解析、捕获异常后接着跑,只是让症状消失。动手前问:这个改动是让问题不再发生,还是让问题发生了也没人看见? ### 错误怎么处理 - **外部错误**(模型接口超时、网络断开):只在调用外部服务的那一处做有限次重试,仍失败就把这一步记为失败并写明状态。 - **内部错误**(资料不满足规则、不该有的状态组合):立刻停下,报错写清是哪条规则、哪个病例的哪个文件。模型交不出合格输出按内部错误处理。 - **交付资料里只落在某一个病例上的问题**(这一例的编号认不出、缺文件、取值不合规则、和别的表对不上等):跳过这一整例,接着处理下一例,并在标准答案库的跳过清单里写明这一例没过的是哪项检查、哪个文件哪一列。对一个病例,遇到第一项不过的检查就不再往下查,所以清单上每例只有一项,后面的检查没做过,不代表没问题。整张表读不了(工作表名或列名不对、文件缺失、配置不全)仍然立刻停下。跳过不是把问题藏起来:跳过清单交给医生核对,评价时要报告跳过了几例、各因为什么。来源:负责人 2026-10-16「都是遇到整个病例跳过,让医生核对去吧」。 中间层不写 try/except,除非它能把错误完整处理掉且上层不需要知道。不写 `except Exception: pass` 及其变体。 ### 提示词 - 提示词引导模型怎么想,不设必填栏目:逼模型填栏目,它会为了填而编。只有程序确实要读的值才做成输出字段。 - 提示词是 `prompts/` 下的版本化文件,不散落在代码字符串里;给模型的提示词用英文。 ### 测试 - 真实模型链路要用真跑证明,假模型测试证明不了;需要显卡或花钱的测试用 pytest 标记 `gpu`、`live` 区分,不进 `make check`。 - 修缺陷时附一条能复现原问题的测试。 - 不为方便测试给产品代码加参数、开关或分支;不靠删断言、改预期值或只保留成功的运行让测试通过。 ### 规模与注释 - 每个模块的预期规模写在结构文档里;超过预期一倍时停下检查:是需求本身复杂,还是专门路径在变多。 - 注释只写这段代码现在做什么、为什么这样做;修改过程写进提交说明和 `WORK_LOG.md`。 ## 文档 - **代码标识符用英文;注释、docstring、文档和回复用简体中文。** - 读者是没参与过讨论的医学 AI 方向硕士生,也包括负责人:懂 Python 和深度学习,不认识 IPMN,也不知道我们讨论过什么。医学词、缩写、代号首次出现处就地解释;每个决策写清为什么。 - 同一个配置值只在一处定义,别处引用。 - `explanation/`、`guides/`、`reference/` 是常青文档:改了就重写那一段,不在后面追加「某日又改了」。 - 决策的来历写进 `research-wiki/design/NNNN-简短主题.md`,按顺序编号,写完冻结;决策变了新写一份,开头写明取代哪份,旧的开头加一行指向新的。 - `WORK_LOG.md` 只记当前状态:目标、已完成(附证据)、未完成、关键决定(附负责人原话)、下一步。一个阶段完成后压缩成几行。没有真实证据的「可能情况」只在汇报里提一句,不写进 `WORK_LOG.md`。 - **新写或大改一份文档**(新文档、重写整节、改动超过约 100 行),过一轮「硕士生阅读」:新鲜上下文的子 Agent 扮演这样的读者,只读这份文档,先列出最可能读不下去或要回头重读的段落、再复述要点,然后报哪句读不懂、哪个医学词没解释、哪个决策缺理由、哪个值两处不一致。不要让它描述自己的思考过程。格式和措辞建议不采纳。 ## 提交规范 提交信息用英文前缀加中文正文,前缀按 Conventional Commits:`feat:`、`fix:`、`docs:`、`test:`、`refactor:`、`chore:` 等,可加作用域,例如 `feat(ceus): 暗区掩膜改用奇偶规则`。 ## 本机环境的坑 - `grep`、`find`、`ps`、`diff` 可能被 shell 包装函数改写,静默少输出。搜索用 `/usr/bin/grep`、`/usr/bin/find`;查进程用 `pgrep -af`;比较文件用 `cmp` 或 `/usr/bin/diff`;看 git 差异用 `git --no-pager diff`。派出去的子 Agent 的 prompt 里也写上这一条。 - 超过约一分钟的命令放进 tmux 跑,日志写到文件。 - 日常开发和实验在本机(一张 RTX 4070 Ti SUPER 16GB)。需要用远程服务器(旧项目所在的 4090 服务器)时,ssh 和 rsync 一律走 `wangwenhan@202.38.247.58`,不走 Tailscale 地址 `100.83.164.94`(同一台机器,后者实测约 150KB/s,前者约 100MB/s)。它的显卡是共用的,命令必须显式设 `CUDA_VISIBLE_DEVICES`。 ## 开发命令 - `make sync`:用 uv 同步项目环境和依赖。 - `make fmt`:用 ruff 自动修正并格式化。 - `make test`:运行不花钱、不需要显卡的测试。 - `make check`:依次检查 lint、格式、类型、导入边界并运行测试;失败即停止。 环境怎么搭见 `research-wiki/guides/开发环境.md`。