36 黑板
以黑板作为事实空间,让彼此独立的代理按规则贡献和消费知识,形成松耦合协作。
学习目标
- 能画出事实、黑板、代理、新事实与收敛条件之间的证据链,并说明每条边传递什么
- 能修改一个代理协作程序,让规则、版本和拒绝条件留在黑板而不是藏在直接调用里
- 能注入代理失效,指出第一处可观察差异,并从原始输入重放到可验收的收敛状态
为什么黑板不能只画成一条消息队列
想象一间只有一块白板的控制室。气象员、路线员和安全员彼此不知道对方的内部实现,却都能读到当前事实,也能在满足规则时把自己的判断写回白板。指挥者不是挨个调用三个人,而是等待白板上的证据满足“可以出发”的条件。
这和一条固定调用链不同:调用方不必知道下一个代理是谁,代理也不必保存其他代理的内部状态。真正需要固定的是黑板上事实的身份、版本、来源和允许的下一步。没有这些边界,所谓“松耦合”只会变成一块没人负责的共享缓存。
本页把 36 黑板 转成一个可重放的协作实验。正常路径验证三个彼此独立的代理能通过黑板完成一次判断;边界路径验证证据不全时不会提前收敛;故障路径只移除一个代理,观察首差是否出现在代理触发处。先预测:如果代理失效,最后仍出现“完成”,这个结果为什么不能被接受?
本章验收合同
本单元固定输入“航班改签请求”,只改变代理是否可用。黑板记录五类字段:事实身份、当前版本、贡献来源、触发条件和收敛条件。每个代理只读自己需要的事实,并写入带来源的新事实;任何代理都不能直接改写另一个代理的结果。
验收不依赖一个漂亮的总分,而依赖可重放的证据:原始输入、黑板快照、代理规则、首个差异、拒绝原因、恢复动作和最终收敛条件。若只保存最后的“已完成”,复核者就无法知道它是由完整证据产生,还是由一个旧响应伪造。
blackboard_run:
input_id: change-204
board_version: 3
contributors: [route-agent, policy-agent, safety-agent]
invariant: every_claim_has_source_and_version
fault: disable_policy-agent
first_changed_node: agent-trigger
recovery: restore-agent-and-replay下面的第一张图先固定概念边界;第二张图把同一条链变成可播放、可单步、可拖动和可重置的实验。两张图使用不同结构:前者展示黑板如何连接独立代理,后者展示一条运行记录如何按时间推进。
黑板上的五种责任
<Term def="可以被唯一识别、带有当前状态和适用边界的一条输入事实。">事实</Term> 不是一段随手写在日志里的文字,而是后续代理可以引用的稳定对象。它至少需要身份、版本和来源;没有身份就无法判断重复,没有版本就无法判断过期。
<Term def="保存共享事实、版本和贡献记录,并依据规则触发下一步工作的事实空间。">黑板</Term> 负责让知识可见,却不负责替任何代理做决定。它应该能回答“现在有哪些事实、谁写入、什么条件还没满足”,也应该能拒绝没有来源或版本落后的写入。
<Term def="只通过黑板读取和贡献知识、彼此不依赖内部实现的独立处理者。">代理</Term> 不等于一个改了名字的函数调用。代理的输入来自可观察事实,输出必须带来源和状态变化;调用方无需持有代理内部的可变对象。
<Term def="规定代理何时可以读取、消费或贡献某类事实的可检查条件。">触发规则</Term> 把“有新消息就处理”改成可验证的条件。例如,路线代理只在事实版本更新且地点完整时运行;安全代理发现风险时可以拒绝收敛,而不是让后续代理猜测它的意见。
<Term def="规定哪些事实齐备、哪些拒绝条件已排除后才允许宣布完成的验收条件。">收敛条件</Term> 是黑板系统的出口合同。它不等于“最后一个代理回复了”,而是每个必需事实都拥有来源、版本和通过理由,并且没有未处理的拒绝记录。
三个代理如何通过黑板完成一次判断
案例是一次改签请求。路线代理根据航段和时间贡献可行路线,政策代理根据票规贡献费用与资格,安全代理检查身份与风险。三者不直接互相调用;它们只在黑板上发现自己关心的事实,并写回带版本的新事实。协调器只负责检查收敛条件。
type BoardFact = {
id: string;
version: number;
source: "input" | "route-agent" | "policy-agent" | "safety-agent";
kind: "request" | "route" | "policy" | "safety";
status: "open" | "accepted" | "rejected";
};
type Blackboard = { facts: BoardFact[]; version: number };
function canConverge(board: Blackboard) {
const required = ["route", "policy", "safety"];
return required.every((kind) =>
board.facts.some((fact) => fact.kind === kind && fact.status === "accepted"),
);
}这段代码刻意没有让代理共享一个可变结果对象。实际写入时还要检查事实身份、版本和来源;版本冲突、重复贡献、未知来源或拒绝状态都必须成为可见记录。canConverge 只是出口检查,不应把缺失的事实补成“成功”。
提示60:使用黑板来协调工作流
提示60的重点不是“把所有东西放进一个全局变量”,而是让彼此独立的工作者通过共同事实协作。黑板需要一个清晰的写入合同:谁可以贡献哪类事实、依据什么输入、写入后由谁消费、失败后如何重放。
当黑板只保存最终值时,代理之间的隐式耦合会重新出现。正确的证据包应同时保存原始请求、黑板版本、每次贡献的来源和触发规则。这样,新增一个代理可以订阅已有事实,而不必修改所有旧代理;删除一个代理也会在它负责的事实处明确停止。
常见误区与回退
三步复核:从事实空间到收敛条件
先预测:当政策代理不可用时,黑板上哪一类事实应该仍然存在?哪一个节点必须拒绝继续?再逐步推进图示;如果答案和图示不一致,保存分叉处而不是修改最后的状态。
1. 冻结输入与黑板合同
写下请求身份、输入版本、三类必需贡献和收敛条件。事实先进入黑板,不能直接塞进某个代理的私有变量。
可重置实验:代理失效时不伪造收敛
动手试:点击“注入单故障”,再把进度推进到代理触发处。正确结果不是红色警告本身,而是三件事同时成立:黑板已有事实没有被擦掉,缺失代理的贡献没有被补造,收敛条件拒绝继续。点击“重置动画与故障”后,实验应回到同一个输入和第一步。
黑板协作实验台
通过播放、单步和拖动进度,观察知识怎样被独立代理消费、贡献并收敛。
第 1 / 5 步 · 事实:先冻结输入身份、边界和拒绝条件
只有黑板事实、代理规则和收敛条件都可核对,结果才可被接受。
每次实验至少记录 input_id、board_version、当前节点、已存在事实、缺失贡献、首个差异和恢复动作。若点击故障后仍可直接进入收敛,说明出口条件只看到了最终响应,没有真正核对黑板。
如何判断黑板边界是否合适
| 场景 | 黑板应保存 | 必须验收 | 不要做什么 |
|---|---|---|---|
| 多个代理逐步补全同一判断 | 事实、来源、版本 | 每类事实的唯一来源 | 允许任意代理覆盖 |
| 代理处理速度不同 | 触发条件、过期时间、拒绝记录 | 慢代理不会让旧事实伪装新事实 | 把等待当成成功 |
| 需要增加或替换代理 | 稳定的事实契约 | 新代理只依赖声明过的输入 | 修改所有旧代理的调用顺序 |
| 某个代理暂时失效 | 缺失事实和首差 | 不提前收敛,可从原始输入重放 | 复制上一轮贡献 |
黑板的大小由事实契约决定。把整个数据库都当成黑板会扩大权限和复核范围;把黑板缩成一条日志又会把状态藏进代理内部。合适的边界应让独立复核者只看输入、事实和规则,就能判断下一步是否有资格发生。
跨团队和现代系统的迁移
在数据处理、CI 流水线或 AI 辅助开发中,黑板可以是带版本的事件表、工件目录或任务状态空间。形式变了,核心合同不变:事实有身份,贡献有来源,规则可检查,拒绝可观察,收敛可重放。
自动化工具可以替代理机械地填充工件,却不能替人决定“哪些事实已经足够”。把工具输出当成无来源的最终答案,会把黑板重新变成黑箱。迁移时要分别记录工具拒绝的输入、运行期拒绝的边界和仍需人工判断的语义问题。
本章回顾
- 黑板保存共享事实和贡献记录,不是没有规则的全局缓存。
- 独立代理只通过声明过的事实协作,写入必须带来源和版本。
- 触发规则把“有消息就做事”变成可检查的工作条件。
- 收敛条件逐项检查必需事实,不能由最后一条响应代替。
- 故障时停在首差,恢复后从原始输入重放,而不是手工补成功。
可验证练习
练习
问题 1:找出首个差异。 路线代理和安全代理都已向黑板写入版本 4 的接受事实,政策代理暂时不可用,但协调器仍返回“改签完成”。指出第一个不能继续的节点,并写出两项必须保存的证据。
问题 2:修改 Demo 代码。 给下面的写入函数补上版本和来源校验,使过期贡献被拒绝,并说明拒绝后如何恢复。要求拒绝结果也能被复核。
function writeFact(board: Blackboard, fact: BoardFact) {
board.facts.push(fact);
return { ok: true };
}问题 3:做边界选型。 三个代理要共同决定一项发布是否安全,其中一个代理的结果可能晚到且会重复提交。你会让它们共享一个可变对象,还是通过黑板传递带版本的事实?写出一个拒绝条件。
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 事实
一条有身份、版本和来源的输入或判断,别人可以据此决定下一步。
- 黑板
保存共享事实和贡献记录的空间;它让代理能看见同一份证据。
- 代理
只通过黑板读取和写入知识的独立处理者,不直接修改别人的内部状态。
- 触发规则
规定什么事实齐备后,哪个代理才可以开始工作的条件。
- 收敛条件
规定哪些必需事实都已通过检查后,系统才可以宣布完成的出口合同。
来源与改写范围
本文为改编重写:目录、版本与提示坐标用于限定范围;正文、代码、双图示、交互实验和练习均为本课程独立设计,不复制原书正文、插图或答案。原书与目录链接仅用于出处核对;本页内容按 CC BY-NC 4.0 的改编重写要求标明来源。