DeepSeek Harness Hub
← 返回列表

会话间通信happyren/dsh-agent-messaging

DeepSeek Harnessspec-screenedmemory在 GitHub 查看 ↗
✓ 可直接安装

让多个 agent 会话互发消息、声明与决策,避免重复冲突

自动检查通过:npm 包已发布且 engines 声明满足基线(声明 Node ^22.19.0 || >=24);该结论来自程序自动检查,未经人工实机验证。 · 最近上游提交 2026/9/12 · 已提供中文文档

DeepSeek Harness 的跨会话验证、声明与决策账本——让两个 agent 会话不会重复、矛盾或互相死锁。

综合分
34.3
GitHub 分
34.3
用户评分
★ Stars
6
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add dsh-agent-messaging
npm 包 dsh-agent-messaging 已校验归属本仓库,走 npm 安装最省事
数据截至 2026/9/12(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查✓ 自动检查通过

以下结论由程序自动检查 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/9/18 05:20:52

依赖的 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
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

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

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群