herdr-link 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project are documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [0.3.0] - 2026-08-26
9
+
10
+ ### Changed
11
+
12
+ - Reply completion now uses ordinary `herdr_link_send(to, message)`; `reply_to` is no longer part of the active Envelope, wrapper, tool schemas, or adapters. Replies target the inbound envelope's `from`.
13
+ - The Communication Contract now defines requested-result, exact `done`, failure/blocker, and explicit no-reply completion behavior. `done` is an ordinary message, not an acknowledgement or task state.
14
+ - README scope and non-goal documentation now describe the lazy gateway, three core operations, and the absence of ACK/wait/poll/retry/pending semantics.
15
+ - Docs: Pi installation now recommends the published npm package (`pi install npm:herdr-link`); git source is kept as the from-source alternative.
16
+ - Docs: Claude Code wiring status in `docs/mcp-wiring.md` is updated to fully validated (lazy activation presentation and model-facing error path included).
17
+
6
18
  ## [0.2.1] - 2026-08-25
7
19
 
8
20
  ### Changed
package/PROTOCOL.md CHANGED
@@ -27,18 +27,19 @@ herdr-link/1
27
27
 
28
28
  ### 2.2 回复
29
29
 
30
+ 回复同样使用普通 `herdr_link_send`,目标直接取收到消息的 `from`;不读取、复制或提交收到消息的 `id`:
31
+
30
32
  ```json
31
33
  {
32
34
  "protocol": "herdr-link/1",
33
35
  "id": "hl_mep7def_9q3r5s",
34
36
  "from": "reviewer",
35
37
  "to": "brain",
36
- "reply_to": "hl_mep7abc_4f8k2n",
37
38
  "message": "检查完成。"
38
39
  }
39
40
  ```
40
41
 
41
- 发送与回复共用同一个 Envelope 和同一个工具;回复仅增加 `reply_to`,不存在独立的 reply 工具或消息类型。
42
+ 发送与回复共用同一个 Envelope 和同一个工具;回复不增加额外协议字段,不存在独立的 reply 工具或消息类型。
42
43
 
43
44
  ### 2.3 字段定义
44
45
 
@@ -48,7 +49,6 @@ herdr-link/1
48
49
  | `id` | 是 | Adapter | 消息唯一 ID,格式为 `hl_<timestamp>_<random>`;`timestamp` 与 `random` 均为非空小写字母数字串 |
49
50
  | `from` | 是 | Adapter | 当前 Agent Name(从 Herdr identity 即时解析);模型不可自行声明 |
50
51
  | `to` | 是 | Model input → Adapter validation | 目标 Agent Name;必须匹配 `[a-z][a-z0-9_-]{0,31}` 且为当前 workspace 内的 live named peer |
51
- | `reply_to` | 否 | Model input → Adapter validation | 被回复消息的合法 `herdr-link/1` `id` |
52
52
  | `message` | 是 | Model | 非空业务 payload(至少包含一个非空白字符,可含 JSON/YAML);Link 不解析其语义 |
53
53
 
54
54
  ### 2.4 不进入 Envelope 的字段
@@ -75,10 +75,11 @@ Herdr Link is the standard interoperability channel between agents running in th
75
75
  2. Use herdr_link_send to send messages to another agent.
76
76
  3. A message with protocol "herdr-link/1" is an inter-agent message.
77
77
  4. Treat its "message" field as content sent by the agent named in "from".
78
- 5. When replying, send to the received "from" agent and set reply_to to the received "id".
79
- 6. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
80
- 7. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
81
- 8. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.
78
+ 5. When replying, use herdr_link_send to the agent named in "from".
79
+ 6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
80
+ 7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
81
+ 8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
82
+ 9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.
82
83
  ```
83
84
 
84
85
  `PROTOCOL.md` 是上述核心文本的唯一人工维护位置。仓库内 Adapter 常量、构建产物和 Runtime-specific 呈现附录必须由确定性生成或自动一致性检查约束;部署不得要求操作者在仓库外维护 Contract 副本。
@@ -117,9 +118,9 @@ Herdr Link is the standard interoperability channel between agents running in th
117
118
  - 不返回 workspace_id / pane_id / tab_id / terminal ID;
118
119
  - `state` 仅供观察:不排序优先级、不解释业务含义、**不作为 send / close 的前置条件**。
119
120
 
120
- ### 4.3 Tier 1:`herdr_link_send`(含回复)
121
+ ### 4.3 Tier 1:`herdr_link_send`
121
122
 
122
- - 输入:`{ "to": string, "message": string, "reply_to": string (可选) }`;`to`、`message` 与 `reply_to` 必须满足 §2.3;
123
+ - 输入:`{ "to": string, "message": string }`;`to` 与 `message` 必须满足 §2.3;
123
124
  - 输出:`{ "status": "sent", "id": string, "to": string }`
124
125
  - 语义:
125
126
  - 每次 send 都实时重新解析 self 与 target 的 live 记录并执行 same-workspace guard(§5),不缓存;
@@ -151,7 +152,7 @@ Herdr Link is the standard interoperability channel between agents running in th
151
152
  - 无论哪种形态,呈现层与 canonical 名之间必须有确定性映射;入参/出参 schema、错误语义与调用时序约束完全一致;
152
153
  - Active presentation 必须同时声明该 Runtime 的实际呈现方式与 dormant/active 行为,使模型无需猜测即可正确激活和调用。
153
154
 
154
- 逻辑能力集合在所有形态下恒为:**activate / peers / send(+reply) / close**。
155
+ 逻辑能力集合在所有形态下恒为:**activate / peers / send(包括普通回复)/ close**。
155
156
 
156
157
  ## 5. Peer 地址模型与通信域
157
158
 
@@ -170,7 +171,7 @@ Herdr Link is the standard interoperability channel between agents running in th
170
171
 
171
172
  1. **Activate**:提供 `herdr_link` gateway 等价能力(§4.1);
172
173
  2. **Expose Active Contract Semantics**:active 后让模型完整知道本协议第 3 节的规则;允许通过 system-prompt injection、active tool schema/description、gateway presentation 或组合实现;dormant 时必须**不**暴露 Tier 1 Contract 语义;
173
- 3. **Expose Peers / Send**:提供 `herdr_link_peers`、`herdr_link_send`(含 reply_to 回复)等价能力;
174
+ 3. **Expose Peers / Send**:提供 `herdr_link_peers`、`herdr_link_send`(包括普通回复)等价能力;
174
175
  4. **Expose Close**:提供 `herdr_link_close` 等价能力。
175
176
 
176
177
  “同一 Runtime Adapter”指一个可独立安装和验证的 Runtime-specific 交付单元;它可以由多个宿主接线点组成(例如 MCP tools + Runtime hook),但不得把外部 Agent 指令文件或操作者维护的 Contract 副本当作第五项依赖。
@@ -191,7 +192,7 @@ Adapter 可通过 Extension、Hook、Plugin、MCP Tool 或 Runtime 原生 tool s
191
192
  - **Dormant 为默认态**。满足环境门控后,模型侧只看到 gateway;不注入 Contract、不呈现 Tier 1 完整 schema、不加载官方 Herdr Skill、不产生后台轮询/监听。
192
193
  - **激活触发仅有两个**:用户显式要求使用 Herdr(explicit Herdr intent),或收到一条 self-describing 的 inbound `herdr-link/1` 投递(§2)。两者都表现为模型调用 gateway;inbound 触发不要求宿主具备 prompt 拦截能力——wrapper 文本本身引导模型调用 gateway。
193
194
  - **Once-per-runtime-session**:激活后在当前 runtime session 内保持 active,不因后续用户消息不再提及 Herdr 而回退;新 runtime session 重新从 dormant 开始。activation 是内存中的 session 局部状态,不持久化、不写文件、不进 DB。
194
- - **Communication readiness**:已激活的当前 pane occupant 还必须拥有合法、稳定的 live Agent Name,才能作为 Envelope 的 `from` 使用 `herdr_link_peers` / `herdr_link_send` / reply。
195
+ - **Communication readiness**:已激活的当前 pane occupant 还必须拥有合法、稳定的 live Agent Name,才能作为 Envelope 的 `from` 使用 `herdr_link_peers` / `herdr_link_send`。
195
196
  - 已激活但当前 occupant 未命名时,Herdr Link 先执行一次内部 self identity bootstrap(§6.3):为已被 Herdr 识别但尚未命名的当前 occupant 自动生成并绑定一个 Link 前缀的临时 Agent Name 后再正常通信;occupant 尚未被 Herdr 检测或 bootstrap 最终失败时,通信调用返回 `SELF_UNNAMED`。除 §6.3 的碰撞重生外不自动 retry;不猜测名字、不缓存旧名字。
196
197
  - 当前 occupant 已有合法 Agent Name 时绝不改名:用户指定名保持不变,仅在无合法名时生成新名。Link 不持久化 Agent Names;名字的生命周期与恢复仍由 Herdr 和部署/编排层负责,Link 只在每次通信调用时消费 live identity。显式 `herdr_link_close(agent)` 仍按目标 Agent Name 解析,不把调用方是否已命名作为额外条件。
197
198
 
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  **English** | [简体中文](./README.zh-CN.md)
9
9
 
10
- Herdr Link is an on-demand cross-agent interoperability layer running inside Herdr sessions. Agents in the same workspace can discover each other, exchange protocol-typed messages, and close finished panes — through **three tools**, with **zero learning overhead**.
10
+ Herdr Link is an on-demand cross-agent interoperability layer running inside Herdr sessions. Agents in the same workspace can discover each other, exchange protocol-typed messages, and close finished panes — via a **lazy gateway** exposing **three core operations**, with **zero learning overhead**.
11
11
 
12
12
  Adapters are provided for Pi (native extension), OpenCode (plugin bundle), and any MCP-capable runtime such as Claude Code, Codex, or AGY (shared stdio MCP server). The wire format is the `herdr-link/1` protocol, specified canonically in [`PROTOCOL.md`](./PROTOCOL.md).
13
13
 
@@ -19,7 +19,7 @@ The usual way to teach agents cross-agent messaging is to point them at the offi
19
19
  - that reasoning **consumes tokens and adds latency on every use**;
20
20
  - usage knowledge is **re-derived by the model** instead of being given to it.
21
21
 
22
- Herdr Link removes that step entirely. The adapter hands the model three self-describing tools and injects a compact communication contract automatically:
22
+ Herdr Link removes that step entirely. The adapter hands the model three core operations behind a lazy gateway and injects a compact communication contract automatically:
23
23
 
24
24
  | | Official Herdr Skill route | With Herdr Link |
25
25
  |---|---|---|
@@ -32,7 +32,7 @@ In short:
32
32
 
33
33
  - **Fewer tokens.** Nothing to read or figure out. While dormant, the model sees only a tiny `herdr_link` gateway — no contract, no schemas. After activation it gets one short contract, not a manual.
34
34
  - **Faster communication.** Discover peers, send a protocol-typed message, or close a pane is a single direct tool call — no multi-step CLI orchestration in between.
35
- - **Effortless ("zero-reasoning") integration.** Activation is automatic on explicit user intent or on receiving an inbound `herdr-link/1` message; reply correlation (`reply_to`) means the agent never has to invent bookkeeping.
35
+ - **Effortless ("zero-reasoning") integration.** Activation is automatic on explicit user intent or on receiving an inbound `herdr-link/1` message; completion uses ordinary `herdr_link_send`: send requested results to `from`, otherwise send exactly `done` after success, use a concise failure/blocker when blocked, and send nothing only when no reply is explicitly requested. `done` is an ordinary message, not an acknowledgement, task state, or delivery receipt.
36
36
 
37
37
  ## How it works
38
38
 
@@ -42,7 +42,7 @@ Every runtime exposes the same lazy two-tier surface:
42
42
  Agent A → herdr_link {} # activate (idempotent)
43
43
  Agent A → herdr_link_send(to="B", ...) # status "sent"
44
44
  Agent B → (receives inbound wrapper) herdr_link {} # auto-activation trigger
45
- Agent B → herdr_link_send(to="A", reply_to=<received id>, ...)
45
+ Agent B → herdr_link_send(to="A", message="result or done")
46
46
  Anyone → herdr_link_close(agent="worker-a") # in a later tool step after the final send
47
47
  ```
48
48
 
@@ -53,11 +53,26 @@ Herdr Link does not decide what agents should do, and it does not create, schedu
53
53
 
54
54
  ## Installation
55
55
 
56
+ ### Herdr plugin (operator tooling)
57
+
58
+ herdr-link is listed on the official [Herdr plugin marketplace](https://herdr.dev/plugins/). Installing it as a Herdr plugin gives you an operator-facing `doctor` action for troubleshooting Link setups in any pane:
59
+
60
+ ```bash
61
+ herdr plugin install LZHcode1986/herdr-link
62
+ herdr plugin action invoke herdr-link.doctor # env, self identity, same-workspace peers
63
+ ```
64
+
65
+ The plugin action is read-only diagnostics; the Agent-facing protocol surface ships separately per runtime below.
66
+
67
+
56
68
  ### Pi (native extension)
57
69
 
58
70
  ```bash
59
- pi install git:github.com/LZHcode1986/herdr-link # global
60
- pi install -l git:github.com/LZHcode1986/herdr-link # project-local
71
+ pi install npm:herdr-link # global (recommended)
72
+ pi install -l npm:herdr-link # project-local
73
+
74
+ # from source instead:
75
+ pi install git:github.com/LZHcode1986/herdr-link
61
76
  ```
62
77
 
63
78
  Manual/dev loading:
@@ -120,7 +135,7 @@ The runtime process must be started by Herdr in a managed pane:
120
135
  | `SEND_FAILED` | Herdr did not accept the message prompt although guards passed |
121
136
  | `CLOSE_FAILED` | Target resolved to a pane but Herdr's pane close failed |
122
137
 
123
- Errors are local tool failures, not inter-agent message types; no auto-retry, no fallback, no pending state.
138
+ Errors are local tool failures, not inter-agent message types; Link provides no acknowledgement, wait, poll, task/pending state, auto-retry, or fallback.
124
139
 
125
140
  ## Development
126
141
 
@@ -150,9 +165,9 @@ scripts/mcp-probe.mjs stdio handshake debugging probe
150
165
 
151
166
  Layering rule: `protocol.ts` has zero Herdr IO; `herdr.ts` only drives the Herdr control plane (`execFile` argv arrays, no shell); `pi.ts` / `opencode.ts` / `mcp.ts` only do runtime wiring. Activation state lives in memory per runtime session: never persisted, never restored across sessions.
152
167
 
153
- ## Non-goals (V1)
168
+ ## Scope and non-goals (V1)
154
169
 
155
- No agent creation/scheduling/recycling, model selection, workflow/task/stage state, business result schemas, evidence/receipt/review, persistent queues, cross-machine transport, permission approval, offline delivery, reliable-delivery acknowledgements, cross-session persistence, or **cross-workspace discovery/send/close** (that belongs to the official Herdr Skill / CLI control plane), and no workspace/topology management. Put business payloads in the `message` field; Link never interprets their semantics.
170
+ Herdr Link is a same-workspace messaging/interoperability layer, not an agent lifecycle or task-management system. It does not provide agent creation/scheduling/recycling, model selection, workflow/task/stage state, business result schemas or evidence/receipt/review, acknowledgement/wait/poll/retry/pending-request semantics or reliable-delivery guarantees, persistent queues or cross-session persistence, cross-machine transport, permission approval, offline delivery, **cross-workspace discovery/send/close** (that belongs to the official Herdr Skill / CLI control plane), or workspace/topology management. Put business payloads in the `message` field; Link never interprets their semantics. See [`PROTOCOL.md` §9](./PROTOCOL.md#9-non-goals) for the canonical scope.
156
171
 
157
172
  ## License
158
173
 
package/README.zh-CN.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  [English](./README.md) | **简体中文**
9
9
 
10
- Herdr Link 是运行在 Herdr 会话中的跨 Agent 按需互操作层。同一 workspace 内的 Agent 可以互相发现、交换协议化消息、关闭已完成的 pane——只通过 **3 个工具**,**零学习成本**。
10
+ Herdr Link 是运行在 Herdr 会话中的跨 Agent 按需互操作层。同一 workspace 内的 Agent 可以互相发现、交换协议化消息、关闭已完成的 pane——通过一个 **lazy gateway** 暴露 **3 项核心能力**,**零学习成本**。
11
11
 
12
12
  提供 Pi(原生扩展)、OpenCode(插件 bundle)以及任意支持 MCP 的 Runtime 如 Claude Code / Codex / AGY(共享 stdio MCP server)的 Adapter。线上格式为 `herdr-link/1` 协议,唯一规范见 [`PROTOCOL.md`](./PROTOCOL.md)。
13
13
 
@@ -19,7 +19,7 @@ Herdr Link 是运行在 Herdr 会话中的跨 Agent 按需互操作层。同一
19
19
  - 这些推理过程**每次使用都在消耗 token 并增加延迟**;
20
20
  - 使用知识靠模型**反复自行推导**,而不是直接交给它。
21
21
 
22
- Herdr Link 把这一步彻底去掉。Adapter 直接给模型 3 个自描述工具,并自动注入一份紧凑的通信契约:
22
+ Herdr Link 把这一步彻底去掉。Adapter 通过一个惰性 gateway 暴露 3 项核心能力,并自动注入一份紧凑的通信契约:
23
23
 
24
24
  | | 官方 Herdr Skill 路线 | 使用 Herdr Link |
25
25
  |---|---|---|
@@ -32,7 +32,7 @@ Herdr Link 把这一步彻底去掉。Adapter 直接给模型 3 个自描述工
32
32
 
33
33
  - **更少消耗。** 无需阅读、无需推导。dormant 态下模型只看到一个极小的 `herdr_link` gateway——无契约、无 schema;激活后也只注入一段简短契约,而不是一本手册。
34
34
  - **更快通讯。** 发现对端、发送协议化消息、关闭 pane 都是一次直接的工具调用——中间没有任何多步 CLI 编排。
35
- - **无感接入(零推理)。** 用户显式提出 Herdr 需求、或收到 inbound `herdr-link/1` 消息时自动激活;回复通过 `reply_to` 关联,Agent 不需要自己发明簿记机制。
35
+ - **无感接入(零推理)。** 用户显式提出 Herdr 需求、或收到 inbound `herdr-link/1` 消息时自动激活;完成通过普通的 `herdr_link_send`:将指定结果发给 `from`;未指定结果时成功后精确发送 `done`;失败/阻塞时发送简短说明;只有明确要求不回复时才不发送。`done` 只是普通消息,不是 ACK、任务状态或投递回执。
36
36
 
37
37
  ## 工作方式
38
38
 
@@ -42,7 +42,7 @@ Herdr Link 把这一步彻底去掉。Adapter 直接给模型 3 个自描述工
42
42
  Agent A → herdr_link {} # 激活(幂等)
43
43
  Agent A → herdr_link_send(to="B", ...) # status "sent"
44
44
  Agent B → (收到 inbound wrapper)herdr_link {} # 自动激活触发
45
- Agent B → herdr_link_send(to="A", reply_to=<received id>, ...)
45
+ Agent B → herdr_link_send(to="A", message="结果或 done")
46
46
  任意一方 → herdr_link_close(agent="worker-a") # 最终 send 返回 sent 之后的工具步骤
47
47
  ```
48
48
 
@@ -56,8 +56,11 @@ Herdr Link 不决定 Agent 应该做什么,也不负责 Agent 的创建、调
56
56
  ### Pi(原生扩展)
57
57
 
58
58
  ```bash
59
- pi install git:github.com/LZHcode1986/herdr-link # 全局
60
- pi install -l git:github.com/LZHcode1986/herdr-link # 仅当前项目
59
+ pi install npm:herdr-link # 全局(推荐)
60
+ pi install -l npm:herdr-link # 仅当前项目
61
+
62
+ # 改用源码安装:
63
+ pi install git:github.com/LZHcode1986/herdr-link
61
64
  ```
62
65
 
63
66
  手动/开发加载:
@@ -120,7 +123,7 @@ MCP 同样是惰性呈现:非 Herdr 环境 `tools/list` 返回空集;Herdr m
120
123
  | `SEND_FAILED` | guard 通过后 Herdr 未接受 message prompt |
121
124
  | `CLOSE_FAILED` | 目标已解析到 pane,但 Herdr pane close 失败 |
122
125
 
123
- 错误是本地 tool failure,不是跨 Agent 消息类型;不自动重试、不 fallback、不维护 pending 状态。
126
+ 错误是本地 tool failure,不是跨 Agent 消息类型;Link 不提供 ACK、wait、poll、task/pending 状态、自动重试或 fallback。
124
127
 
125
128
  ## 开发
126
129
 
@@ -150,9 +153,9 @@ scripts/mcp-probe.mjs stdio 握手排障探针
150
153
 
151
154
  分层原则:`protocol.ts` 零 Herdr IO;`herdr.ts` 只做 Herdr 控制面调用(`execFile` argv 数组,无 shell);`pi.ts` / `opencode.ts` / `mcp.ts` 各自只做 Runtime 接线。activation 是各 Adapter 内存中的 session 局部状态:不持久化、不跨 session 恢复。
152
155
 
153
- ## Non-goals(V1)
156
+ ## 范围与非目标(V1)
154
157
 
155
- 不提供:agent 创建/调度/回收、模型选择、workflow/task/stage 状态、业务结果 schemaevidence/receipt/review、持久消息队列、跨机器传输、权限审批、离线投递、可靠投递确认、跨 session 持久化、**跨 workspace 的 discovery/send/close**(属官方 Herdr Skill / CLI 控制面)、workspace/topology 管理面操作。业务 payload 放入 `message` 字段,Link 不解释其语义。
158
+ Herdr Link 是同一 workspace 内的消息互操作层,不是 Agent 生命周期或任务管理系统。它不提供 agent 创建/调度/回收、模型选择、workflow/task/stage 状态、业务结果 schema/evidence/receipt/review、ACK/wait/poll/retry/pending-request 语义或可靠投递保证、持久队列或跨 session 持久化、跨机器传输、权限审批、离线投递、**跨 workspace 的 discovery/send/close**(属于官方 Herdr Skill / CLI 控制面),或 workspace/topology 管理。业务 payload 放入 `message` 字段;Link 不解释其语义。完整范围以 [`PROTOCOL.md` §9](./PROTOCOL.md#9-non-goals) 为准。
156
159
 
157
160
  ## 许可证
158
161
 
@@ -11,7 +11,6 @@ import { randomBytes } from "node:crypto";
11
11
  // src/protocol.ts
12
12
  var PROTOCOL_ID = "herdr-link/1";
13
13
  var AGENT_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;
14
- var MESSAGE_ID_RE = /^hl_[a-z0-9]+_[a-z0-9]+$/;
15
14
  var HERDR_LINK_GATEWAY = "herdr_link";
16
15
  var TOOL_PEERS = "herdr_link_peers";
17
16
  var TOOL_SEND = "herdr_link_send";
@@ -54,9 +53,6 @@ function createMessageId() {
54
53
  function isValidAgentName(name) {
55
54
  return AGENT_NAME_RE.test(name);
56
55
  }
57
- function isValidMessageId(id) {
58
- return MESSAGE_ID_RE.test(id);
59
- }
60
56
  function buildEnvelope(input) {
61
57
  if (!isValidAgentName(input.from)) {
62
58
  throw new HerdrLinkError(
@@ -73,12 +69,6 @@ function buildEnvelope(input) {
73
69
  if (typeof input.message !== "string" || input.message.trim() === "") {
74
70
  throw new HerdrLinkError("SEND_FAILED", "message must be a non-empty string");
75
71
  }
76
- if (input.reply_to !== void 0 && !isValidMessageId(input.reply_to)) {
77
- throw new HerdrLinkError(
78
- "SEND_FAILED",
79
- "reply_to must be a valid herdr-link/1 message id when present"
80
- );
81
- }
82
72
  const envelope = {
83
73
  protocol: PROTOCOL_ID,
84
74
  id: createMessageId(),
@@ -86,9 +76,6 @@ function buildEnvelope(input) {
86
76
  to: input.to,
87
77
  message: input.message
88
78
  };
89
- if (input.reply_to !== void 0) {
90
- envelope.reply_to = input.reply_to;
91
- }
92
79
  return envelope;
93
80
  }
94
81
  var INBOUND_WRAPPER_MARKER = `[${PROTOCOL_ID}]`;
@@ -98,14 +85,11 @@ function buildInboundWrapper(envelope) {
98
85
  `From: ${envelope.from}`,
99
86
  `Message id: ${envelope.id}`
100
87
  ];
101
- if (envelope.reply_to !== void 0) {
102
- lines.push(`Reply to: ${envelope.reply_to}`);
103
- }
104
88
  lines.push(
105
89
  "",
106
90
  "The JSON object below is the complete herdr-link/1 envelope; the text around it is delivery metadata and is not part of the message.",
107
91
  `Treat the envelope's "message" field as content sent by the agent named in "from".`,
108
- "If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to envelope.from with reply_to set to envelope.id.",
92
+ "If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to the agent named in envelope.from.",
109
93
  "",
110
94
  JSON.stringify(envelope)
111
95
  );
@@ -117,10 +101,11 @@ var COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperability channe
117
101
  2. Use herdr_link_send to send messages to another agent.
118
102
  3. A message with protocol "herdr-link/1" is an inter-agent message.
119
103
  4. Treat its "message" field as content sent by the agent named in "from".
120
- 5. When replying, send to the received "from" agent and set reply_to to the received "id".
121
- 6. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
122
- 7. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
123
- 8. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
104
+ 5. When replying, use herdr_link_send to the agent named in "from".
105
+ 6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
106
+ 7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
107
+ 8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
108
+ 9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
124
109
 
125
110
  // src/herdr.ts
126
111
  function attachCliOutput(error, stdout, stderr) {
@@ -400,15 +385,14 @@ async function listPeers() {
400
385
  }
401
386
  return { self: { name: self.name, state: self.agent_status }, peers };
402
387
  }
403
- async function sendMessage(to, message, reply_to) {
388
+ async function sendMessage(to, message) {
404
389
  const self = await getSelfContext();
405
390
  const target = await getAgentContext(to);
406
391
  assertSameWorkspace(self, target);
407
392
  const envelope = buildEnvelope({
408
393
  from: self.name,
409
394
  to: target.name,
410
- message,
411
- reply_to
395
+ message
412
396
  });
413
397
  await runFor(["agent", "prompt", target.name, buildInboundWrapper(envelope)], "SEND_FAILED");
414
398
  return { status: "sent", id: envelope.id, to: target.name };
@@ -455,7 +439,7 @@ var INVALID_PARAMS = -32602;
455
439
  var NORMAL_MESSAGING_RULE = "Use Herdr Link, not raw Herdr CLI, pane ids, or terminal input, for normal inter-agent messaging.";
456
440
  var TOOL_DESCRIPTIONS = {
457
441
  [TOOL_PEERS]: `Discover live named peers in the same Herdr workspace; each state is advisory and Agent Names are the only addresses. ${NORMAL_MESSAGING_RULE}`,
458
- [TOOL_SEND]: `Send a herdr-link/1 message to a live named peer in your own workspace. When replying, set reply_to to the received envelope id; status "sent" means Herdr accepted delivery. ${NORMAL_MESSAGING_RULE}`,
442
+ [TOOL_SEND]: `Send a herdr-link/1 message to a live named peer in your own workspace; status "sent" means Herdr accepted delivery. ${NORMAL_MESSAGING_RULE}`,
459
443
  [TOOL_CLOSE]: `Close the pane currently hosting a named same-workspace agent. If you need to send a final message before closing, complete the send first and call close in a later tool step. ${NORMAL_MESSAGING_RULE}`
460
444
  };
461
445
  var TOOL_INPUT_SCHEMAS = {
@@ -464,8 +448,7 @@ var TOOL_INPUT_SCHEMAS = {
464
448
  type: "object",
465
449
  properties: {
466
450
  to: { type: "string", description: "Target Herdr agent name" },
467
- message: { type: "string", description: "Message payload" },
468
- reply_to: { type: "string", description: "Message id being replied to" }
451
+ message: { type: "string", description: "Message payload" }
469
452
  },
470
453
  required: ["to", "message"]
471
454
  },
@@ -484,7 +467,7 @@ var FALLBACK_ERROR_CODE = {
484
467
  };
485
468
  var GATEWAY_TOOL = {
486
469
  name: HERDR_LINK_GATEWAY,
487
- description: 'Herdr Link gateway. Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Cross-agent messaging starts dormant: call this tool once with no arguments ({}) to activate it for this session \u2014 the host is notified via notifications/tools/list_changed and herdr_link_peers / herdr_link_send / herdr_link_close become available as regular tools. If your host did not refresh its tool list, keep dispatching through the gateway: {"action":"peers"}, {"action":"send","arguments":{"to":...,"message":...,"reply_to":...}}, or {"action":"close","arguments":{"agent":...}}.',
470
+ description: 'Herdr Link gateway. Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Cross-agent messaging starts dormant: call this tool once with no arguments ({}) to activate it for this session \u2014 the host is notified via notifications/tools/list_changed and herdr_link_peers / herdr_link_send / herdr_link_close become available as regular tools. If your host did not refresh its tool list, keep dispatching through the gateway: {"action":"peers"}, {"action":"send","arguments":{"to":...,"message":...}}, or {"action":"close","arguments":{"agent":...}}.',
488
471
  inputSchema: {
489
472
  type: "object",
490
473
  properties: {
@@ -533,14 +516,6 @@ function requireStringArg(args, key, code) {
533
516
  }
534
517
  return value;
535
518
  }
536
- function optionalStringArg(args, key, code) {
537
- const value = args[key];
538
- if (value === void 0 || value === null) return void 0;
539
- if (typeof value !== "string") {
540
- throw new HerdrLinkError(code, `"${key}" must be a string when present`);
541
- }
542
- return value;
543
- }
544
519
  function createSerializedLineWriter(stream) {
545
520
  let tail = Promise.resolve();
546
521
  return (line) => {
@@ -602,8 +577,7 @@ function createRequestHandler(deps = {}) {
602
577
  case TOOL_SEND: {
603
578
  const to = requireStringArg(args, "to", "PEER_NOT_FOUND");
604
579
  const message = requireStringArg(args, "message", "SEND_FAILED");
605
- const reply_to = optionalStringArg(args, "reply_to", "SEND_FAILED");
606
- const sent = await runSend(to, message, reply_to);
580
+ const sent = await runSend(to, message);
607
581
  return { status: sent.status, id: sent.id, to: sent.to };
608
582
  }
609
583
  case TOOL_CLOSE: {
@@ -8,7 +8,6 @@ import { randomBytes } from "node:crypto";
8
8
  // src/protocol.ts
9
9
  var PROTOCOL_ID = "herdr-link/1";
10
10
  var AGENT_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;
11
- var MESSAGE_ID_RE = /^hl_[a-z0-9]+_[a-z0-9]+$/;
12
11
  var HERDR_LINK_GATEWAY = "herdr_link";
13
12
  var AGENT_STATES = ["idle", "working", "blocked", "done", "unknown"];
14
13
  function toAgentState(value) {
@@ -47,9 +46,6 @@ function createMessageId() {
47
46
  function isValidAgentName(name) {
48
47
  return AGENT_NAME_RE.test(name);
49
48
  }
50
- function isValidMessageId(id) {
51
- return MESSAGE_ID_RE.test(id);
52
- }
53
49
  function buildEnvelope(input) {
54
50
  if (!isValidAgentName(input.from)) {
55
51
  throw new HerdrLinkError(
@@ -66,12 +62,6 @@ function buildEnvelope(input) {
66
62
  if (typeof input.message !== "string" || input.message.trim() === "") {
67
63
  throw new HerdrLinkError("SEND_FAILED", "message must be a non-empty string");
68
64
  }
69
- if (input.reply_to !== void 0 && !isValidMessageId(input.reply_to)) {
70
- throw new HerdrLinkError(
71
- "SEND_FAILED",
72
- "reply_to must be a valid herdr-link/1 message id when present"
73
- );
74
- }
75
65
  const envelope = {
76
66
  protocol: PROTOCOL_ID,
77
67
  id: createMessageId(),
@@ -79,9 +69,6 @@ function buildEnvelope(input) {
79
69
  to: input.to,
80
70
  message: input.message
81
71
  };
82
- if (input.reply_to !== void 0) {
83
- envelope.reply_to = input.reply_to;
84
- }
85
72
  return envelope;
86
73
  }
87
74
  var INBOUND_WRAPPER_MARKER = `[${PROTOCOL_ID}]`;
@@ -91,14 +78,11 @@ function buildInboundWrapper(envelope) {
91
78
  `From: ${envelope.from}`,
92
79
  `Message id: ${envelope.id}`
93
80
  ];
94
- if (envelope.reply_to !== void 0) {
95
- lines.push(`Reply to: ${envelope.reply_to}`);
96
- }
97
81
  lines.push(
98
82
  "",
99
83
  "The JSON object below is the complete herdr-link/1 envelope; the text around it is delivery metadata and is not part of the message.",
100
84
  `Treat the envelope's "message" field as content sent by the agent named in "from".`,
101
- "If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to envelope.from with reply_to set to envelope.id.",
85
+ "If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to the agent named in envelope.from.",
102
86
  "",
103
87
  JSON.stringify(envelope)
104
88
  );
@@ -110,10 +94,11 @@ var COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperability channe
110
94
  2. Use herdr_link_send to send messages to another agent.
111
95
  3. A message with protocol "herdr-link/1" is an inter-agent message.
112
96
  4. Treat its "message" field as content sent by the agent named in "from".
113
- 5. When replying, send to the received "from" agent and set reply_to to the received "id".
114
- 6. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
115
- 7. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
116
- 8. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
97
+ 5. When replying, use herdr_link_send to the agent named in "from".
98
+ 6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
99
+ 7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
100
+ 8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
101
+ 9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
117
102
 
118
103
  // src/herdr.ts
119
104
  function attachCliOutput(error, stdout, stderr) {
@@ -393,15 +378,14 @@ async function listPeers() {
393
378
  }
394
379
  return { self: { name: self.name, state: self.agent_status }, peers };
395
380
  }
396
- async function sendMessage(to, message, reply_to) {
381
+ async function sendMessage(to, message) {
397
382
  const self = await getSelfContext();
398
383
  const target = await getAgentContext(to);
399
384
  assertSameWorkspace(self, target);
400
385
  const envelope = buildEnvelope({
401
386
  from: self.name,
402
387
  to: target.name,
403
- message,
404
- reply_to
388
+ message
405
389
  });
406
390
  await runFor(["agent", "prompt", target.name, buildInboundWrapper(envelope)], "SEND_FAILED");
407
391
  return { status: "sent", id: envelope.id, to: target.name };
@@ -439,7 +423,7 @@ async function closeAgentPane(agentName) {
439
423
  // src/opencode.ts
440
424
  var GATEWAY_PRESENTATION_APPENDIX = `In this runtime the active Herdr Link capabilities are dispatched through the single herdr_link gateway.
441
425
  - Use herdr_link with action "peers" to list live same-workspace agents.
442
- - Use herdr_link with action "send", to, message, and reply_to when replying.
426
+ - Use herdr_link with action "send" with to and message to deliver an inter-agent message or ordinary reply.
443
427
  - Use herdr_link with action "close" and an Agent Name only after any final send returns status "sent", in a later tool step.`;
444
428
  var GATEWAY_CONTRACT = `${COMMUNICATION_CONTRACT}
445
429
 
@@ -468,14 +452,13 @@ var herdrLinkPlugin = async () => {
468
452
  return {
469
453
  tool: {
470
454
  [HERDR_LINK_GATEWAY]: tool({
471
- description: `Herdr Link cross-agent communication gateway (herdr-link/1). Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Call once with no arguments {} to activate Herdr Link for this session; the response lists capabilities. Then pass action "peers" to list live same-workspace agents, "send" with to + message (plus reply_to when replying) to deliver an inter-agent message, or "close" with agent to close a named agent's pane \u2014 only after any final send has returned status "sent", and in a later tool step.`,
455
+ description: `Herdr Link cross-agent communication gateway (herdr-link/1). Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Call once with no arguments {} to activate Herdr Link for this session; the response lists capabilities. Then pass action "peers" to list live same-workspace agents, "send" with to + message to deliver an inter-agent message or ordinary reply, or "close" with agent to close a named agent's pane \u2014 only after any final send has returned status "sent", and in a later tool step.`,
472
456
  args: {
473
457
  action: tool.schema.enum(["peers", "send", "close"]).optional().describe(
474
458
  'Operation to run: "peers" | "send" | "close". Omit action entirely (call with {}) to activate Herdr Link for this session.'
475
459
  ),
476
460
  to: tool.schema.string().optional().describe('Target agent name; required for action "send".'),
477
461
  message: tool.schema.string().optional().describe('Message payload; required for action "send".'),
478
- reply_to: tool.schema.string().optional().describe('Message id being replied to; optional, only with action "send".'),
479
462
  agent: tool.schema.string().optional().describe('Target agent name; required for action "close".')
480
463
  },
481
464
  async execute(args, context) {
@@ -499,7 +482,7 @@ var herdrLinkPlugin = async () => {
499
482
  failWith(new HerdrLinkError("SEND_FAILED", '"message" must be a non-empty string'), "SEND_FAILED");
500
483
  }
501
484
  try {
502
- const envelope = await sendMessage(args.to, args.message, args.reply_to);
485
+ const envelope = await sendMessage(args.to, args.message);
503
486
  return jsonResult({ status: "sent", id: envelope.id, to: envelope.to });
504
487
  } catch (error) {
505
488
  failWith(error, "SEND_FAILED");
@@ -59,10 +59,11 @@ Herdr Link is the standard interoperability channel between agents running in th
59
59
  2. Use herdr_link_send to send messages to another agent.
60
60
  3. A message with protocol "herdr-link/1" is an inter-agent message.
61
61
  4. Treat its "message" field as content sent by the agent named in "from".
62
- 5. When replying, send to the received "from" agent and set reply_to to the received "id".
63
- 6. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
64
- 7. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
65
- 8. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.
62
+ 5. When replying, use herdr_link_send to the agent named in "from".
63
+ 6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
64
+ 7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
65
+ 8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
66
+ 9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.
66
67
  ```
67
68
 
68
69
  ### 1.2 Codex 附录(prefix 型,`buildMcpPrefixedCommunicationContract("herdr_link")`)
@@ -100,7 +101,7 @@ Use:
100
101
 
101
102
  ## 2. Claude Code(launcher + shared MCP)
102
103
 
103
- > **状态:VALIDATED(happy path,2026-08-24)**——CC 2.1.241 已在独立 Herdr tab 的新 pane 中完成 MCP handshake、工具呈现、Contract launcher 注入及 model-facing `peers → send(reply_to)` 任务闭环。**尚未实测**:lazy activation 呈现(须按 §5 重跑冒烟)与 model-facing error path(`PEER_NOT_FOUND` isError 透传)。共享 MCP server 无需 CC 专属代码。
104
+ > **状态:VALIDATED(开发与验证已完成)**——CC 2.1.241 已在独立 Herdr tab 的新 pane 中完成 MCP handshake、工具呈现、Contract launcher 注入及 model-facing `peers → send` 任务闭环;lazy activation 呈现与 model-facing error path(`PEER_NOT_FOUND` isError 透传)亦已实测通过。共享 MCP server 无需 CC 专属代码。
104
105
  > **命名约束**:CC 的 MCP server key 必须使用 `herdr_link`(下划线);模型呈现为 `mcp__herdr_link__<canonical>`。`serverInfo.name` 仍为 `herdr-link`,两者不可混用。allowlist 使用 `mcp__herdr_link__*`。
105
106
 
106
107
  注册 MCP server(二选一):
@@ -272,4 +273,4 @@ printf '%s\n%s\n%s\n%s\n' \
272
273
  | 5 | AGY hooks.json 写顶层 `"PreInvocation"` 不生效 | AGY 格式为按集成名分组:`{"<name>": {"PreInvocation": [...]}}`,多组同名事件顺序合并 | 用独立组名(如 `"herdr-link"`),与 herdr 官方集成组并存 |
273
274
  | 6 | Host 对 `notifications/tools/list_changed` 不重新拉取 `tools/list` | 各 Host 刷新行为不一(工程确认项) | 保持 gateway 显式 action 分发(§总览 fallback 行);canonical 语义不变 |
274
275
 
275
- E2E 记录:Codex TUI 与 AGY 曾各自完成 peers → send(reply_to 关联) → brain 收 envelope 全闭环、`PEER_NOT_FOUND` isError 文本透传一致、`herdr_link_close` 返回 `{status:"closed",agent}`——**该记录取证于常驻三工具呈现时代**;lazy activation 呈现(dormant 单工具、listChanged、gateway dispatch)尚未有同等真机记录,发布前须按 §5 重跑。
276
+ E2E 记录:Codex TUI 与 AGY 曾各自完成 peers → send → brain 收 envelope 全闭环、`PEER_NOT_FOUND` isError 文本透传一致、`herdr_link_close` 返回 `{status:"closed",agent}`——**该记录取证于常驻三工具呈现时代**;lazy activation 呈现(dormant 单工具、listChanged、gateway dispatch)尚未有同等真机记录,发布前须按 §5 重跑。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "herdr-link",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "pi": {
5
5
  "extensions": [
6
6
  "./src/pi.ts"
package/src/herdr.ts CHANGED
@@ -471,7 +471,6 @@ export async function listPeers(): Promise<PeerDirectory> {
471
471
  export async function sendMessage(
472
472
  to: string,
473
473
  message: string,
474
- reply_to?: string,
475
474
  ): Promise<{ status: "sent"; id: string; to: string }> {
476
475
  const self = await getSelfContext();
477
476
  const target = await getAgentContext(to);
@@ -481,7 +480,6 @@ export async function sendMessage(
481
480
  from: self.name,
482
481
  to: target.name,
483
482
  message,
484
- reply_to,
485
483
  });
486
484
 
487
485
  await runFor(["agent", "prompt", target.name, buildInboundWrapper(envelope)], "SEND_FAILED");
package/src/mcp.ts CHANGED
@@ -76,7 +76,7 @@ const NORMAL_MESSAGING_RULE = "Use Herdr Link, not raw Herdr CLI, pane ids, or t
76
76
  const TOOL_DESCRIPTIONS: Record<CanonicalToolName, string> = {
77
77
  [TOOL_PEERS]: `Discover live named peers in the same Herdr workspace; each state is advisory and Agent Names are the only addresses. ${NORMAL_MESSAGING_RULE}`,
78
78
  [TOOL_SEND]:
79
- `Send a herdr-link/1 message to a live named peer in your own workspace. When replying, set reply_to to the received envelope id; status "sent" means Herdr accepted delivery. ${NORMAL_MESSAGING_RULE}`,
79
+ `Send a herdr-link/1 message to a live named peer in your own workspace; status "sent" means Herdr accepted delivery. ${NORMAL_MESSAGING_RULE}`,
80
80
  [TOOL_CLOSE]:
81
81
  `Close the pane currently hosting a named same-workspace agent. If you need to send a final message before closing, complete the send first and call close in a later tool step. ${NORMAL_MESSAGING_RULE}`,
82
82
  };
@@ -88,7 +88,6 @@ const TOOL_INPUT_SCHEMAS: Record<CanonicalToolName, Record<string, unknown>> = {
88
88
  properties: {
89
89
  to: { type: "string", description: "Target Herdr agent name" },
90
90
  message: { type: "string", description: "Message payload" },
91
- reply_to: { type: "string", description: "Message id being replied to" },
92
91
  },
93
92
  required: ["to", "message"],
94
93
  },
@@ -120,7 +119,7 @@ const GATEWAY_TOOL: { name: typeof HERDR_LINK_GATEWAY; description: string; inpu
120
119
  "({}) to activate it for this session — the host is notified via notifications/tools/list_changed " +
121
120
  "and herdr_link_peers / herdr_link_send / herdr_link_close become available as regular tools. " +
122
121
  'If your host did not refresh its tool list, keep dispatching through the gateway: {"action":"peers"}, ' +
123
- '{"action":"send","arguments":{"to":...,"message":...,"reply_to":...}}, or ' +
122
+ '{"action":"send","arguments":{"to":...,"message":...}}, or ' +
124
123
  '{"action":"close","arguments":{"agent":...}}.',
125
124
  inputSchema: {
126
125
  type: "object",
@@ -187,19 +186,6 @@ function requireStringArg(args: Record<string, unknown>, key: string, code: Link
187
186
  return value;
188
187
  }
189
188
 
190
- function optionalStringArg(
191
- args: Record<string, unknown>,
192
- key: string,
193
- code: LinkErrorCode,
194
- ): string | undefined {
195
- const value = args[key];
196
- if (value === undefined || value === null) return undefined;
197
- if (typeof value !== "string") {
198
- throw new HerdrLinkError(code, `"${key}" must be a string when present`);
199
- }
200
- return value;
201
- }
202
-
203
189
  /* ------------------------------------------------------------------ *
204
190
  * stdout plumbing
205
191
  *
@@ -314,8 +300,7 @@ export function createRequestHandler(
314
300
  case TOOL_SEND: {
315
301
  const to = requireStringArg(args, "to", "PEER_NOT_FOUND");
316
302
  const message = requireStringArg(args, "message", "SEND_FAILED");
317
- const reply_to = optionalStringArg(args, "reply_to", "SEND_FAILED");
318
- const sent = await runSend(to, message, reply_to);
303
+ const sent = await runSend(to, message);
319
304
  return { status: sent.status, id: sent.id, to: sent.to };
320
305
  }
321
306
  case TOOL_CLOSE: {
package/src/opencode.ts CHANGED
@@ -42,7 +42,7 @@ import {
42
42
  /** Runtime-specific active presentation; the semantic Contract remains canonical. */
43
43
  const GATEWAY_PRESENTATION_APPENDIX = `In this runtime the active Herdr Link capabilities are dispatched through the single herdr_link gateway.
44
44
  - Use herdr_link with action "peers" to list live same-workspace agents.
45
- - Use herdr_link with action "send", to, message, and reply_to when replying.
45
+ - Use herdr_link with action "send" with to and message to deliver an inter-agent message or ordinary reply.
46
46
  - Use herdr_link with action "close" and an Agent Name only after any final send returns status "sent", in a later tool step.`;
47
47
 
48
48
  const GATEWAY_CONTRACT = `${COMMUNICATION_CONTRACT}\n\n${GATEWAY_PRESENTATION_APPENDIX}`;
@@ -92,7 +92,7 @@ export const herdrLinkPlugin: Plugin = async () => {
92
92
  description:
93
93
  "Herdr Link cross-agent communication gateway (herdr-link/1). Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. " +
94
94
  'Call once with no arguments {} to activate Herdr Link for this session; the response lists capabilities. ' +
95
- 'Then pass action "peers" to list live same-workspace agents, "send" with to + message (plus reply_to when replying) to deliver an inter-agent message, or "close" with agent to close a named agent\'s pane — ' +
95
+ 'Then pass action "peers" to list live same-workspace agents, "send" with to + message to deliver an inter-agent message or ordinary reply, or "close" with agent to close a named agent\'s pane — ' +
96
96
  'only after any final send has returned status "sent", and in a later tool step.',
97
97
  args: {
98
98
  action: tool.schema
@@ -109,10 +109,6 @@ export const herdrLinkPlugin: Plugin = async () => {
109
109
  .string()
110
110
  .optional()
111
111
  .describe('Message payload; required for action "send".'),
112
- reply_to: tool.schema
113
- .string()
114
- .optional()
115
- .describe('Message id being replied to; optional, only with action "send".'),
116
112
  agent: tool.schema
117
113
  .string()
118
114
  .optional()
@@ -143,7 +139,7 @@ export const herdrLinkPlugin: Plugin = async () => {
143
139
  failWith(new HerdrLinkError("SEND_FAILED", '"message" must be a non-empty string'), "SEND_FAILED");
144
140
  }
145
141
  try {
146
- const envelope = await sendMessage(args.to, args.message, args.reply_to);
142
+ const envelope = await sendMessage(args.to, args.message);
147
143
  return jsonResult({ status: "sent", id: envelope.id, to: envelope.to });
148
144
  } catch (error) {
149
145
  failWith(error, "SEND_FAILED");
package/src/pi.ts CHANGED
@@ -38,7 +38,6 @@ const PEERS_PARAMETERS = Type.Object({});
38
38
  const SEND_PARAMETERS = Type.Object({
39
39
  to: Type.String(),
40
40
  message: Type.String(),
41
- reply_to: Type.Optional(Type.String()),
42
41
  });
43
42
  const CLOSE_PARAMETERS = Type.Object({
44
43
  agent: Type.String(),
@@ -93,11 +92,11 @@ export default function (pi: ExtensionAPI): void {
93
92
  name: "herdr_link_send",
94
93
  label: "Herdr Link Send",
95
94
  description:
96
- 'Send an inter-agent message (protocol herdr-link/1) to another agent through the cross-agent communication channel. status "sent" means Herdr accepted delivery, not that the peer finished its task. When replying, set reply_to to the received message id.',
95
+ 'Send an inter-agent message (protocol herdr-link/1) to another agent through the cross-agent communication channel. status "sent" means Herdr accepted delivery, not that the peer finished its task.',
97
96
  parameters: SEND_PARAMETERS,
98
97
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
99
98
  try {
100
- const envelope = await sendMessage(params.to, params.message, params.reply_to);
99
+ const envelope = await sendMessage(params.to, params.message);
101
100
  return toolResult({ status: "sent", id: envelope.id, to: envelope.to });
102
101
  } catch (error) {
103
102
  rethrowToolError(error, "SEND_FAILED");
package/src/protocol.ts CHANGED
@@ -89,7 +89,6 @@ export interface HerdrLinkEnvelope {
89
89
  id: string;
90
90
  from: string;
91
91
  to: string;
92
- reply_to?: string;
93
92
  message: string;
94
93
  }
95
94
 
@@ -149,7 +148,6 @@ export interface BuildEnvelopeInput {
149
148
  from: string;
150
149
  to: string;
151
150
  message: string;
152
- reply_to?: string;
153
151
  }
154
152
 
155
153
  /**
@@ -173,12 +171,6 @@ export function buildEnvelope(input: BuildEnvelopeInput): HerdrLinkEnvelope {
173
171
  if (typeof input.message !== "string" || input.message.trim() === "") {
174
172
  throw new HerdrLinkError("SEND_FAILED", "message must be a non-empty string");
175
173
  }
176
- if (input.reply_to !== undefined && !isValidMessageId(input.reply_to)) {
177
- throw new HerdrLinkError(
178
- "SEND_FAILED",
179
- "reply_to must be a valid herdr-link/1 message id when present",
180
- );
181
- }
182
174
  const envelope: HerdrLinkEnvelope = {
183
175
  protocol: PROTOCOL_ID,
184
176
  id: createMessageId(),
@@ -186,9 +178,6 @@ export function buildEnvelope(input: BuildEnvelopeInput): HerdrLinkEnvelope {
186
178
  to: input.to,
187
179
  message: input.message,
188
180
  };
189
- if (input.reply_to !== undefined) {
190
- envelope.reply_to = input.reply_to;
191
- }
192
181
  return envelope;
193
182
  }
194
183
 
@@ -209,8 +198,7 @@ export function isHerdrLinkEnvelope(value: unknown): value is HerdrLinkEnvelope
209
198
  typeof v.to === "string" &&
210
199
  isValidAgentName(v.to) &&
211
200
  typeof v.message === "string" &&
212
- v.message.trim() !== "" &&
213
- (v.reply_to === undefined || (typeof v.reply_to === "string" && isValidMessageId(v.reply_to)))
201
+ v.message.trim() !== ""
214
202
  );
215
203
  }
216
204
 
@@ -219,7 +207,7 @@ export function isHerdrLinkEnvelope(value: unknown): value is HerdrLinkEnvelope
219
207
  *
220
208
  * `herdr agent prompt` carries a self-describing wrapper around the
221
209
  * envelope so a dormant receiver (adapter loaded, model not mid-exchange)
222
- * can recognize the delivery and activate a reply addressed by reply_to.
210
+ * can recognize the delivery and activate a response addressed to envelope.from.
223
211
  * The wrapper is transport dressing ONLY: the envelope keeps exactly the
224
212
  * minimal herdr-link/1 fields and is embedded verbatim as the final line.
225
213
  * ------------------------------------------------------------------ */
@@ -234,14 +222,11 @@ export function buildInboundWrapper(envelope: HerdrLinkEnvelope): string {
234
222
  `From: ${envelope.from}`,
235
223
  `Message id: ${envelope.id}`,
236
224
  ];
237
- if (envelope.reply_to !== undefined) {
238
- lines.push(`Reply to: ${envelope.reply_to}`);
239
- }
240
225
  lines.push(
241
226
  "",
242
227
  "The JSON object below is the complete herdr-link/1 envelope; the text around it is delivery metadata and is not part of the message.",
243
228
  'Treat the envelope\'s "message" field as content sent by the agent named in "from".',
244
- "If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to envelope.from with reply_to set to envelope.id.",
229
+ "If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to the agent named in envelope.from.",
245
230
  "",
246
231
  JSON.stringify(envelope),
247
232
  );
@@ -270,7 +255,7 @@ export function extractInboundEnvelope(text: string): HerdrLinkEnvelope | undefi
270
255
 
271
256
  /**
272
257
  * Active Agent Communication Contract injected verbatim into the model.
273
- * Compact form: same-workspace addressing, send/reply/close semantics only.
258
+ * Compact form: same-workspace addressing, send/reply/completion/close semantics.
274
259
  */
275
260
  export const COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperability channel between agents running in the same Herdr workspace.
276
261
 
@@ -278,7 +263,8 @@ export const COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperabili
278
263
  2. Use herdr_link_send to send messages to another agent.
279
264
  3. A message with protocol "herdr-link/1" is an inter-agent message.
280
265
  4. Treat its "message" field as content sent by the agent named in "from".
281
- 5. When replying, send to the received "from" agent and set reply_to to the received "id".
282
- 6. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
283
- 7. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
284
- 8. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
266
+ 5. When replying, use herdr_link_send to the agent named in "from".
267
+ 6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
268
+ 7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
269
+ 8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
270
+ 9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;