# 任务书 07:agents(角色会话) 写 agents 模块:把一个任务交给 Pi 里的一个模型去做,等它交上来。它是程序里唯一直接跟 Pi 打交道的模块。 本任务书已由用户和监督者在 2026-10-03 一起确定。下面说的"监督者",是在参考项目目录里和用户讨论设计的另一个会话。 ## 开工前先读 - `CLAUDE.md`:全部,尤其是"罕见情况怎么办"(什么算边界)、"不设默认值"、"测试"、"Pi 与三个角色"。 - `docs/结构.md`: - 第二部分:agents 一行,"给模型看什么只归 materials""收不收一份提交,只在 submissions 一处判"两条边界; - 第三部分:账本记录第 3 种(提交、私人笔记)和第 10 种(用量); - 第六部分:只有 agents 和 plugin 能用 Pi; - 第八部分:vocab 的 `ROLES`、`SUBMISSION_KINDS`、`SUBMISSION_SCHEMAS`、`ModelUsage`。 - `docs/方法.md`: - 第三部分:角色与工具、私人笔记; - 第五部分开头:替换自己刚交的提交; - 第七部分:用量只记录,拿不到记"未知"、不记 0。 - `WORK_LOG.md`。 - 本机 Pi 0.85.1 的包和类型定义:`/home/iomgaa/.npm-global/lib/node_modules/@earendil-works/pi-coding-agent/`。只照这个版本写,不照网上别的版本的示例。 - 参考项目(只读,不要改): - `/home/iomgaa/Projects/RivalBits/src/agents/session.ts`:开会话、核对加载了什么、收用量; - `/home/iomgaa/Projects/RivalBits/src/agents/tools.ts`:各角色的工具、提交工具的两处特殊处理、shell 环境变量; - `/home/iomgaa/Projects/RivalBits/src/agents/contract.ts`。 ## 一、这个模块做什么 用实验员实现候选 c1 举例: 1. flow 交给 agents: - 用哪个模型(provider、模型名、思考档位); - 角色提示词、第一条消息(都是拼好的文字,由 materials 写); - 工作目录、会话文件存放的目录; - 这次要交哪种提交; - 几个回调:模型调某个工具时,要找谁办。 2. agents 在 Pi 里开一个子会话,按角色只发该给的工具,发出第一条消息。 3. 模型干活: - 调"查询",agents 转给回调,materials 回答; - 调"提交",agents 转给回调,flow 经 submissions 判定、写账本,把"收下 + 编号"或"退回 + 原因"交回,agents 再原样回给模型; - 模型每回复一次,agents 把这次的 token 用量交给回调。 4. 会话结束,agents 交回结局和这次会话的墙钟时间。 agents 不碰账本,不决定给模型看什么,也不判断交上来的东西好不好。 ## 二、对外提供什么 经 `src/agents/index.ts` 导出,函数名由你定。 **跑一次会话**:一个函数,输入下面这些,全部必填,可以没有的写 `null`。 - 角色、模型(provider、模型名、思考档位)。思考档位必须写明,不接受 `null`(WORK_LOG"给 agents")。 - 角色提示词、第一条消息。 - 工作目录、会话文件目录。 - 这次要交的提交种类(vocab `SubmissionKind`)。提交工具的参数形状直接用 vocab 的 `SUBMISSION_SCHEMAS`,不另写一份。 - 交上一份被收下以后,是否就结束会话: - 实验员、核验员是"是"; - 研究者是"否":一个研究者可能要交几份提案,或者看了回执发现被截断,要替换。 - 回调: - 查询:给一个编号,交回文字,或者"看不到"; - 提交:给内容和"替换哪一份"(可以没有),交回"收下 + 编号"或"退回 + 原因"; - 用量:每次回复交一份 vocab 的 `ModelUsage`; - 研究者另有"写私人笔记":笔记是账本记录,由回调去写; - 实验员另有"轻量检查":由 flow 用 tasks 和 runner 接好,交回结果文字。 - 研究者和核验员另给"能读的目录"清单。 - 一个"停下"信号:用户中途停下研究时用。 用联合类型按角色区分:研究者才有写笔记和能读的目录,实验员才有轻量检查。写不出"实验员带笔记"这种组合,就不用写检查。 **交回的结局**: - 交了:至少有一份被收下; - 没交:附原因,例如最后一条回复被输出上限截断; - 出错:模型服务报错、子会话核对不通过等,附原因; - 被停下。 每种结局都带这次会话的墙钟时间和会话文件路径。账本记录本身不带时间,flow 把墙钟时间写进"模型调用结束"那条记录(结构.md 第三部分第 10 种)。 ## 三、各角色的工具 | 角色 | 工具 | |---|---| | 研究者 | 查询、读代码、提交、写私人笔记 | | 核验员 | 查询、读代码、提交 | | 实验员 | 查询、提交、轻量检查,加上 Pi 自带的 read、write、edit、bash、grep、find、ls | 工具名、说明文字照参考项目 `tools.ts`,按下面改: - **提交**: - 去掉参考项目的 `slot` 参数:一个会话只处理一个对象,交给谁由 flow 定,模型不用再写一遍。 - `replaces` 只给研究者的提交种类;实现、修复、核验、消融实现、消融核验不能替换(方法.md 第五部分),这几种的工具参数里就没有这个字段。 - 同一次回复里调两次提交时,按顺序执行,照参考项目声明 `executionMode: "sequential"`。 - **读代码**:只能读给定目录里的文件、列目录,读之前按真实路径判断在不在目录里。模型正常探索时就会去 `ls` 上一层目录,看到别人还没公开的东西。单次读多少照参考项目的上限。 - **实验员的 bash**: - 环境变量只放 PATH、HOME、LANG、LC_ALL、TMPDIR。插件进程的环境里可能有模型密钥,不能漏进实验员的 shell。参考项目还额外设了 `OMP_NUM_THREADS` 这一类变量,那是为宿主上跑轻量检查准备的,现在轻量检查在容器里跑,不用设了。 - 不把 Pi 的会话信息放进环境变量(`exposeSessionEnvironment: false`)。 ## 四、要保留的几处特殊处理(都有真实证据) 1. **提交参数不是合法 JSON 时退回**:Pi 0.85.1 遇到写坏的工具参数,会悄悄只取前半截。参考项目 15-real-1 里,一份收尾就这样被截成 214 个字收下了。照参考项目的做法:监听流式输出,拼出参数原文,严格解析;解析失败就退回,告诉模型最常见的原因是正文里有没转义的半角双引号。参考项目提交 bcc422e。 2. **必填文字字段交 null 时退回**:Pi 校验之前会把 null 变成字符串 `"null"`。参考项目 14-real-1 里有两条挑刺回应就这样被收下了。照参考项目的 `nullFieldPaths`,在 Pi 校验之前查出来,报出字段位置。参考项目提交 fd73b78。 3. **形状不合格也要让 flow 知道**:Pi 在调提交工具之前就会把形状错误回给模型,提交回调收不到,flow 就记不下"退回"。所以 1、2 和 Pi 自己的形状校验发现的问题,都交给提交回调记一条退回,再照常回给模型。参考项目用的是 `rejectShape`;新项目里怎么接你定,但只用提交这一个回调,不另加一个。 4. **核对子会话实际加载了什么**(CLAUDE.md"Pi 与三个角色"最后一条): - 会话用的是不是指定的模型,Pi 有没有悄悄换模型; - 有没有加载扩展、技能、提示模板、`AGENTS.md` 这类上下文文件; - 系统提示词是不是我们给的那一份,有没有被追加内容; - 工具表是不是正好这几个,工具实现是不是我们给的。 本机 `~/.pi/agent/` 下有 extensions 和 skills,这件事没人操作也会发生。核对不通过,一个请求都不发,交回"出错"。 另外几点: - **用量**:模型服务没报用量(输入、缓存读、缓存写都是 0)时,这次记"测不到"并附原因,不记 0。价格记"测不到",原因照参考项目:"Pi 的价格表不是账单"。 - **截断**:最后一条回复是因为输出上限停下的、而且没交,结局"没交"的原因里写明。 - **单次回复的输出上限**:不设,用 Pi 模型配置里的值。 - **重试**:模型服务出错时用 Pi 自己的重试。用的是哪些设置,在代码里写明,不另写一层重试。 ## 五、不搬的东西 | 处理 | 内容 | |---|---| | 删除 | `shell-review.ts` 整个文件:Jev 审实验员的命令,方法里已删除 | | 不归 agents | `prompts.ts`:读提示词文件、拼第一条消息、收尾段,归 materials;agents 只收拼好的文字 | | 删除 | `context-window.ts`:估算材料会不会撑爆上下文;以后真出问题再在 materials 里加 | | 删除 | `check.ts`:轻量检查由 flow 接好作为回调传进来 | | 删除 | `redact.ts`:没查到报错信息里真带出过密钥 | | 删除 | `notesSection`:笔记怎么放进第一条消息归 materials | | 删除 | 配置里的"单次回复输出上限"和"服务没照上限截断"的检查 | | 删除 | 提交工具的 `slot` 参数和对它的检查 | | 删除 | `deps.runtime ?? ModelRuntime.create()` 这类可选参数:不设默认值,也不为测试给产品代码加参数 | 实验员会话里的 shell 命令不测 CPU,用户 2026-10-03 定(方法.md 第七部分)。 ## 六、测试 **不花钱的测试**,放进 `npm test`: - 第四部分第 1 条:用一段半角双引号没转义的参数原文喂给监听逻辑,结果是退回,原因写明。参考项目的回归测试可以借,有 slot 在前和在后两种。 - 第四部分第 2 条:必填文字字段交 null,退回并报出字段位置。 - 读代码:目录外的路径读不到,包括用 `..` 和符号链接绕出去的。 - 实验员 bash 的环境变量:插件进程里设一个假的密钥变量,shell 里看不到。 - 工具表:三种角色各自拿到的工具名正好是第三部分那几个。 **真实模型的测试**:CLAUDE.md 说真实模型链路要用真实运行来证明。另起一个命令,例如 `npm run test:pi`,不放进 `npm test`,因为要花钱、要联网。 用配置里的 glm-5.3,至少跑这几个: - 一个研究者会话交一份简单的提交,用你选的最简单的种类,第一次故意让回调退回,模型改了再交,收下; - 一个实验员会话在工作目录里写一个文件、跑一条 shell 命令,再交; - 每次回复都交出了用量,会话交回了墙钟时间; - 核对子会话加载了什么:在本机真实的 `~/.pi/agent/` 下跑一次,确认核对通过,没加载扩展和技能。 报告里贴出这几次的实际结局和用量。 **审阅**:agents 不碰账本,按原型标准,不另派子 Agent 审阅。 ## 七、规模与文档 - 预期 400–600 行,不含测试。超过 600 行就停下来检查。 - 完成后更新: - `docs/结构.md` 第八部分:补 agents 对外的函数和类型,每项一句话; - `docs/结构.md` 第七部分:agents 的预期行数改成 400–600; - `WORK_LOG.md`:在"已完成"补一条,删掉"给 agents:模型的思考档位必须写明"那条。 - 不新建 md 文件。 ## 八、验收与报告 做完后本地提交,然后在回复里交一份报告,监督者会对着实际文件逐项核对: 1. 提交号,新增和修改的文件列表,每个文件的行数,agents 的总行数。 2. `npm run check`、`npm test`、`npm run test:pi` 的完整输出。 3. 真实模型那几次会话的结局、每次回复的用量、墙钟时间。 4. Pi 自己的重试用了什么设置。 5. 你和这份任务书、结构.md、方法.md 不一致的地方,以及为什么。 6. **规则不清楚的地方**:读 CLAUDE.md、结构.md、方法.md 时,哪里让你犹豫、哪里两份文件说法不一致、哪里你是靠猜的。没有就写"没有"。没有真实证据的可能情况,在这里提一句就够,不写进 WORK_LOG。