我一开始只是想解决一个很具体的问题:手里已经有几套能工作的流程,可一旦其中某个小模块、匹配块或者执行方法发生变化,就很难说清楚这次变化到底应该归谁管。
比如一套内容生产流程,前面是选题,中间有多个处理步骤,后面还可能根据素材不同选择方法 A 或方法 B。方法 A 不是另一件完全无关的事,而是主流程里的一个分支;方法 B 也不是随手替换的按钮,它有自己的输入、适用条件和验收结果。流程能跑通时,这些差异常常被临场记忆遮住。等到下次要升级,我才发现没有一处能回答:当前用的是哪个版本,为什么这样改,改完应该检查什么。
成熟工作流需要版本化,不是因为流程一定复杂,而是因为变化已经开始产生归属、依赖和验收问题。
这件事让我重新意识到,一个流程真正成熟的标志,不是它今天能执行一次,而是它在下一次变化时,仍然能让人找到入口,不必从头猜一遍。
表面上是改一个模块
当时我先想到的办法,是做一个专门的管理技能,把现有流程都收进去。这个想法听起来很自然:既然各个流程会变化,就加一个总入口,统一记录和调用。
但继续往下想,问题并不只是“有没有一个总入口”。如果某个分支改了,谁负责真正执行?如果依赖的技能也升级了,主流程要不要跟着变?如果旧版本仍然服务于正在进行的任务,新版本又已经准备给新任务使用,二者能不能同时存在?这些问题都不是目录多写一层说明就能解决的。
如果不处理,后面会导致一种很隐蔽的混乱:大家都知道流程大概怎么走,却不知道眼前这次运行采用了哪套规则。出现问题时,可能把执行技能的错误算到工作流头上,也可能为了修一个局部问题,误改了所有依赖它的流程。最后留下的不是一个可定位的变更,而是一句“之前就是这么做的”。
表面上我是在管理模块变化,其实我是在管理判断的连续性。一次升级至少要留下旧版本、新版本、影响范围和验收边界,否则所谓的升级只是把旧记忆覆盖掉。
先把职责拆开
我后来把调研得到的思路压缩成了3层。
第1层是工作流本身。它负责表达一件完整的事情如何推进,包括有哪些阶段、哪些分支、每个分支需要什么输入,以及最终什么结果才算完成。它不应该把每个执行细节都重新实现一遍,而是引用稳定的能力和版本。
第2层是领域技能。它负责把某个具体动作做好,比如处理一种素材、调用一种执行方式,或者完成一类转换。它可以独立升级,但需要说明自己的输入、输出、适用条件和不保证的部分。这样,工作流只依赖契约,不必和某个具体实现死绑。
第3层是治理层。它负责发现有哪些工作流、它们依赖什么、哪些版本可以使用、变更是否留下证据,以及任务应该派发给哪一个入口。治理层可以帮助人找到流程,却不应该抢走执行层的工作。
管理层的价值是保存生命周期和证据,执行层的价值是完成领域动作;两者混在一起,升级就会变成互相覆盖。
这个拆分也解释了为什么我没有直接把所有已有技能再包一层总管。那种做法只是增加了一个名字,却没有回答版本如何并存、依赖如何追踪、失败如何回退。新的外壳可能比原来的问题更重。
版本不是给文件换名字
“版本化”很容易被误解成给文件加一个数字。实际上,数字只是入口,重要的是版本背后的生命周期。
一个成熟工作流至少需要登记、版本、依赖、变更、验收和退役这几类信息。登记回答它解决什么问题;版本回答当前采用哪一套规则;依赖回答它需要哪些能力;变更回答这次为什么改、影响了什么;验收回答怎样证明它仍然可用;退役则回答旧版本什么时候不再被新任务选用。
这些信息不需要一开始就做成很重的系统。可以先用一份清晰的配置和一张可视化节点图表达主流程,再为每个节点补上版本和状态。图适合看结构,文字适合看契约,运行记录适合看这一次到底发生了什么。它们解决的是不同问题,不能指望其中一个替代全部。
这里最容易误判的是,把“看得见”当成“管得住”。节点图能让我快速看到方法 A 和方法 B 是两个分支,却不能自动说明方法 A 的输入是否已经变化,也不能替我判断旧版本是否还能验收。可视化降低了理解成本,版本记录才降低了变更成本。
我选择先保留轻量的节点图和结构化记录,是因为当前真正缺的是入口和边界,不是一个庞大的平台。等依赖数量、并行版本和运行审计真的增长,再增加更重的索引或调度能力,成本才和问题匹配。
分支要能被看懂,也要能被验收
那次讨论里,我逐渐把“流程可选”理解得更具体。一个流程可以有多个分支,但分支不能只是图上的几条线。每条线都应该说明什么时候进入、需要什么输入、会调用什么能力、输出是什么,以及失败时停在哪里。
比如同一套内容生产流程,方法 A 适合一种输入,方法 B 适合另一种输入。用户在图上看到的是选择关系,管理记录里还要有选择理由。这样,下次发生变化时,才知道这是替换实现、增加分支,还是改变了主流程的判断条件。
如果只记录“用了方法 A”,不记录进入条件和验收结果,运行结束后即使产出了文件,也无法判断这次成功是不是偶然。更麻烦的是,新的执行者可能照着结果复制,却没有意识到前置条件已经变了。
所以我会把验收拆成2层。第1层检查结构:节点是否齐全,连接是否正确,所需依赖是否存在。第2层检查行为:输入是否被正确传递,当前步骤是否完成,输出是否满足这一分支的要求。结构检查通过,不代表行为一定正确;“按钮能运行”,也不代表这次运行已经完成。
一个版本能否交付,不能只看它有没有被保存,还要看它能否被别人按相同边界重新验证。
这也解释了为什么后来我特别在意运行状态、当前提示词、已经到达哪个步骤和预计还要多久。这些信息不是为了让界面更热闹,而是为了让一次运行从黑盒变成可观察的过程。观察到哪里,才能判断停在哪;知道停在哪,才能决定是重试、回退,还是修正输入。
最容易走偏的错误捷径
最容易误判的捷径,是把所有已有能力都复制进一个“超级工作流”,再增加很多目录、说明和总开关。这样看起来集中,实际却会造成新的重复:执行技能有一份规则,管理技能又写一份近似规则,最后没人知道哪份是准的。
另一个捷径,是每出现一个小变化就创建一个全新的流程。短期看很清楚,长期会形成大量几乎一样的流程,真正的差异反而埋在名字里。用户看到的是一排可选项,却不知道应该选哪个,也不知道两个版本到底差在哪里。
我没有选择这两条路,是因为它们都绕开了最核心的判断:这次变化到底属于生命周期管理,还是属于领域执行?如果只是执行方式变了,应该升级执行技能并保留契约;如果分支条件、输入输出或验收规则变了,才需要让工作流产生新版本;如果只是展示方式变了,则不应扩大运行边界。
不抢前台也是同一个原则的延伸。管理层应该在后台准备、检查和留下证据,不能为了确认一个状态就打断正在使用的页面,更不能重复派发已经在执行的任务。否则,治理本来想减少不确定性,反而制造了新的干扰。
一套够轻的版本化方法
把这次思考压缩成可复用的方法,我会按下面的顺序做。先给成熟流程登记一个稳定身份,再记录当前版本和它负责的范围。接着把主流程画成可读的节点结构,把每个可选分支写清输入、输出、进入条件和失败边界。
然后单独登记依赖的执行能力,不把实现细节复制到工作流里。发生变化时,先判断变化落在哪一层,再决定是更新执行版本、增加工作流分支,还是只改展示说明。任何升级都保留变更理由,并写出旧版本是否继续可用。
在交付前,先做结构验收,再做行为验收。结构验收确认节点、依赖和连接;行为验收确认一次真实运行是否经过预期步骤、使用了正确输入,并得到可以解释的结果。最后把验收证据和版本绑定,而不是只留下一个“已完成”的状态。
退役也要有明确条件。旧版本不必因为新版本出现就立即删除,至少要等正在运行的任务结束,或者确认没有仍然依赖它的流程。需要回退时,回退的是版本和入口,不是临时凭记忆重做一遍。
这个方法的重点不在于工具长什么样,而在于每一次变化都能回答6个问题:改了什么,为什么改,影响谁,当前用哪版,怎样验收,何时退役。能回答这6个问题,轻量配置、节点图和运行记录就足够支撑早期管理;回答不了,再加系统也只是把模糊放大。
下次我会怎么做
下次再遇到一套成熟流程需要升级,我会先停在边界上,不急着新增一个总管,也不急着复制出一堆新流程。先看这次变化是执行能力、流程分支,还是展示方式发生了变化,再把它放回正确的层。
我会保留旧版本的入口和验收证据,给新版本写清适用条件,让两个版本在必要时并行一段时间。运行界面则优先呈现输入、当前步骤、状态和结果,让使用者知道自己正在看哪一次运行,而不是只看到一个可以点击的“开始”。
真正值得版本化的不是文件,而是一次变化背后的选择、边界和证据。
这样做以后,工作流就不只是把几个动作连起来的图。它会成为一份能够被阅读、被升级、被验证、也能够安全退役的工作合同。成熟并不意味着它永远不变,恰恰意味着它变化时不会把所有人重新推回记忆和猜测里。