让多个 agent 会话互发消息、声明与决策,避免重复冲突
34.3/100
综合分
7
GitHub 星标
101
npm 周下载
—/5
用户评分
数据截至 2026/9/12 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-agent-messagingnpm 源 · dsh-agent-messaging 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · chat
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 52 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-agent-messaging @ 0.11.0
✓Node 引擎要求 ^22.19.0 || >=24 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 23:05:34
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 46 处,展示前 30 条)
- 删除本地文件lib/index.js:1006
await rm(target, { force: true }); - 写入本地文件lib/index.js:1009
await mkdir(this.#options.dir, { recursive: true }); - 写入本地文件lib/index.js:1017
await writeFile(staging, JSON.stringify(file), "utf8"); - 写入本地文件lib/index.js:1018
await rename(staging, target); - 读取本地文件lib/index.js:1027
names = await readdir(this.#options.dir); - 删除本地文件lib/index.js:1053
await rm(this.#pathFor(sessionId), { force: true }); - 读取本地文件lib/index.js:1060
const parsed = JSON.parse(await readFile(path, "utf8")); - 写入本地文件lib/index.js:1257
await mkdir(this.#dir, { recursive: true }); - 写入本地文件lib/index.js:1260
await writeFile(staging, JSON.stringify({ - 写入本地文件lib/index.js:1264
await rename(staging, this.#path); - 读取本地文件lib/index.js:1277
const parsed = JSON.parse(await readFile(this.#path, "utf8")); - 读取本地文件lib/index.js:1640
await stat(join(directory, marker)); - 读取本地文件lib/index.js:1657
text = (await readFile(join(directory, name$1), "utf8")).slice(0, HEADLINE_BYTES); - 读取本地文件lib/index.js:1749
entries = await readdir(this.#dir); - 删除本地文件lib/index.js:1770
await rm(this.recordPath, { force: true }); - 删除本地文件lib/index.js:1784
await rm(path, { force: true }); - 删除本地文件lib/index.js:1852
await rm(path, { force: true }); - 删除本地文件lib/index.js:1855
await rm(dir, { - 发起网络请求lib/index.js:2012
const response = await fetch(endpoint.url, { - 读取本地文件scripts/doctor.mjs:60
const names = await readdir(join(stateRoot, kind)) - 写入本地文件scripts/doctor.mjs:79
await mkdir(stateRoot, { recursive: true }) - 写入本地文件scripts/doctor.mjs:81
await writeFile(probe, 'ok') - 删除本地文件scripts/doctor.mjs:82
await rm(probe) - 读取本地文件scripts/doctor.mjs:99
names = (await readdir(dir)).filter((name) => name.endsWith('.json')) - 读取本地文件scripts/doctor.mjs:114
record = JSON.parse(await readFile(join(dir, name), 'utf8')) - 读取本地文件scripts/doctor.mjs:159
return (await stat(path)).mode & 0o777 - 读取本地文件scripts/doctor.mjs:187
await access(url, constants.R_OK) - 读取本地文件scripts/doctor.mjs:192
await access(clientUrl, constants.R_OK) - 读取本地文件scripts/report.mjs:44
names = await readdir(dir) - 读取本地文件scripts/report.mjs:51
const parsed = JSON.parse(await readFile(join(dir, name), 'utf8'))
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-attachment@deepseek-ai/dsh-brand@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-ui-chat@deepseek-ai/dsh-client-ui-conversation@deepseek-ai/dsh-client-ui-renderer@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-code-runtime@deepseek-ai/dsh-invariants@deepseek-ai/dsh-llm给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
界面预览
6 张 · 点击查看大图功能亮点
- 跨会话互发消息
- 声明与决策账本
- 避免重复与冲突
跨会话校验、声明与决策账本,给 DeepSeek Harness 用——让两个智能体会话不重复、不矛盾、不互相死锁。
最近更新
在 GitHub 查看全部 →v0.13.12026-09-12
浏览器端原本依赖一个服务 harness,0.1.5 已把它移除。@deepseek-ai/dsh-client-runtime 及其 conversationEvents 注册表都没了,事件注册现在放在 @deepseek-ai/dsh-client-ui-conversation 的 uiConversation.events 上。在 0.1.5 harness 上,客户端 fiber 会等待这个不存在的服务,dsh web 直接启动失败:
dsh-agent-messaging: pending (waiting for service: conversationEvents)
这次把浏览器端移植到当前客户端 API。面向模型的部分没变,宿主端也没动。
· ctx.conversationEvents.register(...) → ctx.uiConversation.events.register(...);inject 名称改为 uiConversation。
· ConversationNodeDefinition 及相关类型从 dsh-client-runtime/client 移到 dsh-client-ui-conversation/client。
· ChatNode → ChatConversationViewNode;ChatNodeDataMap 的合并目标移到 dsh-client-ui-chat/client。
· ctx.slots 现在来自 dsh-client-ui-renderer/client。
· 客户端依赖跟随 ^0.1.5-rc.2。
需要 harness 0.1.5-rc.2(npm 的 next 线)。在 0.1.0-rc.6 上,浏览器端依赖的服务早已不存在,所以要求当前版本不会带来任何回退。
已在 harness 0.1.5-rc.2 上验证:npm run typecheck(宿主和浏览器)、397 个测试、tsdown 构建,以及无头 dsh web 启动后成功挂载 UI,卡片的 style 标签和事件注册均生效。
v0.13.02026-08-15
这个项目提出的主张,第一次拿对照组来检验。五个场景,两组,各跑一次,用 DeepSeek-V4-Flash。
| 场景 | 基线 | 插件 |
|---|---|---|
| stale-contract | 失败 · 2t | 通过 · 4t |
| collision | n/r · 2t | n/r · 3t |
| false-belief | 失败 · 2t | 失败 · 5t |
| mutual-wait | n/r · 2t | n/r · 8t |
| stale-decision | 失败 · 2t | 通过 · 2t |
| 通过数 | 0/3 | 2/3 |
| 计分场景轮数 | 6 | 11 |
3 个里 0 个变成 3 个里 2 个,轮数差不多翻倍。只跑一次——算个轶事,外面套了张表,不是结果。
三个发现来自基准测试,不是我:
有两个场景在这个环境里复现不出失败,所以不计入分数,而不是算作通过。丢失更新被 DSH 基于补丁的编辑器从结构上挡住了;互相等待不会发生,因为这些模型会去做自己能做的那部分,而不是阻塞。两个都标 n/r——作者悄悄把白送的分记进账的基准,测的其实是自己套件的长度。
验证能改变信念,却改不了行动。在 false-belief 里,对端纠正了错误前提,客户端也记下了取代它的决定——然后还是按另一套理由把字段删了。协调生效了;结果照样失败。
过期的对端会招来责任分散。停掉的会话现在会在 peer_list 里带上自己的年龄,工具也直说:没在运行的会话,就没在处理任何事。
引用任何数字之前先读 bench/README.md,包括我给的这些。397 个测试。
v0.12.02026-08-15
五个场景,一对未协调的智能体会话会做错,评分看仓库最终是否正确,并按模型轮次计价。
它评的是世界,不是插件。裁判只读磁盘上的文件,别的什么都不看——发消息、认领任务、给出结论都不加分。选哪条路线由配置档决定,所以这个插件、一个竞品、以及完全不协调,三者用同一套标准衡量。每个场景都配一个会失败的对照,否则就是在测空气。
第一个结果无效,却照样发布了。在插件那条路线里,客户端会话读了台账,找到了需求——协调生效了——然后判定修复该交给 peer_list 里看到的一个会话,而那是上一轮跑剩的尸体。实际测到的是一个没收拾干净的工作区。它留在 bench/results/ 里,附上了解释,隔离现在是明示的前提条件,底下那个发现——过期的同伴会诱发责任扩散——已列入路线图,作为插件要解决的问题。
引用其中任何数字之前先读 bench/README.md,包括我引的。392 个测试。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-agent-messaging
为 DeepSeek Harness 提供跨会话验证、声明和决策账本——让两个 agent 会话不会重复、矛盾或互相死锁。
你自己启动的两个会话——无论是在 Web UI 中、在无头运行中、在独立 worktree 中,还是在独立的 dsh 进程中——彼此无法传达任何信息。当其中一个发现另一个即将踩中的破坏性变更时,你就是传输通道:你在一个终端里读到它,再在另一个终端里重新输入。
这个插件为它们提供了地址和邮箱。一个会话可以指定另一个会话,并将消息投递到它的收件箱;harness 会像调度任何其他面向模型的输入一样调度它。
session "payments-api" session "checkout-client"
│ │
│ peer_send to: checkout-client │
│ mode: steer │
├─────────────────────────────────────────────►│ 在下一步中断 │ "tenant_id is now required on ChargeRequest │ │ — your call site will break" │
展开完整文档(README 剩余 1 节)
dsh-agent-messaging
为 DeepSeek Harness 提供跨会话验证、声明和决策账本——让两个 agent 会话不会重复、矛盾或互相死锁。
你自己启动的两个会话——无论是在 Web UI 中、在无头运行中、在独立 worktree 中,还是在独立的 dsh 进程中——彼此无法传达任何信息。当其中一个发现另一个即将踩中的破坏性变更时,你就是传输通道:你在一个终端里读到它,再在另一个终端里重新输入。
这个插件为它们提供了地址和邮箱。一个会话可以指定另一个会话,并将消息投递到它的收件箱;harness 会像调度任何其他面向模型的输入一样调度它。
session "payments-api" session "checkout-client"
│ │
│ peer_send to: checkout-client │
│ mode: steer │
├─────────────────────────────────────────────►│ 在下一步中断 │ "tenant_id is now required on ChargeRequest │ │ — your call site will break" │
到达的消息在对话记录中会显示为独立的卡片,因此读者一眼就能看出是另一个 agent 在说话——而不是人类,也不是 harness 注入的上下文:
一条 peer 消息渲染为独立卡片:发送方 payments-api,中断了本步骤,仅供参考
同一张卡片的深色模式
它标明了发送方、投递的代价(interrupted this step、next turn 或 delivered quietly)、本会话被允许对此做什么,以及消息本身而非其外围框架。强调色由发送方的会话 id 派生,因此同一个 peer 即使标题改变也会保持同一种颜色。
一次真实运行,端到端
以下内容全部来自一次实时运行:在一个 dsh web 主机中运行四个使用真实模型的会话,仓库中每个会话拥有不同的目录。没有模拟——这些是产生下文数字的那次运行的截图。
1 · 谁是谁。 每个会话发布一张能力卡:一个别名、它拥有什么,以及它不负责什么。
peer_card alias: "payments-api"
role: "Owns api/ and the charge contract. I do NOT own client code."
owns: [{ resource: "api" }]
groups: ["backend"]
2 · 一次冲突,被拒绝。 payments-api 声明拥有 api/charges.ts。片刻之后,checkout 会话试图声明拥有 api/——然后被告知谁拥有它下面的什么,以及为什么。
checkout 会话对 api/ 的声明被拒绝,并指明 payments-api 是持有者
有趣的部分是最后一段:在未被要求的情况下,它决定不并行编辑,而是先协调。这是 MAST 分类法 中最大的单一失败模式——步骤重复,占观察到的多智能体失败的 15.7%——没有发生。
3 · 一次破坏性变更,在任务中途交付。 payments-api 真正编辑了文件,然后通知那个调用点刚被它破坏的对等方。接收会话并不轻信这一声明:它读取两个文件,确认变更属实,然后才采取行动——针对一个它拥有的文件。
接收会话收到卡片,回复,读取两个文件,并在编辑前声明拥有自己的文件
4 · 一个错误信念,在发布前被捕获。 checkout 会话即将删除 currency 字段,因为它认为 API 会拒绝非 USD。它请拥有该文件的对等方核实——然后被驳倒。
payments-api 在读取文件后驳倒了该说法,并拒绝发送确认
一张截图中有两件事。驳斥才是重点:自我验证已知会失败,而没有编写代码的对等方必须去查看。第二件事是模型拒绝发送礼节性回复——“回一个‘已注意到’只会浪费他们一个回合”——这正是下文描述的修复在发挥作用。
5 · 一次相互等待,被显现出来。 docs 会话声明自己被 checkout 阻塞;checkout 已经被 docs 阻塞。循环在闭合的那一刻就被报告。
docs 会话声明自己被阻塞,并被告知它处于死锁循环中
没有这一点,死锁是无声的:每个参与者看起来只是 idle,没有人完成,也没有任何东西报告它。
6 · 一个从未被告知却读取了历史的新来者。 第五个会话全新启动,只被告知要添加货币验证,它找到了已记录的决策,对照当前文件进行检查,然后拒绝——提出取代是唯一正确的途径。
一个新会话找到已记录的决策并拒绝重新打开它
代价是什么
同一场景,在相同模型上运行两次——两次运行之间只改了 peer_send 描述中的一句话:
| | 之前 | 之后 |
| 已送达消息 | 20 | 7 |
| 被循环控制丢弃 | 2 | 0 |
| 避免的冲突 | 1 | 1 |
| 捕获的虚假声明 | 1 | 1 |
| 检测到的死锁 | 1 | 1 |
第一次运行的记录说明了原因:工作完成后,会话仍在继续——“已记录,谢谢。” → “随时——祝你好运。” → “谢谢,会随时告知你。” → “完美——我在这里。”——直到循环控制丢弃了一条重复消息,其中一个会话用自己的话说,交流已经结束了。
自主对等方很有礼貌,而礼貌每次都要花费一个回合。解决办法是一句话,告诉它们不要这样:
每条消息都会消耗接收方一个回合,所以只发送会改变其行为的内容。不要发送确认、感谢、告别或“已记录”——一个无事可做的对等方最好让它继续工作。
流量减少 65%,捕获量完全相同。 这就是记账的用途:它让提示词层面的回归变得可见,然后表明修复有效。用 npx dsh-agent-messaging report 在你自己的工作中运行它。
它不是什么
| 如果你想要 | 使用 |
| 将另一个会话的历史记录拉取到你的下一条消息中 | dsh-session-reference(@label) |
| 一个生成并监督工作者的协调器 | subagent 子系统 |
| 在别处继续一个对话 | 恢复该会话 |
| 现在告诉另一个独立会话某件事 | 本插件 |
消息是文本。绝不是对话历史,绝不是文件。
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add dsh-agent-messaging
重启该配置文件,然后从会话内部或外部检查安装:
npx dsh-agent-messaging doctor
OK node v24.13.1
OK build host and browser bundles present
OK state-root /Users/you/.dsh/agent-messaging (writable)
OK presence 2 live hosts, 0 stale records
OK socket-permissions owner-only (0600)
OK accounting recording; run npx dsh-agent-messaging report to see what this cost and caught
任何会阻止消息传递正常工作的问题都会使其以非零状态退出,并且每一行报告问题的内容也会说明该怎么做——因此,一个怀疑自己的消息传递功能已损坏的会话可以运行此命令并读取答案。
无需其他配置。 会话从启动那一刻起就可寻址且信息丰富:对等方能看到它在哪个目录中工作,以及人类在 AGENTS.md 或 README.md 中对该目录写了什么。peer_card 将其从推断升级为声明;它不是前提条件。
记录卡片需要 Web UI。其他一切都可以无头运行,并且没有浏览器时,半条消息会渲染为 harness 的普通上下文行。
改为从 git 安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:happyren/dsh-agent-messaging
dsh plugin 会调用 pnpm,而 pnpm 会阻止来自 git 依赖的构建脚本,直到你允许它们。第一次 add 会失败并打印出包键;将其添加到该 profile 的 pnpm-workspace.yaml 中:
yaml
allowBuilds:
dsh-agent-messaging: true
然后重新运行 add。固定一个提交(github:happyren/dsh-agent-messaging#),这样之后的推送就无法改变你机器上运行的内容。
工具
默认注册九个工具。这对模型的注意力来说是很大的竞争,因此部署只为实际使用的东西付出代价: yaml
- id: agent-messaging
config:
capabilities:
claims: true # peer_claim
verification: false # peer_verify, peer_verify_reply
identity: false # peer_card, peer_status
decisions: false # peer_decide, peer_decisions
这样就剩下三个:peer_list、peer_send、peer_claim。寻址和投递始终会被注册——没有它们,其他一切都没有意义。所有内容默认开启,因此升级永远不会悄悄移除某个工作流所依赖的工具。
peer_inbox 仅在 inbound: hold 下注册,因为否则被保留的消息就不存在,而一个总是读取空列表的工具纯属额外开销。
peer_list
此会话可以寻址的会话——名称、状态、标题、目录。只有身份信息;绝不包含其内容。
payments-api [running] "Add tenant_id to charges" — /repo/test-project
"payments-api" — Owns api/ and the charge contract. I do NOT own client code. · owns api · groups: #backend working on: api/charges.ts (adding a required tenant_id to ChargeRequest)
checkout-client [idle] "Wire up checkout submit" — /repo/test-project
task: blocked on docs-writer: waiting on billing wording before updating checkout
"checkout-client" — Owns client/ and the checkout flow. · owns client · groups: #backend
ready-57a1 [not running] "ready." — /repo/test-project
发布了卡片的会话会以其别名列出——上面最后一行就是没有发布卡片的会话,它是从一个首次回复恰好是“ready.”的会话折叠而来的,这正是别名值得发布的原因。名称会进行冲突消歧,因此你在一次列表中读到的地址在下一次仍然可以解析。等待被存储为会话 id,因为这是唯一能够遍历死锁循环的形式,但它显示为你用来打破它的地址。
peer_send
投递一条消息。发送者的身份来自执行代理,因此模型无法发送一条声称自己是另一个会话的消息。
| mode | 到达方式 | 用途 |
| steer | 在接收者的下一个步骤边界到达,打断它 | 使其当前工作变得错误的事情 |
| followup (默认) | 作为其后续的独立回合 | 普通的交接 |
| context | 折叠进它接下来所做的任何事情中,而不唤醒它 | 它应该知道但无需采取行动的背景信息 |
这些映射到 Agent.steer()、Agent.followup() 和 Agent.inject() —— 即 harness 已经拥有的收件箱边界。选择是发送者的职责,因为只有发送者知道这条消息是否会使正在进行的工作失效。
一个未运行的会话仍然接受消息:它们会被暂存,并在下次启动时投递,受配置的时效和深度限制约束。
回复通过 reply_to 进行关联。该工具会告知发送者不要发送确认消息——实测原因见上文。
群组。 使用 #backend 可一次性触达整个集合。成员关系在每个会话的 peer_card 上声明,而形态是配置中的一项运维决策——因为更密集并不自动更好,而且每多一个收件人都要消耗一个回合: yaml
- id: agent-messaging
config:
groups:
backend: { topology: star, lead: payments-api }
maxFanout: 8
mesh 触达所有人;star 将成员的消息仅路由给 lead,并让 lead 进行广播——一条消息进入只消耗一个回合,而不是 N 个。每个收件人都是一次普通的发送,因此入站策略、循环控制和计费按收件人分别适用:群组地址对发送者来说是一种便利,绝不是绕过接收者的途径。
将 lead 配置为针对会话的别名(peer_card alias: "payments-api"),而不是其显示名称——显示名称是从会话标题折叠而来的,会发生变化。
peer_card
声明此会话的用途及其所拥有的内容,以便对等方正确路由工作,而不是从折叠后的标题中猜测。
这是一项升级,而非前提条件。 一个从未调用它的会话仍会被列出,并附带可从工作区读取到的信息——它工作的目录,以及该目录的 AGENTS.md 或 README.md 的标题——并标记为 inferred from the workspace, not declared,以免有人将推断误认为声明。模型无法可靠地完成设置,而一个在模型发起工具调用之前什么都不显示的列表,通常是一个毫无意义的列表。
peer_card alias: "payments-api"
role: "Owns api/ and the charge contract. I do NOT own client code."
owns: [{ resource: "api" }, { resource: "charge validation rules", scope: "topic" }]
skills: ["payments-api", "validation-rules"]
groups: ["backend"]
alias 是一个稳定的地址,而非装饰。显示名称是从会话标题折叠而来的,因此会发生变化——而且当标题很短时,读起来像是个意外(ready-57a1)。别名是选定的,并且保持不变。对等方可以使用的每个地址——peer_send、peer_verify、群组 lead、blocked_on——都会优先解析别名,而不是派生名称,并且发布过别名的会话在对等方读取的每条记录中都会被以该别名指代:被拒绝的声明、决策、等待,以及已投递消息上的卡片。
这针对的是 FM-1.2 不服从角色规范 和 FM-2.3 任务脱轨(7.4%);角色规范是 MAST 测量过的仅有的两项干预措施之一直接地,为 +9.4%。仿照 A2A Agent Cards 设计,因此同一份声明日后可以服务于跨供应商发现。
这里的归属是长期责任,而非预留——它从不冲突,也不预留任何东西。peer_claim 是短暂的“我现在正在编辑这个”信号。说明你不拥有什么,和说明你拥有什么一样有用,因为它能阻止同伴把不属于你的工作发给你。
peer_claim
宣告你正在处理什么,并查明是否已有同伴在做这件事。
peer_claim resource: "api" intent: "adding tenant support to the charge call"
→ refused: "api" overlaps a claim held by another session.
payments-api holds "api/charges.ts" — adding a required tenant_id to
ChargeRequest (expires in ~30 min)
Message the holder with peer_send instead of working in parallel.
这针对的是 MAST 分类法中最大的单一失败模式:步骤重复,占观察到的多智能体失败的 15.7%——其在编码中的具体实例就是两个会话编辑同一个文件,或者重新推导兄弟会话已经知道的东西。
路径声明会嵌套,因此持有 client 就覆盖 client/checkout.ts,而同级名称永远不会冲突(src/app 不包含 src/apple)。主题不会嵌套。声明会自行过期,并在持有会话结束时被丢弃。
声明是建议性的,不是锁。 插件无法阻止另一个进程写入文件,而一个无法强制执行的锁比一个诚实的提示更糟——它会诱使调用方跳过他们本来会做的检查。已声明的资源会出现在 peer_list 的 working_on 下。
peer_verify 和 peer_verify_reply
请一个处于不同情境的同伴检查你即将据以行动的一项声明。
peer_verify to: "payments-api"
claim: "createCharge rejects any currency other than usd"
evidence: [{ locator: "api/charges.ts" }]
→ REFUTED — createCharge only validates amount_cents and tenant_id; currency is never checked, so non-USD currencies are accepted.
同伴被告知要检查,而不是同意——“回答之前先去看;不要凭信任接受该声明”——并以带类型的裁决回复:confirmed、refuted、inconclusive 或 declined,外加它实际检查了什么。
这针对的是 MAST 的任务验证类别(占失败的 24.5%),并且是测得增益最大的干预措施(+15.6%)。它属于消息传递插件,而不是智能体自己的循环,因为自我验证已知会失败——模型在很大程度上无法检查自己的推理。同伴是一个在关键意义上不同的验证者:它不是该产物的生产者,因此它必须亲自去看。
refuted 裁决会以 steer 的形式返回,因为提问者很可能此刻正依据该声明行动,而排队的回合会来得太晚。
peer_status
说明你的工作正在做什么——working、blocked、done、abandoned——并查明你是否刚刚陷入死锁。
peer_status phase: "blocked" blocked_on: "checkout-client"
summary: "waiting on the final checkout field list"
→ published: blocked
DEADLOCK — you are in a mutual wait: docs-writer → checkout-client → docs-writer Nobody in this cycle will proceed on their own. Break it: message one of them with peer_send, do the part you can without waiting, or ask your user to decide.
代理注册表已经报告了 idle/running,但那描述的是一个驱动器,而不是一个任务。一个会话在完成时和在等待对等方时都是 idle——从外部无法区分,而这个差异恰恰是对等方决定是否应该等待所需要的。
这针对的是 FM-1.5 未意识到终止(12.4%) 和 FM-3.1 过早终止(6.2%),并且是 Klein 意义上的共同基础——一个无法发出完成或阻塞信号的队友无法被协调。
因为 blocked 携带了它被谁阻塞,相互等待变得可表示,因而可检测。该检查在会话声明自己阻塞时运行,这正是循环首次可能闭合的时刻。
peer_decide 和 peer_decisions
记录已经确定的事项,这样后来启动的会话就不会重新开启它。
peer_decisions about: "api/charges.ts"
→ 2026-08-15 20:40 · payments-api [api/charges.ts] Multi-currency is deferred until tenant billing lands; createCharge accepts any currency string for now. why: Validating currency needs the tenant billing profile, which does not exist yet. id: bd408a8e…
消息是短暂的——投递一次,折叠进转录中,当该会话压缩或结束时便消失。共同基础必须比它们存续得更久,这需要一份记录而不是一段对话。这针对的是 FM-1.4 对话历史丢失 和 FM-2.1 对话重置。
这是交互记忆的方向:与其把每个会话的上下文复制到其他每个会话中,不如发布一份关于结论的小型持久索引,让对等方按领域查询它。一个目录覆盖其下的一切,与声明和所有权相同的嵌套规则。
任何内容都永远不会被编辑或删除——决策会被取代。 后来的决策会指明它所取代的那一个;peer_decisions 只返回仍然有效的决策,这样就没有人会依据一个已被推翻的决策行事,而 include_superseded 会显示历史。
peer_inbox
列出在 hold 策略下为你保留的消息,并在你的操作员要求时释放它们。在默认的 accept 下为空。
peer-coordination 技能
工具说明什么是可能的;技能说明什么是明智的。它随插件一起提供,并传授工具无法承载的判断力——在编辑共享代码之前先声明,验证一个不是你产出的声明,记录已经确定的事项,说明你何时被阻止,并且在交流结束后停止回复。
其中的每条规则都来自一次经过测量的运行,而不是风格指南,包括那条将消息流量削减 65%的规则。如果你的部署自带协调指南,请设置 skill: false。
协作与安全
默认情况下,对等消息是信息,而非指令。接收模型被告知,只有当它自己的用户要求时,它才可以对消息中的请求采取行动。对于两个恰好共用一台机器的会话来说,这是正确的默认设置;而对于你刻意作为一对来运行的两个会话来说,这是错误的。
peerAuthority 和 trustedPeers 会按接收会话改变这一点: yaml
- id: agent-messaging
config:
peerAuthority: act
trustedPeers:
- payments-api
有了这个设置,来自 payments-api 的消息会被框定为来自操作员已授权的对等方,接收方可以直接对其采取行动。其他所有内容仍然作为信息到达。
有三个属性值得精确说明,因为这个设置很容易被过度解读:
- 它是提示层面的,而非强制执行。 它改变的是接收模型被告知的内容。强制执行边界是接收会话自身的权限规则、访问模式和沙箱——在每个授权级别都完全相同。
- 它不授予任何东西。 在两个级别下,消息都明确无法批准某个操作、授予某项权限或更改配置。这些是操作员才能给予的,没有任何设置会将其委托出去。一个已授权的对等方如果请求接收方现有权限之外的东西,会被拒绝。
- 仅提高级别本身不起作用。 trustedPeers 默认为空且精确匹配,因此后来出现的会话永远不会继承从未授予它的地位,而一个相似的名称(payments-api-staging)也不会匹配 payments-api。
inform 并非瘫痪,上面的运行展示了这一区别:结账会话对到达的消息采取了行动——但只有在自行核实该声明之后,并且只针对它拥有且已被要求处理的文件。inform 所防止的是对等方发起授权。
对于应保持在人类控制之下的工作,优先使用 inbound: hold——消息会等待,peer_inbox 会在你说了之后释放它们。
它是否物有所值?
这里的每个功能都由他人的实测失败率来证明其合理性。没有一个是由你的失败率来证明的——所以该插件会统计它花费了什么以及它捕获了什么: bash
npx dsh-agent-messaging report # all recorded activity
npx dsh-agent-messaging report --days 7
COST — turns this plugin caused a session to spend messages delivered 7 dropped by loop control 0 CAUGHT — what would otherwise have gone wrong collisions avoided 1 (a peer already held the resource) false claims caught 1 (verification refuted them) deadlocks detected 1 7 个接收方轮次被消耗,3 个问题被捕获。
刻意以成本与捕获来表述,而非使用计数器:“已发送 42 条消息”说明不了什么,而“消耗了 42 个接收方轮次,避免了 6 次冲突”则是一个你实际可以做出的判断。计数是本地且聚合的——不存储任何消息内容——而 metrics: false 会完全关闭记录。
这是一个命令,而不是第十个 peer_ 工具,这是有意为之。受众是你,由你来决定这个插件是否值得消耗这些轮次;把它摆在模型面前会从真正干活的九个工具那里夺走注意力。
报告在底部陈述了自身的局限,而且是认真的:捕获一次冲突是实实在在的节省,但这些计数无法告诉你所消耗的轮次是否值得。它们确实能够做到的一件事,是捕获协作成本的回归——这就是 20 变成 7 的原因。
基准测试
这个项目所提出的主张——协调会消耗轮次,但节省的比消耗的多——是从由我编写评分的运行中论证出来的。bench/ 用一个可证伪的东西取代了它:五个未协调的一对会做错的场景,按仓库最终是否正确来评分,以模型轮次计价。一个实验臂由配置文件选择,因此这个插件、一个竞争插件,以及完全不协调,都以相同方式被测量。
DeepSeek-V4-Flash,每个实验臂每个场景运行一次:
| 场景 | 基线 | 插件 |
| stale-contract | 失败 · 2t | 通过 · 4t |
| collision | n/r · 2t | n/r · 3t |
| false-belief | 失败 · 2t | 失败 · 5t |
| mutual-wait | n/r · 2t | n/r · 8t |
| stale-decision | 失败 · 2t | 通过 · 2t |
| 通过 | 0/3 | 2/3 |
| 评分场景上的轮次 | 6 | 11 |
3 个中的 0 个变成了 3 个中的 2 个,轮次大约翻倍。 这是该主张首次对照控制组进行测量——而且它只是一次运行,也就是一个周围摆着一张表格的轶事。
基准测试发现了三件我原本不会发现的事:
- 有两个场景在这里无法复现其失败,因此被排除而不是计入。基于补丁的编辑器从结构上防止了丢失更新;互相等待不会发生,因为这些模型会去做它们能做的部分,而不是阻塞。两者都标记为 n/r——一个作者悄悄把免费通过计入的基准测试,测量的是它自己的套件长度。
- 验证可以在不改变行动的情况下改变信念。 在 false-belief 中,对等方审查了文件,纠正了错误前提,客户端记录了一个取代性的决定——然后还是基于另一套理由移除了该字段。协调起了作用;结果仍然失败。
- 过期的对等方会招致责任分散。 早先的一次运行被判定无效,因为一个会话把工作推给了已经死掉数小时的对等方,原因是它读了它们的标题,而没有任何东西反驳这一点。现在已停止的会话会在 peer_list 中携带其年龄。
在引用其中任何数字之前,请先阅读 bench/README.md,包括我的。
触达 DSH 之外的代理
配置一个 Agent2Agent 端点,它就会成为一个普通对等方——它会出现在 peer_list 中,并接受 peer_send:
- id: agent-messaging
config:
a2aEndpoints:
reviewer: { url: "https://reviewer.example/a2a", token: "…" }
A2A 是值得作为构建基础的代理间标准——Google 将其捐赠给了 Linux 基金会,AWS、Cisco、Microsoft、Salesforce、SAP 和 ServiceNow 都是创始成员——它与 MCP 是互补而非竞争关系:MCP 将代理连接到工具,A2A 将代理彼此连接。
有两条边界值得了解:
- 外部发送者永远不会被提权。 A2A 无法表达权限范围,因此无论 peerAuthority 如何设置,也无论外部代理如何自我声称,它始终是 inform。信任是你配置的属性,而不是陌生人可以设置的字段。它的消息在记录卡片上带有 from an external agent 标记。
- 仅限出站。 DSH 会话可以向外触达;外部代理无法向内触达。提供 Agent Card 需要 HTTP 接口及其自身的授权方案,只交付其中一半会比完全不交付更糟。
端点必须是 https,本地开发时可以是 localhost。配置错误的端点会被记录并跳过——本地消息传递仍可正常工作。
它如何触达另一个进程
一个 dsh 主机承载多个会话,因此发现和投递是分开的:
- 发现复用 ctx.sessionQuery,它已经合并了实时存储与持久化后端,并报告两者的可用性。该插件只补充服务无法知道的事实——当前是哪个其他主机进程持有某个会话。
- 投递在接收方是同一进程中的活跃代理时是直接调用;否则它会跨越每个主机的 Unix 域套接字,通过 $DSH_HOME/agent-messaging/hosts/ 下的咨询性存在记录来发现。进程或套接字已不存在的记录一经发现即被清除。
两条路径最终都汇聚到同一条准入路径,因此接收方的策略不会因为恰好与其共享一个进程而被绕过。
配置
在你的 profile 的 cordis.patch.yml 中覆盖:
- id: agent-messaging
config:
inbound: accept
spoolOffline: true
| 键 | 默认值 | 含义 |
| inbound | accept | accept、hold(等待操作员放行)或 refuse |
| peerAuthority | inform | act 允许直接对已授权的对等方执行操作 |
| trustedPeers | [] | 由 peerAuthority: act 授权的对等方,精确匹配 |
| capabilities | 全部开启 | 哪些可选工具组会注册 |
| groups | {} | 命名组及其拓扑(mesh 或 star) |
| maxFanout | 8 | 一次组发送可触达的接收方数量 |
| stateRoot | $DSH_HOME/agent-messaging | 存在记录、声明、卡片、账本、暂存区 |
| includeSubagents | false | 使子代理的子项可被寻址 |
| spoolOffline | true | 为未运行的会话保留消息 |
| spoolMaxAgeMs | 86400000 | 丢弃早于此时间的暂存消息 |
| spoolMaxPerSession | 20 | 每个接收者的暂存深度 |
| rateMaxPerWindow | 10 | 一个发送者在每个窗口内可投递的消息数 |
| rateWindowMs | 60000 | 速率窗口 |
| duplicateWindowMs | 30000 | 在此窗口内丢弃内容相同的消息 |
| maxHeld | 100 | 每个会话保留的暂存消息数 |
| deliveryTimeoutMs | 5000 | 等待对等主机的回执 |
| metrics | true | 记录 npm run report 读取的成本/捕获计数 |
| a2aEndpoints | {} | 外部 Agent2Agent 对等端 |
要完全停止接收,请设置 inbound: refuse。要停止发送,请在权限规则中拒绝这些工具。
安全模型
对等端是另一个代理,而不是你的操作者,该插件的构建方式使这一区分在接触后依然成立。
- 入站消息被框定为不受信任。 每次投递都携带一条固定警告,描述该块是什么以及它不能做什么。这遵循了测试框架为跨会话引用建立的约定。转录卡片是该消息的一种呈现,而绝不是替代品:测试框架自身的上下文行仍保留在其下方,保存模型读取的确切字节。
- 消息体无法伪造自己的框架。 数据区域是 JSON,其中每个 < 都以无损 JSON unicode 转义形式输出,因此任何由对等端提供的字符串都无法拼出周围的标签并逃逸到指令区域。
- 发送者无法被冒充。 身份从正在执行的代理读取,而绝不从工具参数读取。
- 循环控制可终止失控。 按发送者限速和重复抑制意味着两个自动互相应答的代理会自行停止——这不是理论上的:这正是结束上述测得礼貌循环的原因。
- 收件箱仅限所有者访问。 套接字权限为 chmod 0600;在共享机器上,其他用户的进程无法访问它。
- 线上输入在到达策略之前经过验证。 未知协议版本、错误类型、超大消息体和超大帧都会在边界处被拒绝。
权限边界保持按会话划分:到达的消息绝不会应答待处理的提示,并且它请求的任何内容仍受接收会话自身规则的约束。
限制
- 仅限同一台机器。 投递通过 Unix 域套接字进行,因此两个会话只有在共享文件系统时才能互相访问。容器及其主机不能;同一容器内的两个会话可以。
- 仅限纯文本。 没有结构化负载,没有附件。
- 暂存消息是尽力而为的。 它们会过期,且最深的消息会最先被丢弃。
- 存在性是建议性的。 在发布和投递之间死亡的主机会使会话看起来可访问,直到该记录被修剪。
- 工具调用卡片不会被渲染。 每个工具都声明 presentCall/presentResult,这是 harness 文档中记载的呈现词汇,但 Web UI 仍然针对开发时所基于的 rc 构建绘制通用行。
这些声明无需任何代价即可保留;不要围绕它们制定任何计划。
- 该 harness 是开发者预览版,不提供兼容性承诺。本项目的构建针对 npm 的 rc 系列;服务键在之前的版本之间曾被重命名,因此在 harness 升级后请重新验证。
开发
npm install
npm run verify # typecheck (host + browser), tests, build
分层设计使得策略无需运行中的 harness 即可测试:src/domain 是纯的,不导入任何框架,src/app 在 src/ports 中的接口背后承载用例,而 src/adapters 将这些接口绑定到 Cordis、agent 注册表、socket 和磁盘。
src/client 是浏览器那一半——即 transcript 卡片——单独构建(lib/client.js,拥有自己的 tsconfig,使用 DOM 和 JSX 而非 Node),并由 harness 提供给 Web UI。它的投影和格式化都是纯函数,因此该卡片在此处而非浏览器中进行测试。
367 个测试。 其中三个比其余的分量更重:
- tests/scenario.integration.test.ts 在真实技术栈上让一个三会话团队经历一次破坏性的契约变更——真实的存储、真实的 socket、真实的循环控制、真实的核算——并固定下得出的确切数字。如果某项变更使协作变得更安静或更嘈杂,那些数字就会变动,测试会指出这一点。
- tests/agent-sink.test.ts 通过卡片自身的读取器读取宿主写回的记录,因此插件的两半无法悄然偏离。
- tests/tool-guidance.test.ts 固定下工具描述中那些经实际运行证明至关重要的句子——包括那句将消息流量削减了 65% 的句子。
传输、存在和 spool 测试针对真实的 Unix socket 和真实文件运行,而非 mock。
docs/design.md 涵盖了每个接缝为何位于其所在之处,以及哪些替代方案被否决。docs/roadmap.md 是接下来要构建什么背后的研究笔记:多 agent 文献实际展示了什么(包括在相同 token 预算下 agent 辩论通常落败*),每个计划中的功能针对哪些已测量的失败模式,以及哪些是刻意不构建的。
贡献
欢迎提交 pull request——请参阅 CONTRIBUTING.md。
你能发送的最有用的东西不是补丁:而是一次协调成本高于其捕获成果的运行。粘贴 npx dsh-agent-messaging report 的输出,如果两个会话各说各话,请附上 transcript。到目前为止,这个项目中每一个重要的修复都来自观察真实会话的失败,而到目前为止所有那些运行都是我的。
问题、想法和设计反馈请发到 Discussions。
许可证
MIT © Kaixiang Ren预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
happyren/dsh-agent-messaging
让多个 agent 会话互发消息、声明与决策,避免重复冲突
相关插件 · 对话 / 记忆
上下文中枢oceanbase/powercontext
上下文跟着工作走 —— 跨会话、跨智能体、跨人。
★ 1,487已汉化DSH
73
智能体记忆库plastic-labs/honcho
Honcho 是记忆基础设施,用来构建有状态的智能体。这些智能体能理解不断变化的人、智能体、群组、项目和想法。
★ 7,894已汉化DSH
71.7
长程智能体控制面loopx-project/loopx
给 Agent 一个目标,让工作持续向前。
★ 6,358已汉化DSH
71.3
智能体上下文数据库volcengine/OpenViking
给 AI 智能体用的上下文数据库
★ 41,379已汉化DSH
71.3
长效机器人团队Fei2411/DS-Bot
DS Bot 是一个基于 DeepSeek Harness简称 dsh的 bot 插件,插件将 dsh 改造为 bot…
★ 5,084已汉化DSH
71.1
可携带记忆层EverMind-AI/EverOS
为什么选择 EverOS
★ 13,778已汉化DSH
71.1
这是你的仓库?认领条目