From d09883bb9637cddc365c30258bebd4b00a85c7a9 Mon Sep 17 00:00:00 2001 From: Jasontopia Date: Sat, 26 Sep 2026 23:56:30 -0600 Subject: [PATCH 1/3] =?UTF-8?q?docs(chapters):=20=E6=8C=89=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E7=AB=A0=E7=9A=84=E5=86=99=E6=B3=95=E5=92=8C=E8=AF=91?= =?UTF-8?q?=E6=B3=95=E8=A1=A8=E9=87=8D=E5=86=99=20README=20=E4=B8=8E?= =?UTF-8?q?=E7=AC=AC=202=20=E5=88=B0=2012=20=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 GLOSSARY.md 作为全书唯一的术语译法表 - README 与各章统一用工作树、会话窗口、交付方式、yolo 开关等译法 - 删去直译腔、重复铺垫和入门用不上的实现细节,出处集中到章首说明 - 修正跨章引用第一章旧说法的地方,README 目录标题与各章标题对齐 - AGENTS.md 写作规则加一条:术语统一按 GLOSSARY.md --- AGENTS.md | 2 + GLOSSARY.md | 94 +++++++++++++ README.md | 52 +++---- chapters/02-proposing-a-task.md | 126 +++++++++-------- chapters/03-crewmates-and-worktrees.md | 93 ++++++------- chapters/04-no-mistakes-pipeline.md | 95 +++++++------ chapters/05-pr-merge-authority.md | 111 ++++++++------- chapters/06-scout-tasks-and-reports.md | 93 ++++++------- chapters/07-backlog-and-captain-decisions.md | 100 +++++++------- chapters/08-supervision-and-wakeups.md | 136 +++++++++---------- chapters/09-secondmates-and-routing.md | 115 ++++++++-------- chapters/10-afk-mode.md | 87 +++++++----- chapters/11-harness-and-runtime-backend.md | 88 ++++++------ chapters/12-relay.md | 86 ++++++------ 14 files changed, 676 insertions(+), 602 deletions(-) create mode 100644 GLOSSARY.md diff --git a/AGENTS.md b/AGENTS.md index 723d244..776480f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,8 @@ A pure documentation repo: a Chinese-language tutorial teaching newcomers how to ## Writing rules - Write in Chinese. Give the English term alongside the Chinese one on first use (船长 / captain, 大副 / firstmate, 船员 / crewmate), then use Chinese only. +- Translate every term exactly as `GLOSSARY.md` does, one rendering per term across the whole book. It is the only copy of the term table: add a new term there rather than coining a rendering inline. +- Match the plain style of `chapters/01-what-is-firstmate.md`: short plain sentences, no translationese, no filler or repeated points, sources noted once per chapter or section rather than per sentence. - Every statement about how FirstMate behaves must be traceable to an authoritative tracked file in the local FirstMate checkout, read in this priority order: `AGENTS.md` (the firstmate supervisor contract), `README.md`, the relevant file under `docs/`, then the header comment of the relevant `bin/` script. Do not describe FirstMate behavior from memory or inference - if it has no source there, leave it out. - That checkout is READ-ONLY. Never write to it or run a state-changing command in it. - `.env`, `data/`, `state/`, `config/`, `projects/`, and `.no-mistakes/` in that checkout are captain-private. Never use their contents as tutorial material, and never put a captain's local absolute path into this repo. diff --git a/GLOSSARY.md b/GLOSSARY.md new file mode 100644 index 0000000..c430e33 --- /dev/null +++ b/GLOSSARY.md @@ -0,0 +1,94 @@ +# 术语译法表 + +全书的术语统一按这张表翻译,同一个词只用一种译法。第一次出现时中英文并列,比如「工作树(worktree)」,之后只用中文。 + +命令名、脚本名、配置名、工具名一律保留原样,比如 `no-mistakes`、`yolo`、`bin/fm-spawn.sh`、`tasks-axi`、tmux。表里没有的词,按同样的原则来:中文里自然、准确,能直接说清楚做什么就不另造新词。 + +## 角色 + +| 英文原词 | 译法 | +| --- | --- | +| captain | 船长 | +| firstmate | 大副 | +| crewmate | 船员 | +| secondmate | 二副 | +| fleet / crew | 队伍 | + +## 任务与交付 + +| 英文原词 | 译法 | +| --- | --- | +| ship task | 交付任务 | +| scout task | 调研任务 | +| intake | 立项 | +| brief | 任务简报(brief) | +| Captain's intent / Firstmate spec | 船长意图 / 大副规格 | +| charter / charter brief | 职责简报 | +| backlog / backlog item | 待办队列(backlog)/ 工单 | +| In flight / Queued / Done | 在办 / 待办 / 已完成 | +| delivery mode | 交付方式 | +| delivery posture | 交付方式和合并权限 | +| yolo posture | yolo 开关 | +| merge authority | 合并权限 | +| pipeline | 质检流水线 | +| respond to gates | 流水线停下来等回复时作答 | +| finding (ask-user) | ask-user 类问题 | +| accepted design / accepted intent | 已确认的设计 | +| CI green / red | CI 全部通过 / CI 没通过 | +| arm merge poll | 开始定期查看 PR 是否已合并 | +| landed / unlanded | 已合入 / 还没合入 | +| teardown | 收尾清理 | +| promote (scout → ship) | 转成交付任务 | +| escalate | 交给大副 / 来问你(按对象说) | +| hold (captain hold) | 挂起 | +| blocker | 阻塞 | + +## 目录与会话 + +| 英文原词 | 译法 | +| --- | --- | +| worktree | 工作树 | +| primary checkout | 主仓库目录 | +| isolation assertion | 隔离检查 | +| endpoint | 会话窗口 | +| home(firstmate home) | FirstMate 目录 | +| main home | 主 FirstMate 目录 | +| harness | harness(跑船员的 agent 工具,比如 Claude Code) | +| runtime backend | 运行时后端(提供会话窗口的终端工具,比如 tmux) | +| status line / status file | 状态行 / 状态文件 | +| volatile state | 临时状态 | + +## 监督 + +| 英文原词 | 译法 | +| --- | --- | +| supervision | 监督 | +| wake | 唤醒 | +| watcher | 监控脚本 | +| daemon | 守护进程 | +| heartbeat | 心跳 | +| stale | 久无动静 | +| away mode / afk | 离席模式(afk) | +| quiet mode | 静默模式 | +| return (from afk) | 回来 | + +## 分派与 Relay + +| 英文原词 | 译法 | +| --- | --- | +| routing | 分派 | +| scope(secondmate scope) | 负责范围 | +| parent channel | 上报渠道 | +| Relay | Relay(保留英文) | +| mention | 提及 | +| pairing token | 配对令牌 | +| trusted channel | 可信渠道 | + +## 常用动词和形容词 + +| 英文原词 | 译法 | +| --- | --- | +| reconcile | 据此调整 | +| orthogonal | 无关 / 互不影响 | +| dispatch / spawn | 派出(派工) | +| verified | 验证过的 | diff --git a/README.md b/README.md index 2177d29..c09945c 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,39 @@ # Learn FirstMate -FirstMate 的中文入门教程与动手练习。 +FirstMate 的中文入门教程,每章配一个动手练习。 -## 这个仓库是什么 +## 这是什么 -FirstMate(大副 / firstmate)是一个「agent distro」- 一个可移植的指令、技能、工具、策略与状态约定的目录,它把一个通用的编码 agent 变成一个专门的 agent。你只跟一个 agent 对话,它替你调度和监督一整支 agent 船员 / crewmate 队伍,最后把可以合并的 PR、已批准的本地合并,或者独立的调查报告交到你手上。 +FirstMate 是一个「agent 发行版」(agent distro):一个目录,里面装着指令、技能、脚本、规则和状态约定,能把一个通用的编码 agent 变成专门带队干活的 agent。你只跟一个 agent 说话,它就是大副 / firstmate。大副替你派出和监督一队船员 / crewmate,最后交给你可以合并的 PR、你批准过的本地合并,或者一份调研报告。 -这个仓库不是 FirstMate 本身,而是学它的地方。它把 FirstMate 的每一项功能拆成一节,每节先讲清楚这项功能解决什么问题、它的规矩是什么,再给一个你可以真的让 FirstMate 跑一遍的练习。目标很直接:读完并做完之后,你能把这些做法直接用在自己的真实项目上。 +这个仓库不是 FirstMate 本身,是学它用的。每章讲一项功能:它解决什么问题、有哪些规矩,然后给一个你可以在自己的 FirstMate 上跑一遍的练习。读完做完,你就能在自己的项目上照着用。 -教程里所有关于 FirstMate 行为的陈述,都以 FirstMate 仓库里被版本追踪的权威文件为依据 - `AGENTS.md`(大副的职责契约)、`README.md`、`docs/` 下的文档,以及 `bin/` 下脚本的头部注释。每一章都会指出对应的出处,方便你自己回去核对。 +教程里关于 FirstMate 的每一条说法,都能在 FirstMate 仓库里找到出处:`AGENTS.md`(大副的工作守则)、`README.md`、`docs/` 下的文档,以及 `bin/` 下脚本开头的注释。每章开头会说明这一章依据的是哪些文件。 -## 怎么用它学习 +## 怎么学 -每一节都是同一个节奏,两步: +每章两步: -1. **读一章。** 一章只讲一个主题,篇幅控制在一次能读完。遇到不确定的机制性说法,顺着章节里给出的出处回去读原文 - 这本身也是使用 FirstMate 的基本功。 -2. **做一次练习。** 每章末尾有「动手练习」,给出你(船长 / captain)可以照着对大副说的中文原话,以及预期会看到什么、怎么判断做成了。练习都是只读或安全可逆的,不会改动别人的仓库,也不会做不可回退的操作。 +1. **读。** 一章一个主题,一次能读完。哪里拿不准,就去翻这一章列出的原文。 +2. **练。** 每章最后有「动手练习」,给出你(船长 / captain)可以直接对大副说的话,以及应该看到什么、怎么检查。练习都是只读或可以撤回的,不会改别人的仓库,也不会做收不回的操作。 -建议按顺序走。前面几章建立的词汇和心智模型,后面的章节会一直用到。 +最好按顺序读,后面的章节会用到前面讲过的概念。全书的术语译法统一收在 [术语译法表](GLOSSARY.md) 里。 -## 学习路线图 +## 章节 -章节顺序是按「先懂全局,再懂每一关,最后懂长期运营」排的:第 1 章先给你整幅地图;第 2 到 5 章沿着一件活的真实旅程往下走,每章钉住旅程中的一关;第 6 到 8 章是从单件活扩展到一支队伍要处理的事 - 知识产出、排队决策、无人值守的监督;第 9 到 11 章是规模、离席、以及「到底是哪个工具在跑你的船员」这类进阶话题;第 12 章单独讲 Relay,它默认关着,不开就完全不影响你。 +第 1 章先讲全貌。第 2 到 5 章跟着一件活往下走,每章讲其中一步。第 6 到 8 章讲活多了以后的事:调研报告、待办队列、监督。第 9 到 11 章讲进阶话题:二副、离席、用什么工具跑船员。第 12 章讲 Relay,它默认关着,不打开就不影响你。 -| # | 章节 | 这章要解决什么问题 | 状态 | -| -- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---- | -| 1 | [FirstMate 是什么:三方分工与一件活的旅程](chapters/01-what-is-firstmate.md) | 船长、大副、船员各自负责什么,一件活从人开口到落地合并要过哪几关,以及为什么要这样切分。 | 已写 | -| 2 | [如何提出一件活](chapters/02-proposing-a-task.md) | 一句话的请求怎么变成一件有明确项目、明确交付形态、明确验收边界的活,以及你怎么说能少走弯路。 | 已写 | -| 3 | [船员与隔离工作副本](chapters/03-crewmates-and-worktrees.md) | 每个船员为什么要在自己的一份 git 工作副本里干活,并行的多件活为什么不会互相踩,出事时怎么看。 | 已写 | -| 4 | [no-mistakes 质检流水线](chapters/04-no-mistakes-pipeline.md) | 交付前那道自动化关卡都查什么,它自己修什么,什么情况下会停下来问人,以及问到人时该怎么回答。 | 已写 | -| 5 | [PR 与合并权限](chapters/05-pr-merge-authority.md) | 为什么默认必须你点头才合并,yolo 姿态放开了什么、没放开什么,以及红色 CI 为什么依然不能合。 | 已写 | -| 6 | [scout(侦察)任务与调查报告](chapters/06-scout-tasks-and-reports.md) | 什么时候该派侦察而不是直接开工,报告长什么样、存在哪里,以及一份报告为什么不等于动工的授权。 | 已写 | -| 7 | [backlog(工单)与船长决策](chapters/07-backlog-and-captain-decisions.md) | 工单队列怎么记录在办和待办的活,一个等你拍板的决策为什么本身也是一张工单,以及它怎么被关闭。 | 已写 | -| 8 | [supervision(监督)与唤醒机制](chapters/08-supervision-and-wakeups.md) | 大副怎么在不烧 token 的前提下盯住整支队伍,什么事件会把它叫醒,什么时候才该轮到你被打扰。 | 已写 | -| 9 | [二副 / secondmate 与分工路由](chapters/09-secondmates-and-routing.md) | 队伍变大之后怎么按领域拆出常驻的二副,一件活按什么规则路由给谁,以及哪些活必须留在主家。 | 已写 | -| 10 | [afk 离席模式](chapters/10-afk-mode.md) | 你离开键盘期间谁接手监督,离席期间它敢自己做什么、必须留着等你什么,以及你回来时怎么交接回来。 | 已写 | -| 11 | [运行时与 harness 选择](chapters/11-harness-and-runtime-backend.md) | 哪个工具在跑你的船员,运行时后端与 harness 怎么配,以及什么时候值得为一件活单独指定。 | 已写 | -| 12 | [Relay](chapters/12-relay.md) | 默认关闭的公开提及接入,开启它意味着授权了什么,以及哪些动作它永远不会自动替你做。 | 已写 | +| # | 章节 | 讲什么 | +| -- | ------------------------------------------------------------ | -------------------------------------------------------------------------- | +| 1 | [FirstMate 是什么](chapters/01-what-is-firstmate.md) | 船长、大副、船员各管什么,四条硬规矩,一件活从提出到合并要经过哪几步。 | +| 2 | [怎么提出一件活](chapters/02-proposing-a-task.md) | 一句话的要求怎么变成项目、任务类型、交付方式和验收标准都明确的任务。 | +| 3 | [船员与工作树](chapters/03-crewmates-and-worktrees.md) | 为什么每个船员有自己的工作树,几件活同时做为什么不打架,出事时怎么查。 | +| 4 | [no-mistakes 质检流水线](chapters/04-no-mistakes-pipeline.md) | 流水线查什么、自己修什么,什么时候停下来问人,问到时大副怎么答。 | +| 5 | [PR 与合并权限](chapters/05-pr-merge-authority.md) | 为什么默认要你同意才合并,`yolo` 开关放开了什么,CI 没通过为什么不能合。 | +| 6 | [调研任务与报告](chapters/06-scout-tasks-and-reports.md) | 什么时候该先调研,报告放在哪里,为什么报告本身不等于同意动手改。 | +| 7 | [待办队列与船长决策](chapters/07-backlog-and-captain-decisions.md) | 待办队列怎么记活,等你拍板的问题为什么也是一张工单,你的回答怎么关掉它。 | +| 8 | [监督与唤醒](chapters/08-supervision-and-wakeups.md) | 大副怎么在不耗 token 的情况下盯住队伍,什么事会叫醒它,什么事才来找你。 | +| 9 | [二副与分派](chapters/09-secondmates-and-routing.md) | 队伍大了以后怎么按领域设常驻的二副,一件活怎么分给谁,哪些活留在大副这边。 | +| 10 | [离席模式(afk)](chapters/10-afk-mode.md) | 你走开时谁来监督,大副能自己做什么、必须等你什么,你回来时怎么交接。 | +| 11 | [harness 与运行时后端](chapters/11-harness-and-runtime-backend.md) | 船员是用什么工具、在什么终端里跑的,默认怎么选,什么时候值得单独指定。 | +| 12 | [Relay](chapters/12-relay.md) | 默认关着的公开提及功能,打开它等于同意了什么,哪些事它永远不会自己做。 | diff --git a/chapters/02-proposing-a-task.md b/chapters/02-proposing-a-task.md index 48005b3..8cc58ed 100644 --- a/chapters/02-proposing-a-task.md +++ b/chapters/02-proposing-a-task.md @@ -1,116 +1,114 @@ -# 第 2 章:如何提出一件活 +# 第 2 章:怎么提出一件活 ## 这一章讲什么 -第 1 章讲了三方分工和一件活从开口到落地合并要过的每一关。这一章把镜头拉近,只看第 0 关到第 1 关之间发生的事:你说的一句话,怎么被大副 / firstmate 变成一件可以派出去、也可以验收的活。 +第 1 章讲了一件活从提出到合并的全过程。这一章只看其中第 0 步到第 1 步:你说的一句话,大副 / firstmate 怎么把它变成一件能派出去、也能验收的任务。 -这一步定的东西,会决定后面每一关怎么走:这件活算谁的项目、产出是一份报告还是一个项目改动、走哪条交付路径、谁有权点头合并、以及流水线最后拿什么标准来判断这件活算不算做成了。这一章只讲这一步,不重复第 1 关之后的旅程 - 那是第 1 章已经走过的。 +这一步要定下好几件事:属于哪个项目,要一份报告还是要改项目,用哪种交付方式,谁有权同意合并,以及做成什么样才算做完。后面每一步都按这里定下的来。 -本章依据的权威文件,主要是 `AGENTS.md` 第 7 节「Task lifecycle」下的「Intake and authority」小节,按需引用 `bin/fm-brief.sh` 和 `bin/fm-project-mode.sh` 的头部注释。每个小节会指出具体出处。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 7 节的「Intake and authority」小节和第 11 节,以及 `bin/fm-project-mode.sh`、`bin/fm-brief.sh` 开头的注释。 -## 大副在这一步要定三件事 +## 大副要定的几件事 -一句话的请求进来之后,大副不会立刻去改代码,而是先做判断。判断结果落在三件事上: +### 属于哪个项目 -1. **这是谁的项目。** -2. **产出形态是什么** - 报告还是项目改动,走哪条交付路径,谁有权合并。 -3. **验收边界写在哪里** - 也就是这件活做成什么样才算做成了。 +每次你提要求,大副都会重新判断它属于哪个项目: -下面依次展开。 +1. 你点名了项目,就用它。 +2. 这句话明显是接着上一件事说的,就沿用上一件的项目。 +3. 都没有,就拿你的话去比对已登记的项目、正在做的活,以及项目的代码和 README。 -### 认项目 +只有一个项目明显对得上时,大副直接开始,并用平常的话告诉你选的是哪个。对得上好几个,或者一个都对不上,它就问你一个简短的问题,不会自己猜一个。 -`AGENTS.md` 第 7 节「Intake and authority」原文:「Resolve the project independently for every request. An explicit project wins, a clear follow-up inherits its referent, and otherwise match the request against the registry, work under way, and project code or README. Proceed on one confident match while naming the project in plain language; ask one concise question when multiple or no projects plausibly match.」 +### 交付任务还是调研任务 -翻成大白话:每一次请求都要独立判定项目归属,判定顺序是三层 - 你明确点了名的项目优先;如果这句话是接着上一句的追问,就沿用上一句认定的项目;两者都没有的话,才去拿这句话跟项目登记表、正在办的活、以及项目自己的代码和 README 做比对。只要有一个有把握的匹配,大副就直接推进,并且用平实的话说出它选了哪个项目 - 不会不声不响地就开始干。只有匹配不上或者匹配出好几个的时候,它才会停下来问你**一个**简洁的问题,而不是自己猜一个就上。 +- **交付任务(ship)** 是默认的,产出是对项目的改动,按选定的交付方式交付。 +- **调研任务(scout)** 产出的是一份报告,放在 `data//report.md`,不开 PR。适合调查、诊断、规划、复现问题、审计这类工作,前提是你明确要一份单独的报告或设计,或者还有没弄清的问题,可能影响要不要做、做成什么样。 -### 定交付形态:ship 还是 scout +要注意:一个诊断请求、一份报告、一条建议,都只是参考信息,不等于同意改代码。你看完调研报告,还要再说一句「照着改」,才会进入交付。反过来,如果已有的报告或证据已经能回答你的问题,大副会直接告诉你,不会再派一次调研。第 6 章会细讲调研任务。 -同一小节接着定义了两种产出形态:「Ship is the default and produces a project change through the selected delivery mode... Scout produces knowledge in `data//report.md`, never a PR, and is appropriate for investigation, diagnosis, planning, reproduction, or audit work when the captain explicitly requests a separate knowledge or design deliverable or unresolved uncertainty could materially change whether or what to build.」 +### 交付方式和合并权限 -**ship**(出活)是默认,产出是一个真实的项目改动,走你选定的交付路径。**scout**(侦察)产出的是知识,落在 `data//report.md`,永远不开 PR - 适合调查、诊断、规划、复现、审计这类工作,前提是你明确要一份单独的知识或设计产出,或者眼下还有一处不确定会实质影响「要不要做、做成什么样」。 +每件交付任务的交付方式和 `yolo` 开关,都在立项时就定好。派船员的 `bin/fm-spawn.sh`、把调研转成交付的 `bin/fm-promote.sh`,都要求明确传入这两个值,不会自己去猜。 -这里有一条边界值得单独说清楚:一份诊断报告、一个结论、一条建议,本身不等于「去改代码」的授权。同一小节写得很直接:「A diagnostic request, report, recommendation, or implementation-ready finding is evidence, not authorization to change code.」你读完一份侦察报告之后,还是得再说一句「照着做」,活才会真正进入 ship。反过来,如果一个已有的报告或既定证据已经能回答你的问题,大副也不会为了走流程再派一次侦察去重新调查一遍同一件事。 +交付方式有三种: -### 定交付路径:三选一,再加一个正交的合并姿态 +- **no-mistakes**:完整的质检流水线,最后开 PR,等有合并权限的人同意。这是默认的。 +- **direct-PR**:不跑流水线,直接推送、开 PR,等有合并权限的人同意。 +- **local-only**:只在本地分支上做,不推送、不开 PR。有合并权限的人同意后,大副用受保护的本地快进合并把它合进来。 -同一小节接着定下更具体的东西:「Resolve every ship task's concrete delivery mode and yolo merge posture at intake. Pass the mode explicitly to the brief, and pass both values explicitly to the spawn and any scout promotion; each command refuses to guess the values it consumes. A current explicit captain instruction wins; otherwise the project's registry entry is the captain's standing posture, and dropping below its rigor needs a reason you can state.」 +`yolo` 开关跟交付方式无关,只管合并权限:关着,每次合并都要你同意;开着,大副可以自己合并 CI 全部通过、而且没超出任务范围的活。第 5 章会细讲。 -也就是说,交付路径和合并姿态这两个值,必须在立项这一刻就定死,而不是留到后面船员干活时再看着办 - 派工命令 `bin/fm-spawn.sh` 和把侦察升级成出活的 `bin/fm-promote.sh` 都要求这两个值显式传入,从不自己去猜。三条交付路径,出处是 `bin/fm-project-mode.sh` 的头部注释: +这两个值按这个顺序定:你这次明确说了的,听你的;你没说,就用这个项目登记时定的设置;大副要用比登记设置更宽松的方式,必须说得出理由。项目没登记过,或者根本没有项目登记表,就用最严格的一档:`no-mistakes` 加 `yolo` 关着,并告诉你这个项目还没登记。 -- **no-mistakes**:完整质检流水线 - 出活 -> `/no-mistakes` -> PR -> 配置好的合并权限方点头(默认)。 -- **direct-PR**:不跑流水线,出活后直接用 gh-axi 推送并开 PR,等配置好的合并权限方点头。 -- **local-only**:只在本地分支上出活,不推送、不开 PR,等配置好的合并权限方批准后,由大副走一条有守卫的本地快进合并。 +### 验收标准:写进任务简报 -`yolo`(合并姿态)是一个跟交付路径正交的开关,只管一件事 - 谁有权点头合并:关着,你批准每一次合并;开着,大副对绿色、且在授权范围内的活自己合并。这两个值**怎么定下来**,也有优先顺序:你当下明确说的话最优先;如果你没明确说,就用这个项目在登记表里的标准姿态;如果要比登记表里定的更松,大副得能说出一个理由。一个没登记过、或者压根没有登记表的项目,默认落在最严格的一档 - `no-mistakes` 加 `yolo` 关着,登记的缺口本身会上报给你。 +最后要定的是做成什么样才算做完。这一条写进派工前的任务简报(brief),分成两块: -### 定验收边界:写进简报的「船长意图」 +- **船长意图**(Captain's intent):你的原话和你提的限制,加上读懂这句话需要的背景。如果你提到了某份报告、某个决定或某个 PR,要把里面的具体内容写进来,不能只留一个链接。 +- **大副规格**(Firstmate spec):具体怎么做。这一块不算船长意图。 -前面两件事定完项目和形态之后,还差一件事:这件活做成什么样才算做成了。这个边界不是靠大副事后回忆你说过什么,而是写进派工前的任务简报(brief)里。`bin/fm-brief.sh` 的头部注释规定了简报里必须有的两段,且分开写: +两块分开写,是因为质检流水线拿「船长意图」当验收标准。所以大副不会在这一块里把你的要求扩大成一个笼统的目标。派工命令也会拒绝还有空白没填的简报。 -一段是 `## Captain's intent`(船长意图)- 原文是「the captain's own ask plus the context needed to read it, including the substance of any report, decision, or PR the ask refers to, without added speaker labels or direct address」,也就是你自己的原话,加上读懂这句话所必需的上下文;如果你的话里提到了某份报告、某个决定、或者某个 PR 的具体条目,那些条目的**实际内容**也要写进来,而不是只留一个指针。另一段是 `## Firstmate spec`(大副规格)- 施工指令,这一段从来不算船长意图。 +## 怎么说能少走弯路 -这两段为什么必须分开,第 1 章已经点过一句:质检流水线会把前一段当验收标准来读。这里补一句出处:`bin/fm-brief.sh` 的头部注释指出,`bin/fm-dod-lib.sh` 拥有把这两段喂给 `no-mistakes` 的 `--intent` 契约那份定义;派工命令还会拒绝任何留有占位符没填的简报,以及任何在「船长意图」段开头就带上对船长称呼或直接喊话的写法。 +把上面几件事反过来看,就是你这边能做的: -## 你怎么说,能少走弯路 - -把上面三件事倒过来看,就是你这边可以主动做的事: - -- **想省一次追问,就把项目名字说出来,或者让这句话清楚地接着上一句。** 大副只在匹配不上或者匹配出好几个时才会停下来问你;一个明确点名的项目,或者一句清楚的追问,直接省掉这一步。 -- **想清楚这活是要一份报告还是要真的改东西,就把这句话说明白。** 「帮我看看」和「去把它实现了」在大副眼里是两条完全不同的路 - 一个落 `data//report.md`,一个落项目改动加 PR。如果你自己也还没想清楚该不该做,大副会问你一句,而不是替你先斩后奏。 -- **如果你想让这件活偏离这个项目登记的标准交付路径或合并姿态,就明说,并给个理由。** 沉默会被当成「用登记表里的标准姿态」;一句「这次直接开 PR 就行,不用走完整流水线」加一句理由,才会被当成有效的偏离。 -- **如果你的话里引用了一份报告、一个决定、或者一个 PR 的某几条,把那几条的实际内容说出来,而不是只说「照着那份东西做」。** 验收标准只认写进简报里的话,一个孤零零的指针在流水线眼里等于没有上下文。 -- **涉及合并、破坏性、不可逆、涉及安全的选择,点名说清楚要做的具体动作。** 这类选择永远停在你这里,一句宽泛的「你看着办」换不来授权 - 这也是第 1 章规矩二背后同一条道理在立项这一步的体现。 +- **说出项目名,或者让这句话明显接着上一件事。** 这样大副不用再问你是哪个项目。 +- **说清楚要报告还是要改东西。** 「帮我看看」和「去把它实现了」是两条路:一个产出 `data//report.md`,一个产出项目改动和 PR。你自己也没想好要不要做,大副会问你,不会先动手。 +- **想用跟项目登记设置不同的交付方式或合并权限,就明说,并给个理由。** 你不说,就按登记的来。比如「这次直接开 PR,不用走完整流水线」,再加一句为什么。 +- **提到某份报告、某个决定或某个 PR 时,把相关的内容说出来。** 验收只认写进简报的内容,只给一个链接,流水线看不到背景。 +- **涉及合并、破坏性、不可逆或安全的操作,说清楚具体要做什么。** 这类事最后总要你来定,一句「你看着办」不算授权。 ## 动手练习 -下面两个练习都是**只读**的:只是让大副读取信息、做推理并汇报,不会创建工单、不会派出真正的船员、不会改动任何被版本追踪的文件,也不会做任何不可回退的操作。你随时可以停下。 +两个练习都是只读的:只让大副读资料、推演、汇报,不登记工单,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习 1:拿一句故意含糊的话试一次分类 +### 练习 1:用一句含糊的话测试立项判断 对大副说: -> 大副,我要试一下你立项时怎么判断,不是真的要你干活。假设我只说了一句很含糊的话:「东西好像有点不对劲,帮我看看」- 没点名项目,也没说清楚是要一份报告还是要真的动手改。请你按平常立项的步骤,讲一遍你会怎么判断项目归属、会把它归成 ship 还是 scout、以及你会不会先停下来问我一个问题 - 如果会,那个问题是什么。这只是一次推演,不要真的创建工单,也不要派任何船员。 +> 大副,我想看看你立项时怎么判断,不是真要你干活。假设我只说了一句:「东西好像有点不对劲,帮我看看」,没说是哪个项目,也没说要报告还是要改。请按你平时立项的步骤讲一遍:你怎么判断属于哪个项目,归成交付任务还是调研任务,会不会先问我,问的话问什么。只推演,不要登记工单,也不要派船员。 -**预期会看到什么** +**你应该看到** -- 它会指出这句话在项目归属上是模糊的,因为既没有点名项目,也不是接着某个具体项目的追问。 -- 它会说因为无法确认唯一匹配,按规矩会停下来问你**一个**简洁的问题,而不是自己挑一个项目就往下走。 -- 它会指出「帮我看看」这类措辞本身也没有说清是要报告还是要真的动手改,所以形态判断(ship / scout)同样需要先问清楚,或者至少会指出这处不确定性。 -- 它会点出出处:`AGENTS.md` 第 7 节 "Intake and authority"。 +- 它指出这句话判断不出项目:没点名,也不是接着哪个项目说的。 +- 它说会先问你一个简短的问题,而不是自己挑一个项目往下做。 +- 它指出「帮我看看」也没说清要报告还是要改,这一点同样要先问清楚,或者至少点出来。 +- 说明出处:`AGENTS.md` 第 7 节「Intake and authority」。 -**怎么判断做成了** +**怎么检查** -三个都满足才算:它讲清楚了「为什么这句话触发追问」而不是替你瞎猜一个项目就往下推进;它给出了可核对的出处;以及 - 最关键的 - 它**没有创建任何工单,也没有派出任何船员**,你可以直接看一眼有没有新开的会话窗口来确认。 +- 它讲清了为什么要先问你,而不是替你猜一个项目。 +- 没有登记工单,也没有多出新的船员窗口。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -文件层面的验证办法跟第 1 章一样,**前后对比**:提问之前在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见,会话期间写这些不算数。 +如果它没问就自己挑了项目往下做,或者把这句话当成了动手改的授权,就算没做成。 -如果它没问就自己挑了个项目往下走,或者把这句含糊的话直接当成了 ship 的施工授权,那就是没做成。 +### 练习 2:对照一件做完的活,看它立项时定了什么 -### 练习 2:把一件已经落地的活,拆回它的立项决定 - -练习 1 练的是推演一件假想的活,这次练的是核对一件**真实发生过**的立项决定。这个仓库本身就有现成的材料:`AGENTS.md` 第 1 节的四条硬规矩讲过,一件活从开口到合并的每一步都要留痕,第 1 章写出来、合并成 PR 的那件活,以及给这个仓库加上真实 CI 检查的那件活,都是可以拿来核对的真实例子。 +练习 1 是假设的情况,这次看一件真实做完的活。这本教程所在的仓库就有现成的例子:写第一章的那件活,以及给仓库加 CI 检查的那件活。 对大副说: -> 大副,回头看这个仓库已经做完的一件活 - 比如写出第一章《FirstMate 是什么》那次,或者给这个仓库加真实 CI 检查那次。讲清楚立项那一刻定下的三件事:这件活当时是怎么认定归属这个项目的,它被归成了 ship 还是 scout,走的是 no-mistakes、direct-PR 还是 local-only,yolo 合并姿态开没开,以及当初写进简报「船长意图」那段的验收边界大致是什么。如果这些记录已经不在了,就直接说清楚为什么找不到,而不是编一个说法。不要改任何文件,也不要派任何船员,只讲这件已经发生过的事。 - -**预期会看到什么** +> 大副,挑这个仓库做完的一件活,比如写第一章《FirstMate 是什么》那次,或者给仓库加 CI 检查那次。讲讲立项时定下了什么:怎么判断它属于这个项目,是交付任务还是调研任务,用的是 no-mistakes、direct-PR 还是 local-only,`yolo` 开没开,船长意图里的验收标准大致是什么。如果记录已经找不到了,就直接说找不到、为什么,不要编。不要改文件,也不要派船员,只讲已经发生的事。 -- 它讲的是这两件真实活里的一件,带得出你能自己核对的东西 - 比如对应的 PR 链接或者提交记录,而不是抽象地复述规则。 -- 它会明确区分项目归属、ship/scout 判断、交付路径、`yolo` 姿态、验收边界这几件事分别是什么,而不是笼统地说「按规矩走的」。 -- 如果当初的工单笔记或简报因为清理、归档、或者只保留近期 Done 记录这类原因已经找不到完整原文,它会老实说找不到、说清楚可能的原因,而不是替你编一段听起来合理的「船长意图」。 +**你应该看到** -**怎么判断做成了** +- 讲的是其中一件真实的活,带着你能自己核对的信息,比如 PR 链接或提交记录。 +- 分开讲清项目、任务类型、交付方式、`yolo` 开关、验收标准这几项,而不是笼统一句「按规矩走的」。 +- 如果当时的工单记录或简报已经找不到(比如清理过,或者已完成的工单只保留最近几条),它会直接说找不到并说明可能的原因,不会编一段「船长意图」出来。 -两条都满足才算:它讲的是可核对的真事,不是泛泛的规则复述;以及 - 找不到记录时它承认找不到,而不是编造。 +**怎么检查** -文件层面的判断跟练习 1 一样:提问前后各跑一次 `git status --short` 和 `git diff HEAD`,两次输出应该一样,并且没有新开的船员会话窗口。 +- 讲的是能核对的真事,不是复述规则。 +- 找不到记录时承认找不到。 +- 提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样,也没有多出新的船员窗口。 -如果它把这次问答本身讲成了一件新的活并打算立项,或者对记录缺失避而不谈、直接编了一段验收边界出来,那就是没做成。 +如果它把这次提问当成一件新活去立项,或者在找不到记录时编了一段验收标准,就算没做成。 ## 下一章 -第 3 章讲**船员与隔离工作副本**:每个船员为什么要在自己的一份 git 工作副本里干活,并行的多件活为什么不会互相踩,出事时怎么看。 +第 3 章讲船员与工作树:为什么每个船员有自己的工作树,几件活同时做为什么不打架,出事时怎么查。 diff --git a/chapters/03-crewmates-and-worktrees.md b/chapters/03-crewmates-and-worktrees.md index 9e34c85..398dc91 100644 --- a/chapters/03-crewmates-and-worktrees.md +++ b/chapters/03-crewmates-and-worktrees.md @@ -1,90 +1,91 @@ -# 第 3 章:船员与隔离工作副本 +# 第 3 章:船员与工作树 ## 这一章讲什么 -第 1 章提到过一句话:船员是大副派出去的自主 agent,「一个船员一件活,每人一个可见的会话端点...和一份独立的 git 工作副本」。第 2 章讲完了一句话怎么变成一件可以派出去的活。这一章把镜头再往后挪一格,落在第 1 章流程图里「派出船员到隔离工作副本」这一关本身:为什么船员一定要在自己的一份 git 工作副本里干活,并行的多件活为什么不会互相踩,以及出事的时候,你和大副怎么去看一件活现在是什么状态。 +第 1 章说过,每个船员 / crewmate 有自己的会话窗口和自己的工作树(worktree)。第 2 章讲了一句话怎么变成一件能派出去的活。这一章讲第 1 章的「第 2 步:派船员到独立的工作树」:为什么每个船员要有自己的工作树,几件活同时做为什么不打架,以及出了问题怎么查一件活现在的情况。 -本章依据的权威文件:`AGENTS.md` 第 1 节「硬规矩」、第 2 节「布局」、第 7 节「Intake and authority」「Dispatch and supervision handoff」两个小节,以及第 11 节(船员自验隔离)。每个小节会指出具体出处。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 1 节(硬规矩)、第 2 节(目录结构)、第 7 节的「Intake and authority」和「Dispatch and supervision handoff」两个小节,以及第 11 节。 -## 为什么每个船员要有自己的一份工作副本 +## 为什么每个船员要有自己的工作树 -先看第 1 节硬规矩第一条,原文是:「Never write to a project. Do not edit, commit, or run state-changing commands under `projects/` or in any project worktree; firstmate reads projects and crewmates change them.」 +硬规矩一说,大副 / firstmate 不改项目:不在 `projects/` 下或任何项目工作树里编辑、提交,也不运行会改变状态的命令。大副只读项目,改项目的是船员。 -翻成大白话:大副从不写项目 - 不在 `projects/` 或任何项目工作副本下编辑、提交、或运行任何会改变状态的命令。这句话的后半句才是这一章的关键:大副读项目,**船员改项目**。真正动手改代码的,一直是船员,而且是在自己的工作副本里改。 +`projects/` 里放的是各个项目的克隆,对大副来说是只读的(硬规矩一里那条需要你当场批准的例外除外)。真正被改动的,是为某一件活单独准备的工作树。 -第 2 节「布局」里给 `projects/` 下的定义呼应了这一点:「`projects/` contains clones that are read-only to firstmate except under hard rule 1's concrete captain-approved project operation exception.」`projects/` 里放的是一批克隆,对大副来说是只读的,只有硬规矩一里那条窄到具体某个项目、某个操作、且船长当场批准的例外能打破这一点。也就是说,`projects/` 本身对大副而言接近一份只读的项目登记,真正会被写入、被改动的,是从这些克隆里派生出来、专门为某一件活准备的工作副本。 +派船员只能用 `bin/fm-spawn.sh`。派工时会做隔离检查:船员拿到的必须是一个真正独立的工作树,不能是大副自己用的主仓库目录。检查不通过,这件活就停下,不会退而求其次在主仓库目录里干。这样,船员的改动和大副用的主仓库目录分在两处,大副不会一不留神和船员改到同一个地方。 -这份专用工作副本从哪来,第 7 节「Dispatch and supervision handoff」写得很具体:「Spawn only through `bin/fm-spawn.sh` after the profile and backend checks in section 4. The spawn must resolve a genuine isolated task worktree distinct from the primary checkout; a failed isolation assertion stops the task.」 +船员启动后还会自己再查一遍(`AGENTS.md` 第 11 节,由 `bin/fm-brief.sh` 生成的简报模板要求):当前目录和 git 顶层目录都指向这件活专用的工作树,而不是主仓库目录。写这一章的船员,动笔前做的第一件事就是这个:运行 `pwd -P` 和 `git rev-parse --show-toplevel`,确认两者一致,而且是一个独立的工作树,然后才建分支、开始写。 -派工只能通过 `bin/fm-spawn.sh`,而且这一步必须解析出一份**真正独立于主检出目录**的隔离任务工作副本 - 隔离断言失败就直接中止这件活,而不是退而求其次改在主检出里干。这条断言不是走个过场:它把「船员的改动」和「大副操作的那份主检出」在物理上分开,硬规矩一「大副从不写项目」才有地方落地 - 如果船员的工作副本就是主检出本身,那大副稍不留神就会和船员的活撞在同一份工作树上。 +## 几件活同时做,为什么不打架 -第 1 章还提过一句,船员开机后会自己再验一遍这份隔离(`AGENTS.md` 第 11 节,由 `bin/fm-brief.sh` 的简报脚手架落实):确认当前目录和 git 顶层目录都指向那份一次性的任务工作副本,而不是大副操作的那个主检出。写这一章的这个船员,动笔前做的第一件事就是这个:跑了一遍 `pwd -P` 和 `git rev-parse --show-toplevel`,确认两条命令报出来的路径一致,并且是一份跟主检出分开的一次性工作副本,才敢开分支、动笔写文件。这不是一句抽象的规矩,是这份文件本身写出来之前,真实发生过的一步。 +`AGENTS.md` 第 7 节「Intake and authority」规定:几件活改到同一个文件或同一块代码,只算一个风险提示,不是自动排队的理由。只要每件活都能单独实现、单独验证,选定的交付方式也能处理普通的 rebase 和冲突,大副就马上把它们都派出去,不限同时做几件。 -## 为什么并行的多件活不会互相踩 +只有下面这些情况才需要一件一件来: -工作副本隔离带来的直接好处,是并行不用靠「大家小心点」来维持。第 7 节「Intake and authority」原文: +- 真正的依赖:一件活的结果是另一件活的前提。 +- 两件活要读写同一份外部的、会变的状态。 +- 两个互不兼容的迁移同时在跑。 +- 其他让分头推进或事后合并变得不安全的具体情况。 -「Treat file or subsystem overlap as a risk signal rather than an automatic reason to wait, and dispatch isolated work immediately with no concurrency cap when each change can be independently implemented and validated and the selected delivery path can reconcile ordinary rebases or conflicts. Serialize only for a true semantic dependency, shared mutable external state, incompatible concurrent migration, or another concrete condition that makes independent progress or reconciliation unsafe; same-file editing alone is insufficient, and genuine blockers remain durable.」 +光是改到同一个文件,不够成为排队的理由。 -翻成大白话:文件或子系统重叠,只被当成一个**风险信号**,不是一个自动要排队等待的理由。只要每件活能够独立实现、独立验证,而且选定的交付路径能收敛得了普通的 rebase 或冲突,大副就会立刻派出隔离的活,不设并发上限。只有出现真正的语义依赖、共享的可变外部状态、互不兼容的并发迁移,或者其他让独立推进或事后合并变得不安全的具体条件,才需要串行 - 单纯「改的是同一个文件」不足以构成串行的理由,而真正的阻塞会一直挡在那里,不会不了了之。 +这条规矩能成立,靠的就是工作树隔离。两个船员在各自的分支、各自的工作树里改同一个文件,干活时互相看不见,也不会覆盖对方的改动。两份改动要到合并时才碰面,变成一次普通的合并冲突或 rebase,跟平时几个人用 git 协作一样处理。 -这条规则之所以成立,正是因为工作副本隔离已经把「同一个文件被两件活同时改」这件事,从一个运行时问题变成了一个合并时问题。两个船员各自在自己的分支、自己的工作副本里改同一个文件,干活期间根本不会碰面 - 谁也看不见谁的改动,谁也不会覆盖谁的改动。这两份改动只会在合并这一步才第一次相遇,变成一次普通的合并冲突或者一次 rebase,跟平时多人协作用 git 处理冲突没有本质区别。真正会挡住并行的,从来不是「文件重叠」本身,而是重叠背后有没有第 7 节点名的那几种更硬的东西:一件活的结果是另一件活的前提(语义依赖)、两件活要共同读写同一份跑在别处的可变状态、或者两个互相不兼容的迁移同时在跑。 +## 出事时怎么查 -## 出事时怎么看 +工作树隔离还有一个好处:查一件活现在的情况,不需要什么专门工具。 -这份隔离还带来一个经常被忽略的好处:**看一件活现在是什么状态,不需要任何专门工具**。 +船员一直在一个普通的 git 工作树里干活,大副又只读不改。所以一件活在任何时刻的真实情况,都完整留在它自己的工作树里:分支叫什么,有没有提交,提交改了什么,离要合并的目标差多少。你或者大副想知道一件活走到哪了,用普通的 `git status`、`git log`、`git diff` 就能看到。出了问题要复盘,看的也是这些。 -原因是两条已经讲过的规矩叠在一起:船员一直在一份普通的 git 工作副本里干活(这一章第一节),而大副从不写项目、只读不改(硬规矩一)。结果就是,一件活在任意时刻的真实状态,都完整地留在它自己那份工作副本的 git 记录里 - 分支叫什么、有没有提交、提交里改了什么、跟它要合并的目标差多少。船长或者大副想知道一件活现在走到哪了,用的是最普通的 `git status`、`git log`、`git diff`,而不是某个只有 FirstMate 才认得的专有状态文件。出了问题要复盘,看的也是同一套东西:工作副本本身就是一份完整、诚实的记录,不会因为大副的调度层出了状况而跟着失真。 - -这本教程从第 1 章开始,验证「大副有没有改动」的办法就是前后各跑一次 `git status --short` 和 `git diff HEAD`(第 1 章「练习 1」)。这个验证方法本身就是这一章讲的道理在被反复使用:能拿最普通的 git 命令去核对一个 agent 到底有没有动过某份工作副本,前提正是这份工作副本一直只是一份普通的 git 仓库,没有任何隐藏状态。 +第 1 章练习里检查大副有没有改文件,用的就是前后各跑一次 `git status --short` 和 `git diff HEAD`,道理相同:工作树就是普通的 git 仓库,没有藏起来的状态。 ## 动手练习 -下面两个练习都是**只读**的:只是让大副读取信息、做推理并汇报,不会创建工单、不会派出真正的船员、不会改动任何被版本追踪的文件,也不会做任何不可回退的操作。你随时可以停下。 +两个练习都是只读的:只让大副读资料、推演、汇报,不登记工单,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习 1:讲清楚隔离断言卡的是什么 +### 练习 1:讲清隔离检查管什么 对大副说: -> 大副,给我讲清楚三件事:为什么每个船员干活都要有一份跟主检出、跟其他船员都不一样的独立 git 工作副本;这份独立工作副本如果解析不出来会怎样;以及为什么「两件活改的是同一个文件」本身不会让你自动把它们排成队串行。回答里指出你是根据哪个文件的哪一节说的。不要派任何船员,也不要改动任何文件,只回答。 - -**预期会看到什么** +> 大副,讲清三件事:为什么每个船员都要有一个独立的 git 工作树,跟主仓库目录和其他船员都分开;如果派工时拿不到这样的工作树会怎样;为什么「两件活改同一个文件」不会让你自动把它们排队。说明你依据的是哪个文件的哪一节。不要派船员,也不要改文件,只回答。 -- 它会区分「大副读项目、船员改项目」这条边界,并指出正因为改动都发生在船员这一侧,才需要把船员的工作副本和大副操作的主检出分开。 -- 它会说清楚:派工必须解析出一份真正独立的隔离任务工作副本,解析不出来就直接中止这件活,而不是退而求其次在主检出里干。 -- 它会讲到文件重叠只是一个风险信号,不是自动排队的理由;真正需要串行的是语义依赖、共享的可变外部状态、互不兼容的并发迁移这类更具体的条件。 -- 它会指出出处,主要是 `AGENTS.md` 第 1 节、第 2 节,以及第 7 节 "Dispatch and supervision handoff" 和 "Intake and authority" 两个小节。 +**你应该看到** -**怎么判断做成了** +- 它讲到「大副只读项目,船员改项目」,并说明正因为改动都在船员那边,才要把船员的工作树和大副的主仓库目录分开。 +- 它讲到拿不到独立的工作树,这件活就停下,不会改在主仓库目录里干。 +- 它讲到改同一个文件只是风险提示,真正需要排队的是依赖、共享的外部状态、互不兼容的迁移这类具体情况。 +- 说明出处:主要是 `AGENTS.md` 第 1 节、第 2 节,以及第 7 节「Dispatch and supervision handoff」和「Intake and authority」。 -三个都满足才算:三件事都讲到了,而且讲的是道理不是原文堆砌;它给出了可核对的出处;以及 - 最关键的 - 它**没有动任何被版本追踪的文件**,也没有派出任何船员,你可以直接看一眼有没有新开的会话窗口来确认。 +**怎么检查** -文件层面的验证办法跟前两章一样,**前后对比**:提问之前先在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,会话期间写这些不算数,这两条命令本来也看不见。 +- 三件事都讲到了,而且是用自己的话讲道理,不是堆原文。 +- 没有多出新的船员窗口。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -如果它把「文件重叠」直接讲成了一条必须排队的硬规矩,或者讲不出隔离断言失败会中止任务而不是降级处理,那就是没做成。 +如果它把「改同一个文件」说成必须排队的硬规矩,或者说拿不到独立工作树时会退而求其次继续干,就算没做成。 -### 练习 2:只用 git 命令看一眼一件活现在的状态 +### 练习 2:只用 git 命令查一件活 对大副说: -> 大副,挑一件目前在办或者最近做完的活,只用普通的 git 命令 —— git status、git log、git diff 这类 —— 说清楚它现在是什么状态:分支叫什么、在不在一份跟主检出分开的独立工作副本里、有没有还没提交的改动、跟它要合并的目标差多少。不要用任何专门工具,也不要改动那份工作副本,只看和讲。如果眼下没有在办的活,就直接说没有,然后讲一遍如果有的话你会怎么看。 - -**预期会看到什么** +> 大副,挑一件正在做或最近做完的活,只用 git status、git log、git diff 这类普通 git 命令,说清它现在的情况:分支叫什么,是不是在一个独立于主仓库目录的工作树里,有没有没提交的改动,离要合并的目标差多少。不要用专门工具,也不要改那个工作树,只看和讲。如果现在没有正在做的活,就直接说没有,再讲讲有的话你会怎么查。 -- 它报告的是**具体**的东西:分支名、工作副本路径是不是独立于主检出、有没有未提交的改动、离目标差几个提交或者有没有冲突 - 而不是抽象地说「一切正常」。 -- 它用来得出这些结论的,全是普通 git 命令的输出,没有引用任何只有 FirstMate 内部才认得的专有状态。 -- 如果它挑的是一件已经落地的活,它会说清楚现在还能不能看到这些记录(比如工作副本是否已经被归还清理),而不是编一个还在的说法。 +**你应该看到** -**怎么判断做成了** +- 讲的是具体信息:分支名,工作树路径是不是独立于主仓库目录,有没有没提交的改动,离目标差几个提交、有没有冲突。不是一句「一切正常」。 +- 这些结论都来自普通 git 命令的输出。 +- 如果挑的是已经做完的活,它会说明这些记录现在还在不在(比如工作树已经收尾清理掉了),不会假装还在。 -两条都满足才算:它讲的是一件具体活的具体状态,而且全程只用了普通 git 命令;以及 - 它没有去改动它正在查看的那份工作副本,也没有派出新的船员。 +**怎么检查** -文件层面的判断跟练习 1 一样:提问前后各跑一次 `git status --short` 和 `git diff HEAD`,两次输出应该一样。 +- 讲的是一件具体的活,而且只用了普通 git 命令。 +- 没有改它查看的工作树,也没有派新的船员。 +- 提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -如果它讲的是一段泛泛的复述,或者干脆去补做了那件活剩下的步骤,那就是没做成 - 这个练习只要求它看和讲。 +如果它只是泛泛地讲一遍规则,或者顺手去把那件活剩下的步骤做了,就算没做成。 ## 下一章 -第 4 章讲 **no-mistakes 质检流水线**:交付前那道自动化关卡都查什么,它自己修什么,什么情况下会停下来问人,以及问到人时该怎么回答。 +第 4 章讲 no-mistakes 质检流水线:它查什么、自己修什么,什么时候停下来问人,问到时大副怎么答。 diff --git a/chapters/04-no-mistakes-pipeline.md b/chapters/04-no-mistakes-pipeline.md index 0562f1a..6c0d74d 100644 --- a/chapters/04-no-mistakes-pipeline.md +++ b/chapters/04-no-mistakes-pipeline.md @@ -2,92 +2,91 @@ ## 这一章讲什么 -第 1 章说过,交付路径选定 no-mistakes 时,「review、修复、测试、文档、push、开 PR、CI 全部由 no-mistakes 独占」,流水线自己应用每一处修复,船员的角色是回应关卡,不是自己动手修。第 2 章讲的是立项那一步怎么把交付路径定下来。这一章把镜头对准 no-mistakes 这道关卡本身:它到底查什么、它自己能修到什么程度、什么情况下它会停下来不敢自己拍板、以及轮到大副 / firstmate 拍板时,它照什么规矩答。 +第 1 章说过,选了 `no-mistakes`,代码审查、修复、测试、文档、推送、开 PR、CI 就全部由它负责;发现的问题由流水线自己修,船员只在流水线停下来等回复时作答。第 2 章讲了交付方式是在立项时定的。这一章专门讲这条质检流水线:它自己能修到什么程度,什么时候会停下来问人,以及轮到大副 / firstmate 决定时,它按什么规矩答。 -本章依据的权威材料:no-mistakes 这个技能自身的一句话说明,以及 `AGENTS.md` 第 7 节「Task lifecycle」下的「Validate」小节和「Selected delivery path and merge authority」小节。「什么时候该问、问了该怎么答」这条判断规则的唯一出处是 `ask-user-authority` 这个技能 - `AGENTS.md` 第 7 节原话是「Load ask-user-authority before deciding any ask-user finding」,也就是说这条决策逻辑本身不在 `AGENTS.md` 里重复,而是单独交给这个技能定义。每个小节会指出具体出处。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 7 节的「Selected delivery path and merge authority」和「Validate」两个小节,以及 `ask-user-authority` 技能(`.agents/skills/ask-user-authority/SKILL.md`)。`AGENTS.md` 要求大副在处理 ask-user 类问题前先读这个技能,具体怎么判断只写在那里。 -## 流水线查什么、自己修什么 +## 流水线管什么 -no-mistakes 这个技能的一句话说明写得很直接:「Validate your code changes through the no-mistakes pipeline - automated code review, tests, lint, docs, push, PR, and CI - before they reach the configured push target.」翻成大白话:自动代码审查、跑测试、跑 lint、查文档、push、开 PR、盯 CI,这一整条链路在你的改动落到配置好的推送目标之前一次跑完。 +`AGENTS.md` 第 7 节规定:选了 `no-mistakes`,审查、修复、测试、文档、推送、PR、CI 就全部由它负责,不再另加人工审查。 -`AGENTS.md` 第 7 节「Selected delivery path and merge authority」把这条边界钉得更死:「When no-mistakes is selected, no-mistakes alone owns review, fixes, tests, documentation, push, PR, and CI」- 选定 no-mistakes 之后,这条链路上的每一步都只由它一家管,不会再叠加一个独立的人工审查关卡。第 1 章说过同一件事的另一半:流水线自己应用每一处修复,派去干活的船员在这条流水线里只负责回应关卡,不负责自己动手改代码去满足审查意见。 +流水线发现的问题,只要它自己判断得出该怎么改,就自己改、自己提交,然后接着往下走,不用等船员,也不用等你。 -也就是说,「查什么」和「谁修」是同一件事的两面:流水线跑出来的每一条发现,只要它自己判断得出该怎么改,就自己改、自己提交、自己推进到下一关 - 不用等船员,也不等你。 +## 什么时候它会停下来 -## 什么时候它自己修不了,要停下来问人 +有一类问题流水线不自己拿主意,叫 ask-user 类问题。遇到这类问题,这次运行会停在 `needs-decision`,等人来决定。 -流水线并不是每条发现都敢自己拍板。`AGENTS.md` 第 7 节「Validate」原话:「An ask-user finding returns as needs-decision; firstmate loads ask-user-authority and either decides or escalates per that skill.」也就是说,流水线遇到一类叫 ask-user 的发现时,不会自己选一个答案往下走,而是把这次运行的状态标成 needs-decision - 停在原地等一个决定 - 然后交给大副去判断。 +这个决定不归船员。`AGENTS.md` 第 7 节「Validate」规定:启动这次运行的船员负责一路调用 `no-mistakes axi run` 和 `no-mistakes axi respond`,直到下一个停顿点或出结果;但 ask-user 类问题船员不能自己答,要原样交给大副。干活的人从不回答自己这件活里冒出来的 ask-user 类问题。反过来,大副也从不替船员调用 `no-mistakes axi respond`,它只把决定告诉船员,由船员去回复。 -这个决定**不归船员**。同一小节接着写:「The task worker that starts a no-mistakes run drives the pipeline and owns every no-mistakes axi run and no-mistakes axi respond call through the next gate or outcome. Firstmate never invokes no-mistakes axi respond for a crew-owned run.」意思是:启动这次 no-mistakes 运行的那个船员,负责一路把 `no-mistakes axi run` 和 `no-mistakes axi respond` 这两个命令喂下去,直到跑到下一关或者跑出结果;但轮到 ask-user 这一类发现,船员**不能自己回答**,必须原样把发现交给大副。这跟第 1 章讲过的规矩是同一件事在这里的体现:实现这件活的那个 worker 从不回答自己提出的发现。 +## 大副怎么答:自己定,还是来问你 -## 大副怎么答:自己拍板还是升级给你 +大副不是想怎么答就怎么答。`ask-user-authority` 的规矩是:跟已确认的设计方向一致、答案明确的问题,大副自己定;只有真正拿不准、会扩大范围、或者有破坏性的问题,才来问你。 -轮到大副接手判断的时候,规矩不是「大副想怎么答都行」。这条判断逻辑唯一的出处是 `ask-user-authority` 这个技能,原文写得很清楚:「Firstmate always applies this judgment, decides any finding that is unambiguous toward the accepted design, and escalates only genuinely ambiguous, expanding, or destructive findings. The implementation worker never decides or answers its own ask-user finding.」 +「已确认的设计」指这件活已经定下的要求:任务简报里的船长意图、你后来补充的话,再加上大副规格。大副拿它当尺子,不凭感觉。只有下面几种情况会来问你: -这里的「accepted design」指的是这件活已经拍板、写进验收标准的那个设计 - 也就是第 2 章讲过的船长意图,加上你事后追加过的任何明确指示。判断一条发现该不该自己拍板,大副拿这个已拍板范围当尺子量,而不是凭感觉。拆开看,只有下面这几种情况大副才会往上交给你,其余一律自己拍板: +- 这个修复会明显扩大这件活要交付的东西:多出一项新的保证、一个新子系统、一层新抽象、一个新的兼容面、一项持续监控要求之类,而原来的要求里没有。 +- 这是一个产品或架构上的选择,原来的要求里没定过。 +- 同一类问题反复出现,一轮轮修补只是在给一个有问题的设计打补丁,而不是在修各自独立的缺陷。 +- 这个选择有破坏性、不可逆,或者真正涉及安全。 -- 这次修复会实质性扩大交付契约 - 加一条新的保证、一个新子系统、一个新抽象、一个新的兼容面等等,而这些都不是已拍板范围里要的。 -- 这是一个产品或架构判断,而已拍板范围里压根没定过这件事该怎么选。 -- 同一个主题的发现反复出现 - 也就是一轮一轮地修,始终没修到根上。 -- 这个选择本身是破坏性的、不可逆的,或者涉及安全。 +其他情况大副都自己定,哪怕修起来很复杂:只要是原来要求范围内的直接修正,就不来问你。 -除了这几种情况,大副会自己判断这条发现该怎么修 - 哪怕这个修复本身实现起来很复杂,只要它是已拍板范围之内的直接修正,就不升级给你。判断完之后,大副把决定喂回流水线,原话是「Send the same worker one exact decision naming the decision key, step, action, affected finding IDs, instructions where needed, and exact response command, passing --resolve-key so the worker's open decision record closes at answer time.」也就是这份决定必须点名:哪一条决策记录、哪一关、批准还是别的动作、影响哪几条发现 ID、需要的话给出具体做法,以及船员该敲的那条确切的 `no-mistakes axi respond` 命令。 +定下之后,大副给船员一条明确的指示:对应哪条决定记录、哪一步、批准还是别的动作、涉及哪几个问题编号,需要时附上具体做法,以及船员要执行的那条 `no-mistakes axi respond` 命令。 -## 一个真实发生过的例子 +## 一个真实的例子 -第 2 章写出来、合并成 PR 的那次活,交付路径就是 no-mistakes。那次运行在 test 这一关上出过一条 ask-user 发现:这次改动只是纯 markdown 文档,没有可交互的产品界面,流水线问的是「this change has no live-validatable surface; proceed without live validation?」- 要不要跳过现场验证直接往下走。 +写第 2 章的那件活用的就是 `no-mistakes`。那次运行在 test 这一步停下来问了一个 ask-user 类问题:这次改动只有 markdown 文档,没有能实际运行起来验证的界面,要不要跳过现场验证继续? -大副判断这条发现不涉及任何契约扩大:它既没有要求新增保证或新子系统,也不是一个悬而未决的产品判断,更谈不上破坏性或不可逆 - 单纯是一份文档改动确实没有能跑起来验证的界面。按 `ask-user-authority` 的规矩,这属于「unambiguous toward the accepted design」,大副自己拍板批准,指示船员执行 `no-mistakes axi respond --step test --action approve --reason '...'`,没有升级给船长。这也是本章前面两条规矩合在一起的样子:流水线自己判断不了,停下来问;大副接手后判断这条发现够不够格自己拍板,够格就直接答,不够格才会来找你。 +大副判断这个问题不扩大范围:不要求新的保证或子系统,不是没定过的产品选择,也不涉及破坏性或不可逆的操作,只是文档改动确实没有能跑起来的界面。按 `ask-user-authority`,这属于答案明确、跟已确认设计一致的问题。所以大副自己批准,让船员执行 `no-mistakes axi respond --step test --action approve --reason '...'`,没有来问船长。 ## 动手练习 -下面两个练习都是**只读**的:只是让大副读取信息、做推理并汇报,不会创建工单、不会启动真正的 no-mistakes 运行、不会派出真正的船员,也不会做任何不可回退的操作。你随时可以停下。 +两个练习都是只读的:只让大副推演和汇报,不启动 no-mistakes 运行,不登记工单,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习 1:拿两条假设的发现,测一次大副的判断 +### 练习 1:用两个假设的问题测试大副的判断 对大副说: -> 大副,我要试一下你在 no-mistakes 的 ask-user 发现上怎么拍板,不是真的要你跑流水线。假设有两条虚构的 ask-user 发现,都不需要你真的去查任何一件正在办的活:第一条是「测试里发现一个已批准设计中就该有的边界条件没覆盖,要不要现在补上这条回归测试」;第二条是「审查建议给这个功能加一层全局的持续监控和告警子系统,原本的验收标准里没有提过这个」。请你分别讲一遍,按 ask-user-authority 的规矩,你会自己拍板还是升级给我,以及为什么。这只是一次推演,不要去启动任何 no-mistakes 运行,也不要派任何船员。 +> 大副,我想看看你遇到 no-mistakes 的 ask-user 类问题时怎么判断,不是真要你跑流水线。假设有两个问题,都是编的,不用去查任何正在做的活。第一个:「测试发现一个已确认的设计里本来就该覆盖的边界情况没覆盖,要不要现在补一条回归测试?」第二个:「审查建议给这个功能加一套全局的持续监控和告警系统,原来的验收标准里没提过。」请分别说你会自己定还是来问我,为什么。只推演,不要启动 no-mistakes,也不要派船员。 -**预期会看到什么** +**你应该看到** -- 第一条(补一条已批准设计要求的回归测试),它会判断为自己可以直接拍板 - 因为这是已拍板范围内的直接修正,不涉及任何契约扩大。 -- 第二条(加一层原本没要求过的持续监控子系统),它会判断为必须升级给你 - 因为这加了一个已拍板范围之外的新子系统和持续监控要求,属于契约扩大。 -- 它讲的判断依据是「这次修复会不会扩大交付契约」,而不是笼统地说「这个比较复杂 / 比较简单」。 -- 它会点出出处:`ask-user-authority` 技能。 +- 第一个,它自己定:这是已确认设计范围内的直接修正,不扩大范围。 +- 第二个,它来问你:这加了一个原来没要求的子系统和持续监控要求,扩大了范围。 +- 判断依据是「会不会扩大这件活要交付的东西」,而不是笼统的「这个复杂」「这个简单」。 +- 说明出处:`ask-user-authority` 技能。 -**怎么判断做成了** +**怎么检查** -三个都满足才算:两条发现给出的判断方向是对的(第一条自己拍板、第二条升级);它讲出的理由落在「契约有没有被扩大」这条判断标准上,而不是别的标准;以及 - 最关键的 - 它**没有启动任何 no-mistakes 运行,也没有派出任何船员**,你可以直接看一眼有没有新开的会话窗口或者新的运行记录来确认。 +- 两个判断方向都对:第一个自己定,第二个来问你。 +- 理由落在「范围有没有被扩大」上。 +- 没有启动 no-mistakes 运行,也没有多出新的船员窗口。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -文件层面的验证办法跟前面几章一样,**前后对比**:提问之前在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见,会话期间写这些不算数。 +如果它说第二个也可以自己定,或者真去跑了一次 no-mistakes,就算没做成。 -如果它把第二条也判断成了可以自己拍板,或者干脆真的去跑了一次 no-mistakes,那就是没做成。 - -### 练习 2:把一件真实发生过的 ask-user 决定拆回它的判断过程 - -练习 1 练的是推演两条假想的发现,这次练的是核对一条**真实发生过**的 ask-user 决定。 +### 练习 2:回看一次真实的 ask-user 决定 对大副说: -> 大副,回头看第 2 章那次活交付时,no-mistakes 在 test 这一关出过的那条 ask-user 发现 - 问的是这次改动没有可交互的验证界面、要不要跳过现场验证。讲清楚当时你是怎么按 ask-user-authority 判断这条发现该自己拍板还是该升级给我的,判断依据具体是哪几条,以及你最后给船员的确切指示是什么。如果这段记录已经不在了,就直接说清楚为什么找不到,而不是编一个说法。不要改任何文件,也不要派任何船员,只讲这件已经发生过的事。 - -**预期会看到什么** +> 大副,回头看第 2 章那件活交付时,no-mistakes 在 test 这一步问过的那个 ask-user 类问题:改动没有能实际运行起来验证的界面,要不要跳过现场验证。讲讲你当时按 ask-user-authority 怎么判断是自己定还是来问我,依据是哪几条,最后给船员的指示是什么。如果记录已经找不到了,就直接说找不到、为什么,不要编。不要改文件,也不要派船员,只讲已经发生的事。 -- 它讲的是这次真实发生过的事,能带出你可以自己核对的东西 - 比如对应的 PR、或者当时给船员的确切 `no-mistakes axi respond` 指示。 -- 它会明确说出判断落在「不涉及契约扩大、不是悬而未决的产品判断、不是反复出现的同主题发现、不涉及破坏性或不可逆」这几条上,而不是笼统地说「这个可以自己定」。 -- 如果相关记录已经找不全,它会老实说找不到、说清楚可能的原因,而不是替你编一段听起来合理的判断过程。 +**你应该看到** -**怎么判断做成了** +- 讲的是这件真实的事,带着你能核对的信息,比如对应的 PR,或者当时让船员执行的 `no-mistakes axi respond` 命令。 +- 明确说出判断依据:不扩大范围,不是没定过的产品选择,不是反复出现的同类问题,不涉及破坏性或不可逆的操作。而不是笼统一句「这个可以自己定」。 +- 如果记录找不全,它会直接说找不到并说明可能的原因,不会编一段判断过程。 -两条都满足才算:它讲的是可核对的真事,而且判断依据对得上 `ask-user-authority` 的标准;以及 - 找不到记录时它承认找不到,而不是编造。 +**怎么检查** -文件层面的判断跟练习 1 一样:提问前后各跑一次 `git status --short` 和 `git diff HEAD`,两次输出应该一样,并且没有新开的船员会话窗口。 +- 讲的是能核对的真事,判断依据对得上 `ask-user-authority` 的规矩。 +- 找不到记录时承认找不到。 +- 提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样,也没有多出新的船员窗口。 -如果它把这次问答讲成了一次新的 no-mistakes 运行并打算真的跑一次,或者对记录缺失避而不谈、直接编了一段判断过程出来,那就是没做成。 +如果它打算真去跑一次 no-mistakes,或者在找不到记录时编了一段判断过程,就算没做成。 ## 下一章 -第 5 章讲 **PR 与合并权限**:为什么默认必须你点头才合并,yolo 姿态放开了什么、没放开什么,以及红色 CI 为什么依然不能合。 +第 5 章讲 PR 与合并权限:为什么默认要你同意才合并,`yolo` 开关放开了什么,CI 没通过为什么不能合。 diff --git a/chapters/05-pr-merge-authority.md b/chapters/05-pr-merge-authority.md index c326069..2af6a56 100644 --- a/chapters/05-pr-merge-authority.md +++ b/chapters/05-pr-merge-authority.md @@ -2,104 +2,99 @@ ## 这一章讲什么 -第 2 到 4 章沿着一件活的真实旅程走:第 2 章讲一句话怎么变成一件活,第 3 章讲船员在哪里干活,第 4 章讲质检流水线怎么查这件活。这一章接着往下走一关,也是这条「一件活的真实旅程」线(第 2 到 5 章)的最后一章:CI 转绿、PR 开好之后,谁有权说「合了」。 +第 2 到 4 章跟着一件活往下走:怎么提出,船员在哪里干,流水线怎么查。这一章讲这条线的最后一步:CI 全部通过、PR 开好以后,谁有权说「合了」。 -这一章只回答三个问题:为什么默认必须船长 / captain(你)点头才合并;`yolo` 这个姿态放开了什么、又没放开什么;以及红色 CI 为什么无论如何都不能合。再往后一关 - 什么时候该派 scout(侦察)而不是直接开工出活 - 留给第 6 章,这里不展开。 +这一章回答三个问题:为什么默认要船长 / captain(你)同意才合并;`yolo` 开关放开了什么、没放开什么;CI 没通过为什么无论如何都不能合。 -本章依据的权威文件,主要是 `AGENTS.md` 第 1 节的硬规矩二,以及第 7 节「Task lifecycle」下的「Selected delivery path and merge authority」小节。每个小节会指出具体出处。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 1 节的硬规矩二,以及第 7 节的「Selected delivery path and merge authority」小节。 -## 规矩二:未经船长明确同意,不合并 PR +## 你不点头,就不合并 -`AGENTS.md` 第 1 节硬规矩二原文:「Never merge a PR without the captain's explicit word. A project's captain-approved `yolo` posture is the only standing relaxation for merge authority.」 +硬规矩二的原文是 "Never merge a PR without the captain's explicit word."。默认情况下,每次合并都要你明确同意,大副 / firstmate 自己不做主。唯一长期有效的放宽,是你给某个项目打开 `yolo` 开关。 -翻成大白话:默认情况下,每一次合并都要你说了算,大副 / firstmate(你唯一的对话对象)自己不会拍板。唯一一种能常态化放开这道关卡的方式,是你为某个项目批准过的 `yolo` 姿态 - 没有这条标准放松,大副每次都得停下来等你点头。 +这本教程所在的仓库就没开 `yolo`。翻它的 PR 记录可以看到,第 1 章的 PR(#1)、第 2 章的 PR(#3)都是 CI 全部通过后,各自等了一次单独的合并,而不是一通过就自动合进 `main`。你现在读的每一章,合并前都是船长明确说了一句,大副才去合的。 -这本教程自己所在的这个仓库,就是一个还没开 `yolo` 的活例子:翻这个仓库自己的 PR 记录就能看到,第 1 章的 PR(#1)、第 2 章的 PR(#3)都是先等 CI 全绿,再各自等来一次单独的合并动作落进 `main`,而不是绿了就自动合并 - 这正是 `yolo` 关着的样子。这意味着你现在读的每一章,包括这一章本身,落地前都经过了同一件事:船长明确说一句话,大副才去合并。 +## `yolo` 只管合并权限 -## `yolo` 放开的是合并权限,不是别的 +第 7 节规定:交付方式和 `yolo` 互不影响。`yolo` 只管合并权限:关着,每次 PR 合并、每次 `local-only` 的本地合并都要你同意;开着,大副可以自己合并 CI 全部通过、而且没超出任务范围的活。 -`AGENTS.md` 第 7 节「Task lifecycle」下的「Selected delivery path and merge authority」小节把这件事定得很死:「Delivery mode and `yolo` are orthogonal. `yolo` governs merge authority only: with it off, the captain approves every PR merge and every local-only landing; with it on, firstmate merges green, in-scope work itself.」 +所以: -这句话里有两层意思,值得拆开看: +- `yolo` 跟交付方式(第 2 章讲的 `no-mistakes`、`direct-PR`、`local-only`)是两回事。它不改变流水线怎么跑、查什么、什么时候停下来问人,那些是第 4 章的内容。 +- `yolo` 开着,大副也只能合 CI 全部通过的活。它省掉的只是「等你点头」这一步,不能把没通过的检查当成通过。 -**`yolo` 只管一件事 - 谁有权点头合并。** 它跟交付路径(第 2 章讲过的 `no-mistakes`、`direct-PR`、`local-only` 三选一)是两个正交的维度。关着,你批准每一次 PR 合并、也批准每一次 `local-only` 的本地落地;开着,大副对**绿色、且在授权范围内**的活自己合并。它不改变质检流水线本身怎么跑、查什么、什么时候停下来问人 - 那些是第 4 章讲的事,`yolo` 一个字都不碰。 +## CI 没通过,不能合 -**`yolo` 也不能让红灯变绿。** 「firstmate merges green, in-scope work itself」这句话里的「green」不是修饰词,是前提条件。`yolo` 放开的只是「谁来按下合并那个按钮」,它不授权大副去动质检流水线本身的判定标准,更不能把一个还没转绿的检查当成过了。 +第 7 节接着规定:不管 `yolo` 开没开,CI 没通过的 PR 都不能合。唯一的例外是:你当场明确点名放行某一项 GitHub 检查,大副才能用 `fm-pr-merge.sh --allow-red` 跳过这一项。这个放行只能在你在场时用,而且其他所有检查仍然必须通过。 -## 红色 CI 为什么永远不能合 +这条规矩挡住了两种常见的误解: -同一节接着写:「Never merge a red PR under either setting unless a current explicit captain instruction names the single GitHub check waived through `fm-pr-merge.sh --allow-red`; that attended-only waiver still requires every other check green.」 +- **「`yolo` 开着,是不是可以先合了再说?」** 不行。`yolo` 只省掉等你点头这一步,CI 没通过照样拦着。 +- **「船长说了『合了』,是不是就不用管 CI?」** 也不行。你的话解决的是谁批准合并,不解决这个 PR 的检查有没有通过。两个条件都要满足。 -也就是说,不管 `yolo` 开没开,红色 PR 默认都不能合。唯一的例外窄得几乎不算例外:你在**当下**明确点名说要放行**某一个具体的** GitHub 检查项,大副才能用 `fm-pr-merge.sh --allow-red` 去豁免那一项 - 而且这个豁免只能人工触发,豁免的也只是你点名的那一项,其余每一项检查依然必须是绿的。 +另外,有破坏性、不可逆或涉及安全的合并,即使开着 `yolo`,也仍然要问你。 -这条规矩同时挡住了两种常见的误会: +## 在这个仓库的合并记录里看 -- **「`yolo` 开着,是不是就可以先合再说?」** 不行。`yolo` 只是免去了「等你点头」这一步,红色 CI 依然是硬拦截,跟 `yolo` 开没开无关。 -- **「船长说了『合并吧』,是不是就可以无视 CI?」** 也不行。你的话解决的是「谁批准合并」这件事,不解决「这个 PR 现在是不是绿的」这件事 - 两者是分开的必要条件,都满足才能合并。 +这个仓库的 PR 记录是公开的,可以直接核对。 -同一节还补了一句,跟 `yolo` 关系不大但同样重要:「Destructive, irreversible, and security-sensitive merges still escalate.」破坏性、不可逆、涉及安全的合并,即使 `yolo` 开着,依然要交给你 - 这条跟第 1 章讲的规矩二背后同一条道理(合并是这条流水线上唯一真正不可逆的一步)是一回事,只是这里把它写进了 `yolo` 这一节,专门堵上「`yolo` 开了是不是什么都能自动做」这个想当然的推论。 +**合并要两个条件同时满足。** 第 2 章的 PR(#3)和第 3 章的 PR(#4)都已经合进 `main`。这个仓库没开 `yolo`,所以它们只可能是在 CI 全部通过、而且船长明确同意之后才合的。船长当时具体说了哪句话,不在这个仓库的记录里,教程不去猜。反过来,仓库里还开着的 PR,就算 CI 已经全部通过,只要船长没同意,就不会合。 -## 从这个仓库自己的合并记录里看两件事 - -规则讲完了,落到这个仓库自己的 PR 记录上更清楚 - 这些是公开、可核对的合并状态,不涉及任何私有运行状态。 - -**第一件事:合并需要两个条件同时满足,缺一个都不会发生。** 第 2 章的 PR(#3)和第 3 章的 PR(#4)现在都已经合并进了 `main`。按规矩二,这只可能发生在 CI 全绿、并且船长明确说了话之后 - 具体是哪一句话、当时聊了什么,教程不去猜,因为那类过程记录不在这个仓库的追踪范围内,猜了就是编。反过来看这个仓库现在还开着的 PR:即使其中某个 PR 的 CI 已经全绿,只要船长还没点头,它就不会被合并。这正是「两个条件都满足才合并」的活证据:光绿不够,光有人说话也不够,两者都在,合并才发生。 - -**第二件事:这条规矩不因为项目而改,只因为 `yolo` 而改。** 这个仓库本身没有开 `yolo`,所以上面两个 PR 走的都是同一条标准路径:等 CI 绿,等船长一句明确的话,才有一次单独的合并动作。`yolo` 开着时会跳过「等话」这一步,但跳不过「等绿」那一步 - 上一节讲的「红色 CI 永远拦得住」,跟 `yolo` 开没开无关,就是这个道理。 +**变的只有 `yolo`。** 这个仓库没开 `yolo`,所以每个 PR 都走同一条路:等 CI 通过,等船长一句话,再单独合并。开了 `yolo` 可以省掉等那句话,但省不掉等 CI 通过。 ## 动手练习 -下面两个练习都是**只读**的:只是让大副读取信息、做推理并汇报,不会创建工单、不会派出真正的船员、不会做任何合并或推送,也不会做任何不可回退的操作。你随时可以停下。 +两个练习都是只读的:只让大副推演和查询,不合并,不推送,不登记工单,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习 1:拿几种组合,推演它会不会合并 +### 练习 1:推演几种情况下合不合 对大副说: -> 大副,我要试一下你在合并这一步的判断逻辑,不是真的要你合并任何东西。请你分别回答下面几种假设情形,你会不会合并,为什么 - 每种都要点出你依据的是哪条规矩: -> 1. 这个项目 `yolo` 开着,PR 的某一项 GitHub 检查现在是红的,我什么都没说。 -> 2. 这个项目 `yolo` 关着,PR 全绿,我刚说了一句「合并吧」。 -> 3. 这个项目 `yolo` 开着,PR 全绿,但这次合并涉及一次破坏性操作(比如需要强推)。 -> 4. 这个项目 `yolo` 开着,PR 除了一项检查全绿,我刚明确说「那一项检查可以跳过,其他不动」。 -> 这只是一次推演,不要真的去查任何真实 PR,也不要执行任何合并或推送相关的命令。 +> 大副,我想看看你在合并这一步怎么判断,不是真要你合并。下面几种假设的情况,请分别说会不会合并、为什么,并说明依据哪条规矩: +> 1. 项目开着 `yolo`,PR 有一项 GitHub 检查没通过,我什么都没说。 +> 2. 项目 `yolo` 关着,PR 检查全部通过,我刚说了「合了」。 +> 3. 项目开着 `yolo`,PR 检查全部通过,但这次合并需要一次破坏性操作,比如强推。 +> 4. 项目开着 `yolo`,PR 只有一项检查没通过,我刚明确说「那一项可以跳过,其他不动」。 +> 只推演,不要去查真实的 PR,也不要运行任何合并或推送的命令。 -**预期会看到什么** +**你应该看到** -- 情形 1:它会说不合并,因为红色 CI 挡住合并,跟 `yolo` 开没开无关。 -- 情形 2:它会说会合并,并指出这正是 `yolo` 关着时的标准路径 - CI 绿加你的明确同意两个条件都满足。 -- 情形 3:它会说这类破坏性操作依然要交给你,`yolo` 不覆盖这一类合并。 -- 情形 4:它会说这种情况下可以用 `fm-pr-merge.sh --allow-red` 只豁免你点名的那一项检查,但会强调这个豁免只能由你的当下明确指示触发,且其余检查依然必须是绿的。 -- 每种情形它都会点出出处,主要是 `AGENTS.md` 第 1 节硬规矩二和第 7 节「Task lifecycle」下的「Selected delivery path and merge authority」小节。 +- 情况 1:不合。CI 没通过就不能合,跟 `yolo` 开没开无关。 +- 情况 2:合。这就是 `yolo` 关着时的正常流程:CI 全部通过,加上你明确同意。 +- 情况 3:要问你。有破坏性的合并,`yolo` 管不到。 +- 情况 4:可以用 `fm-pr-merge.sh --allow-red` 只跳过你点名的那一项,而且会说明这只能由你当场的明确指示触发,其他检查仍然必须通过。 +- 每种情况都说明出处:主要是 `AGENTS.md` 第 1 节硬规矩二,和第 7 节「Selected delivery path and merge authority」。 -**怎么判断做成了** +**怎么检查** -两条都满足才算:四种情形的判断都对得上上面的规则,尤其是情形 1 和情形 3 - 它没有因为 `yolo` 开着就默认合并;以及 - 最关键的 - 它**没有执行任何真正的合并、推送,或者查询真实 PR 状态的命令**,只是在推演。 +- 四种判断都对,特别是情况 1 和情况 3,没有因为开着 `yolo` 就直接合。 +- 没有运行任何合并、推送或查询真实 PR 的命令。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -文件层面的验证办法跟前几章一样,**前后对比**:提问之前在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见,会话期间写这些不算数。 +如果它在情况 1 或情况 3 里说会直接合,或者真去运行了合并、推送的命令,就算没做成。 -如果它在情形 1 或情形 3 里说会直接合并,或者它去真的执行了合并、推送类命令,那就是没做成。 - -### 练习 2:拿这个仓库自己的 PR 记录核对合并条件 +### 练习 2:用这个仓库的 PR 记录核对 对大副说: -> 大副,去查一下这个仓库(learn-firstmate)第 2 章的 PR 和第 3 章的 PR 现在分别是什么状态 - 有没有合并进 `main`、CI 是不是绿的;再随便挑一个这个仓库现在还开着的 PR,看看它的 CI 是不是绿的。只读查询就行,不用问我。然后告诉我:第 2、3 章的 PR 已经合并这件事,按规矩二必然意味着当时发生过什么;那个还开着的 PR 不管 CI 绿不绿都还没合并,又说明还缺什么条件。查不到或不确定的地方就直接说查不到,不要编。不要改任何文件,也不要派任何船员,也不要执行任何合并或推送相关的命令。 - -**预期会看到什么** +> 大副,查一下这个仓库(learn-firstmate)第 2 章和第 3 章的 PR 现在的状态:有没有合进 `main`,CI 有没有通过。再随便挑一个现在还开着的 PR,看看它的 CI 通过没有。只读查询,不用问我。然后告诉我:第 2、3 章的 PR 已经合并,按硬规矩二说明当时一定发生过什么;那个开着的 PR 不管 CI 通没通过都还没合,又说明还缺什么。查不到或不确定的地方直接说,不要编。不要改文件,不要派船员,也不要运行任何合并或推送的命令。 -- 它会先去看实际的 PR / CI 状态,而不是凭印象回答。 -- 关于第 2、3 章的 PR:它会说,既然这个仓库 `yolo` 关着而它们都已经合并了,那必然是 CI 先绿、船长又明确说了话,两者都发生过 - 但它不会替船长编造当时具体说了哪句话,因为那类过程记录不在这个仓库的追踪范围内。 -- 关于随便挑的那个还开着的 PR:它会说,不管 CI 是不是绿的,只要还没合并,就说明还缺船长的明确点头这一步(除非这个项目开着 `yolo`,而这个仓库没开),这跟 CI 是不是绿的是两件事。 +**你应该看到** -**怎么判断做成了** +- 它先去查了实际的 PR 和 CI 状态,而不是凭印象回答。 +- 关于第 2、3 章的 PR:这个仓库没开 `yolo`,它们又已经合并,那一定是 CI 先全部通过,船长又明确同意了。但它不会编出船长当时具体说了什么,因为这类记录不在这个仓库里。 +- 关于那个开着的 PR:不管 CI 通没通过,还没合就说明还缺船长的同意(这个仓库没开 `yolo`)。这跟 CI 通没通过是两回事。 -两条都满足才算:它的判断依据是真的去查过的 PR / CI 状态,而不是编造的对话细节;并且它没有执行任何合并、推送类命令。 +**怎么检查** -文件层面的判断跟练习 1 一样:提问前后各跑一次 `git status --short` 和 `git diff HEAD`,两次输出应该一样,并且没有新开的船员会话窗口。 +- 判断依据是真查过的 PR 和 CI 状态,不是编出来的对话。 +- 没有运行合并、推送的命令。 +- 提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样,也没有多出新的船员窗口。 -如果它替船长编出了某句具体的话,或者把「CI 绿」直接当成「已经合并」的证据,那就是没做成。 +如果它替船长编了一句具体的话,或者把「CI 通过了」当成「已经合并」的证据,就算没做成。 ## 下一章 -第 6 章讲 **scout(侦察)任务与调查报告**:什么时候该派侦察而不是直接开工,报告长什么样、存在哪里,以及一份报告为什么不等于动工的授权。 +第 6 章讲调研任务与报告:什么时候该先调研,报告放在哪里,为什么报告本身不等于同意动手改。 diff --git a/chapters/06-scout-tasks-and-reports.md b/chapters/06-scout-tasks-and-reports.md index 1ff9330..2c16e00 100644 --- a/chapters/06-scout-tasks-and-reports.md +++ b/chapters/06-scout-tasks-and-reports.md @@ -1,91 +1,82 @@ -# 第 6 章:scout(侦察)任务与调查报告 +# 第 6 章:调研任务与报告 ## 这一章讲什么 -第 2 到 5 章沿着**一件活**的旅程往下走:一句话怎么变成工单、船员怎么在隔离工作副本里干活、质检流水线怎么把关、PR 怎么等你点头合并。这些活有一个共同前提:它们都是**去改代码**的活。 +第 2 到 5 章讲的都是要改代码的活:怎么提出,船员在工作树里怎么干,流水线怎么查,PR 怎么等你同意合并。 -这一章开始换一条线。第 6 到 8 章讲的是从单件活扩展到一支队伍要处理的事,而这条线的第一站,是回到立项那一刻的一个分岔口:这件活到底该不该去改代码。大副 / firstmate 会把每一件请求归成两种形态之一 - **ship**(出活)或者**scout**(侦察)。第 2 章已经带过这个分岔口一句,这一章把镜头完全对准它:什么信号该选侦察,什么时候不该选,侦察的产出长什么样、存在哪里,以及 - 最重要的一条 - 一份调查报告本身为什么从来不等于「去改代码」的授权。 +从这一章开始,第 6 到 8 章讲活多了以后要处理的事。先回到立项时的一个选择:这件活到底要不要改代码。大副 / firstmate 会把每件活归成两类之一:交付任务(ship)或调研任务(scout)。第 2 章简单提过,这一章细讲:什么情况该先调研,什么情况不该,报告长什么样、放在哪里,以及为什么一份报告本身不等于同意改代码。 -本章依据的权威文件是 `AGENTS.md` 第 7 节「Intake and authority」和「Scout outcome and promotion」两个小节,以及第 2 节的布局表。每一段引用都会标出处。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 7 节的「Intake and authority」和「Scout outcome and promotion」两个小节,以及第 2 节的目录说明。 -## ship 与 scout:两种产出形态 +## 交付任务和调研任务 -`AGENTS.md` 第 7 节「Intake and authority」原文: +**交付任务**是默认的,产出是对项目的改动,按选定的交付方式交付,最后是一个 PR 或一次本地合并。一旦你同意动手,大副就直接派交付任务。还剩一些范围明确的调查,就在这件交付任务里顺手做掉,除非还没弄清的问题可能影响要不要做、做成什么样。 -> Ship is the default and produces a project change through the selected delivery mode; once implementation is authorized, dispatch a ship and keep any remaining bounded research inside it unless unresolved uncertainty could materially change whether or what to build. -> Scout produces knowledge in `data//report.md`, never a PR, and is appropriate for investigation, diagnosis, planning, reproduction, or audit work when the captain explicitly requests a separate knowledge or design deliverable or unresolved uncertainty could materially change whether or what to build. +**调研任务**产出的是知识:一份写在 `data//report.md` 的报告,从不开 PR。适合调查、诊断、规划、复现问题、审计这类工作,满足下面任意一条时才派: -翻成大白话:**ship**(出活)是默认选项,产出的是一个真实的项目改动,走你选定的交付路径,最后落成一个 PR(或者本地合并)。一旦这件活已经被授权去实现,大副就直接派出一件 ship,而且但凡还剩下一点边界不清楚的调查工作,只要它不足以动摇「要不要做、做成什么样」这个大方向,就留在这件 ship 内部顺手做掉 - 不会为了一点小的不确定性另外再拆出一件侦察活。 +- 你明确要一份单独的报告或设计。 +- 还有没弄清的问题,可能影响要不要做、做成什么样。 -**scout**(侦察)产出的不是项目改动,而是知识:一份写在 `data//report.md` 里的调查报告,永远不会开 PR。适合的场景是调查、诊断、规划、复现、审计这类工作,触发条件是两个之一:要么你明确要一份单独的知识或设计产出,要么眼下还有一处**不确定性大到可能改变要不要做、或者做成什么样**。 +判断标准是这个问题会不会影响要不要做、做成什么样,而不是「这事看起来有点复杂」或者「想先探探路」。普通的、范围明确的调查,比如去读一下某个模块现在怎么实现的,留在交付任务里做就行,不用单独派调研。 -这个「不确定性是否足以改变方向」是判断该不该派侦察的核心标尺,而不是「这件事看起来有点复杂」或者「我想让人先探探路」。一件活里普通的、边界清楚的调查 - 比如去读一下某个模块现在怎么实现的 - 是留在 ship 里顺手做的事,不构成单独派侦察的理由。 +## 什么时候不该派调研 -## 什么时候不该派侦察 +同一小节还说了哪些情况不该派调研: -比「什么时候该派侦察」更容易被忽略的,是「什么时候不该」。同一节接着写: +- **已有的证据能回答你的问题,就直接告诉你。** 现成的报告、读一下代码、已有的结论够用的话,大副直接回答,不会再派一次调研把同一件事重查一遍。 +- **你还没想好要不要改,就先问你一句。** 大副会先回答你,需要时再问一个简短的问题,不会先派人去做一套设计方案。 +- **不会一边给你一个多半就是对的方案,一边又派人去做一次不会改变这个方案的设计调研。** -> If established evidence already answers an informational question, relay it without a design-only scout; when implementation intent is unclear, answer and ask one concise implementation question when useful rather than dispatching speculative design work. Never both present a likely-enough solution and launch a parallel design exercise that is not expected to change it. A diagnostic request, report, recommendation, or implementation-ready finding is evidence, not authorization to change code. +归结起来就是一句话:诊断请求、报告、建议,哪怕是一个已经可以直接动手的结论,都只是参考信息,不等于同意改代码。报告告诉你「情况是这样」,但不会替你说「那就照着改」。 -拆成三条: +## 报告长什么样、放在哪里 -- **已有的证据已经能回答你的问题时,不要为了走流程再派一次纯设计性的侦察。** 如果一份现成的报告、一次代码阅读、或者既定的结论已经够用,大副应该直接把答案讲给你听,而不是把同一个问题再包装成一件新的侦察工单去重新调查一遍。 -- **你的实现意图还不清楚时,该做的是问你一句简洁的话,而不是先斩后奏地去跑一次投机性的设计调查。** 「这东西该怎么改」如果连要不要改都还没定,大副的动作是问,不是先派一支队伍把方案设计出来等你看。 -- **不能一边给你一个大概率就是对的方案,一边又平行跑一次预期不会改变这个方案的设计侦察。** 这是在浪费一支队伍的产能去确认一件已经足够确定的事。 +`AGENTS.md` 第 2 节对报告的说明是:"scout task deliverable, written by the crewmate; survives teardown"。意思是:报告放在 `data//report.md`,由做这件调研的船员 / crewmate 写,收尾清理之后还在。 -这三条背后是同一句话:**一份诊断请求、一份报告、一条建议、或者一个已经具备实现条件的发现,都是证据,不是「去改代码」的授权。** 报告能告诉你「情况是这样」,但从来不能替你说出「所以照着做」。 +报告和船员的工作树是两回事。工作树是船员干活用的临时地方,活干完就收尾清理掉;报告是留下来的东西。 -## 报告长什么样,存在哪里 +「Scout outcome and promotion」小节规定了先后顺序:调研做完,必须先留下一份内容完整、单独能看懂的报告,才能清理它的工作树。大副读完报告,把结论告诉你,把这份报告记成这张工单的成果,再重新看一遍待办队列:有了新结论,有些活可能可以开始了。 -`AGENTS.md` 第 2 节的布局表这样定义侦察的产出物: +报告可以建议怎么改,但不代表同意改。 -> `/report.md` scout task deliverable, written by the crewmate; survives teardown +## 报告怎么变成交付任务 -拆开看有两层意思。第一层是位置和作者:报告写在这件侦察任务自己的 `data//report.md` 里,由执行这件侦察的船员 / crewmate 写出来。第二层,也是容易被忽略的一层:**报告跟船员的工作副本不是一回事**。工作副本是船员干活用的临时地方 - 一份一次性的、隔离的 git 检出,事情办完之后会被收回(teardown);报告是留下来的东西,工作副本被收回之后,报告依然在。 +你看完报告,决定照着改,大副不会另开一张新工单重新调查一遍。同一小节规定:你另外同意动手之后,大副用 `bin/fm-promote.sh` 把这件调研转成交付任务。 -`AGENTS.md` 第 7 节「Scout outcome and promotion」把这个先后顺序钉得更死: +「另外同意」是关键:调研做完时并没有带着动手的授权,要你看完报告再说一句「照着改」才算。 -> A completed scout must leave a self-contained report before its scratch worktree can be discarded; read and relay its findings, record the report as the Done artifact, and re-evaluate the queue. A report may recommend implementation but does not authorize it. +转成交付任务时,船员要先清点调研时留下的临时改动,回到一个干净的、基于默认分支的起点,只带上真正要用的修复,再建交付分支,按项目选定的交付方式走下去。调研时的草稿提交和调试改动都不带过去;调研时复现过的 bug,要变成一条回归测试。 -也就是说,一件侦察任务的工作副本能不能被收回,前提是这份**自成一体**的报告已经写好并留下 - 报告写不出来,工作副本就不能被扔掉。大副读完报告、把结论转述给你、把这份报告记成这件工单的 Done 产物,再重新评估队列里还有哪些活因为这份新知识而值得动一动。而这份报告,即便它明确建议了「应该这么实现」,也依然不构成授权 - **一份报告可以推荐实现,但不能授权实现**。 - -## 报告变成活:promotion,而不是重新立项 - -一份侦察报告读完之后,如果你确实决定照着做,下一步不是让大副凭空立一件新的 ship 工单去重复调查一遍。同一节接着写: - -> When implementation is separately authorized, promote the existing scout through `bin/fm-promote.sh` rather than creating a duplicate task. The promoted worker must inventory scratch state, return to a clean default-branch base, carry over only intended fix changes, create the ship branch, and follow the project's selected delivery path while leaving scratch commits and debug edits behind and turning a reproduced bug into the regression test. - -这里的关键词是「separately authorized」- **单独**授权。侦察本身完成时并没有带着实现的授权;你读完报告,另外说一句「照着做」,这件事才算被单独授权。授权之后,大副走的是 `bin/fm-promote.sh` 把这件已有的侦察**提升**成一件 ship,而不是另开一件新工单去凭空重做一遍。这个提升过程本身也有讲究:被提升的船员要先清点自己在侦察阶段留下的临时状态,退回到一份干净的默认分支基线,只把真正要带进实现里的改动搬过来,再开一条新的 ship 分支,照选定的交付路径走下去 - 侦察阶段的草稿提交、调试用的临时改动都留在原地不带过来;如果侦察阶段复现过一个 bug,那次复现要在提升之后变成一条回归测试,而不是原样搬过去的调试脚本。 - -打一个比方(下面这段是**假设的场景**,不是这个仓库里真实发生过的事,纯粹用来帮你建立直觉):假设你怀疑某个接口偶尔超时,但不确定是网络问题还是代码里某处死锁,你让大副派一支队伍去查。这支队伍最后交给你的是一份报告,写着「复现到了,是第 43 行那把锁在特定顺序下会死锁,建议这样改」。你读完这份报告,这一刻你手上有的是一个**结论**,还没有任何代码改动发生。你说「那就照这个改」之后,大副才会把这件侦察提升成一件 ship,由船员把死锁复现步骤变成一条回归测试,再实现修复,走质检流水线,开 PR。报告本身,从头到尾都没有直接产出任何项目改动。 +举个假设的例子,帮你建立直觉:你怀疑某个接口偶尔超时,但不知道是网络问题还是代码里有死锁,就让大副派人去查。交回来的是一份报告:「复现了,第 43 行那把锁在特定顺序下会死锁,建议这样改。」这时你手上只有一个结论,代码一行没动。你说「照这个改」之后,大副才把这件调研转成交付任务,由船员把复现步骤写成回归测试,再做修复,走流水线,开 PR。报告本身从头到尾没有改过项目。 ## 动手练习 -下面的练习是**只读**的:只是让大副读取信息、做推理并汇报,不会创建工单、不会派出真正的船员、不会改动任何被版本追踪的文件,也不会做任何不可回退的操作。你随时可以停下。 +这个练习是只读的:只让大副推演和汇报,不登记工单,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习:拿三句话分别试一次 ship / scout 判断 +### 练习:用三句话测试交付和调研的判断 对大副说: -> 大副,我要试一下你立项时怎么在 ship 和 scout 之间判断,不是真的要你干活。我依次给你三句话,请你对每一句话分别说清楚:你会把它归成 ship 还是 scout,理由是什么,以及如果归成 scout,报告最后会落在哪个文件里。第一句:「我们那个登录接口好像偶尔会超时,原因不知道,你先别改,帮我查清楚是哪里的问题,写份报告给我」。第二句:「刚才那份关于登录超时的报告我看完了,就按报告里建议的方案改」。第三句:「README 里现在有没有提到这个项目支持 Windows,直接告诉我就行」。这只是一次推演,不要真的创建工单,也不要派任何船员。 - -**预期会看到什么** +> 大副,我想看看你立项时怎么在交付任务和调研任务之间判断,不是真要你干活。下面三句话,请分别说你会归成哪一类、为什么,如果是调研任务,报告会放在哪个文件。第一句:「登录接口好像偶尔会超时,原因不知道,先别改,帮我查清楚是哪里的问题,写份报告给我。」第二句:「刚才那份登录超时的报告我看完了,就按报告里建议的方案改。」第三句:「README 里现在有没有写这个项目支持 Windows?直接告诉我就行。」只推演,不要登记工单,也不要派船员。 -- 第一句:归成 scout,理由是原因不明、且这处不确定性会实质影响该怎么修 - 这正是派侦察的触发条件;它会说出报告会落在 `data//report.md`,并且指出这件事完成后工作副本会被收回、但报告会留下来。 -- 第二句:归成 ship,理由是报告已经给出结论、你也明确说了「照着做」,这是一次独立的授权;它会提到这一步走的是 `bin/fm-promote.sh` 把已有的侦察提升成 ship,而不是另开一件新工单重新调查。 -- 第三句:它会指出这是一个已有证据(README 本身)就能直接回答的问题,应该直接把答案讲给你听,不需要派任何侦察去调查。 -- 三句话的判断都会点出处:`AGENTS.md` 第 7 节「Intake and authority」和「Scout outcome and promotion」。 +**你应该看到** -**怎么判断做成了** +- 第一句:调研任务。原因不明,而且会影响怎么修,正是该调研的情况。报告放在 `data//report.md`,调研做完后工作树会清理掉,报告会留下。 +- 第二句:交付任务。报告已经给出结论,你也明确说了照着改,这是单独的一次同意。它会提到用 `bin/fm-promote.sh` 把原来的调研转成交付任务,不另开新工单重新查。 +- 第三句:不用派任何任务。README 本身就能回答,直接告诉你就行。 +- 说明出处:`AGENTS.md` 第 7 节「Intake and authority」和「Scout outcome and promotion」。 -三个都满足才算:三句话的 ship/scout 归类都对,且理由讲的是「不确定性是否会改变要不要做、做成什么样」而不是「事情听起来复杂/简单」;它准确讲出了报告的位置和「报告不等于授权,需要你另外说一句才会提升成 ship」这条边界;以及 - 最关键的 - 它**没有创建任何工单,也没有派出任何船员**,你可以直接看一眼有没有新开的会话窗口来确认。 +**怎么检查** -文件层面的验证办法跟前几章一样,**前后对比**:提问之前在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见,会话期间写这些不算数。 +- 三句话的归类都对,理由是「这个问题会不会影响要不要做、做成什么样」,而不是「听起来复杂还是简单」。 +- 讲对了报告放在哪里,也讲清了报告不等于同意动手,要你另外说一句才会转成交付任务。 +- 没有登记工单,也没有多出新的船员窗口。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -如果它把第二句也判断成了需要重新派一件侦察,或者把第一句直接当成了可以动手改代码的授权,那就是没做成。 +如果它说第二句还要再派一次调研,或者把第一句当成了可以直接改代码的授权,就算没做成。 ## 下一章 -第 7 章讲**backlog(工单)与船长决策**:工单队列怎么记录在办和待办的活,一个等你拍板的决策为什么本身也是一张工单,以及它怎么被关闭。 +第 7 章讲待办队列与船长决策:待办队列怎么记活,等你拍板的问题为什么也是一张工单,你的回答怎么关掉它。 diff --git a/chapters/07-backlog-and-captain-decisions.md b/chapters/07-backlog-and-captain-decisions.md index fa31bf8..e87b467 100644 --- a/chapters/07-backlog-and-captain-decisions.md +++ b/chapters/07-backlog-and-captain-decisions.md @@ -1,98 +1,90 @@ -# 第 7 章:backlog(工单)与船长决策 +# 第 7 章:待办队列与船长决策 ## 这一章讲什么 -前面几章沿着**一件活**的旅程往下走:怎么提出它、怎么派工、怎么过质检、怎么合并。这一章把镜头切到旁边:大副 / firstmate 手上从来不止一件活,这些活怎么被记下来、排成队,而其中有一类活很特殊 —— 它不需要船员 / crewmate 去改任何代码,只需要**船长 / 你**说一句话。 +前面几章跟着一件活走。实际上大副 / firstmate 手上从来不止一件活,这些活要记下来、排好队。其中有一类很特别:不用船员 / crewmate 改任何代码,只要船长 / captain(你)说一句话。 -这一章只讲三件事:backlog(工单队列)怎么分「在办」和「待办」;一个等你拍板的决策,为什么在系统眼里跟一件普通的活没有本质区别,只是被「挂起」等你;以及你的回答怎么把它关掉。怎么在不吵到你的前提下盯住整支队伍、什么事件会主动叫醒大副,这些是下一章的内容,这里不展开。 +这一章讲三件事:待办队列(backlog)怎么把活分成在办、待办和已完成;一个等你拍板的问题,为什么也只是一张工单,只是被挂起等你;你的回答怎么把它关掉。大副怎么盯住整支队伍、什么事会叫醒它,是下一章的内容。 -本章依据的权威文件是 `AGENTS.md` 第 10 节「Backlog contract」,按需引用 `bin/fm-tasks-axi.sh` 和 `bin/fm-captain-hold.sh` 的头部注释。每个小节会指出具体出处。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 10 节「Backlog contract」、`docs/configuration.md` 的「Backlog backend」一节,以及 `bin/fm-captain-hold.sh` 开头的注释。 -## backlog 是什么:活按状态分三档 +## 待办队列:活分三类 -`AGENTS.md` 第 10 节开头就定了 backlog 的位置:「The configured `tasks-axi` backend is the durable queue... It tracks work items only, never agents; persistent secondmates never appear as backlog items.」 +待办队列是整支队伍所有活的长期记录,用 `tasks-axi` 管理,默认存在 `data/backlog.md`。它只记活,不记人:常驻的二副 / secondmate 不会作为一条工单出现在队列里。队列里的每一条是一件事。 -翻成大白话:backlog(工单队列)是这支队伍手上所有活的**持久记录**,靠 `tasks-axi` 这套工具管理,落地文件默认是 `data/backlog.md`。它记的是**活本身**,不是船员这个人 —— 一个常驻的二副 / secondmate 永远不会作为一个条目出现在这份队列里,队列里的每一行是一件事,不是一个人。 +每件活都在下面三类之一: -一件活在队列里只会落在三档之一,出处是 `docs/configuration.md`「Backlog backend」一节:「On the default markdown adapter, tasks-axi and manual edits produce the same `## In flight`, `## Queued`, and `## Done` sections.」 +- **待办**(`## Queued`):已经登记了工单,还没派出去。 +- **在办**(`## In flight`):已经派了船员,正在工作树里干。 +- **已完成**(`## Done`):已经做完,只保留最近几条。 -- **`## Queued`(待办)**:已经立了工单,但还没派出去。 -- **`## In flight`(在办)**:已经派了船员,正在一份隔离工作副本里干活。 -- **`## Done`(已完成)**:已经收工,只保留配置允许的近期条数,更老的挪进归档。 +从待办到在办、从在办到已完成,这两步不用大副手动去改。派船员的 `bin/fm-spawn.sh` 和收尾清理的 `bin/fm-teardown.sh` 会自己挪动工单;挪不成功,它们会拒绝继续,不会报告「已经派出去了」或「已经做完了」。 -一件活从待办变在办、从在办变已完成,这两次转移在系统里**不是**大副手动去记的两个动作。同一节写得很直接:「When the automatic transition gate applies, dispatch and completion move the item themselves - `bin/fm-spawn.sh` and `bin/fm-teardown.sh` own those transitions and refuse rather than report success without them。」也就是说,派工命令 `bin/fm-spawn.sh` 和收尾命令 `bin/fm-teardown.sh` 自己把队列条目挪到该去的位置,而且如果没挪成功,它们会拒绝报告「这件活派出去了」或「这件活收工了」,而不是先斩后奏。 +所以大副要自己做的只剩三件:派工前登记工单,记录决定,保持工单备注是最新的。另外,每次有活收尾清理完,或者每次心跳检查时,大副都会回头看一遍待办的活,把依赖和时间条件都满足了的派出去。队列不是一张放着不动的清单,有变化就要重新过一遍。 -这样一来,大副真正要操心的,只剩三件事:**派工前把这件活立成工单**、**记录决策**、**把工单笔记维护成最新的**。同一节接着写:「Re-evaluate queued work after every teardown and heartbeat, dispatching items only when dependencies and time gates have cleared。」—— 每次有活收尾、或者每次心跳巡检,大副都要回头再看一眼待办区,把依赖和时间条件都已经解除的活派出去。这就是「队列」这个词在这里的实际含义:待办区不是一个静止的清单,而是每次有动静就要重新过一遍的东西。 +## 等你拍板的问题,也是一张工单 -## 一个决策本质上只是一张等船长拍板的工单 +第 10 节的原话是 "A decision is simply a task held for the captain.":一个决定就是一张挂起等船长的工单。 -这一章要讲的核心概念,`AGENTS.md` 第 10 节只用一句话就定死了:「A decision is simply a task held for the captain.」 +决定没有单独的类型。它不是队列外的另一种东西,也不是聊天记录里说过就算。它就是一张普通工单,只是被挂起等你回答。所以一个等你拍板的问题,和一件等船员去改代码的活,记录方式完全一样:会出现在队列里,会被跟踪,不会因为「只是问一句」就被漏掉。 -这句话值得多读一遍:**决策没有单独的类型**。它不是队列之外另开的一种东西,也不是聊天记录里的一句话就算数 —— 它就是一件普通的工单,只是被「挂起」(hold)在等你回答这一个状态上。这意味着一个等你拍板的问题,跟一件等船员去改代码的活,享受的是同一套记账规则:它会出现在队列里,它会被追踪,它不会因为「只是问一句话」就漏记或者忘掉。 +具体做法分两步: -同一节接着给出这件事具体怎么做:「create the task with `bin/fm-tasks-axi.sh add` when needed, then always hold it through `bin/fm-captain-hold.sh hold --reason ""`, with `--until ` when the captain defers it。」 +1. **还没有对应的工单,就先用 `bin/fm-tasks-axi.sh add` 登记一张。** 如果这个问题卡住的是一件正在做的活,就直接挂起那件活,不另开新工单。`bin/fm-captain-hold.sh` 的注释写着:"Prefer holding the work item the question gates over minting a new row." +2. **然后一定要用 `bin/fm-captain-hold.sh hold --reason "<原因>"` 挂起它。** `--reason` 写清为什么这件事要你来定。你暂时不想回答、要过一阵再看,就加上 `--until <日期>`,把「以后再说」变成一个具体日期,而不是一张一直挂着、没人知道什么时候该回头看的工单。 -拆开看是两步: +还有一种情况:有些事不属于哪个项目的具体任务,比如你和大副对话里一个待你决定的问题,或者一条要转告你的提醒。只要值得长期跟踪,也要单独登记一张工单,用同样的方式挂起,不能只留在某次对话里。 -1. **如果这件事还没有对应的工单,先用 `bin/fm-tasks-axi.sh add` 立一张。** 如果这件事本来就挂在某件正在办的活底下,就不用另开一张新的,直接挂起那件活本身就行 —— `bin/fm-captain-hold.sh` 的头部注释写得很明确:「Prefer holding the work item the question gates over minting a new row.」 -2. **然后一定要用 `bin/fm-captain-hold.sh hold --reason "<原因>"` 把它挂起。** 这一步不是可选项,「always」两个字说得很清楚。`--reason` 要写清楚为什么这件事需要你来定,而不是船员或大副自己就能判断的。如果你当下没法立刻回答、需要过阵子再看,就再加一个 `--until `,把「以后再说」变成一个具体的日期,而不是一张长期悬着、没人知道什么时候该回头看的卡片。 +## 挂起后,工单里记了什么 -还有一类容易被忽略的情况,同一节也点了出来:「When a main-side thread such as a pending captain decision or relay reminder is worth durable tracking, file it as its own work item and hold it through that wrapper.」也就是说,即便这件事没有对应哪个具体项目的具体任务 —— 比如一条纯粹发生在跟你对话主线里的、值得长期留痕的提醒或待决问题 —— 只要它值得追踪,也要单独立一张工单,用同一套挂起机制记下来,而不是让它只活在某一次对话的上下文里,问完就消失。 +按 `bin/fm-captain-hold.sh` 的注释,`hold` 会在工单正文里记一个 UTC 时间戳「Captain hold set:」。重复挂起同一张工单,原来的时间戳不会被覆盖;一件活解除挂起后又被挂起,算一次新的等待。已经关掉的工单不能再挂起,会被拒绝。 -## 挂起之后:这张工单长什么样 +这些细节都是为了让记录如实反映经过,不会每操作一次就把之前的痕迹抹掉。以后回头看一个决定是怎么走的,看到的是真实的时间线。 -`bin/fm-captain-hold.sh` 的头部注释里,`hold` 子命令的行为写得很具体:这一步会在工单正文里记一条 UTC 时间戳「Captain hold set:」。如果这件事重复被挂起,已有的时间戳不会被覆盖;如果一件之前已经解除挂起、又重新回来办的活被再次挂起,那算作开启一段新的等待周期。一件已经关闭的工单不会被重新打开去挂起 —— 它会被拒绝。 +## 你的回答怎么关掉它 -这几条细节背后是同一个原则:挂起状态本身要**如实反映历史**,而不是每次操作都把之前的痕迹抹掉重写。你后面回头去看一件决策是怎么走的,看到的应该是真实发生过的时间线,而不是最后一次操作留下的孤零零的一条记录。 +`bin/fm-captain-hold.sh` 的 `answer` 子命令会记下你的原话,并在同一步里把这件事了结:记录你的回答和关闭工单是同一个动作,不用再有人去手动关一下。 -## 船长的回答怎么把它关闭 +分两种情况: -工单被挂起之后,怎么解除?`bin/fm-captain-hold.sh` 头部注释里 `answer` 子命令的说明写得很直接:「`answer` records the captain's exact words and resolves the call in the same act... It closes a question with `tasks-axi done` - or, with `--release`, lifts the hold with `tasks-axi unhold` so a captain-gated WORK item resumes without closing。」 +- 挂起的是**专门为等你拍板而登记的工单**(比如「要不要给这个仓库配 CI」,本身不需要船员干活),你的回答一记下,工单就用 `tasks-axi done` 关掉,进入已完成。 +- 挂起的是**一件正在做、只是卡在等你判断的活**,就用 `answer --release`:只解除挂起(`tasks-axi unhold`),让这件活接着做,不把它标成完成,因为活本身还没干完。 -这句话里有一个容易被忽略、但其实是这一章最关键的细节:**记录你的原话,和关闭这张工单,是同一个动作**。不是先记录、再由某个人手动去点一下「关闭」;你的回答一落到工单正文里,`tasks-axi done` 就在同一次操作里把它标成已完成。你的这句话本身就是关闭这张工单的动作 —— 它既是答案,也是收尾。 +## 例子:这个仓库的 CI 是怎么来的 -这里还分两种情况,取决于挂起的是什么: +这个仓库自己的历史里,有一段能公开核对的经过,正好串起了这几件事。 -- 如果挂起的是一件**只为了等你拍板而单独立的工单**(比如「要不要给这个仓库配 CI」这种本身不需要船员去干活的问题),你的回答记录下来,这张工单就直接进「已完成」区。 -- 如果挂起的是**一件本来就在办、只是卡在等你某个判断上的活**(比如一件正在推进但被某个开放问题拦住的工作项),`answer` 命令支持 `--release`:只解除挂起,让这件活回到该接着办的状态,而不把它整个标成完成 —— 因为活本身还没干完,只是不再等你了。 +交付第一章时,质检流水线走到 CI 那一步,但仓库里还没有任何 CI 检查,PR 上的检查列表是空的,流水线等不到检查通过。要不要给一个纯文档仓库配 CI,是一个该由船长定的产品问题,船员不能自己拍板。当时挂起写的 `--reason` 和船长回答的原话,是大副私有的记录,不在这个仓库的 GitHub 历史里,教程不去猜。 -## 案例:从这个仓库自己的 PR 记录看这三件事怎么串在一起 - -这个仓库自己的交付历史里,能公开核对的那部分,正好把这三件事串在了一起。 - -第一章《FirstMate 是什么》交付的时候,质检流水线走到了 CI 关卡 —— 但当时这个仓库里还没有任何 CI 检查可跑,PR 上的检查列表是空的,流水线卡在「等检查变绿」这一步永远等不到结果。要不要给一个纯文档教程仓库配置真正的 CI,这是一个只有船长才该定的产品判断,不是随便哪个船员能自己拍板的事。这次挂起具体记的是哪一句 `--reason`、你当时回答的原话是什么,属于大副私有的运营记录,不在这个仓库的 GitHub 历史里,教程没法把它当作可核对的公开材料引用 —— 找不到出处的具体措辞,教程不去猜,猜了就是编。 - -公开、可核对的部分是它促成的结果:[PR #2「ci: 加一套零依赖的 markdown 链接与格式检查」](https://github.com/jasontopia/learn-firstmate/pull/2)先于[PR #1「docs: 搭建教程骨架并写出第一章《FirstMate 是什么》」](https://github.com/jasontopia/learn-firstmate/pull/1)合并进 `main`。按上一节讲的规则倒推:一张挂起的工单只会在船长的回答落地时关闭,所以这唯一说得通的解释是,那次挂起先被你的回答关闭,随后催生了 PR #2 这件新工单去真正配置 CI;CI 配好合并后,被卡住的第一章才能重新跑绿、合并为 PR #1。这也是编号在前的那件活(PR #1),反而要等编号在后的那件活(PR #2)先落地才能收尾的原因。 - -这一个例子对上了这一章讲的三件事里公开可查的那部分:一个决策以工单形式存在、被挂起等船长拍板,以及船长的回答怎么关闭它并催生后续的活 —— 挂起当时具体的原因文字和你回答的原话,则是私有运营记录,如实注明「找不到公开出处」比编一段读起来更完整的叙述更重要。 +能公开核对的是结果:[PR #2「ci: 加一套零依赖的 markdown 链接与格式检查」](https://github.com/jasontopia/learn-firstmate/pull/2)比[PR #1「docs: 搭建教程骨架并写出第一章《FirstMate 是什么》」](https://github.com/jasontopia/learn-firstmate/pull/1)先合进 `main`。按上面的规矩倒推:那次挂起先被船长的回答关掉,接着有了 PR #2 这件新活去配 CI;CI 配好合并后,卡住的第一章才重新跑通,作为 PR #1 合并。这就是编号在前的 PR #1 反而要等 PR #2 先合并的原因。 ## 动手练习 -下面这个练习是**只读**的:只是让大副去读队列和工单记录并汇报,不会创建工单、不会派出真正的船员、不会改动任何被版本追踪的文件,也不会做任何不可回退的操作。你随时可以停下。 +这个练习是只读的:只让大副读队列和工单并汇报,不登记工单,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习:读一遍你自己的队列,找出所有挂起的工单 +### 练习:读一遍你的队列,找出挂起的工单 对大副说: -> 大副,不用派任何船员,只帮我读一遍现在的 backlog:现在「在办」和「待办」各有哪些活,分别是什么状态;有没有工单目前正被「挂起」等我拍板 —— 如果有,讲清楚每一张挂起的工单是为什么被挂起的、给了我什么样的选项或者在等什么。如果队列是空的,或者目前没有任何工单在挂起,就直接说没有,不要为了有话说而编一个出来。这只是读一遍现状,不要创建工单,也不要改任何东西。 - -**预期会看到什么** +> 大副,不用派船员,帮我读一遍现在的待办队列:在办和待办各有哪些活,各是什么情况;有没有工单正挂起等我拍板,有的话,每一张为什么挂起、给了我哪些选项、在等什么。如果队列是空的,或者没有挂起的工单,就直接说没有,不要编一个出来。只读现状,不要登记工单,也不要改任何东西。 -- 它会按「在办 / 待办 / 已完成」这几档分类讲,而不是把所有条目混在一起报一遍。 -- 对每一张目前被挂起的工单,它会说清楚挂起的原因(对应 `--reason` 记的那句话),以及你需要回答什么,而不是只说「有一件事在等你」。 -- 如果眼下没有任何工单被挂起,它会直接说没有 —— 不会硬凑一个听起来像决策的东西出来给你看。 -- 它会点出出处:`AGENTS.md` 第 10 节「Backlog contract」,以及它读的是 `tasks-axi`(或 `bin/fm-tasks-axi.sh`)报出来的实际队列状态,而不是凭印象讲。 +**你应该看到** -**怎么判断做成了** +- 按在办、待办、已完成分开讲,而不是把所有工单混在一起。 +- 每一张挂起的工单,都说清挂起的原因(也就是 `--reason` 里写的)和需要你回答什么,而不是只说「有件事在等你」。 +- 没有挂起的工单时直接说没有,不会凑一个像决定的东西出来。 +- 说明出处:`AGENTS.md` 第 10 节「Backlog contract」,并说明它读的是 `tasks-axi`(或 `bin/fm-tasks-axi.sh`)给出的实际队列,不是凭印象讲。 -两条都满足才算:它讲的是队列的**真实当前状态**,你能自己拿它报的条目去对照 `bin/fm-tasks-axi.sh`(或者它落地的 `data/backlog.md`)核对是不是真有这些内容;以及 —— 如果它说某张工单被挂起了,它讲出的原因和选项经得起你追问「这是不是真的写在那张工单里」。 +**怎么检查** -文件层面的验证跟前面几章一致,**前后对比**:提问之前在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见,会话期间读这些不算数。 +- 讲的是队列的真实状态。你可以自己用 `bin/fm-tasks-axi.sh` 或打开 `data/backlog.md` 对照,看是不是真有这些工单。 +- 如果它说某张工单被挂起了,你追问「这真的写在那张工单里吗」,它的回答站得住。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -如果它趁机替你把某张挂起的工单答了、或者顺手立了一张新工单,那就是没做成 —— 这个练习只要求它读和讲。 +如果它顺手替你回答了某张挂起的工单,或者登记了一张新工单,就算没做成。这个练习只要它读和讲。 ## 下一章 -第 8 章讲 **supervision(监督)与唤醒机制**:大副怎么在不烧 token 的前提下盯住整支队伍,什么事件会把它叫醒,什么时候才该轮到你被打扰。 +第 8 章讲监督与唤醒:大副怎么在不耗 token 的情况下盯住队伍,什么事会叫醒它,什么事才来找你。 diff --git a/chapters/08-supervision-and-wakeups.md b/chapters/08-supervision-and-wakeups.md index 6587205..7f74a10 100644 --- a/chapters/08-supervision-and-wakeups.md +++ b/chapters/08-supervision-and-wakeups.md @@ -1,72 +1,57 @@ -# 第 8 章:supervision(监督)与唤醒机制 +# 第 8 章:监督与唤醒 ## 这一章讲什么 -第 6 章讲了侦察任务怎么产出一份报告,第 7 章讲了工单队列怎么记录在办和待办的活、以及一个决策本身怎么变成一张工单。这两章都是从**一件活**的角度看问题。这一章把镜头拉远:当队伍里同时有好几件活在跑的时候,大副 / firstmate 怎么盯住整支船员 / crewmate 队伍,而不需要为了「盯着」这件事本身持续消耗 token。 +第 6 章讲调研报告,第 7 章讲待办队列,都是从一件活的角度看。这一章看整支队伍:好几件活同时在跑时,大副 / firstmate 怎么盯住所有船员 / crewmate,又不会为了「盯着」本身一直耗 token。 -这一章只回答三个问题:大副是怎么在没有事情发生的时候完全不跑、不烧 token 的;几种唤醒(wake)事件分别代表什么;唤醒之后,哪些事大副自己按规矩就能拍板,哪些才轮到船长 / captain 被打扰。二副 / secondmate 怎么帮大副分担这些监督工作,是下一章的内容,这里不展开。 +这一章回答三个问题:没事发生时,大副为什么完全不耗 token;几种唤醒(wake)各代表什么;被叫醒后,哪些事大副自己处理,哪些事才来找船长 / captain。二副 / secondmate 怎么帮大副分担,是下一章的内容。 -本章依据的权威文件:FirstMate 仓库 `AGENTS.md` 第 8 节「Supervision protocol」和第 9 节「Escalation and captain etiquette」,以及 `docs/architecture.md` 里「Event-driven supervision」一节和 `bin/fm-watch.sh`、`bin/fm-crew-state.sh` 的头部注释。每个小节会指出具体出处。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 8 节「Supervision protocol」和第 9 节「Escalation and captain etiquette」、`docs/architecture.md` 的「Event-driven supervision」一节,以及 `bin/fm-watch.sh`、`bin/fm-crew-state.sh` 开头的注释。 -## 大副不是靠死循环盯着队伍的 +## 盯着队伍的是一个脚本 -如果大副要盯住三件同时在跑的活,最直觉的做法是:开一个循环,每隔几秒就把三件活的状态都问一遍模型「有没有变化」。这样做能盯住,但代价是那个循环里的每一次询问都是一次真实的模型调用 - 队伍越大、活跑得越久,这个盯梢的开销就越大,而且跟活本身有没有进展毫无关系。 +要盯住三件同时在跑的活,最直接的办法是写个循环,每隔几秒就让模型看一遍三件活有没有变化。这样确实盯得住,但每看一次都是一次真实的模型调用。队伍越大、活跑得越久,花的就越多,而且跟活有没有进展毫无关系。 -FirstMate 不是这样做的。`docs/architecture.md`「Event-driven supervision」一节开头写得很直接:「A zero-token bash watcher (`bin/fm-watch.sh`) sleeps on the fleet, classifies detected wakes in bash, and wakes the first mate only when something is actionable.」 +FirstMate 不这么做。`docs/architecture.md` 里写:一个不耗 token 的 bash 监控脚本(`bin/fm-watch.sh`)守着整支队伍,用 shell 逻辑给发现的动静分类,只有真正需要处理时才叫醒大副。按 `bin/fm-watch.sh` 的注释,不需要处理的动静它自己消化掉,继续等;只有需要处理的唤醒,它才退出,把控制权交回大副。 -翻成大白话:真正「盯着」船员队伍的,是一个纯 bash 脚本,不是大副这个模型会话。这个脚本自己睡着等,用 shell 逻辑去分类它看到的各种动静,只有当动静够得上「需要大副处理」这条线时,才把大副的会话叫醒。`bin/fm-watch.sh` 的头部注释补了一句关键的话:它默默吸收掉良性的动静,继续睡着等下去;只有真正需要处理的唤醒,才会让它退出并把控制权交还给大副。 +所以: -这解释了「不烧 token」这件事到底是怎么成立的: +- 没有唤醒时,大副的会话闲着,不调用模型,不耗 token。 +- 大部分动静(比如某个船员的流水线正常往下走了一步)被监控脚本直接消化掉,连大副都不用叫醒。 +- 只有需要处理的事,才会叫醒大副一次。处理完,大副又回到闲着的状态。 -- 没有唤醒的时候,大副的会话**根本不在跑**,不产生任何模型调用,自然没有 token 开销。 -- 大部分动静(比如某个船员的质检流水线正常地往下走了一步)被这个 bash 脚本自己判定成良性、直接吸收掉,连叫醒大副这一步都省了。 -- 只有动静够得上「大副需要来处理」的门槛,大副的会话才被真正唤醒一次,处理完之后再度回到不跑的状态。 +一直开着但多数时候什么都不用做的「盯着」,交给不花钱的 shell 脚本;需要判断和拍板的事,才交给被叫醒的大副。 -所以这一章标题里的「不烧 token」不是一句宣传语,而是一个具体的架构选择:把「持续盯着」这件需要一直运行、但绝大多数时候什么也不用做的工作,交给一个不花钱的 shell 脚本;把「需要判断、需要拍板」这种真正要用到模型的工作,只留给被叫醒之后的大副。 +## 四种唤醒 -## 四种唤醒事件 +`AGENTS.md` 第 8 节把传到大副手上的唤醒分成四种。唤醒的类型名保留英文,因为它们就写在唤醒原因的开头: -`AGENTS.md` 第 8 节把每一次真正传到大副手上的唤醒,按处理方式分成四种。原文: +- **`signal`(信号)**:某个船员往自己的状态文件里写了一行,比如「做完了」「卡在一个要决定的地方了」。大副先读写了什么;只有接下来的动作取决于当前状态时,才去核对当前状态。 +- **`stale`(久无动静)**:某个船员很久没有动静,可能停了、在原地打转、糊涂了或者没反应。这只说明该去看一眼,不代表确实出了问题。大副会去看它的会话窗口;船员真的卡住了,就按 `stuck-crewmate-recovery` 技能处理。 +- **`check`(检查结果)**:大副安排的某个定期检查有了结果,比如 PR 有没有合并、有没有新的外部贡献动静。这是之前问出去的问题有了答案,不是船员主动报告。 +- **`heartbeat`(心跳)**:按固定节奏触发的兜底检查。大副借这个机会把整支队伍从头看一遍,核对有没有可疑的任务或 PR 状态,更新待办队列。队伍没变化,不能当成进展报告给船长。 -> 1. For `signal:`, read the listed event lines first, then reconcile current state only where action depends on it. -> 2. For `stale:`, inspect the recorded endpoint and load `stuck-crewmate-recovery` for a stopped, looping, confused, or unresponsive worker... -> 3. For `check:`, act on the named poll result, including merges, contribution signals... -> 4. For `heartbeat:`, review the whole fleet from the structured fleet view, reconcile suspicious tasks and PR state, update the backlog, and never report an unchanged fleet as progress. +四种里只有 `signal` 是船员主动发出的,另外三种都来自大副自己安排的监督机制。 -翻成大白话,这四种唤醒分别对应四种不同的动静: +### 状态行记的是当时发生的事,不是现在的状态 -- **signal(信号)**:某个船员自己往状态文件里写了一行,比如「活干完了」「卡在一个需要拍板的地方了」。大副先读这行写了什么,只有当接下来要做的事真的依赖「现在到底是什么状态」时,才去核对当前状态 - 不是每次都要核对。 -- **stale(安静太久)**:一个船员安静了超过预期的时长,看起来可能停了、在死循环、或者卡住了。这不是「已经确认出问题」,只是「该去看一眼了」的提示。 -- **check(查询式检查结果)**:大副自己派出去的某个后台查询有了结果 - 比如 PR 有没有合并、有没有新的外部贡献信号。这是「问出去的问题有答案了」,不是船员主动喊的。 -- **heartbeat(心跳)**:一个兜底的、按固定节奏触发的全队检查,逼着大副把整支队伍从头看一遍,核对有没有可疑的任务或 PR 状态、更新一下工单队列 - 而且明确不允许把「队伍状态没变化」本身当成一条汇报给船长的进展。 +第 8 节还特别写了一句:"A status line is a wake event, not current state"。 -四种唤醒里,只有第一种(signal)是船员自己主动喊的;剩下三种都是大副自己安排的机制在替它盯梢的产物。 +船员写进状态文件的一行(比如 `done: ...`),记的是写下那一刻发生了什么,不是现在的状态。一个船员写了 `blocked: ...`,等大副回复之后可能已经接着干下去了,但状态文件最后一行还是那句 `blocked:`。只看这一行,会以为它还卡着。 -### status line 是事件,不是当前状态 - -第 8 节紧接着这四条之后,补了一句对新手最容易踩坑的话:「A status line is a wake event, not current state; use `bin/fm-crew-state.sh` when current state matters, especially before re-escalating an old decision, blocker, or pause.」 - -意思是:船员写进状态文件的那一行(比如「done: ...」),记录的是**它写下那一刻**发生了什么,不是「现在」的状态。一个船员可能在写完「blocked: ...」之后,等大副回应、它自己继续把活推进下去了 - 但状态文件的最后一行还停在那句旧的 `blocked:` 上,如果只看这一行,会误以为它还卡着。`bin/fm-crew-state.sh` 的头部注释把这个问题说得更直白:状态文件是一份「只增不改的事件日志」,船员只在真正值得叫醒大副的时刻才往里写一行,悄悄恢复干活时什么也不写,所以直接看这份日志的最后一行,读到的是**最后一次事件**,不是**当前状态**。这个脚本会去读权威源头(比如质检流水线自己记录的运行步骤、或者会话端点是不是正忙着),把可能过时的日志和这份权威状态对一遍,再吐出一行报告,格式大致是: +`bin/fm-crew-state.sh` 的注释说得更直接:状态文件是只追加的事件日志,船员只在值得叫醒大副时才写一行,悄悄恢复干活时什么也不写。所以读最后一行,读到的是最后一次事件,不是当前状态。这个脚本会去读更可靠的来源(比如质检流水线自己记录的当前步骤,或者会话窗口是不是正在忙),拿它和日志对一遍,然后输出一行结果: ``` state: · source: · ``` -`AGENTS.md` 特别点名:在要重新去打扰船长之前 - 尤其是那种「之前搁置的一个决策/阻塞/等待,是不是还悬着」 - 必须先用这个工具核对一遍当前状态,不能只凭一条旧的状态行就动作。 - -## 案例:一次被误判成「卡住了」的 stale 唤醒 - -打一个比方(下面这段是**假设的场景**,不是这个仓库里真实发生过的事,纯粹用来帮你建立直觉):假设某个船员的质检流水线正在后台跑着,一时半会儿没有新的动静。负责盯梢的机制把这段安静判定成 `stale` 唤醒,把大副叫醒去看一眼,理由是「这个船员看起来可能卡住了」。 - -大副没有直接信这条唤醒本身的措辞。按上面那条规矩,`stale` 只是一个「该去看一眼」的提示,不等于「已经确认卡住」。它跑了一次 `bin/fm-crew-state.sh`,核对权威的当前状态 - 结果发现这个船员其实正处在「验证中」这个完全正常的阶段,质检流水线还在按部就班地往下走,没有任何异常。大副什么也没做,唤醒处理完了,继续回到不跑的状态。 +`AGENTS.md` 要求:需要知道当前状态时就用这个脚本,特别是要为之前一个还没解决的决定、阻塞或等待再去找船长之前,必须先查一遍当前状态,不能只凭一条旧的状态行就行动。 -这正好对应上一节那条规矩:一次唤醒只是「叫你来看一眼」的事件,不能替代对当前状态的核对。如果大副当时直接信了「安静太久 = 卡住了」这条唤醒的字面意思,可能就会去做一次没必要的介入,甚至去打扰船长确认要不要重启这个船员 - 而实际上什么都没坏。 +## 被叫醒后:什么事才来找你 -## 唤醒之后,什么该自己拍板,什么才轮到船长 +大多数唤醒处理完,都不需要找船长。比如一次 `stale` 唤醒,去看了发现船员一切正常,处理就到此为止,船长根本不会知道,也没有什么需要知道的。第 9 节写得很明确:等待一个正常运转的监督周期时保持安静;空的检查结果、单纯过去的时间、没有变化的更新,都不算要报告给船长的进展。 -四种唤醒被处理之后,绝大多数都不会走到「去找船长说一句话」这一步。上面那个案例就是这样:一次 `stale` 唤醒,核对完发现一切正常,处理到此结束,船长完全不知道这件事发生过 - 这本身就没什么该知道的。`AGENTS.md` 第 9 节「Escalation and captain etiquette」把这条原则写得很直接:「Waiting on a healthy supervision cycle is silent; empty polls, elapsed time, and no-change updates are not captain-facing progress.」空转的轮询、单纯过去的时间、没有变化的检查结果,本身都不是该报给船长的进展。 - -同一节接着列出了真正需要马上去找船长的几种情形,原文摘录: +同一节列了需要马上找船长的几种情况,原文是: > Reach the captain immediately for: > - Work ready for their review, with the PR's recorded URL. @@ -76,59 +61,68 @@ state: · source: - Anything destructive, irreversible, or security-sensitive. > - A needed credential or login. -对照这个仓库自己的历史看会更清楚:前几章写完、质检流水线跑绿、PR 真正准备好被合并的那几次,大副才把结果连着 PR 的完整链接一起报给船长 - 这命中的正是「work ready for their review」这一条,跟上面那次被误判的 `stale` 唤醒完全不是一类事情。一次是队伍自己运转正常、什么决定都不需要船长做;另一次是活真的做完了,下一步的决定权必须交到船长手上。区分这两者,靠的不是唤醒本身叫什么名字,而是唤醒处理完之后,落在的是「继续无声运转」还是上面这份清单里的某一条。 +也就是: + +- 活做完了,等你审查,附上 PR 链接。 +- 调研做完了,告诉你结论本身,而不只是一句「做完了」。 +- 质检流水线提出的问题,按 `ask-user-authority` 需要你来定。 +- 把该试的处理办法都试过之后,仍然存在的真实阻塞或失败。 +- 任何有破坏性、不可逆或涉及安全的事。 +- 需要你提供凭证或登录。 + +这个仓库前几章写完、流水线跑通、PR 可以合并时,大副才把结果连同 PR 的完整链接报给船长,对应的正是第一条。一次 `stale` 唤醒查下来一切正常,和一件活真的做完、要船长决定下一步,是完全不同的两件事。区分它们靠的不是唤醒叫什么,而是处理完之后,这件事是继续安静地运转,还是落在上面这份清单里。 -把这一章和第 6、7 章连起来看:第 6 章的侦察报告、第 7 章工单队列里那些等船长拍板的决策,本质上都是「上面这份清单里的某一条被命中」的具体表现 - 一份侦察报告命中的是「finished investigation findings」,一个需要船长拍板的工单命中的是它自己的决策性质。而这一章讲的监督机制,决定的是**在那之前**,大副怎么用几乎不花 token 的方式,把队伍里绝大多数不需要船长知道的动静都自己处理掉,只把真正命中那份清单的事情,过滤出来递给船长。 +跟前两章连起来看:第 6 章的调研报告对应「调研做完了」这一条,第 7 章挂起等你拍板的工单,也是需要你来定的事。这一章讲的监督机制,是在这之前,用几乎不耗 token 的方式把队伍里大部分不需要你知道的动静处理掉,只把真正落在这份清单里的事交给你。 ## 动手练习 -下面两个练习都是**只读**的:只是让大副读取并汇报状态,不会改动任何仓库,也不会派出真正的船员,也不会做任何不可回退的操作。你随时可以停下。 +两个练习都是只读的:只让大副读状态并汇报,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话(例如 `claude`),这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话(比如运行 `claude`),这个会话里的 agent 就是你的大副。 -### 练习 1:让大副讲清楚它自己是怎么盯着船的 +### 练习 1:让大副讲清它怎么盯着队伍 对大副说: -> 大副,不用管队伍里具体某件活,先讲清楚你自己的监督机制:没有唤醒的时候,是谁在替你盯着,你的会话这时候在不在跑、有没有 token 开销;真正传到你手上的唤醒分那几种,分别代表什么。然后,如果队伍里现在有正在办的活,挑一件,用 `bin/fm-crew-state.sh` 读一下它现在的真实状态,而不是只看状态文件最后一行写的是什么;如果队伍里现在没有正在办的活,就直接说没有。不要改任何文件,也不要派任何船员,只回答。 +> 大副,先不管具体哪件活,讲讲你自己的监督机制:没有唤醒时是谁在替你盯着,你的会话这时在不在跑、耗不耗 token;传到你手上的唤醒分哪几种,各代表什么。然后,如果现在有正在做的活,挑一件,用 `bin/fm-crew-state.sh` 查它现在的真实状态,不要只看状态文件的最后一行;没有正在做的活,就直接说没有。不要改文件,也不要派船员,只回答。 -**预期会看到什么** +**你应该看到** -- 它会点出真正盯梢的是一个不花 token 的 bash 脚本(`bin/fm-watch.sh`),自己睡着等,只在动静够得上处理门槛时才把它的会话叫醒;没有唤醒时,它的会话不在跑。 -- 它会讲出四种唤醒 - `signal`、`stale`、`check`、`heartbeat` - 分别对应什么动静,而不是笼统地说「有事就叫我」。 -- 如果队伍里有正在办的活,它会**真的跑一次** `bin/fm-crew-state.sh`,把命令输出讲给你,而不是转述状态文件最后一行写的内容;如果没有正在办的活,它会老实说没有。 -- 它会指出出处,主要是 `AGENTS.md` 第 8 节和 `docs/architecture.md` 的「Event-driven supervision」一节。 +- 它讲到真正盯着队伍的是不耗 token 的 bash 脚本 `bin/fm-watch.sh`,只在需要处理时才叫醒它;没有唤醒时,它的会话闲着。 +- 它讲出四种唤醒:`signal`、`stale`、`check`、`heartbeat`,各对应什么动静,而不是笼统一句「有事就叫我」。 +- 有正在做的活时,它真的跑了一次 `bin/fm-crew-state.sh`,把输出讲给你,而不是转述状态文件最后一行;没有时直接说没有。 +- 说明出处:主要是 `AGENTS.md` 第 8 节,和 `docs/architecture.md` 的「Event-driven supervision」一节。 -**怎么判断做成了** +**怎么检查** -三个都满足才算:它讲的是这套机制的结论,而不是把原文整段贴给你;它清楚区分了「状态文件最后一行」和「用 `bin/fm-crew-state.sh` 核对出来的当前状态」这两件事,没有把两者混为一谈;以及 - 最关键的 - 它**没有动任何被版本追踪的文件,也没有派出任何船员**。 +- 它用自己的话讲结论,不是整段贴原文。 +- 它分清了「状态文件最后一行」和「用 `bin/fm-crew-state.sh` 查出的当前状态」。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样,也没有多出新的船员窗口。 -验证办法跟前几章一样,**前后对比**:提问之前先在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,会话期间读写这些不算对项目的改动,这两条命令本来也看不见。 +如果它把状态文件最后一行直接当成当前状态讲给你,或者顺手改了文件,就算没做成。 -如果它把「状态文件最后一行」直接当成了「当前状态」讲给你,或者顺手改了什么文件,那就是没做成。 +### 练习 2:回看一次真实的唤醒有没有来找你 -### 练习 2:核对一次真实的唤醒有没有惊动过船长 - -练习 1 练的是机制本身,这次练的是核对一件**真实发生过**的唤醒处理:它有没有走到「该找船长」那一步,以及为什么。 +练习 1 讲机制,这次回看一次真实处理过的唤醒:它有没有走到需要找你那一步,为什么。 对大副说: -> 大副,回头看队伍最近处理过的一次唤醒 - 不管是 `signal`、`stale`、`check` 还是 `heartbeat` 哪一种。讲清楚那次唤醒具体是什么动静触发的,你核对之后判断的结果是什么,以及有没有因为这次唤醒去打扰过我。如果没有打扰我,说清楚是因为它落在「继续无声运转」这一类,还是核对完发现是误报;如果确实打扰过我,说清楚命中的是第 9 节「马上找船长」那份清单里的哪一条。如果队伍里还没有可查的唤醒记录,就直接说没有,不要编一个。不要改任何文件,也不要派任何船员,只讲这件已经发生过的事。 - -**预期会看到什么** +> 大副,回头看队伍最近处理过的一次唤醒,`signal`、`stale`、`check`、`heartbeat` 哪种都行。讲讲那次是什么动静触发的,你查完之后的结论是什么,有没有因此来找我。没找我的话,说清是因为它属于正常运转、不用打扰我,还是查完发现是虚惊一场;找过我的话,说清对应第 9 节「马上找船长」清单里的哪一条。如果还没有能查的唤醒记录,就直接说没有,不要编。不要改文件,也不要派船员,只讲已经发生的事。 -- 它讲的是一次真实发生过的唤醒,带得出你能自己核对的东西 - 比如对应的任务标识、时间,而不是抽象地复述这一章的机制。 -- 它明确说出了那次唤醒属于四种里的哪一种,以及核对之后的真实结论。 -- 它清楚地讲出「有没有打扰船长」这个判断的理由,并且这个理由能对上第 9 节的原文 - 要么是「empty polls, elapsed time, and no-change updates are not captain-facing progress」这一类不需要打扰的情形,要么命中了「reach the captain immediately for」清单里的具体一条。 +**你应该看到** -**怎么判断做成了** +- 讲的是一次真实的唤醒,带着你能核对的信息,比如对应的任务编号和时间,而不是把这一章的机制再讲一遍。 +- 说清那次唤醒属于四种里的哪一种,以及查完之后的真实结论。 +- 说清有没有来找你、为什么,而且理由对得上第 9 节:要么是空的检查结果、单纯过去的时间、没有变化的更新这类不用打扰你的情况,要么对应「马上找船长」清单里的某一条。 -两条都满足才算:它讲的是可核对的真事,不是泛泛地复述这一章的规则;以及 - 如果找不到可查的唤醒记录,它老实说找不到,而不是编一个听起来合理的例子出来。 +**怎么检查** -文件层面的判断跟练习 1 一样:提问前后各跑一次 `git status --short` 和 `git diff HEAD`,两次输出应该一样,并且没有新开的船员会话窗口。 +- 讲的是能核对的真事,不是复述规则。 +- 找不到唤醒记录时承认找不到,不编一个听起来合理的例子。 +- 提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样,也没有多出新的船员窗口。 -如果它把这次问答本身讲成了一次新的唤醒,或者对「没有记录可查」避而不谈、直接编了一段听起来合理的唤醒经过,那就是没做成。 +如果它把这次提问本身说成一次新的唤醒,或者在没有记录时编了一段唤醒经过,就算没做成。 ## 下一章 -第 9 章讲**二副 / secondmate 与分工路由**:队伍变大之后怎么按领域拆出常驻的二副,一件活按什么规则路由给谁,以及哪些活必须留在主家。 +第 9 章讲二副与分派:队伍大了以后怎么按领域设常驻的二副,一件活怎么分给谁,哪些活留在大副这边。 diff --git a/chapters/09-secondmates-and-routing.md b/chapters/09-secondmates-and-routing.md index ad271fd..6f0b97e 100644 --- a/chapters/09-secondmates-and-routing.md +++ b/chapters/09-secondmates-and-routing.md @@ -1,107 +1,106 @@ -# 第 9 章:二副 / secondmate 与分工路由 +# 第 9 章:二副与分派 ## 这一章讲什么 -第 6 到 8 章讲的是一支队伍要处理的通用问题:知识怎么沉淀、排队怎么决策、没人盯着时怎么监督。这一章往前迈一步,进入路线图里「规模、离席、以及到底是哪个工具在跑你的船员」这条线的第一站:队伍真的变大之后,一个大副 / firstmate 怎么按领域拆分成常驻的二副 / secondmate,一件活怎么按登记的范围路由给合适的二副,以及哪些活无论如何都必须留在主家。 +第 6 到 8 章讲队伍要处理的日常问题:调研报告、待办队列、监督。从这一章开始,第 9 到 11 章讲进阶话题。这一章讲队伍真的变大以后:怎么按领域设常驻的二副 / secondmate,一件活怎么按登记的负责范围分派给合适的二副,哪些活留在大副 / firstmate 这边。 -离席期间谁接手监督,留给第 10 章;用什么工具、什么运行时去跑船员,留给第 11 章。这一章只讲二副本身是什么、什么时候才值得建它、以及一件活进来之后怎么被分流。 +你走开时谁接手监督,是第 10 章的内容;船员用什么工具、在什么终端里跑,是第 11 章的内容。 -本章依据的权威文件是 `AGENTS.md` 第 1 节「Identity and prime directives」(二副的定义与硬规矩四)、第 6 节「Project and knowledge management」(二副的行为边界)、以及第 7 节「Task lifecycle」下的「Intake and authority」小节(路由规则)。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 1 节(二副的定义和硬规矩四)、第 7 节「Intake and authority」小节(分派规则)和第 9 节(二副怎么上报),以及 `README.md`。 -## 二副是什么:不是另一套架构 +## 二副是什么 -`AGENTS.md` 第 1 节原文:「A secondmate is a crewmate with an isolated firstmate home and a charter, not a second architecture.」 +`AGENTS.md` 第 1 节的原文是:"A secondmate is a crewmate with an isolated firstmate home and a charter, not a second architecture." -把这句话拆开看: +拆开看: -- 二副首先**是一个船员** - 它不是凌驾于船员之上的新角色,也不是给船长另开的一个入口。 -- 二副比一般船员多两样东西:一份**独立的 firstmate 家目录**(自己的状态、自己的工单队列、自己的项目登记),以及一份 **charter**(对二副而言,就是那份特殊的简报 - 每个二副家目录下的 `brief.md` 在这种情况下就是它的 charter brief)。 -- 除此之外,二副运行的是**同一套**大副架构 - 同一套硬规矩、同一套立项判断、同一套质检流水线。它不是又发明了一套新机制。 +- 二副首先是一个船员 / crewmate,不是比船员高一级的新角色,也不是给船长另开的入口。`README.md` 也说,二副仍然是向大副汇报的普通下属。 +- 二副比普通船员多两样东西:一个独立的 FirstMate 目录(有自己的状态、待办队列和项目),以及一份职责简报(charter),写明它负责什么。 +- 除此之外,二副跑的是同一套大副机制:同样的硬规矩,同样的立项判断,同样的质检流水线。没有另起一套。 -类比一下会更直观:普通船员是一次性的 - 一件活分配一份隔离工作副本,活干完副本就收走。二副则是**常驻的** - 它有自己长期存在的家,工单队列可以是空的,但家本身不会随着某一件活的结束而消失。 +对比一下:普通船员是一次性的,一件活配一个工作树,活干完工作树就收尾清理掉。二副是常驻的,有自己长期存在的目录,队列空着也没关系,不会因为某件活做完就消失。 -## 为什么要等队伍真的变大:重复到值得专门拆一个 +## 为什么等队伍真的变大才设二副 -`AGENTS.md` 第 7 节「Intake and authority」在路由规则旁边,紧接着写了一条更基础的原则:「For one-off or infrequent operational work, start with the simplest direct end-to-end path. Do not build wrappers, control planes, policy layers, custom verifiers, or automation unless the direct path exposes a concrete blocker or repeated need that justifies the added machinery.」 +第 7 节在分派规则旁边还有一条更基本的原则:只做一次或很少做的运维类工作,先走最简单直接的路。除非直接的路碰到了具体障碍,或者出现了反复的需求,才值得加一层包装、控制层、规则层或自动化。 -二副本身就是这条原则里说的「额外机制」。一个新家目录、一份 charter、登记表里的一条记录 - 这些都是要长期维护的东西,不是免费的。所以默认路径是:所有活都留在主家里,由大副自己判断、自己派船员去做。只有当某个领域的活**重复到**一个具体的瓶颈,或者反复出现的需求,值得专门为它拆出一份持续存在的家目录时,才轮到建二副。 +二副就是这样一层额外的机制。一个新目录、一份职责简报、登记表里的一条记录,都要长期维护,不是免费的。所以默认的做法是:所有活都在大副这边,由大副判断、派船员去做。只有某个领域的活多到成了具体的瓶颈,或者一再出现,值得为它长期单独设一个目录时,才考虑设二副。 -一个假设的场景帮助理解这一点(这是一个用来说明道理的假想例子,不是这个教程仓库里发生过的真事):假设船长手上同时挂着好几个项目,其中一个项目每周都有好几件活找上门,而且都需要同一套专门的上下文和判断习惯。每次都要在主家里从头判断项目归属、从头理解这套上下文,慢慢就会变成一个具体的、可以指名道姓的瓶颈。这时候才值得跟大副商量,为这一个领域单独拆一个常驻的二副 - 而不是在项目刚起步、活还只是零星几件的时候就先把架子搭好。 +举个假设的例子:你手上同时有好几个项目,其中一个每周都有好几件活,而且都需要同一套专门的背景知识和判断习惯。每次都在大副这边从头判断项目、从头理解这套背景,慢慢就成了一个说得出来的瓶颈。这时才值得跟大副商量,为这个领域设一个常驻的二副。项目刚起步、活还零零星星时,没必要先把架子搭起来。 -## 一件活怎么按登记的范围路由 +## 一件活怎么分派 -第 7 节「Intake and authority」原文:「Route by the nature of the work against each registered secondmate scope, not by a non-exclusive clone list. Keep `local-only` work in the main home. Send in-scope work to the fitting secondmate unless it is blocked or the captain explicitly redirects it; do not read the secondmate's chat because marked routed replies return through its status or referenced document. If no secondmate scope fits, use the main home or discuss creating an appropriate persistent secondmate.」 +第 7 节「Intake and authority」的分派规则可以拆成几条: -拆开看,这条规矩定了几件事: +- **按活的性质比对负责范围,不按克隆清单查。** 每个登记过的二副都有一个负责范围。大副拿这件活的性质去比对这些范围,而不是去翻「这个二副名下克隆了哪些项目」的清单。这跟第 2 章讲的判断项目是同一个思路:先看清这是什么性质的活,再看它落在谁的范围里。 +- **在范围内,就交给对应的二副。** 默认直接交出去,不用每次问你。例外是这件活现在被卡住了,或者你明确要求改由别人做。 +- **不去看二副的聊天记录。** 交出去之后,大副不读二副自己会话里的内容。二副的回复会通过它的状态记录,或者它指明的文档传回来。 +- **哪个范围都不合适,就留在大副这边,或者商量要不要新设一个二副。** 对不上不等于没人管,默认由大副自己处理。如果这类活开始一再出现,就是上一节说的该设二副的信号,大副会跟你商量。 -- **按活的性质匹配范围,不是按清单查找。** 每个登记过的二副都有一个「范围(scope)」- 大副拿一件活的性质去跟这个范围比对,而不是翻一张「这个二副名下挂着哪些项目」的清单去核对。这跟第 2 章讲的「认项目」是同一层判断逻辑的延伸:先认清这件活是什么性质的活,再看它落在哪个登记过的范围里。 -- **范围匹配就送出去,除非被卡住或者船长明确改路。** 只要活的性质落在某个二副的登记范围里,默认就把它路由给这个二副 - 不需要每次都问一遍。例外是这件活当下被挡住了,或者船长当场明确说要改走别的路。 -- **不去偷看二副的对话。** 送出去之后,大副不会跑去读那个二副自己会话里的内容 - 标记过的路由回复,会通过它自己的状态或者它指定的文档传回来。 -- **没有范围能匹配,就留在主家,或者商量要不要新建一个。** 匹配不上不代表这件活没人管 - 默认落回主家处理;如果这类活开始重复出现,这也是上一节讲的「值得建二副」的信号,大副会跟船长商量要不要专门为它拆一个。 +## 哪些活留在大副这边 -## 哪些活必须留在主家 +同一条规则里写明:"Keep `local-only` work in the main home.":交付方式是 `local-only` 的活,不分给任何二副,留在主 FirstMate 目录里做。 -同一句规矩里还有一条明确的兜底:「Keep `local-only` work in the main home.」- 性质上属于本地专属的活,不路由给任何二副,留在主家里处理。 +结合上面几条看,留在大副这边不是分派失败后的补救,而是默认的去处:先看有没有合适的负责范围,没有,或者这件活本来就该大副自己管,就留在大副这边。二副只处理明确划给它的领域。 -结合前面几条一起看,「留在主家」不是路由失败之后才想起来的兜底,而是这套判断本身默认的落点:先看有没有匹配的登记范围,匹配不上,或者活的性质本来就是主家自己的事,就留在主家 - 二副只处理那些明确、专门划给它的领域。 +## 二副也不直接找船长 -## 二副永远经过大副,从不越级找船长 +硬规矩四的原文是 "Crewmates never address the captain. All crewmate communication flows through firstmate.":船员不直接找船长,所有消息都经过大副。 -硬规矩四(`AGENTS.md` 第 1 节)原文:「Crewmates never address the captain. All crewmate communication flows through firstmate.」 +二副也是船员,这条规矩同样管它。正因为二副有自己的目录和会话,这一点更容易被误会成「二副可以自己直接汇报」。`AGENTS.md` 第 9 节写得很清楚:在二副的目录里,要把消息送到船长那里,唯一的办法是把结果追加到它的职责简报里指定的上报渠道。二副在自己会话里对「船长」说的话,不算发出去了。按 `docs/secondmate-parent-channel.md`,没有人读二副的聊天,船长和大副只看得到追加到上报渠道里的内容。 -二副首先是一个船员,这条硬规矩自然也管着它 - 而且正因为二副有自己独立的家目录、独立的会话,这条边界反而更容易被误解成「二副是不是可以自己直接汇报」。`AGENTS.md` 第 9 节「Escalation and captain etiquette」把这一点钉得更死:在二副家目录里,触达船长的唯一方式,是把结果写进这个二副的 charter 里指名的「父频道(parent channel)」- 二副自己会话里说的话,不算是对船长说的话。这个父频道,兜兜转转还是经过大副。 - -这跟第 1 章规矩四背后的道理是同一条:船长之所以能同时跑好几件事而不被扎着,正是因为**只有一个声音**会来找他 - 大副。队伍规模变大、多出几个常驻的二副之后,这条约束不但没有放松,反而更重要 - 否则规模一大,跟船长说话的就不是一个声音,而是好几个。 +道理跟第 1 章规矩四一样:你能同时跑好几件活而不被打扰,是因为只有大副一个人来找你。队伍大了、多了几个常驻的二副,这条规矩更重要,不然来找你的就不是一个人,而是好几个。 ## 动手练习 -下面两个练习都是**只读**的:只是让大副读取并讲清楚这套二副与路由的规矩,不会创建工单,不会派任何真的船员或二副去改项目,也不会做任何不可回退的操作。你随时可以停下。 +两个练习都是只读的:只让大副讲清二副和分派的规矩,不登记工单,不设二副,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习 1:核对二副是什么、什么时候才值得建 +### 练习 1:讲清二副是什么、什么时候才值得设 对大副说: -> 大副,讲清楚二副 / secondmate 到底是什么 - 它跟你自己(大副)以及普通船员分别是什么关系,它比普通船员多出了哪两样东西。再讲清楚为什么不该在队伍刚起步时就先建好几个二副,什么样的信号才说明真的值得为一个领域拆一个常驻的二副出来。回答里指出你是根据 `AGENTS.md` 哪一节说的。这只是让我核对认识,不要创建任何工单,也不要真的去建一个二副或派任何船员。 - -**预期会看到什么** +> 大副,讲讲二副(secondmate)到底是什么:它跟你、跟普通船员分别是什么关系,比普通船员多了哪两样东西。再讲讲为什么不该在队伍刚起步时就先设几个二副,什么信号说明真的值得为一个领域设一个常驻的二副。说明你依据的是 `AGENTS.md` 哪一节。只是让我核对理解,不要登记工单,不要真的设二副,也不要派船员。 -- 它会说清楚二副是船员的一种,多出的是一份独立的 firstmate 家目录和一份 charter,而不是另一套架构。 -- 它会讲清楚默认路径是不建二副,只有活重复到一个具体的瓶颈或反复出现的需求时才值得拆。 -- 它会指出出处,主要是 `AGENTS.md` 第 1 节和第 7 节「Intake and authority」。 -- 如果这个仓库此刻还没有登记过任何一个真的二副,它会老实说清楚这一点,而不是编一个现成的例子出来。 +**你应该看到** -**怎么判断做成了** +- 它讲清二副是一种船员,多的是一个独立的 FirstMate 目录和一份职责简报,不是另一套机制。 +- 它讲清默认不设二副,只有活多到成了具体瓶颈或一再出现时才值得设。 +- 说明出处:主要是 `AGENTS.md` 第 1 节和第 7 节「Intake and authority」。 +- 如果你的 FirstMate 还没有登记任何二副,它会直接说明,不会编一个现成的例子。 -三个都满足才算:它讲的是概念和判断标准,而不是一个编造出来的「我们有个二副叫 XX」;它给出了可核对的出处;以及 - 最关键的 - 它**没有创建任何工单,也没有建任何二副或派任何船员**。 +**怎么检查** -验证办法跟前面几章一样,**前后对比**:提问之前先在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见,会话期间写这些不算数。 +- 它讲的是概念和判断标准,不是编出来的「我们有个二副叫某某」。 +- 说明了能核对的出处。 +- 没有登记工单,没有设二副,也没有多出新的船员窗口。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -如果它编了一个具体的、听起来像真事的二副案例,或者顺手就去尝试建一个,那就是没做成。 +如果它编了一个听起来像真事的二副案例,或者顺手去设了一个,就算没做成。 -### 练习 2:拿一件假想的活推演路由结果 +### 练习 2:推演一件活怎么分派 对大副说: -> 大副,我要试一下你怎么路由一件活,不是真的要你干活。假设一件新活进来了,性质上明显属于某个已经登记过范围的领域。请你讲一遍你会怎么判断:怎么拿这件活的性质去跟登记过的范围比对、什么情况下会直接送给对应的二副、什么情况下即使性质对得上也不会送出去(比如被卡住或者我明确要求改路)、以及如果压根没有登记范围能匹配,你会怎么处理。再讲清楚送出去之后你会不会去看那个二副自己会话里的内容,以及你怎么知道它做完了。这只是一次推演,不要真的路由任何活,也不要派任何船员或二副。 - -**预期会看到什么** +> 大副,我想看看你怎么分派一件活,不是真要你干活。假设来了一件新活,性质上明显属于某个已登记二副的负责范围。请讲讲你怎么判断:怎么拿这件活的性质去比对登记的负责范围,什么情况下直接交给对应的二副,什么情况下即使对得上也不交出去(比如被卡住了,或者我明确要求改由别人做),如果哪个范围都对不上又怎么办。再讲讲交出去之后,你会不会去看那个二副自己会话里的内容,你怎么知道它做完了。只推演,不要真的分派,也不要派船员或二副。 -- 它会讲清楚路由靠的是拿活的性质去匹配登记过的范围,而不是查一张项目清单。 -- 它会讲清楚「性质对得上但不送出去」的两种例外:活被卡住,或者船长明确要求改路。 -- 它会明确说不会去读二副自己会话里的内容,路由出去之后的回复是通过对方的状态或者指定文档传回来的。 -- 它会讲清楚没有范围匹配时默认留在主家,而不是自己现造一个二副去处理。 -- 它会指出出处,主要是 `AGENTS.md` 第 7 节「Intake and authority」。 +**你应该看到** -**怎么判断做成了** +- 它讲清分派靠的是拿活的性质比对登记的负责范围,而不是查一张项目清单。 +- 它讲清对得上也不交出去的两种例外:活被卡住了,或者你明确要求改由别人做。 +- 它明确说不会看二副自己会话里的内容,二副的回复通过状态记录或指明的文档传回来。 +- 它讲清对不上时默认留在大副这边,而不是自己临时设一个二副。 +- 说明出处:主要是 `AGENTS.md` 第 7 节「Intake and authority」。 -两条都满足才算:它讲的路由逻辑跟这一章讲的对得上,尤其是「不偷看二副对话」和「没匹配就留主家」这两点不能讲反;以及它**没有真的路由任何活,也没有派出任何船员或二副**。 +**怎么检查** -文件层面的验证办法跟练习 1 一样:提问前后各跑一次 `git status --short` 和 `git diff HEAD`,两次输出应该完全一样。 +- 讲的分派规则跟这一章一致,特别是「不看二副的聊天记录」和「对不上就留在大副这边」这两点没讲反。 +- 没有真的分派任何活,也没有派船员或二副。 +- 提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -如果它把这次推演当真派出了什么,或者讲的路由逻辑跟规矩对不上,那就是没做成。 +如果它把这次推演当真派出了什么,或者讲的分派规则跟规矩对不上,就算没做成。 ## 下一章 -第 10 章讲**afk 离席模式**:你离开键盘期间谁接手监督,离席期间它敢自己做什么、必须留着等你什么,以及你回来时怎么交接回来。 +第 10 章讲离席模式(afk):你走开时谁来监督,大副能自己做什么、必须等你什么,你回来时怎么交接。 diff --git a/chapters/10-afk-mode.md b/chapters/10-afk-mode.md index a64c817..0c382cf 100644 --- a/chapters/10-afk-mode.md +++ b/chapters/10-afk-mode.md @@ -1,79 +1,92 @@ -# 第 10 章:afk 离席模式 +# 第 10 章:离席模式(afk) ## 这一章讲什么 -第 9 到 11 章是规模、离席、以及「到底是哪个工具在跑你的船员」这类进阶话题。这一章讲的是其中的离席部分:你(船长 / captain)不可能一直守在屏幕前,但一支队伍不能因为你走开就没人管。这一章要解决的问题是四件事:你离开键盘期间谁接手监督、你说出「离席」这句话本身算不算已经生效、离席期间大副 / firstmate 敢凭你留下的话往前走到什么程度,以及最重要的一条 - 遇到真正伤筋动骨的选择时它依然会不会停下来等你、你回来时它又怎么先把这段时间发生的事交代清楚。 +你(船长 / captain)不可能一直守在屏幕前,但队伍不能因为你走开就没人管。这一章讲离席模式(afk):你走开时谁接手监督;你说「离席」这句话是不是马上生效;离席期间大副 / firstmate 能凭你留下的话做到哪一步;遇到真正要紧的选择时它会不会停下来等你;你回来时它怎么先把这段时间的事交代清楚。 -本章依据的权威文件是 `AGENTS.md` 第 8 节「Supervision protocol」中的「Away-mode and quiet-mode stub」小节,以及 `/afk` 技能自身的说明。这个教程仓库到目前为止船长一直在场,还没有真的用过离席模式,所以本章不会讲一个「真实发生过的案例」,全部依据原文和类比来讲;如果用到具体场景,会明确标注是假设。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 8 节里的「Away-mode and quiet-mode stub」小节,以及 `/afk` 技能(`.agents/skills/afk/SKILL.md`)。这本教程写作期间船长一直在场,没有真正用过离席模式,所以本章没有真实案例,用到的场景都是假设的,会标出来。 -## 离席期间谁在盯着:不是没人管,是换了一套机制 +## 你走开后谁在监督 -原文写得很直接: +原文是 "While `state/.afk` exists, the daemon owns supervision; do not arm a separate watcher."。 -> While `state/.afk` exists, the daemon owns supervision; do not arm a separate watcher. +你走开后监督不会停,而是交给一个专门的守护进程(daemon)。只有它一个在管,大副不会再另外启动一个监控脚本做同样的事。你走开后,队伍仍然有人看着,只是换了一个看着它的。 -翻成大白话:你说走开之后,监督这件事不会停摆,而是从「你在场时那一套盯着队伍的机制」切换成一个专门的守护进程 / daemon。这个 daemon 是唯一在管的那个 - 大副不会因为你走开就额外再武装一个 watcher 来做同样的事,一件事只由一个机制盯着。你走开留下的是一支依然有人看着的队伍,只是看着它的角色换了。 +## 说出 `/afk`,就已经生效 -## `/afk` 这句话本身就是行动的开始,不用再等一次确认 - -前几章遇到的大多数场景里,你说一句话,大副会先讲清楚它打算怎么做,再动手 - 这是第 2 章讲过的「先立项、边界说清楚」的节奏。离席模式不一样。原文: +前面几章里,你说一句话,大副通常先讲清打算怎么做,再动手。离席模式不一样。原文是: > `state/.afk-contract` is the away posture, written in the same turn as `/afk` before any other work, because `/afk` is itself the go: no read-back gates entry or waits for a go; entry announces hold-for-return only, and the away session acts on those words by its own judgment through the guarded scripts under standing authority, holding for the return on doubt. -拆开看:你说出 `/afk` 连同你交代的那几句话,这件事本身就是「开始执行」的信号,不是「请求批准」的信号。大副会在同一个动作里,把你说的话原样记成一份留存的记录,在做任何别的事之前先记下来 - 因为等你确认,你可能已经真的走开、看不到屏幕了。它随后会把记下来的内容念给你听一遍,但这一步只是核对「我记的对不对」,不是在等你点头,记录本身在你说完那一刻就已经生效。 +意思是:你说 `/afk` 加上你交代的话,本身就是「开始」,不是「请你批准」。大副会在同一轮、做任何别的事之前,先把你的话原样记下来(`state/.afk-contract`),因为再等你确认,你可能已经走了、看不到屏幕了。它接着会告诉你:没把握的事会留着等你回来。这只是告知,不是在等你点头,你的话说完就已经生效。 + +## 离席期间大副能做到哪一步 -## 离席期间它敢自己往前走到什么程度 +上面那段原文的后半句划了范围:离席期间,大副根据你留下的话,用本来就允许用的、带保护的脚本做事,由它自己判断你的话管不管得到眼下这件事,拿不准就留着等你回来。 -同一段原文的后半句给了边界:大副「acts on those words by its own judgment through the guarded scripts under standing authority, holding for the return on doubt」- 靠已有的、被认可过的脚本和流程去做事,用自己的判断去认,你留下的那句话在眼下这件事上算不算数,拿不准的时候就按兵不动、等你回来。 +这里有两层意思: -这里有两层意思容易混在一起,需要拆开:第一层,它能用的手段是「guarded scripts under standing authority」- 也就是本来就被允许使用的、带着护栏的既有流程,不是临时现编一套新做法;第二层,你留下的那句话到底覆盖不覆盖眼下这件事,由它自己判断,不是机械匹配关键词。判断不准的时候,答案永远是「等你回来」,而不是「反正问题不大,我自己拿主意」。 +- 它用的是本来就允许用的、带保护的现有流程,不会临时另搞一套做法。 +- 你的话管不管得到眼下这件事,由它自己判断,不是机械地匹配关键词。拿不准时,答案总是等你回来,而不是「问题不大,我自己拿主意」。 -## 红线不会因为离席而放宽 +## 离席不会放宽底线 -这是这一章最重要的一条,原文写得毫不含糊: +原文写得很清楚: > Away and quiet mode never expand approval authority for merges, ask-user findings, destructive actions, irreversible actions, or security-sensitive choices. -也就是说,离席(`/afk`)和静默(`/quiet`)这两种姿态,改变的只是「你怎么被告知」和「一个原本要问你的节点现在怎么处理」,从来不会让大副对合并、ask-user 发现、破坏性动作、不可逆动作、涉及安全的选择这几类事拥有比你在场时更大的权限。`/afk` 技能自己的说明里也把这条线重复了一遍,说得更直白:不管你走之前那句话怎么说,这几类动作都不能被预先授权。换句话说,哪怕你临走前明确交代过「这种情况你就自己去改」,只要这件事落在这几类里,大副依然会把它留着等你,而不是拿你那句话当挡箭牌往前冲。离席解决的是「谁盯着」和「一般性的活怎么往前推」,不是把这几道闸门也一并交出去。 +离席模式(`/afk`)和静默模式(`/quiet`)都不会让大副在合并、ask-user 类问题、破坏性操作、不可逆操作、涉及安全的选择这几类事上,拥有比你在场时更大的权限。 -## 船长回来了:先交代清楚,再干活 +`/afk` 技能说得更直接:有破坏性、不可逆或涉及安全的操作,不管你走之前怎么说,都不能提前授权。ask-user 类问题照常按 `ask-user-authority` 的规矩处理,除非你留下的话正好回答了那个具体问题。其他需要你决定的事,都留着等你回来。所以,哪怕你临走前说过「遇到这种情况你就自己改」,只要那件事有破坏性、不可逆或涉及安全,大副仍然会留着等你。离席解决的是谁来盯着、一般的活怎么往前推,不会把这几道关也交出去。 -那大副怎么知道你回来了?原文给的判断标准很朴素: +## 你回来了:先交代,再干活 + +大副怎么知道你回来了?原文是: > A marked message while away or quiet mode is active is internal escalation and does not exit that mode. Any other unmarked message means the captain returned in away mode (load `/afk`, run the return owner, and do not process that message as ordinary work until its durable catch-up gate clears)... Bias ambiguous input toward exit because a present captain takes precedence. -翻译过来:大副能分清两种消息。一种是它自己那套监督机制内部产生的、带着特定标记的消息 - 这种不算你回来,离席姿态原样继续。另一种是任何不带这种标记的普通消息 - 哪怕只是随口一句话,不带 `/afk`,也一律当成「你回来了」。而且规则明确偏向「就当你回来了」:拿不准的时候,宁可当你已经回来,也不要因为省一次交接就继续把你当成不在场 - 一个走了眼的判断,大不了你再说一次 `/afk` 纠正回来,代价很小;反过来把已经回来的你继续当成不在场,代价要大得多。 +大副能分清两种消息: + +- 一种是监督机制内部发来的、带特定标记的消息。这不算你回来,离席模式照旧。 +- 另一种是任何不带标记的普通消息,哪怕只是随口一句、没带 `/afk`,都当作你回来了。 -判断出你回来之后,大副不会直接就着你那句随口的话开始干活,而是先跑一遍返场流程,把离席期间发生的事交代清楚 - 这道交接闸门清完之前,你带回来的那句话不按普通工作处理。打个比方(下面这段是**假设**的场景,不是这个仓库里真实发生过的事):假设你交代完一句「你按流程正常推」就走开了,回来后你只是问了句「现在几点了」- 大副不会先答「现在几点」,而是先说清楚这段时间队伍做了什么、还有哪些事在等你,交代完之后才轮到回答你那句「现在几点」。这就是「先交代清楚再继续干活」的意思。 +拿不准时,就当你回来了,因为你在场时以你为准。 + +判断你回来后,大副不会直接接着你那句话干活,而是先跑一遍回来后的交接流程,把离席期间发生的事交代清楚。交接完成之前,你那句话不当作普通的活来处理。 + +举个假设的例子:你交代了一句「按流程正常推进」就走了,回来后只问了句「现在几点了」。大副不会先回答几点,而是先讲清这段时间队伍做了什么、还有哪些事在等你,交代完才回答你几点了。 ## 动手练习 -下面的练习是**只读且不可执行**的:只是让大副就着一个假设场景做推演、把规则讲清楚,不会真的创建或改动 `state/.afk-contract`、`state/.afk` 这类运行状态文件,不会真的运行 `/afk` 或任何相关脚本,不会派出真正的船员,也不会改动任何被版本追踪的文件。你随时可以停下。 +这个练习是只读的,而且只在对话里推演:不创建也不修改 `state/.afk-contract`、`state/.afk` 这类文件,不真的运行 `/afk` 或相关脚本,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习:推演一次离席场景,不真的进入离席姿态 +### 练习:推演一次离席,但不真的进入离席模式 对大副说: -> 大副,我想跟你推演一次离席模式的规则,不是真的要你进入离席姿态:请不要创建或改动 `state/.afk-contract` 或 `state/.afk`,也不要真的运行 `/afk` 或任何相关脚本。假设我刚才对你说了这样一句话:「/afk,如果有活已经走完质检流水线,你就按原来的节奏正常往下推进就行,其他一切都留着等我」。请你不要真的执行这句话,纯粹在纸面上帮我推演,回答四个问题:一,这段时间谁在替你盯着整支队伍;二,我刚才那句「/afk...」本身是不是要先经过你确认、等我点头才算数,还是我说完那一刻就已经生效了;三,如果这期间真有一件活走完了质检流水线,你会怎么处理,如果同一时间又冒出一个必须靠强制推送、覆盖掉别人已提交历史才能推进的情况,你会不会因为我说了「你就按原来的节奏推进」就去做;四,假设推演到这里结束,我之后随口发一句不带 `/afk` 的普通话,比如「现在几点了」,你会怎么处理这条消息 - 是直接回答,还是要先做点别的。 - -**预期会看到什么** +> 大副,我想跟你推演一下离席模式的规矩,不是真要你进入离席模式:不要创建或修改 `state/.afk-contract` 和 `state/.afk`,也不要真的运行 `/afk` 或相关脚本。假设我刚说了一句:「/afk,已经走完质检流水线的活,你就按原来的节奏往下推,其他都留着等我。」不要执行这句话,只在纸面上推演,回答四个问题。一:这段时间谁替你盯着队伍?二:我这句话要等你确认、我点头才算数,还是说完就生效了?三:如果这期间有一件活走完了质检流水线,你怎么处理?如果同时冒出一件必须强推、覆盖别人已提交历史才能推进的事,你会不会因为我说了「按原来的节奏往下推」就去做?四:推演到这里结束,之后我随口发一句不带 `/afk` 的话,比如「现在几点了」,你会直接回答,还是先做点别的? -- 第一句:它会说这段时间由一个专门的守护机制(daemon)接管监督,而且不会再另外武装一个 watcher 去做重复的事。 -- 第二句:它会说这句话在我说完那一刻就已经生效、已经被记下来了,不等我确认;它随后的复述只是把记下来的内容念给我听、核对有没有记错,不是在等我点头。 -- 第三句:对「已经走完质检流水线、按原来节奏正常推进」的情况,它会说这属于我离席前那句话已经覆盖、走的也是既有的、被认可的流程,所以它会凭自己的判断往前推,不会为这个专门跑来问我;但对「需要强制推送、覆盖别人历史」这种破坏性且不可逆的操作,它会明确说这条线不会因为我说了「你就按原来的节奏推进」就被放开,依然会停下来把它留着等我回来处理。 -- 第四句:它会说自己先要把离席期间发生的事交代清楚(一份交接),交代完之后才会把「现在几点了」当成一个普通问题来回答 - 不会一上来就直接回答;它还会指出,如果换成是它自己监督机制内部产生、带着内部标记的消息,那不算我回来,不会触发这段交接。 -- 四个回答都会点出处:`AGENTS.md` 第 8 节「Away-mode and quiet-mode stub」小节,以及 `/afk` 技能自身的说明。 +**你应该看到** -**怎么判断做成了** +- 第一问:由一个专门的守护进程接管监督,不会再另外启动一个监控脚本。 +- 第二问:说完就生效,已经记下来了,不等你确认。它之后告诉你的话只是告知,不是在等你点头。 +- 第三问:走完流水线、按原来节奏推进,是你的话管得到的,也走的是本来就允许的流程,它会自己判断往下推,不会特意来问你。但强推、覆盖别人历史是有破坏性、不可逆的操作,不会因为你那句话就放开,它会留着等你回来。 +- 第四问:它会先把离席期间的事交代清楚,交代完才把「现在几点了」当普通问题回答。它还会指出,如果是监督机制内部发来的、带标记的消息,就不算你回来,不会触发交接。 +- 说明出处:`AGENTS.md` 第 8 节「Away-mode and quiet-mode stub」,以及 `/afk` 技能。 -四点都满足才算:第一句准确讲出「daemon 接管、不另外武装 watcher」;第二句准确讲出「/afk 这句话说完即生效,不等确认」而不是「它需要先跟我确认了才算数」;第三句既讲对了「已授权范围内的活可以凭判断继续推」,又讲对了「破坏性/不可逆操作不会因为你的话被放开」这条红线 - 少了后半句就是没做成;第四句准确讲出「先交代清楚,再回答」而不是直接就手回答你的新问题。以及 - 最关键的一条 - 它**没有真的创建或改动任何 `state/.afk` 相关文件,也没有真的运行 `/afk`**,这段推演从头到尾只停留在对话里。 +**怎么检查** -文件层面的验证办法跟前几章一样,**前后对比**:提问之前在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`state/` 是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见,会话期间它会不会被真的写入不影响这两条命令的输出,但你依然应该在对话里确认大副没有说自己创建或改动了 `state/.afk-contract`、`state/.afk` 这类文件。 +- 第一问讲对了守护进程接管,而且不另外启动监控脚本。 +- 第二问讲对了说完就生效,而不是「要先跟你确认」。 +- 第三问两半都讲对:你授权范围内的活可以自己判断往下推;有破坏性、不可逆的操作不会因为你的话放开。少了后一半就不算对。 +- 第四问讲对了先交代、再回答。 +- 它没有说自己创建或修改了 `state/.afk-contract`、`state/.afk`,也没有真的运行 `/afk`。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。`state/` 是你本机私有的目录,已经被 gitignore,这两条命令看不到它,所以还要在对话里确认大副没有动这些文件。 -如果它说离席期间「没人管,等你回来再说」,或者说那句 `/afk` 要先经过你确认才算数,或者认为你交代过「你就按原来的节奏推进」之后连强制推送覆盖历史这种事也可以自己做,或者收到你那句普通话后直接就回答而不先交代离席期间发生了什么,那就是没做成。 +如果它说离席期间没人管,或者说 `/afk` 要先确认才算数,或者认为你说了「按原来的节奏往下推」连强推覆盖历史也能做,或者收到你那句话后直接回答、不先交代,就算没做成。 ## 下一章 -第 11 章讲**运行时与 harness 选择**:哪个工具在跑你的船员,运行时后端与 harness 怎么配,以及什么时候值得为一件活单独指定。 +第 11 章讲 harness 与运行时后端:船员是用什么工具、在什么终端里跑的,默认怎么选,什么时候值得单独指定。 diff --git a/chapters/11-harness-and-runtime-backend.md b/chapters/11-harness-and-runtime-backend.md index 0ce64a4..9fb6f44 100644 --- a/chapters/11-harness-and-runtime-backend.md +++ b/chapters/11-harness-and-runtime-backend.md @@ -1,87 +1,93 @@ -# 第 11 章:运行时与 harness 选择 +# 第 11 章:harness 与运行时后端 ## 这一章讲什么 -第 9 到 11 章是路线图里「规模、离席、以及到底是哪个工具在跑你的船员」这条线,这一章是这条线的最后一站。前面几章讲的是队伍怎么拆分、怎么路由、离席时谁接手监督;这一章换一个更底层的问题:派出去的每一个船员 / crewmate,到底是被什么工具执行的,这个工具又跑在什么样的终端载体上。 +前面几章讲队伍怎么分工、怎么分派、你走开时谁来监督。这一章讲一个更底层的问题:每个船员 / crewmate 到底是用什么工具在跑,这个工具又跑在什么终端里。 -这两件事经常被当成一回事,其实是两个正交(orthogonal,互不影响)的旋钮:**harness** 指跑在船员那份工作副本里的具体工具本身,比如 Claude Code;**运行时后端 / runtime backend** 指这个工具跑在什么样的终端会话载体上,比如一个 tmux 窗口。这一章讲清楚三件事:harness 和运行时后端为什么是两个独立的选择,默认情况下用的是什么,以及什么时候才值得为单独一件活指定别的 harness 或后端。 +这是两件事,而且互不影响: -本章依据的权威文件是 `AGENTS.md` 第 4 节「Harness and runtime dispatch」和第 2 节「Layout and state」布局表里 `config/backend`、`config/crew-harness` 两行,`bin/fm-spawn.sh` 的头部注释,以及 `docs/configuration.md`「Harness support」一节。每一段引用都会标出处。 +- **harness**:在船员的工作树里实际干活的 agent 工具,比如 Claude Code。 +- **运行时后端**(runtime backend):给这个工具提供会话窗口的终端工具,比如 tmux。 -## harness 和运行时后端:两个正交的旋钮 +这一章讲清三件事:为什么这是两个独立的选择,默认用的是什么,什么时候才值得为某一件活单独指定。 -`bin/fm-spawn.sh` 的头部注释把这两个东西写成了同一条命令里两个各自独立的参数: +本章依据 FirstMate 仓库 `AGENTS.md` 第 4 节「Harness and runtime dispatch」、第 2 节目录说明里 `config/backend` 和 `config/crew-harness` 两行、`bin/fm-spawn.sh` 开头的注释,以及 `docs/configuration.md` 的「Harness support」一节。 -> `fm-spawn.sh --mode --yolo [--branch-prefix ] [--harness |harness|launch-command] [--model ] [--effort ] [--backend ]` - -`--harness` 和 `--backend` 是两个不同的选项,互不依赖:改一个不需要跟着改另一个。这跟凭直觉理解的顺序可能有点反:很多人第一反应是「工具」和「跑在哪」应该是同一个设置,但这里从命令行参数的形状上就能看出来,FirstMate 把它们当成两件完全独立的事在管理。 +## 两个独立的选择 -`AGENTS.md` 第 4 节列出了 harness 这一侧可选的范围: +`bin/fm-spawn.sh` 的注释里,这两项是同一条命令里两个独立的参数: -> The verified harnesses are `claude`, `codex`, `opencode`, `pi`, `pi-signed`, `grok`, `kimi`, `cursor`, and `omp`, plus `muse`, `gemini`, `rovo`, `agy`, and `devin` for crewmates and scouts only; never dispatch on an unverified adapter. +> `fm-spawn.sh --mode --yolo [--branch-prefix ] [--harness |harness|launch-command] [--model ] [--effort ] [--backend ]` -也就是说,harness 是一份经过验证的清单,`claude`、`codex`、`opencode`、`pi`、`pi-signed`、`grok`、`kimi`、`cursor`、`omp` 这九个对所有身份都可用,另外 `muse`、`gemini`、`rovo`、`agy`、`devin` 五个只对船员和侦察兵(scout)开放。清单之外的适配器,不管看起来多顺手,都不会被派上用场。 +`--harness` 和 `--backend` 是两个参数,改一个不用跟着改另一个。 -运行时后端这一侧,第 2 节布局表里 `config/backend` 这一行写的是: +**harness 能选哪些。** `AGENTS.md` 第 4 节列了验证过的 harness:`claude`、`codex`、`opencode`、`pi`、`pi-signed`、`grok`、`kimi`、`cursor`、`omp` 这九个;另外 `muse`、`gemini`、`rovo`、`agy`、`devin` 五个只能用于船员和调研任务。清单以外的,不管看起来多顺手,都不会用来派活。 -> runtime session-provider backend override for new tasks; LOCAL, gitignored; absent = falls through to runtime auto-detection ..., then tmux; tmux is the verified reference backend, herdr has its own required CI lane, while zellij, orca, and cmux remain experimental with no dedicated real-backend CI lane +**运行时后端能选哪些。** 第 2 节对 `config/backend` 的说明是:tmux 是验证过的标准后端;herdr 有自己必须通过的 CI 测试;zellij、orca、cmux 还是实验性的,没有专门跑真实后端的 CI 测试。 -翻译过来:运行时后端管的是这个工具被塞进什么样的终端会话容器里去跑 - tmux 窗口是这里的参照实现(verified reference backend),herdr 有自己独立的必需 CI 测试通道,而 zellij、orca、cmux 目前还是实验性的,没有专门的真实后端 CI 通道兜底。这份清单本身,跟上面 harness 那份清单,是两份完全不相干的名单:`--harness` 和 `--backend` 这两个旋钮各自独立配置,改一个不需要跟着改另一个。但这不等于任意组合都能跑起来 - `fm-spawn` 在派出前仍会校验具体的 harness / 后端组合,遇到不支持的组合会直接拒绝。`docs/configuration.md`「Harness support」一节记录了一个具体例子:`fm-spawn.sh` 会在 preflight 阶段拒绝把 kimi 派到 cmux 或 Orca 后端上,因为回答 kimi 的 folder-trust 对话框需要一种这两个后端都不具备的、经过验证的仅视口(viewport-only)捕获能力。 +两份清单互不相干,但不是任意组合都能跑。`fm-spawn.sh` 派出前会检查具体的组合,不支持的直接拒绝。`docs/configuration.md` 的「Harness support」一节举了一个例子:`fm-spawn.sh` 会在启动前的检查里拒绝把 kimi 派到 cmux 或 Orca 上,因为处理 kimi 的「是否信任此文件夹」对话框,需要一种经过验证的、只截取当前可见画面的能力,这两个后端都没有。 -## 默认情况下用什么 +## 默认用什么 -两边都有各自独立的默认值和兜底顺序。harness 这一侧,`AGENTS.md` 第 4 节写的路由优先级是: +两边各有自己的默认值和兜底顺序。 -> Routing precedence is an explicit per-task captain override, then the best-fit configured rule, then the configured default, then the static crewmate harness. +**harness。** 第 4 节规定的顺序是: -拆开看是一条从「这一件活单独说了什么」到「这个家一直以来配的是什么」的降级链条:先看你有没有对这一件活单独指定过;没有的话看有没有配置好的、跟这件活最匹配的规则;再没有就看有没有配置好的默认值;以上都没有,就落到最底层、静态配置的船员 harness(`config/crew-harness`,第 2 节布局表里写的是「crewmate harness override; ... absent or "default" = same as firstmate」- 不单独设置的话,跟大副自己用的 harness 一致)。 +1. 你对这一件活单独指定的。 +2. 配置好的、跟这件活最匹配的规则。 +3. 配置好的默认值。 +4. 静态的船员 harness,也就是 `config/crew-harness`。第 2 节说明,这个文件不存在或写着 `default`,就跟大副自己用的 harness 一样。 -运行时后端这一侧,兜底顺序更简单,就是上一节引用过的那句:不设置 `config/backend` 的话,先落到运行时自动探测,探测不出结果的话再落到 tmux。两条链条彼此独立地往下降级,谁也不会去看另一边选了什么。 +**运行时后端。** 没设 `config/backend`,就用运行时自动检测的结果(大副自己正在哪种终端里跑);检测不出来,就用 tmux。 -这个「不特意配置就一路落到静态默认值」的效果,是上面两条路由/兜底规则本身的直接结果:只要一件活不需要任何特定工具才有的能力,没有人为它显式传过 per-spawn 的 `--harness` 或 `--backend`,harness 这一侧就会一路降级到 `config/crew-harness` 的静态默认值(缺省时同大副自己的 harness),后端这一侧就会落到运行时自动探测、探测不出来再落到 tmux。写文档这类同质工作,通常就落在这种「默认值已经够用,没理由破例」的情形里 - 但这仓库自己实际用的是哪个 harness、哪种后端,属于这份大副运行状态本身的记录,下面的练习会让你直接向自己的大副核实,而不是在这里替你断言。 +两边各自往下找,谁也不看对方选了什么。所以一件活只要不需要某个工具特有的能力,也没人为它单独传 `--harness` 或 `--backend`,harness 就一路落到 `config/crew-harness`(没设就跟大副一样),后端就落到自动检测的结果或 tmux。写文档这类活,通常用默认值就够了。你的 FirstMate 实际用的是哪个 harness、哪种后端,下面的练习会让你直接问你的大副,这里不替你下结论。 -## 什么时候才值得为一件活单独指定 +## 什么时候值得单独指定 -正因为默认路径已经够用,「单独指定」应该是例外,而不是顺手换着玩。`AGENTS.md` 第 4 节对单独指定后端划了一条明确的边界: +默认值已经够用,所以单独指定应该是例外。第 4 节对单独指定后端划了边界: > Dispatch only on a backend that `fm-spawn` validates as spawn-capable; pass an explicit per-spawn `--backend` only under that exact task's own authority, never as later-task precedent. -两层意思:第一,只能派到 `fm-spawn` 自己验证过、确认能承载这个 harness 的后端上,不是随便指哪个都行。第二,也是更容易被忽略的一层 - 一次显式的 `--backend` 只对**这一件活自己**的授权范围有效,不会变成「以后这类活都这么办」的先例。也就是说,就算这一次为了某个特定需求指定了别的 harness 或后端,下一件活默认还是会走回静态配置,不会因为上一次这么干过就自动延续下去。 +两层意思: -单独指定的正当理由,落在「这件活需要某个特定工具才具备的能力」这一类上 - 比如某个 harness 独有的一项功能是这件活非用不可的,而默认的静态 harness 并不具备。反过来,「随手试试另一个工具」「觉得换一个可能更快」都不构成理由。 +- 只能派到 `fm-spawn` 验证过能用的后端上,不是想指哪个就指哪个。 +- 单独传一次 `--backend`,只对这一件活有效,不会变成以后同类活的先例。下一件活默认还是走回配置好的设置。 -同一节还写了一条配套的失败处理规矩,同样值得记住: +单独指定的正当理由是:这件活需要某个工具特有的能力,默认的工具没有。「随手试试另一个」「觉得换一个可能更快」都不算理由。 + +同一节还规定了跑不起来时怎么办: > A missing dependency, authentication failure, unsupported backend, or version refusal is a blocker; never silently retry on another backend. -也就是说,指定的 harness 或后端跑不起来 - 缺依赖、认证失败、后端不支持、版本被拒 - 这些情况下正确的动作是停下来报告成一个 blocker,而不是自己悄悄换一个后端接着跑。换后端本身不是问题,但换后端必须是一次可见的、有授权的决定,不能是遇到麻烦之后的自动补救动作。 +缺依赖、认证失败、后端不支持、版本被拒,都算阻塞,要停下来报告,不能自己悄悄换一个后端重试。换后端本身没问题,但必须是一次看得见、有授权的决定,不能是遇到麻烦后的自动补救。 ## 动手练习 -下面的练习是**只读**的:只是让大副读取信息、做推理并汇报,不会创建工单、不会派出真正的船员、不会改动任何被版本追踪的文件,也不会做任何不可回退的操作。你随时可以停下。 +这个练习是只读的:只让大副读资料、推演、汇报,不登记工单,不派船员,不改被 git 追踪的文件。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习:核对这个教程仓库到目前为止的 harness / 后端选择,再推演一次单独指定 +### 练习:核对这个仓库用过的 harness 和后端,再推演一次单独指定 对大副说: -> 大副,我想核对两件事,都不是真的要你干活。第一件:这个 learn-firstmate 教程仓库从第 1 章写到现在,派出去的船员实际用的是哪个 harness、哪种运行时后端,是不是每一件活都一样,这是落到了静态默认值还是有哪一件活单独指定过。第二件,纯推演:假设未来有一件活,必须用到某个特定 harness 才有的一项能力,默认的船员 harness 没有这项能力,你会怎么处理 - 会不会单独指定一次 `--backend` 或者别的 harness,这次指定会不会变成以后同类活的先例,以及如果指定的后端起不来(比如认证失败)你会怎么办。请你分别说清楚,并点出你依据的是 `AGENTS.md` 哪一节。这只是核对和推演,不要真的创建工单,也不要派任何船员。 - -**预期会看到什么** +> 大副,我想核对两件事,都不是真要你干活。第一件:learn-firstmate 这个教程仓库从第 1 章写到现在,派出去的船员实际用的是哪个 harness、哪种运行时后端,每件活是不是都一样,是用的默认设置,还是有哪件活单独指定过。第二件,只推演:假设以后有一件活必须用到某个 harness 特有的能力,默认的船员 harness 没有,你会怎么处理?会不会为它单独指定 harness 或 `--backend`?这次指定会不会成为以后同类活的先例?如果指定的后端起不来,比如认证失败,你怎么办?请分别说清,并说明依据 `AGENTS.md` 哪一节。只核对和推演,不要登记工单,也不要派船员。 -- 第一件事:它会如实说出这个仓库到目前为止实际派出船员用的是哪个 harness、哪种运行时后端、是不是每一件活都一样,并说清楚这对应的是 `config/crew-harness` 和 `config/backend` 缺省时落到的静态默认值,还是曾经有某一件活单独指定过 - 具体历史以它自己观察到的为准,这一章不预设答案。 -- 第二件事:它会讲清楚单独指定只在「这件活需要某个特定工具才有的能力」时才成立,且明确指出一次 `--backend` 的显式指定只对这一件活自己有效,不会变成以后同类活的先例;遇到认证失败这类情况,它会说要停下来报告成一个 blocker,而不是自己悄悄换一个后端重试。 -- 它会点出出处:`AGENTS.md` 第 4 节「Harness and runtime dispatch」,以及第 2 节布局表里 `config/backend`、`config/crew-harness` 两行。 +**你应该看到** -**怎么判断做成了** +- 第一件:它如实说出这个仓库到现在实际用的 harness 和后端,每件活是否一样,是 `config/crew-harness` 和 `config/backend` 没设时的默认值,还是某件活单独指定过。以它自己查到的记录为准,这一章不预设答案。 +- 第二件:它讲清只有这件活需要某个工具特有的能力时才单独指定;单独传一次 `--backend` 只对这一件活有效,不成为先例;认证失败这类情况要停下来报告阻塞,不自己悄悄换后端重试。 +- 说明出处:`AGENTS.md` 第 4 节「Harness and runtime dispatch」,以及第 2 节里 `config/backend`、`config/crew-harness` 两行。 -三个都满足才算:它对这个仓库历史的描述准确对应它自己实际观察到的记录(无论是一路落到默认值,还是曾经有过一次显式指定,都要如实说出来,而不是想当然地套用「肯定是默认值」);推演部分讲清楚了「单独指定不设先例」和「失败即 blocker、不悄悄换后端重试」这两条边界,而不是笼统地说「可以换一个试试」;以及 - 最关键的 - 它**没有创建任何工单,也没有派出任何船员**,你可以直接看一眼有没有新开的会话窗口来确认。 +**怎么检查** -文件层面的验证办法跟前几章一样,**前后对比**:提问之前在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见,会话期间写这些不算数。 +- 第一件讲的跟它实际查到的记录一致:用的是默认值还是单独指定过,都如实说,不想当然地说「肯定是默认的」。 +- 第二件讲清了「单独指定不成为先例」和「起不来就报告阻塞,不悄悄换后端」这两条,而不是笼统一句「可以换一个试试」。 +- 没有登记工单,也没有多出新的船员窗口。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。 -如果它把「单独指定一次 `--backend`」讲成了「以后这类活都会自动沿用这个选择」,或者把认证失败之类的问题讲成了「自己换个后端重试就行」,那就是没做成。 +如果它说单独指定一次 `--backend` 以后同类活都会沿用,或者说认证失败就自己换个后端重试,就算没做成。 ## 下一章 -第 12 章讲 **Relay**:默认关闭的公开提及接入,开启它意味着授权了什么,以及哪些动作它永远不会自动替你做。 +第 12 章讲 Relay:默认关着的公开提及功能,打开它等于同意了什么,哪些事它永远不会自己做。 diff --git a/chapters/12-relay.md b/chapters/12-relay.md index a7f0235..2c497b8 100644 --- a/chapters/12-relay.md +++ b/chapters/12-relay.md @@ -2,99 +2,89 @@ ## 这一章讲什么 -这是这本教程的最后一章。前面十一章讲的都是你和大副之间的对话,或者大副和船员之间的调度 - 无论走到哪一关,说话的入口始终是你自己敲给大副的那句话。这一章讲一个例外:**Relay**(公开提及接入),一个让大副能够被 X(原 Twitter)或 Discord 上的公开提及唤醒、并直接公开回复的开关。 +这是最后一章。前面讲的都是你和大副 / firstmate 之间、大副和船员 / crewmate 之间的事,不管走到哪一步,发话的始终是你自己对大副说的话。这一章讲一个例外:Relay。打开它之后,别人在 X(原 Twitter)或 Discord 上公开提及(@)你的大副,就能叫醒它,它也会公开回复。 -这个开关默认是关的。它单独占一章,不是因为它常用,而是因为它牵扯到这本教程之前从没出现过的东西 - 一个不是你本人、却能触发大副做事的入口。所以这一章要讲清楚三件事:这个入口打开之后,那句「同意」到底同意了什么;哪些动作无论开不开,大副都永远不会自己拍板;以及为什么在它关着的时候,这一切都不会碰到你。 +Relay 默认关着。单独讲一章,不是因为它常用,而是因为它带来了前面没出现过的东西:一个不是你本人直接发话、却能让大副做事的入口。这一章讲三件事:打开它等于同意了什么;哪些事不管开不开,大副都不会自己做主;关着的时候为什么完全不影响你。 -本章依据的权威文件是 `AGENTS.md` 第 14 节「Relay」,以及 `docs/configuration.md` 中「Relay (.env)」一节。每一段引用都会标出处。 +本章依据 FirstMate 仓库 `AGENTS.md` 第 14 节「Relay」,以及 `docs/configuration.md` 的「Relay (.env)」一节。 ## Relay 是什么 -`AGENTS.md` 第 14 节开头这样定义它: +`docs/configuration.md` 的说明是:Relay 让一个大副能回复公开提及,并通过正常的任务流程处理提及里那些普通的、可以撤回的请求。它覆盖两个公开入口:X 上对 `@myfirstmate` 的提及,以及装了 myfirstmate 机器人的 Discord 服务器里对它的提及。两边用的是同一个配对令牌、同一套轮询和同一条回复路径。 -> Relay is the public-mention integration older docs and some emitted lines still call "X mode"; its identifiers keep the `FMX_`, `x-`, and `fm-x-` spellings. -> Relay ships inert and causes no behavior change until the home opts in by placing `FMX_PAIRING_TOKEN` in its gitignored `.env`. +有个历史遗留要先说一下:`AGENTS.md` 第 14 节说,一些旧文档和大副输出的状态行仍然把这个功能叫「X mode」,相关的名字也还保留 `FMX_`、`x-`、`fm-x-` 这几种写法。本书统一叫 Relay。你在自己的 FirstMate 里看到 `x-mode-error`、`FMX_PAIRING_TOKEN` 这类字样,说的就是这一章的东西。 -**Relay**(公开提及接入)让一份 FirstMate 安装能够被公开平台上的一句提及唤醒。`docs/configuration.md` 补充了它具体覆盖哪些平台: +同一节还说:Relay 装好时就是关着的,在你的 FirstMate 目录主动打开它之前,不会带来任何行为变化。 -> Relay lets a firstmate instance answer public mentions and act on normal reversible mention requests through firstmate's normal lifecycle. -> It covers both public surfaces the relay supports: `@myfirstmate` mentions on X, and mentions of the myfirstmate bot in a Discord server where it is installed. +## 怎么打开 -也就是说,Relay 覆盖两个公开入口 - X 上 `@myfirstmate` 的提及,以及安装了 myfirstmate 机器人的 Discord 服务器里对它的提及 - 用的是同一套配对、同一条轮询、同一条回复路径。 - -这里有个命名上的历史包袱值得先说清楚:一部分较老的文档和大副自己写出来的状态行,仍然把这个功能叫「X mode」,相关的标识符也还留着 `FMX_`、`x-`、`fm-x-` 这几种写法。这本教程之后统一用 Relay 这个名字,但如果你在自己那份 FirstMate 安装里看到 `x-mode-error`、`FMX_PAIRING_TOKEN` 这类字样,知道它们说的就是这一章讲的同一件事。 - -`AGENTS.md` 这两句话里最关键的词是「inert」(惰性):Relay 出厂就是关着的,而且关着的时候不只是「没人给你发提及」,是**这份代码路径本身完全不运行**,直到这份 FirstMate 安装(术语叫「home」)主动打开它。 - -## 打开它,动的是什么 - -打开的动作本身很小:把配对令牌(pairing token)写进这份 FirstMate 安装自己的、被 gitignore 掉的 `.env` 文件里的 `FMX_PAIRING_TOKEN` 这一行。`docs/configuration.md` 把整套开启步骤写在「Relay (.env)」一节: +打开只需要一步本地操作:把配对令牌写进你的 FirstMate 目录里、被 gitignore 的 `.env` 文件,写成 `FMX_PAIRING_TOKEN=`。`docs/configuration.md` 列的完整步骤是: > 1. Sign in at myfirstmate.io with X or Discord. > 2. For the Discord surface, use the dashboard's install link to add the myfirstmate bot to a server you administer; the X surface needs no install step. > 3. Copy the pairing token from the dashboard into this firstmate home's gitignored `.env` as `FMX_PAIRING_TOKEN=`. > 4. Start a new firstmate session so bootstrap picks the token up, then mention `@myfirstmate` on X or mention the bot in a server where it is installed. -这四步之外没有第二个开关。同一节说得很直接:账号创建、身份绑定、机器人安装、令牌发放这些事都在 myfirstmate.io 那个网站的仪表盘上办完,`.env` 只负责接住最后那一个令牌。这份文件不属于你的项目仓库,也从不入版本库 - 打开或关闭 Relay,从头到尾都不会碰到任何一个被版本追踪的文件。 +也就是:用 X 或 Discord 登录 myfirstmate.io;要用 Discord 的话,用控制台里的安装链接把机器人加到你管理的服务器(X 不用这一步);把控制台里的配对令牌复制进 `.env`;新开一个大副会话让它读到令牌,然后去 X 或 Discord 上提及它。 -## 那一个令牌到底同意了什么 +账号注册、身份绑定、安装机器人、发放令牌,都在 myfirstmate.io 的控制台完成,`.env` 只负责存最后这个令牌。`.env` 不进版本库,所以打开或关闭 Relay 都不会动到任何被 git 追踪的文件。 -这是这一章最重要的一句话,出自 `AGENTS.md` 第 14 节: +## 这个令牌同意了什么 -> That token is consent for public replies and normal reversible lifecycle actions from eligible mentions, not authority for destructive, irreversible, or security-sensitive action; those still require trusted-channel confirmation. +`AGENTS.md` 第 14 节的原文是: -拆成两半读。 +> That token is consent for public replies and normal reversible lifecycle actions from eligible mentions, not authority for destructive, irreversible, or security-sensitive action; those still require trusted-channel confirmation. -**同意的部分:** 把令牌放进 `.env`,等于你同意大副对**合法的提及**(eligible mentions)自主做两件事 - 公开回复,以及走它平时正常流程里那些**可逆**的动作。`docs/configuration.md` 还补了一句关于「谁算合法提及」的判断依据: +分两半看。 -> The relay uses owner-only routing: a mention delivered to a home is from that home's owner/captain, while its surrounding conversation context may still include other public accounts. +**同意了的。** 把令牌放进 `.env`,就是同意大副对合格的提及自己做两件事:公开回复,以及正常流程里那些可以撤回的操作。 -也就是说,Relay 用的是「按所有者路由」:一条送到你这份 FirstMate 安装的提及,认定它就是来自这份安装的所有者本人 - 也就是你,船长。这句里也提了一个容易忽略的细节:一条提及**周围的对话上下文**(比如它是回复谁、这条线索上还有谁说过什么)可能包含别的公开账号说的话;这些话只是背景,不是船长本人下的指令。 +至于谁的提及算数,`docs/configuration.md` 说 Relay 只认主人:送到你这个 FirstMate 目录的提及,都当作这个目录的主人,也就是你本人发的。不过一条提及周围的对话(比如它在回复谁、这条讨论里还有谁说过话)可能有别的公开账号的内容。 -**没同意的部分:** 破坏性、不可逆、涉及安全的动作,这个令牌完全不构成授权。这三类动作依然要走**可信渠道确认**(trusted-channel confirmation)- 不管 Relay 开没开,也不管这条提及看起来多像是船长本人发的。 +**没同意的。** 有破坏性、不可逆或涉及安全的操作,这个令牌完全不授权。这类操作仍然要通过可信渠道确认,不管 Relay 开没开,也不管那条提及看起来多像你本人发的。`docs/configuration.md` 也写了:这类请求会标记出来,等可信渠道确认,而不会因为一条公开提及就执行。 -打个比方帮助建立直觉(下面这段是**假设的场景**,不是这个仓库或这份教程用的这套 FirstMate 安装里真实发生过的事):假设 Relay 已经打开,船长在 X 上 `@myfirstmate` 说「帮我看看那个 PR 的 CI 为什么红了,查完直接回我」。这是一次正常、可逆的请求 - 查一下状态、回一句话 - 大副会自主处理并直接公开回复,不会跑回来问你「要不要我查」。但如果那条提及说的是「直接把那个 PR 合了」或者「把生产环境那个进程杀了」,这两个都是不可逆或涉及安全的动作,Relay 打开也不会让它们自动发生 - 大副会在公开回复里说这件事已经报给船长确认,然后走可信渠道等你点头,而不是从一条公开提及直接执行。 +举个假设的例子:Relay 已经打开,你在 X 上 @myfirstmate 说「帮我看看那个 PR 的 CI 为什么没通过,查完回我」。这是普通的、可以撤回的请求:查一下,回一句话。大副会自己处理,直接公开回复,不会回来问你要不要查。但如果那条提及说的是「直接把那个 PR 合了」或者「把生产环境那个进程杀掉」,这些是不可逆或涉及安全的操作,Relay 开着也不会让它们直接执行,要等你通过可信渠道确认。 -## 关着的时候,完全不受影响 +## 关着的时候,完全不影响你 -`AGENTS.md` 第 14 节开头那句「Relay ships inert」不是一句空话,`docs/configuration.md` 把「关着」具体落到了状态层面: +`docs/configuration.md` 说明了关着时的情况: > When the token is removed or empty, the next locked session-start bootstrap step removes those artifacts. > Steady-state off is silent and writes nothing. > Relay remains additive to non-Relay lifecycle behavior: homes without the generated artifacts keep the default watcher cadence and do not run the Relay poll. -翻成大白话:`.env` 里没有那个令牌,大副每次开机时的例行检查都不会为 Relay 生成任何本地文件,稳定态下的「关」是**静默的、不写任何东西的**;没开 Relay 的这份安装,监督节奏跟平时完全一样,也根本不会去跑那条 Relay 轮询。「Relay 是加法,不是改动现有行为」这句话就是 README 学习路线图里说「默认关着,不开就完全不影响你」的出处。 +也就是:`.env` 里的令牌删掉或留空后,下一次会话启动时会清掉 Relay 生成的本地文件。一直关着时什么都不输出,什么都不写。没开 Relay 的 FirstMate 目录,监控脚本的检查节奏照旧,也不会跑 Relay 的轮询。Relay 只是在原有流程上加东西,不改变原有行为,所以 README 里说它「不打开就不影响你」。 -这一章没有引用任何一次真实发生过的 Relay 事件:Relay 默认关着,只有船长主动把配对令牌写进自己那份 FirstMate 安装的 `.env`(`FMX_PAIRING_TOKEN`)才会生效,在没人这么做之前,就不存在任何一次真实的公开提及、回复或跟进可以拿来当例子。 +这一章没有真实的 Relay 例子:Relay 默认关着,在有人把配对令牌写进自己的 `.env` 之前,没有真实的公开提及、回复可以拿来举例。 ## 动手练习 -下面的练习是**只读**的:只是让大副读取当前状态、做推理并汇报,不会创建工单、不会派出真正的船员、不会改动任何被版本追踪的文件,也不会碰 `.env` 或任何 Relay 配置。你随时可以停下。 +这个练习是只读的:只让大副读当前状态、推演、汇报,不登记工单,不派船员,不改被 git 追踪的文件,也不碰 `.env` 或任何 Relay 配置。 -做练习前,先在你的 FirstMate 目录里启动一个 primary 会话,这样你面前的这个 agent 就是你的大副。 +开始前,在你的 FirstMate 目录里启动一个主会话,这个会话里的 agent 就是你的大副。 -### 练习:搞清楚这一个开关到底同意了什么 +### 练习:弄清这个开关同意了什么 对大副说: -> 大副,我要搞清楚 Relay 这个开关对我这份 FirstMate 安装意味着什么,这只是一次问答,不要真的去改任何东西。请分两部分回答:第一,直接告诉我这份安装现在 Relay 是开着还是关着,你是怎么判断出来的;第二,不管现在开没开,讲清楚如果打开它,那个配对令牌到底自动授权你做哪些事,以及不管开不开,哪些动作你永远不会不经我确认就自己执行。请指出你说的每一句依据是哪个文件的哪一节。全程不要碰 `.env`,也不要改动任何 Relay 相关的配置或生成文件。 - -**预期会看到什么** +> 大副,我想弄清 Relay 对我的 FirstMate 意味着什么,只是问答,不要改任何东西。请分两部分回答。第一,我这里 Relay 现在开着还是关着,你是怎么判断的。第二,不管现在开没开,讲清打开后那个配对令牌授权你自己做哪些事,以及不管开不开,哪些事你永远不会不经我确认就做。每一条都说明依据哪个文件的哪一节。全程不要碰 `.env`,也不要改任何 Relay 相关的配置或生成的文件。 -- 大副会先给出这份安装当前 Relay 的开关状态(开或关),并说明判断依据是 `.env` 里那个 `FMX_PAIRING_TOKEN` 是不是非空 - 它会汇报「有没有」这个结论,而不会把令牌本身的内容念出来。 -- 它会讲清楚令牌同意的是什么:公开回复,以及来自合法提及(按所有者路由认定为你本人)的正常可逆动作。 -- 它会讲清楚令牌没同意的是什么:破坏性、不可逆、涉及安全的动作从来不由这个令牌授权,永远要走可信渠道确认,跟 Relay 开没开无关。 -- 它会指出出处:`AGENTS.md` 第 14 节,以及 `docs/configuration.md` 的「Relay (.env)」一节。 -- 它没有修改 `.env`,没有生成任何 Relay 相关的本地文件,也没有派出任何船员。 +**你应该看到** -**怎么判断做成了** +- 它先说 Relay 现在开着还是关着,依据是 `.env` 里的 `FMX_PAIRING_TOKEN` 是不是非空。它只说有没有,不会把令牌内容念出来。 +- 它讲清令牌同意的是:公开回复,以及合格提及(只认主人,视为你本人发的)里那些正常的、可以撤回的操作。 +- 它讲清令牌没同意的是:有破坏性、不可逆或涉及安全的操作,令牌从不授权,总要通过可信渠道确认,跟 Relay 开没开无关。 +- 说明出处:`AGENTS.md` 第 14 节,以及 `docs/configuration.md` 的「Relay (.env)」一节。 -三个都满足才算:它准确报告了当前的开关状态并说清楚判断依据;它把「同意了什么」和「永远不会自动做什么」讲成了两件分开的事,而不是含糊地说一句「Relay 比较安全」;以及 - 最关键的 - 它**没有改动任何文件**,也没有派出任何船员。 +**怎么检查** -验证办法跟前几章一样,**前后对比**:提问之前在 FirstMate 目录里跑一次 `git status --short` 和 `git diff HEAD`,把两份输出都留着;问完之后再各跑一次,两次应该完全一样。`data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 和 `.env` 都是大副私有的运行状态,被 gitignore 掉了,这两条命令本来也看不见它们,会话期间读取(不是写入)这些也不算数。 +- 它说对了当前开关状态,也说清了判断依据。 +- 它把「同意了什么」和「永远不会自己做什么」分开讲清,而不是含糊一句「Relay 挺安全的」。 +- 没有改 `.env`,没有生成 Relay 相关的文件,也没有派船员。 +- 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。`.env` 和 `data/`、`state/`、`config/`、`projects/`、`.no-mistakes/` 一样是你本机私有的,已经被 gitignore,这两条命令看不到它们,所以还要在对话里确认大副没有改 `.env`。 -如果它把「破坏性/不可逆/涉及安全的动作」也说成了打开 Relay 就能自动执行,或者它去改了 `.env`、生成了 Relay 相关的配置文件,那就是没做成。 +如果它说打开 Relay 后,有破坏性、不可逆或涉及安全的操作也能自己执行,或者它改了 `.env`、生成了 Relay 相关的配置文件,就算没做成。 ## 全书完 -十二章走到这里,这本教程从「船长、大副、船员各自是谁」讲到了「一个不是船长本人、却能触发大副做事的入口该怎么被约束」。中间穿过的是同一条线:无论请求从哪里来 - 你的一句话,一份侦察报告,一条公开提及 - 决定权在哪一步该停在谁手上,从来不是靠自觉,而是写进了权威文件、也写进了工具本身。希望你读完这十二章、做完每一章的动手练习之后,能把这套判断直接用在自己的真实项目上。 +十二章从「船长、大副、船员各管什么」讲到「一个不是你本人直接发话的入口怎么约束」。贯穿全书的是同一件事:不管请求从哪里来,是你的一句话、一份调研报告,还是一条公开提及,哪一步该停下来由谁决定,都不靠自觉,而是写在 FirstMate 的规则里,也写在它的脚本里。希望你读完这十二章、做完练习后,能把这些做法用在自己的项目上。 From 2b4788506c2c2d09a2d7b9e05a0cb59ce8966796 Mon Sep 17 00:00:00 2001 From: Jasontopia Date: Sun, 27 Sep 2026 00:02:47 -0600 Subject: [PATCH 2/3] no-mistakes(review): Restore dropped source qualifiers and first-use English terms --- GLOSSARY.md | 1 + README.md | 2 +- chapters/01-what-is-firstmate.md | 4 ++-- chapters/02-proposing-a-task.md | 4 ++-- chapters/03-crewmates-and-worktrees.md | 2 +- chapters/04-no-mistakes-pipeline.md | 2 +- chapters/07-backlog-and-captain-decisions.md | 4 ++-- chapters/08-supervision-and-wakeups.md | 8 ++++---- chapters/09-secondmates-and-routing.md | 4 ++-- chapters/10-afk-mode.md | 4 ++-- chapters/12-relay.md | 8 ++++---- 11 files changed, 22 insertions(+), 21 deletions(-) diff --git a/GLOSSARY.md b/GLOSSARY.md index c430e33..283fc9c 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -51,6 +51,7 @@ | primary checkout | 主仓库目录 | | isolation assertion | 隔离检查 | | endpoint | 会话窗口 | +| primary session | 主会话 | | home(firstmate home) | FirstMate 目录 | | main home | 主 FirstMate 目录 | | harness | harness(跑船员的 agent 工具,比如 Claude Code) | diff --git a/README.md b/README.md index c09945c..fb0eeae 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ FirstMate 是一个「agent 发行版」(agent distro):一个目录,里 | 6 | [调研任务与报告](chapters/06-scout-tasks-and-reports.md) | 什么时候该先调研,报告放在哪里,为什么报告本身不等于同意动手改。 | | 7 | [待办队列与船长决策](chapters/07-backlog-and-captain-decisions.md) | 待办队列怎么记活,等你拍板的问题为什么也是一张工单,你的回答怎么关掉它。 | | 8 | [监督与唤醒](chapters/08-supervision-and-wakeups.md) | 大副怎么在不耗 token 的情况下盯住队伍,什么事会叫醒它,什么事才来找你。 | -| 9 | [二副与分派](chapters/09-secondmates-and-routing.md) | 队伍大了以后怎么按领域设常驻的二副,一件活怎么分给谁,哪些活留在大副这边。 | +| 9 | [二副与分派](chapters/09-secondmates-and-routing.md) | 队伍大了以后怎么按领域设常驻的二副,一件活怎么分派(routing)给谁,哪些活留在大副这边。 | | 10 | [离席模式(afk)](chapters/10-afk-mode.md) | 你走开时谁来监督,大副能自己做什么、必须等你什么,你回来时怎么交接。 | | 11 | [harness 与运行时后端](chapters/11-harness-and-runtime-backend.md) | 船员是用什么工具、在什么终端里跑的,默认怎么选,什么时候值得单独指定。 | | 12 | [Relay](chapters/12-relay.md) | 默认关着的公开提及功能,打开它等于同意了什么,哪些事它永远不会自己做。 | diff --git a/chapters/01-what-is-firstmate.md b/chapters/01-what-is-firstmate.md index 8135e03..4a1e445 100644 --- a/chapters/01-what-is-firstmate.md +++ b/chapters/01-what-is-firstmate.md @@ -28,11 +28,11 @@ FirstMate 借用了船上的称呼。 你所有项目上的软件工作,都只跟大副说。它负责: -- 判断这件活属于哪个项目,是直接改,还是先调查清楚。 +- 判断这件活属于哪个项目,是直接改,还是先调研清楚。 - 登记工单,给船员写任务简报(brief)。 - 派船员干活,每个船员一个独立的 git 工作树(worktree)。 - 盯着整支队伍把活干完,只在真正需要你决定时才来找你。 -- 用简单的话告诉你结果:一个 PR、一次你批准过的本地合并,或者一份调查报告。 +- 用简单的话告诉你结果:一个 PR、一次你批准过的本地合并,或者一份调研报告。 大副自己不改项目代码,原因见下面的规矩一。 diff --git a/chapters/02-proposing-a-task.md b/chapters/02-proposing-a-task.md index 8cc58ed..adfa7c9 100644 --- a/chapters/02-proposing-a-task.md +++ b/chapters/02-proposing-a-task.md @@ -23,7 +23,7 @@ ### 交付任务还是调研任务 - **交付任务(ship)** 是默认的,产出是对项目的改动,按选定的交付方式交付。 -- **调研任务(scout)** 产出的是一份报告,放在 `data//report.md`,不开 PR。适合调查、诊断、规划、复现问题、审计这类工作,前提是你明确要一份单独的报告或设计,或者还有没弄清的问题,可能影响要不要做、做成什么样。 +- **调研任务(scout)** 产出的是一份报告,放在 `data//report.md`,不开 PR。适合调查、诊断、规划、复现问题、审计这类工作,前提是你明确要一份单独的报告或设计,或者还有没弄清的问题,可能明显影响要不要做、做成什么样。 要注意:一个诊断请求、一份报告、一条建议,都只是参考信息,不等于同意改代码。你看完调研报告,还要再说一句「照着改」,才会进入交付。反过来,如果已有的报告或证据已经能回答你的问题,大副会直接告诉你,不会再派一次调研。第 6 章会细讲调研任务。 @@ -48,7 +48,7 @@ - **船长意图**(Captain's intent):你的原话和你提的限制,加上读懂这句话需要的背景。如果你提到了某份报告、某个决定或某个 PR,要把里面的具体内容写进来,不能只留一个链接。 - **大副规格**(Firstmate spec):具体怎么做。这一块不算船长意图。 -两块分开写,是因为质检流水线拿「船长意图」当验收标准。所以大副不会在这一块里把你的要求扩大成一个笼统的目标。派工命令也会拒绝还有空白没填的简报。 +两块分开写,是因为质检流水线拿「船长意图」当验收标准。所以大副不会在这一块里把你的要求扩大成一个笼统的目标。派工命令会拒绝两种简报:还有空白没填的,和「船长意图」里有一行以「船长」之类的称呼开头、或者直接对船长说话的。 ## 怎么说能少走弯路 diff --git a/chapters/03-crewmates-and-worktrees.md b/chapters/03-crewmates-and-worktrees.md index 398dc91..f3443f0 100644 --- a/chapters/03-crewmates-and-worktrees.md +++ b/chapters/03-crewmates-and-worktrees.md @@ -27,7 +27,7 @@ - 两个互不兼容的迁移同时在跑。 - 其他让分头推进或事后合并变得不安全的具体情况。 -光是改到同一个文件,不够成为排队的理由。 +光是改到同一个文件,不够成为排队的理由。反过来,真正的阻塞会一直挡在那里,不会自己消失。 这条规矩能成立,靠的就是工作树隔离。两个船员在各自的分支、各自的工作树里改同一个文件,干活时互相看不见,也不会覆盖对方的改动。两份改动要到合并时才碰面,变成一次普通的合并冲突或 rebase,跟平时几个人用 git 协作一样处理。 diff --git a/chapters/04-no-mistakes-pipeline.md b/chapters/04-no-mistakes-pipeline.md index 6c0d74d..424af10 100644 --- a/chapters/04-no-mistakes-pipeline.md +++ b/chapters/04-no-mistakes-pipeline.md @@ -31,7 +31,7 @@ 其他情况大副都自己定,哪怕修起来很复杂:只要是原来要求范围内的直接修正,就不来问你。 -定下之后,大副给船员一条明确的指示:对应哪条决定记录、哪一步、批准还是别的动作、涉及哪几个问题编号,需要时附上具体做法,以及船员要执行的那条 `no-mistakes axi respond` 命令。 +定下之后,大副给船员一条明确的指示:对应哪条决定记录、哪一步、批准还是别的动作、涉及哪几个问题编号,需要时附上具体做法,以及船员要执行的那条 `no-mistakes axi respond` 命令。这条指示一送到,船员那边对应的决定记录就关掉了(靠的是 `--resolve-key`)。 ## 一个真实的例子 diff --git a/chapters/07-backlog-and-captain-decisions.md b/chapters/07-backlog-and-captain-decisions.md index e87b467..9e52e43 100644 --- a/chapters/07-backlog-and-captain-decisions.md +++ b/chapters/07-backlog-and-captain-decisions.md @@ -4,7 +4,7 @@ 前面几章跟着一件活走。实际上大副 / firstmate 手上从来不止一件活,这些活要记下来、排好队。其中有一类很特别:不用船员 / crewmate 改任何代码,只要船长 / captain(你)说一句话。 -这一章讲三件事:待办队列(backlog)怎么把活分成在办、待办和已完成;一个等你拍板的问题,为什么也只是一张工单,只是被挂起等你;你的回答怎么把它关掉。大副怎么盯住整支队伍、什么事会叫醒它,是下一章的内容。 +这一章讲三件事:待办队列(backlog)怎么把活分成在办、待办和已完成;一个等你拍板的问题,为什么也只是一张工单,只是被挂起(hold)等你;你的回答怎么把它关掉。大副怎么盯住整支队伍、什么事会叫醒它,是下一章的内容。 本章依据 FirstMate 仓库 `AGENTS.md` 第 10 节「Backlog contract」、`docs/configuration.md` 的「Backlog backend」一节,以及 `bin/fm-captain-hold.sh` 开头的注释。 @@ -18,7 +18,7 @@ - **在办**(`## In flight`):已经派了船员,正在工作树里干。 - **已完成**(`## Done`):已经做完,只保留最近几条。 -从待办到在办、从在办到已完成,这两步不用大副手动去改。派船员的 `bin/fm-spawn.sh` 和收尾清理的 `bin/fm-teardown.sh` 会自己挪动工单;挪不成功,它们会拒绝继续,不会报告「已经派出去了」或「已经做完了」。 +在自动挪动工单的机制生效时,从待办到在办、从在办到已完成,这两步不用大副手动去改(什么时候生效、有什么例外,见 `docs/configuration.md`)。派船员的 `bin/fm-spawn.sh` 和收尾清理的 `bin/fm-teardown.sh` 会自己挪动工单;挪不成功,它们会拒绝继续,不会报告「已经派出去了」或「已经做完了」。 所以大副要自己做的只剩三件:派工前登记工单,记录决定,保持工单备注是最新的。另外,每次有活收尾清理完,或者每次心跳检查时,大副都会回头看一遍待办的活,把依赖和时间条件都满足了的派出去。队列不是一张放着不动的清单,有变化就要重新过一遍。 diff --git a/chapters/08-supervision-and-wakeups.md b/chapters/08-supervision-and-wakeups.md index 7f74a10..85d0e67 100644 --- a/chapters/08-supervision-and-wakeups.md +++ b/chapters/08-supervision-and-wakeups.md @@ -12,7 +12,7 @@ 要盯住三件同时在跑的活,最直接的办法是写个循环,每隔几秒就让模型看一遍三件活有没有变化。这样确实盯得住,但每看一次都是一次真实的模型调用。队伍越大、活跑得越久,花的就越多,而且跟活有没有进展毫无关系。 -FirstMate 不这么做。`docs/architecture.md` 里写:一个不耗 token 的 bash 监控脚本(`bin/fm-watch.sh`)守着整支队伍,用 shell 逻辑给发现的动静分类,只有真正需要处理时才叫醒大副。按 `bin/fm-watch.sh` 的注释,不需要处理的动静它自己消化掉,继续等;只有需要处理的唤醒,它才退出,把控制权交回大副。 +FirstMate 不这么做。`docs/architecture.md` 里写:一个不耗 token 的 bash 监控脚本(watcher,`bin/fm-watch.sh`)守着整支队伍,用 shell 逻辑给发现的动静分类,只有真正需要处理时才叫醒大副。按 `bin/fm-watch.sh` 的注释,不需要处理的动静它自己消化掉,继续等;只有需要处理的唤醒,它才退出,把控制权交回大副。 所以: @@ -26,14 +26,14 @@ FirstMate 不这么做。`docs/architecture.md` 里写:一个不耗 token 的 `AGENTS.md` 第 8 节把传到大副手上的唤醒分成四种。唤醒的类型名保留英文,因为它们就写在唤醒原因的开头: -- **`signal`(信号)**:某个船员往自己的状态文件里写了一行,比如「做完了」「卡在一个要决定的地方了」。大副先读写了什么;只有接下来的动作取决于当前状态时,才去核对当前状态。 +- **`signal`(信号)**:某个船员往自己的状态文件(status file)里写了一行,比如「做完了」「卡在一个要决定的地方了」。大副先读写了什么;只有接下来的动作取决于当前状态时,才去核对当前状态。 - **`stale`(久无动静)**:某个船员很久没有动静,可能停了、在原地打转、糊涂了或者没反应。这只说明该去看一眼,不代表确实出了问题。大副会去看它的会话窗口;船员真的卡住了,就按 `stuck-crewmate-recovery` 技能处理。 - **`check`(检查结果)**:大副安排的某个定期检查有了结果,比如 PR 有没有合并、有没有新的外部贡献动静。这是之前问出去的问题有了答案,不是船员主动报告。 - **`heartbeat`(心跳)**:按固定节奏触发的兜底检查。大副借这个机会把整支队伍从头看一遍,核对有没有可疑的任务或 PR 状态,更新待办队列。队伍没变化,不能当成进展报告给船长。 四种里只有 `signal` 是船员主动发出的,另外三种都来自大副自己安排的监督机制。 -### 状态行记的是当时发生的事,不是现在的状态 +### 状态行(status line)记的是当时发生的事,不是现在的状态 第 8 节还特别写了一句:"A status line is a wake event, not current state"。 @@ -125,4 +125,4 @@ state: · source: `state/.afk-contract` is the away posture, written in the same turn as `/afk` before any other work, because `/afk` is itself the go: no read-back gates entry or waits for a go; entry announces hold-for-return only, and the away session acts on those words by its own judgment through the guarded scripts under standing authority, holding for the return on doubt. -意思是:你说 `/afk` 加上你交代的话,本身就是「开始」,不是「请你批准」。大副会在同一轮、做任何别的事之前,先把你的话原样记下来(`state/.afk-contract`),因为再等你确认,你可能已经走了、看不到屏幕了。它接着会告诉你:没把握的事会留着等你回来。这只是告知,不是在等你点头,你的话说完就已经生效。 +意思是:你说 `/afk` 加上你交代的话,本身就是「开始」,不是「请你批准」。大副会在同一轮、做任何别的事之前,先把你的话原样记下来(`state/.afk-contract`),因为再等你确认,你可能已经走了、看不到屏幕了。它接着会告诉你:没把握的事会留着等你回来。然后它用自己的话把你交代的内容复述一遍,说明哪句话离席期间做不了。这些只是告知,不是在等你点头,你的话说完就已经生效。如果它理解错了,你再发一次 `/afk` 加上新的话就行。 ## 离席期间大副能做到哪一步 @@ -71,7 +71,7 @@ **你应该看到** - 第一问:由一个专门的守护进程接管监督,不会再另外启动一个监控脚本。 -- 第二问:说完就生效,已经记下来了,不等你确认。它之后告诉你的话只是告知,不是在等你点头。 +- 第二问:说完就生效,已经记下来了,不等你确认。它之后复述你的话只是告知,不是在等你点头;理解错了,你再发一次 `/afk` 纠正。 - 第三问:走完流水线、按原来节奏推进,是你的话管得到的,也走的是本来就允许的流程,它会自己判断往下推,不会特意来问你。但强推、覆盖别人历史是有破坏性、不可逆的操作,不会因为你那句话就放开,它会留着等你回来。 - 第四问:它会先把离席期间的事交代清楚,交代完才把「现在几点了」当普通问题回答。它还会指出,如果是监督机制内部发来的、带标记的消息,就不算你回来,不会触发交接。 - 说明出处:`AGENTS.md` 第 8 节「Away-mode and quiet-mode stub」,以及 `/afk` 技能。 diff --git a/chapters/12-relay.md b/chapters/12-relay.md index 2c497b8..9819385 100644 --- a/chapters/12-relay.md +++ b/chapters/12-relay.md @@ -2,7 +2,7 @@ ## 这一章讲什么 -这是最后一章。前面讲的都是你和大副 / firstmate 之间、大副和船员 / crewmate 之间的事,不管走到哪一步,发话的始终是你自己对大副说的话。这一章讲一个例外:Relay。打开它之后,别人在 X(原 Twitter)或 Discord 上公开提及(@)你的大副,就能叫醒它,它也会公开回复。 +这是最后一章。前面讲的都是你和大副 / firstmate 之间、大副和船员 / crewmate 之间的事,不管走到哪一步,发话的始终是你自己对大副说的话。这一章讲一个例外:Relay。打开它之后,在 X(原 Twitter)或 Discord 上公开提及(mention)你的大副,就能叫醒它,它也会公开回复。 Relay 默认关着。单独讲一章,不是因为它常用,而是因为它带来了前面没出现过的东西:一个不是你本人直接发话、却能让大副做事的入口。这一章讲三件事:打开它等于同意了什么;哪些事不管开不开,大副都不会自己做主;关着的时候为什么完全不影响你。 @@ -10,7 +10,7 @@ Relay 默认关着。单独讲一章,不是因为它常用,而是因为它 ## Relay 是什么 -`docs/configuration.md` 的说明是:Relay 让一个大副能回复公开提及,并通过正常的任务流程处理提及里那些普通的、可以撤回的请求。它覆盖两个公开入口:X 上对 `@myfirstmate` 的提及,以及装了 myfirstmate 机器人的 Discord 服务器里对它的提及。两边用的是同一个配对令牌、同一套轮询和同一条回复路径。 +`docs/configuration.md` 的说明是:Relay 让一个大副能回复公开提及,并通过正常的任务流程处理提及里那些普通的、可以撤回的请求。它覆盖两个公开入口:X 上对 `@myfirstmate` 的提及,以及装了 myfirstmate 机器人的 Discord 服务器里对它的提及。两边用的是同一个配对令牌(pairing token)、同一套轮询和同一条回复路径。 有个历史遗留要先说一下:`AGENTS.md` 第 14 节说,一些旧文档和大副输出的状态行仍然把这个功能叫「X mode」,相关的名字也还保留 `FMX_`、`x-`、`fm-x-` 这几种写法。本书统一叫 Relay。你在自己的 FirstMate 里看到 `x-mode-error`、`FMX_PAIRING_TOKEN` 这类字样,说的就是这一章的东西。 @@ -39,9 +39,9 @@ Relay 默认关着。单独讲一章,不是因为它常用,而是因为它 **同意了的。** 把令牌放进 `.env`,就是同意大副对合格的提及自己做两件事:公开回复,以及正常流程里那些可以撤回的操作。 -至于谁的提及算数,`docs/configuration.md` 说 Relay 只认主人:送到你这个 FirstMate 目录的提及,都当作这个目录的主人,也就是你本人发的。不过一条提及周围的对话(比如它在回复谁、这条讨论里还有谁说过话)可能有别的公开账号的内容。 +至于谁的提及算数,`docs/configuration.md` 说 Relay 只认主人:送到你这个 FirstMate 目录的提及,都当作这个目录的主人,也就是你本人发的。不过一条提及周围的对话(比如它在回复谁、这条讨论里还有谁说过话)可能有别的公开账号的内容。这些内容只是背景,不算你下的指令。 -**没同意的。** 有破坏性、不可逆或涉及安全的操作,这个令牌完全不授权。这类操作仍然要通过可信渠道确认,不管 Relay 开没开,也不管那条提及看起来多像你本人发的。`docs/configuration.md` 也写了:这类请求会标记出来,等可信渠道确认,而不会因为一条公开提及就执行。 +**没同意的。** 有破坏性、不可逆或涉及安全的操作,这个令牌完全不授权。这类操作仍然要通过可信渠道(trusted channel)确认,不管 Relay 开没开,也不管那条提及看起来多像你本人发的。`docs/configuration.md` 也写了:这类请求会标记出来,等可信渠道确认,而不会因为一条公开提及就执行。 举个假设的例子:Relay 已经打开,你在 X 上 @myfirstmate 说「帮我看看那个 PR 的 CI 为什么没通过,查完回我」。这是普通的、可以撤回的请求:查一下,回一句话。大副会自己处理,直接公开回复,不会回来问你要不要查。但如果那条提及说的是「直接把那个 PR 合了」或者「把生产环境那个进程杀掉」,这些是不可逆或涉及安全的操作,Relay 开着也不会让它们直接执行,要等你通过可信渠道确认。 From 5036ab578fd86b1fbf5983e2fe374068735d90c9 Mon Sep 17 00:00:00 2001 From: Jasontopia Date: Sun, 27 Sep 2026 00:04:03 -0600 Subject: [PATCH 3/3] no-mistakes(review): Restore scout threshold in ch6 and fix ch3 blocker wording --- chapters/03-crewmates-and-worktrees.md | 2 +- chapters/06-scout-tasks-and-reports.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chapters/03-crewmates-and-worktrees.md b/chapters/03-crewmates-and-worktrees.md index f3443f0..af7a02d 100644 --- a/chapters/03-crewmates-and-worktrees.md +++ b/chapters/03-crewmates-and-worktrees.md @@ -27,7 +27,7 @@ - 两个互不兼容的迁移同时在跑。 - 其他让分头推进或事后合并变得不安全的具体情况。 -光是改到同一个文件,不够成为排队的理由。反过来,真正的阻塞会一直挡在那里,不会自己消失。 +光是改到同一个文件,不够成为排队的理由。但真正的阻塞仍然是要等的理由。 这条规矩能成立,靠的就是工作树隔离。两个船员在各自的分支、各自的工作树里改同一个文件,干活时互相看不见,也不会覆盖对方的改动。两份改动要到合并时才碰面,变成一次普通的合并冲突或 rebase,跟平时几个人用 git 协作一样处理。 diff --git a/chapters/06-scout-tasks-and-reports.md b/chapters/06-scout-tasks-and-reports.md index 2c16e00..9867507 100644 --- a/chapters/06-scout-tasks-and-reports.md +++ b/chapters/06-scout-tasks-and-reports.md @@ -10,14 +10,14 @@ ## 交付任务和调研任务 -**交付任务**是默认的,产出是对项目的改动,按选定的交付方式交付,最后是一个 PR 或一次本地合并。一旦你同意动手,大副就直接派交付任务。还剩一些范围明确的调查,就在这件交付任务里顺手做掉,除非还没弄清的问题可能影响要不要做、做成什么样。 +**交付任务**是默认的,产出是对项目的改动,按选定的交付方式交付,最后是一个 PR 或一次本地合并。一旦你同意动手,大副就直接派交付任务。还剩一些范围明确的调查,就在这件交付任务里顺手做掉,除非还没弄清的问题可能明显影响要不要做、做成什么样。 **调研任务**产出的是知识:一份写在 `data//report.md` 的报告,从不开 PR。适合调查、诊断、规划、复现问题、审计这类工作,满足下面任意一条时才派: - 你明确要一份单独的报告或设计。 -- 还有没弄清的问题,可能影响要不要做、做成什么样。 +- 还有没弄清的问题,可能明显影响要不要做、做成什么样。 -判断标准是这个问题会不会影响要不要做、做成什么样,而不是「这事看起来有点复杂」或者「想先探探路」。普通的、范围明确的调查,比如去读一下某个模块现在怎么实现的,留在交付任务里做就行,不用单独派调研。 +判断标准是这个问题会不会明显影响要不要做、做成什么样,而不是「这事看起来有点复杂」或者「想先探探路」。普通的、范围明确的调查,比如去读一下某个模块现在怎么实现的,留在交付任务里做就行,不用单独派调研。 ## 什么时候不该派调研 @@ -70,7 +70,7 @@ **怎么检查** -- 三句话的归类都对,理由是「这个问题会不会影响要不要做、做成什么样」,而不是「听起来复杂还是简单」。 +- 三句话的归类都对,理由是「这个问题会不会明显影响要不要做、做成什么样」,而不是「听起来复杂还是简单」。 - 讲对了报告放在哪里,也讲清了报告不等于同意动手,要你另外说一句才会转成交付任务。 - 没有登记工单,也没有多出新的船员窗口。 - 跟第 1 章一样,提问前后各跑一次 `git status --short` 和 `git diff HEAD`,输出应该一样。