为什么一张票的「状态」,就是交付的闸门
thesis 系列 · 第二篇 · Ready / blocked / assigned_to 第一人称 shuke。忠实,但求风趣。
一、没有它的时候,疼在哪
上一篇讲到,explore 会把一个想清楚的念头凝结成一张票,交出去。可”交出去”之后呢?
我的 repo 里同时躺着几十张 issue。有的已经想透了,交付 agent 可以立刻上手;有的还在探讨,方案没定;有的等着另一件事先做完;有的卡在一个只有我能拍的产品决策上。
问题是:交付 agent 怎么知道哪张能动、哪张碰不得?
如果这个”状态”只活在我脑子里,那我就又变成瓶颈了——agent 每想动一张票都得先问我”这张能开工吗?“,或者更糟,它自作主张抓了一张我还没定案的票就开干。我筑 my-ai-team 的初衷就是不想 babysit,结果卡在”哪张票能动”这种小事上,天天被 at。
二、现在是什么样
票的状态被做成了一条四态流,外加两种”拦法”。
四态流(靠标签):
- backlog:无标签。想法躺着,没人会碰。
- refining:
mat issue refining。有人(Planner / Adhoc / Explore)正在把它打磨成规格。 - ready:
mat issue ready。质量门通过了,这就是交付闸门——只有ready的票会被自动认领去交付。 - blocked:
mat issue blocked。需要一个产品/范围决策才能往下走。
两种拦法(这是全文的关键,也是我后来才拆对的地方):
- 票挡票,用 GitHub 原生依赖。 A 要等 B 先完成,就给 A 记一条原生
blocked_by(mat issue block A --by B)。它自清:B 一 close,A 自动变回可认领,不需要任何人手动撕标签。 - 非票的门,用
blocked标签。 占位、外部依赖、等我拍板的人类决策——这些不是”某张 issue 没做完”,而是 GitHub 之外的什么东西没到位。这种才用blocked标签,它需要人手动摘掉。
一条统一判据把两者收口:一张票可被认领,当且仅当它既没有 blocked 标签,也没有任何未关闭的原生 blocker。
三、为什么是这样(又是踩出来的)
最早,blocked 标签是一肩挑两活的:既标”等另一张 issue”,也标”等一个外部条件”。能用,但很快就别扭。
第一步先立闸门。5 月 31 日(#209,“require ready before delivery claims”),我把”能不能被认领”从我脑子里挪进了票的状态里:交付端只认领 ready。从这一刻起,一张没有 blocked、够格的票就是明确需求,交付 agent 可以一路跑到合并,不必回头问我。闸门不是我在 PR 前新加的一道检查,它就是票自己的状态。
真正让我想拆开的,是”票挡票”这件事的一个特性:它本该自己就能判定。 A 等 B——B 关没关,GitHub 一清二楚,根本不需要我这个人肉去盯着、等 B 合并了再跑去把 A 的 blocked 标签撕掉。可只要它还是一个标签,就总得有人手动撕。这活不该是人的。
于是 6 月 29 日(#699 / #842,“move issue-to-issue blocking onto GitHub native dependencies”),我把票挡票整个搬到了 GitHub 原生 blocked_by 依赖上——它自清。blocked 标签同时收窄,只留给”非票的门”。7 月 12 日(#1298)再补上 mat issue block / unblock 的人体工学,把解析 issue 内部 id、防环、幂等这些琐事包掉。
所以今天是双轨并存,但不是冗余——是两把工具干两件本质不同的活。
四、为什么这样更好
拆分的正确性,藏在一个朴素的分工原则里:
机器能判定的事,交给机器自清;机器判定不了的事,才留一个需要人动手的信号。
- “B 这张 issue 关了没有”——机器能判定,所以用自清的原生依赖,人一次都不用插手。
- “这个产品决策我拍了没有”——机器判定不了,所以留一个
blocked标签,而撕掉这个标签的动作本身,就是”决策已作出”的信号。手动,在这里不是负担,是语义。
这样一来,几十张票的认领逻辑坍缩成一句可机械执行的判据(无 blocked 标签 ∧ 无 open 原生 blocker),我彻底从”哪张能动”里退了出来。这和上一篇的分界膜是同一套哲学的两面:把状态钉在物件(票)自己身上,而不是钉在人(我)的脑子里或流程的某个临时检查点上。 物件自己携带足够的状态,协作就不需要一个中心去问、去答。
五、副产品(写这篇时挖出来的)
- explore 的宪法里有一句”
Use blocked only for a placeholder or a genuine external dependency”——它正确地划定了标签的边界,却没告诉 explorer 遇到”票挡票”时该改用原生blocked_by。plan.md 里写全了这条(blocked_by依赖 + 保留标签给非 issue 门),explore.md 只写了半条。一个刚把两张有先后依赖的票理清的 explorer,很可能顺手抓blocked标签,正好踩进我 6 月 29 日拆开的那个坑。值得开一张票,把原生依赖这条路补进 explore(以及其他会开票的角色)的宪法——照例,加这一条的同时看看那句能不能压得更短。
下一篇:宪法即可执行文本 —— 为什么我给每个 agent 写的提示词,加一句就要删一句。