1. 引言
在智能体(Agent)协作的浪潮里,主流方案大多围绕「云端编排 + 多服务通信」展开,动辄要启动 Redis、消息队列、独立进程,甚至拉起整套 Kubernetes。但对个人开发者来说,很多场景其实并不需要这么重:我只是想在本机同时跑几个各司其职的智能体,让它们协同改代码、做调研、写文档,互相传递上下文,最后汇总结果。
chaitanyagiri/munder-difflin 正是为这种「轻量本地多智能体」需求而生的一个实验性项目。它用 TypeScript 实现了一个 local multi-agent harness:所有智能体都运行在同一个进程里,通过进程内消息总线派发子任务、归并结果,免去了外部中间件的运维负担。
这篇文章就从它的核心场景和架构原理出发,拆解一下这类本地多智能体 harness 是怎么运转的。
2. 项目概览
先看几个关键标签:
- 项目名:munder-difflin(名字致敬了经典美剧《The Office》里的 Dunder Mifflin)
- 语言:TypeScript
- 定位:local multi-agent harness,即本机多智能体协作骨架
- 核心机制:进程内消息总线派发子 Agent 任务并归并结果
它并不是一个大而全的 Agent 框架,而更像一个「骨架」或「实验场」:把「如何组织多个智能体协作」这件事,用最小的进程模型实现出来,方便开发者在此基础上接入自己的模型、工具和任务编排逻辑。
3. 核心场景
3.1 本机协同改代码
这是最典型的用法:不依赖远程服务,在本地启动一个编排进程,下面挂着几个职责不同的智能体,例如:
- 调研 Agent:定位相关代码、阅读依赖与文档;
- 修改 Agent:根据调研结果生成代码补丁;
- 审查 Agent:检查补丁质量、潜在问题;
- 汇总 Agent:把上述产出合并成最终变更。
它们在同一个进程内通过消息总线交换上下文,最终形成一条「调研 → 修改 → 审查 → 归并」的流水线。
3.2 多视角调研
另一个常见场景是让多个智能体从不同角度研究同一个问题——比如一个看代码实现、一个看测试用例、一个看上游 issue——最后由协调者把分散的结论整合成一份可读的调研报告。
这些场景的共同点是:任务可以被拆分成多个相对独立的子任务,分配给不同角色,再归并输出。这正是 harness 模式最擅长的地方。
4. 架构原理
下面这张图直观展示了协调者、进程内消息总线与多个子 Agent 之间的任务派发与结果归并流程:
#mermaid-svg-KLsV4o5SislJJd7q{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KLsV4o5SislJJd7q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KLsV4o5SislJJd7q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KLsV4o5SislJJd7q .error-icon{fill:#552222;}#mermaid-svg-KLsV4o5SislJJd7q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KLsV4o5SislJJd7q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KLsV4o5SislJJd7q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KLsV4o5SislJJd7q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KLsV4o5SislJJd7q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KLsV4o5SislJJd7q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KLsV4o5SislJJd7q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KLsV4o5SislJJd7q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KLsV4o5SislJJd7q .marker.cross{stroke:#333333;}#mermaid-svg-KLsV4o5SislJJd7q svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KLsV4o5SislJJd7q p{margin:0;}#mermaid-svg-KLsV4o5SislJJd7q .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-KLsV4o5SislJJd7q .cluster-label text{fill:#333;}#mermaid-svg-KLsV4o5SislJJd7q .cluster-label span{color:#333;}#mermaid-svg-KLsV4o5SislJJd7q .cluster-label span p{background-color:transparent;}#mermaid-svg-KLsV4o5SislJJd7q .label text,#mermaid-svg-KLsV4o5SislJJd7q span{fill:#333;color:#333;}#mermaid-svg-KLsV4o5SislJJd7q .node rect,#mermaid-svg-KLsV4o5SislJJd7q .node circle,#mermaid-svg-KLsV4o5SislJJd7q .node ellipse,#mermaid-svg-KLsV4o5SislJJd7q .node polygon,#mermaid-svg-KLsV4o5SislJJd7q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KLsV4o5SislJJd7q .rough-node .label text,#mermaid-svg-KLsV4o5SislJJd7q .node .label text,#mermaid-svg-KLsV4o5SislJJd7q .image-shape .label,#mermaid-svg-KLsV4o5SislJJd7q .icon-shape .label{text-anchor:middle;}#mermaid-svg-KLsV4o5SislJJd7q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KLsV4o5SislJJd7q .rough-node .label,#mermaid-svg-KLsV4o5SislJJd7q .node .label,#mermaid-svg-KLsV4o5SislJJd7q .image-shape .label,#mermaid-svg-KLsV4o5SislJJd7q .icon-shape .label{text-align:center;}#mermaid-svg-KLsV4o5SislJJd7q .node.clickable{cursor:pointer;}#mermaid-svg-KLsV4o5SislJJd7q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KLsV4o5SislJJd7q .arrowheadPath{fill:#333333;}#mermaid-svg-KLsV4o5SislJJd7q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KLsV4o5SislJJd7q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KLsV4o5SislJJd7q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KLsV4o5SislJJd7q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KLsV4o5SislJJd7q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KLsV4o5SislJJd7q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KLsV4o5SislJJd7q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KLsV4o5SislJJd7q .cluster text{fill:#333;}#mermaid-svg-KLsV4o5SislJJd7q .cluster span{color:#333;}#mermaid-svg-KLsV4o5SislJJd7q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KLsV4o5SislJJd7q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KLsV4o5SislJJd7q rect.text{fill:none;stroke-width:0;}#mermaid-svg-KLsV4o5SislJJd7q .icon-shape,#mermaid-svg-KLsV4o5SislJJd7q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KLsV4o5SislJJd7q .icon-shape p,#mermaid-svg-KLsV4o5SislJJd7q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KLsV4o5SislJJd7q .icon-shape .label rect,#mermaid-svg-KLsV4o5SislJJd7q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KLsV4o5SislJJd7q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KLsV4o5SislJJd7q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KLsV4o5SislJJd7q :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
多个子 Agent(各司其职)
1. 拆解任务并 publish 子任务
2. 按消息类型路由 subscribe
3. 返回结果消息
4. 结果回流并 merge 归并
协调者 Coordinator拆解任务 / 派发消息 / 归并结果
进程内消息总线 Bus同一 Node.js 进程内传递消息
调研 Agent
修改 Agent
审查 Agent
汇总 Agent
munder-difflin 的架构可以用一句话概括:在一个 Node.js 进程里,用消息总线把多个子 Agent 串起来,由协调者统一派发与归并。
4.1 进程内消息总线
与传统多 Agent 系统依赖 Redis、Kafka 等外部消息中间件不同,这里的总线是进程内的。它的好处非常明显:
- 无需额外基础设施,开箱即用;
- 消息传递零网络开销,延迟极低;
- 调试简单,所有状态都可在同一个进程内观察。
代价则是可扩展性受限:它天然适合单机多智能体协作,而不适合需要跨机器、跨进程容灾的大规模分布式部署。对这个项目的定位来说,这是一个合理的取舍。
4.2 子 Agent 派发
协调者拿到一个整体任务后,会将其拆解为若干子任务,并以消息的形式通过总线发布出去。每个子 Agent 都监听自己关心的消息类型,收到任务后执行,并将结果回投到总线上。
这种「发布/订阅 + 请求/响应」混合的消息模型,让智能体之间保持松耦合:新增一个角色只需要挂载一个新的监听者,不必改动其他 Agent 的代码。
4.3 结果归并
各个子 Agent 的产出最终会回流到协调者,由归并逻辑整合成最终答复。归并的核心在于:
- 收集所有子任务的输出;
- 按任务定义的结构进行组合或摘要;
- 处理失败、超时与部分成功的情况。
整个过程在一个进程的生命周期内完成,状态管理也因此简单得多。
5. 实现要点
虽然不同版本的实现细节会有差异,但这类本地 multi-agent harness 通常会围绕下面几个核心模块展开:
// 示意:消息总线的最小接口
type Message<T = unknown> = {
type: string;
payload: T;
replyTo?: string;
};
interface Bus {
publish(msg: Message): void;
subscribe(type: string, handler: (msg: Message) => Promise<void> | void): void;
}
// 示意:子 Agent 的基本形态
interface SubAgent {
name: string;
handle(msg: Message): Promise<Message | void>;
}
// 示意:协调者负责拆解任务、派发并归并结果
class Coordinator {
constructor(
private bus: Bus,
private agents: SubAgent[],
) {}
async run(task: string): Promise<string> {
// 1. 拆解任务
const subtasks = this.decompose(task);
// 2. 派发给对应子 Agent
const results = await Promise.all(
subtasks.map((st) => this.dispatch(st)),
);
// 3. 归并结果
return this.merge(results);
}
private decompose(task: string) {
// 根据任务类型拆分出子任务
return [{ agent: 'researcher', input: task }];
}
private async dispatch(subtask: unknown) {
// 通过总线把消息路由给目标 Agent
return undefined as never;
}
private merge(results: unknown[]) {
// 汇总各 Agent 输出
return results.join('\\n');
}
}
注意:以上是基于通用模式的重构示意,用于说明这类 harness 的骨架,不代表项目的逐行实现。
从这段骨架可以看出,真正需要花心思的地方在于三点:任务怎么拆、结果怎么归并、失败怎么处理。把这三件事抽象干净,剩下的总线通信反而是最机械的部分。
6. 与主流框架的差异
| 部署形态 | 多进程 / 多服务 | 单进程 |
| 基础设施 | 依赖消息队列、存储等 | 无外部依赖 |
| 通信开销 | 有网络与序列化开销 | 进程内直接传递 |
| 扩展性 | 可水平扩展 | 受限于单机资源 |
| 适用场景 | 生产级大规模协作 | 本机实验、研发辅助、快速验证 |
可以看到,它解决的不是「大规模生产编排」的问题,而是用一个进程就能跑起来的多智能体协作原型。对于想快速验证「多 Agent 协同改代码」想法的开发者来说,这种轻量形态反而更友好。
7. 总结
munder-difflin 的价值在于提供了一种极简的本地多 Agent 协作范式:以 TypeScript 单进程为载体,用进程内消息总线完成子任务的派发与结果归并,让「多个各司其职的智能体协同工作」这件事摆脱了对重型基础设施的依赖。
如果你的需求是本机跑几个协同改代码、做调研的智能体,又不想为服务编排付出额外成本,那么这类 local multi-agent harness 是一个值得关注的思路。把它拿来做实验、做二次开发,都能帮你更快地理解多智能体协作里的任务拆解与结果汇聚这两个核心命题。


