Merge pull request #5885 from deepseek-harness/fix/team-send-tool-receipt

fix(agent-team): omit message IDs from send tool results
This commit is contained in:
Dudu-0223 authored and GitHub committed 2026-10-08 16:10:36 +08:00
commit 12b8066c28
14 files changed
+30 -28

No files matched your search

+2 -2
View File
@@ -264,8 +264,8 @@
en: f9457f033f75f00a
zh: a2d60ec44118c34c
/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/send-message:
en: 0746bb11dae8db55
zh: 8b634add76c4d88a
en: ec50e6a0b86aecc6
zh: 29c8728536a5adf1
/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/spawn-teammate:
en: 02d6cc746e234b99
zh: 49bc60ac098bca48
+1 -1
View File
@@ -2257,7 +2257,7 @@ Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/exper
### `send_message`
Send one message to another Team member and return its accepted message id. A running target receives it at the nearest step boundary; an inactive target starts or resumes a turn.
Send one message to another Team member. A running target receives it at the nearest step boundary; an inactive target starts or resumes a turn.
```json
{
+1 -1
View File
@@ -2263,7 +2263,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
### `send_message`
向另一名 Team member 发送一条消息,并返回获准消息 id。running target 会在最近的步骤边界收到消息;inactive target 会启动或恢复一个 turn。
向另一名 Team member 发送一条消息。running target 会在最近的步骤边界收到消息;inactive target 会启动或恢复一个 turn。
```json
{
@@ -9,8 +9,8 @@
en: 04d7dcbdc1679eae
zh: 8b083641a9262d80
/agent-teams-messages-use-target-inboxes/change:
en: 94d62cf9d98270ab
zh: c589725cd35ccd06
en: 9968decf3dd340af
zh: 736a32caac4b5103
/agent-teams-messages-use-target-inboxes/migration:
en: f7811928da727edc
zh: 9a28b6b745725a6d
en: 7ffd442bfd55769e
zh: ffb11a143f11ce7f
@@ -9,7 +9,7 @@ English | [中文](guide.zh.md)
## Change
The experimental Agent Teams plugin sends new messages directly to the target Agent inbox instead of retaining a separate Team mailbox. `send_message` returns only `messageId`; it no longer returns an `accepted` or `queued` status. Acceptance does not confirm model processing or a synchronous storage flush. Failed sends are not retained for retry, and explicit resends are not deduplicated.
The experimental Agent Teams plugin sends new messages directly to the target Agent inbox instead of retaining a separate Team mailbox. `send_message` returns only `{ "sent": true }`; it exposes neither a message ID nor an `accepted` or `queued` status. Acceptance does not confirm model processing or a synchronous storage flush. Failed sends are not retained for retry, and explicit resends are not deduplicated.
The `maxPendingMessagesPerMember` configuration key is removed. Historical `team/message/queued` and `team/message/delivered` records remain readable, but queued messages that were not delivered before upgrading are never dispatched. Roster and task state remain durable.
@@ -17,5 +17,5 @@ The `maxPendingMessagesPerMember` configuration key is removed. Historical `team
1. Before upgrading, let pending Team messages finish and confirm that their targets received them. After upgrading, explicitly resend any required undelivered message; check the target's history first to avoid duplicates. Do not edit historical Session records.
2. Remove `maxPendingMessagesPerMember` from the `@deepseek-ai/dsh-experimental-agent-team` plugin configuration in your `cordis.yml` or overlays. There is no replacement Team queue limit; `maxMessageBytes` still bounds each complete sender-framed message.
3. Update any consumer of `send_message` results to use `messageId` alone and handle send errors explicitly. Do not treat success as completed target work.
4. Start your Team-enabled profile, send a message to another member, and confirm that the tool returns `messageId` and that the target receives it. Consult the [package reference](../../../../packages/experimental/agent-team/README.md#use-this-package) for delivery semantics.
3. Update any consumer of `send_message` results to read `sent` instead of message IDs or delivery statuses, and handle send errors explicitly. Do not treat success as completed target work. The `ctx.agentTeams.sendMessage()` service still returns `messageId`.
4. Start your Team-enabled profile, send a message to another member, and confirm that the tool returns `{ "sent": true }` and that the target receives it. Consult the [package reference](../../../../packages/experimental/agent-team/README.md#use-this-package) for delivery semantics.
@@ -9,7 +9,7 @@ description: "Agent Teams 移除待投递消息数量限制,并停止投递历
## 变更
实验性 Agent Teams 插件将新消息直接发送到目标 Agent 收件箱,不再保留独立的 Team 邮箱。`send_message` 仅返回 `messageId`,不再返回 `accepted` 或 `queued` 状态。接收成功不表示模型已处理消息,也不保证同步刷新到存储。失败的发送不会保留以供重试,显式重发也不会去重。
实验性 Agent Teams 插件将新消息直接发送到目标 Agent 收件箱,不再保留独立的 Team 邮箱。`send_message` 仅返回 `{ "sent": true }`,不暴露消息 ID,也不返回 `accepted` 或 `queued` 状态。接收成功不表示模型已处理消息,也不保证同步刷新到存储。失败的发送不会保留以供重试,显式重发也不会去重。
配置键 `maxPendingMessagesPerMember` 已移除。历史 `team/message/queued` 和 `team/message/delivered` 记录仍可读取,但升级前尚未投递的排队消息永远不会被派发。成员名单与任务状态仍然持久保存。
@@ -17,5 +17,5 @@ description: "Agent Teams 移除待投递消息数量限制,并停止投递历
1. 升级前,让待投递的 Team 消息完成投递,并确认目标已收到。升级后,显式重发仍需处理的未投递消息;先检查目标历史,避免重复。不要编辑历史 Session 记录。
2. 从 `cordis.yml` 或覆盖配置中 `@deepseek-ai/dsh-experimental-agent-team` 插件的配置移除 `maxPendingMessagesPerMember`。没有替代的 Team 队列限制;`maxMessageBytes` 仍限制包含发送者标识的完整消息大小。
3. 更新所有读取 `send_message` 结果的调用方,仅使用 `messageId`,并显式处理发送错误。不要把发送成功视为目标工作已完成。
4. 启动启用 Team 的 profile,向其他成员发送消息,确认工具返回 `messageId` 且目标收到消息。投递语义参见[包参考](../../../../packages/experimental/agent-team/README.zh.md#use-this-package)。
3. 更新所有读取 `send_message` 结果的调用方,改为读取 `sent` 而非消息 ID 或投递状态,并显式处理发送错误。不要把发送成功视为目标工作已完成。`ctx.agentTeams.sendMessage()` 服务仍返回 `messageId`。
4. 启动启用 Team 的 profile,向其他成员发送消息,确认工具返回 `{ "sent": true }` 且目标收到消息。投递语义参见[包参考](../../../../packages/experimental/agent-team/README.zh.md#use-this-package)。
@@ -27,8 +27,8 @@
en: be42811bbcc3b438
zh: 014bd055ca694315
/deepseek-ai-dsh-experimental-tool-agent-team/use-this-package/what-success-and-failure-look-like:
en: 259e9d39e1d1579a
zh: c61feed86e1ba2d2
en: e49a980372c55892
zh: d4bcdd36278d040d
/deepseek-ai-dsh-experimental-tool-agent-team/understand-the-implementation:
en: 3952eb1d9de1f988
zh: 789d51b88f41b710
@@ -65,7 +65,7 @@ Creation and listing results identify members by `target`, with no member Sessio
### What success and failure look like
Sending a message succeeds as soon as it is safely stored: the result is `accepted` (delivered now) or `queued` (waiting), and a queued message must not be resent. `wait_agent` returns `noProgress` right away when no other member is running or provisioning, telling the caller to wake a teammate first; otherwise it waits for the next change and the caller re-reads state afterward. Task edits based on an outdated revision are rejected rather than overwriting newer work.
`send_message` returns `{ "sent": true }` after inbox acceptance, without exposing the message ID. Success does not confirm recipient processing or a synchronous storage flush; send failures are reported as tool errors. `wait_agent` returns `noProgress` right away when no other member is running or provisioning, telling the caller to wake a teammate first; otherwise it waits for the next change and the caller re-reads state afterward. Task edits based on an outdated revision are rejected rather than overwriting newer work.
-----
@@ -65,7 +65,7 @@ kind: "package-reference"
### 成功与失败的表现
发送消息在安全存储后即成功:结果为 `accepted`(已立即送达)或 `queued`(等待中),排队的消息绝不能重发。当没有其他成员 running 或 provisioning 时,`wait_agent` 会立即返回 `noProgress`,提示调用方先唤醒 teammate;否则它会等待下一次变化,调用方随后重新读取状态。基于过期 revision 的任务编辑会被拒绝,而不是覆盖更新的成果。
`send_message` 在收件箱接收消息后返回 `{ "sent": true }`,不暴露消息 ID。成功不表示接收方已处理消息,也不保证同步刷新到存储;发送失败会作为工具错误报告。当没有其他成员 running 或 provisioning 时,`wait_agent` 会立即返回 `noProgress`,提示调用方先唤醒 teammate;否则它会等待下一次变化,调用方随后重新读取状态。基于过期 revision 的任务编辑会被拒绝,而不是覆盖更新的成果。
-----
@@ -34,7 +34,7 @@ The Team Lead and all teammates share the same working directory and filesystem.
Prefer read/edit/write for file changes. If a file operation returns FS_STALE_VERSION, read the current file, rebase your intended change onto the new content, and retry. Bash, formatters, code generators, and scripts are not fully protected by the filesystem version guard; coordinate them explicitly and have the Lead review the final diff and run tests.
Use the target returned by spawn_teammate or list_agents for send_message and interrupt_agent, or as owner when assigning or filtering shared tasks. send_message steers a running target at its nearest step boundary and starts or resumes an inactive target. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. provisioning means member creation is in progress; failed means member creation failed. A peer message starts with its sender name. A successful send returns the accepted message id; it does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, use list_agents and make sure another required member is running or provisioning; use send_message first when the required member is inactive. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member can produce a change. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer.`
Use the target returned by spawn_teammate or list_agents for send_message and interrupt_agent, or as owner when assigning or filtering shared tasks. send_message steers a running target at its nearest step boundary and starts or resumes an inactive target. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. provisioning means member creation is in progress; failed means member creation failed. A peer message starts with its sender name. A successful send does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, use list_agents and make sure another required member is running or provisioning; use send_message first when the required member is inactive. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member can produce a change. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer.`
const ACTIVE_WAIT_STATUSES: ReadonlySet<TeamMemberView['status']> = new Set(['running', 'provisioning'])
const NO_ACTIVE_PEER_MESSAGE = 'No other Team member is running or provisioning. wait_agent cannot make progress or wake inactive teammates. Re-list with list_agents and team_task_list, then use send_message to wake each required inactive teammate before waiting again.'
@@ -97,7 +97,7 @@ const SEND_VALUE_SCHEMA = {
type: 'object',
additionalProperties: false,
properties: {
messageId: { type: 'string', required: true },
sent: { type: 'boolean', required: true, const: true },
},
} as const
@@ -213,18 +213,19 @@ To message another teammate, use send_message({ target: "<teammate name>", messa
register(scoped.tools.register(defineTool({
name: 'send_message',
description: 'Send one message to another Team member and return its accepted message id. A running target receives it at the nearest step boundary; an inactive target starts or resumes a turn.',
description: 'Send one message to another Team member. A running target receives it at the nearest step boundary; an inactive target starts or resumes a turn.',
parameters: {
target: { type: 'string', required: true, description: 'Member target returned by spawn_teammate or list_agents, including lead.' },
message: { type: 'string', required: true, description: 'Self-contained message for the target.' },
},
output: jsonOutput(SEND_VALUE_SCHEMA),
execute(args, exec) {
return ctx.agentTeams.sendMessage(callingAgent(exec.agent, 'send_message'), {
async execute(args, exec) {
await ctx.agentTeams.sendMessage(callingAgent(exec.agent, 'send_message'), {
target: args.target,
content: [{ type: 'text', text: args.message }],
signal: exec.signal,
})
return { sent: true }
},
})))
@@ -202,6 +202,7 @@ describe('dsh-tool-team', () => {
expect(JSON.parse(text(tasks))).toMatchObject({ tasks: [{ id: task.id, ownerName: member.target }] })
const sent = await execute(ctx, lead, 'send_message', { target: member.target, message: 'review the diff' })
expect(sent.isError).toBe(false)
expect(JSON.parse(text(sent))).toEqual({ sent: true })
const interrupted = await execute(ctx, lead, 'interrupt_agent', { target: listed[1]!.target })
expect(interrupted.isError).toBe(false)
await child.whenIdle()
@@ -483,10 +484,10 @@ describe('dsh-tool-team', () => {
expect(text(roster)).toBe(JSON.stringify(JSON.parse(text(roster))))
const peer = await execute(ctx, child, 'send_message', { target: 'lead', message: 'progress report' })
expect(peer.isError).toBe(false)
expect(JSON.parse(text(peer))).toHaveProperty('messageId')
expect(JSON.parse(text(peer))).toEqual({ sent: true })
const followup = await execute(ctx, child, 'send_message', { target: 'lead', message: 'review the report' })
expect(followup.isError).toBe(false)
expect(JSON.parse(text(followup))).toHaveProperty('messageId')
expect(JSON.parse(text(followup))).toEqual({ sent: true })
await lead.whenIdle()
const created = await execute(ctx, lead, 'team_task_create', {
@@ -27,7 +27,7 @@
{"type":"step/start","data":{"turn":1,"step":3}}
{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"direct-3","name":"send_message","arguments":"{\"target\":\"reviewer\",\"message\":\"Review the direct inbox delivery.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1790429366017,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790429366017,"index":0,"dt":[],"id":"direct-3","name":"send_message","args":["{\"target\":\"reviewer\",\"message\":\"Review the direct inbox delivery.\"}"]},{"type":"chunk","time":1790429366017,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"direct-3","name":"send_message","arguments":"{\"target\":\"reviewer\",\"message\":\"Review the direct inbox delivery.\"}"}}},{"type":"chunk","time":1790429366017,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1790429366017,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":3,"callId":"direct-3","name":"send_message","arguments":"{\"target\":\"reviewer\",\"message\":\"Review the direct inbox delivery.\"}"}}
{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"direct-3"},"toolCallId":"direct-3","content":[{"type":"text","text":"{\"messageId\":\"{{message:17}}\"}"}],"isError":false,"id":"{{message:9}}"}},"sourceEventSeqs":[27],"surfaceOp":"append"}
{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"direct-3"},"toolCallId":"direct-3","content":[{"type":"text","text":"{\"sent\":true}"}],"isError":false,"id":"{{message:9}}"}},"sourceEventSeqs":[27],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":3}}
{"type":"step/start","data":{"turn":1,"step":4}}
{"type":"assistant/message","data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"direct-4","name":"interrupt_agent","arguments":"{\"target\":\"reviewer\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:10}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1790429366033,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790429366033,"index":0,"dt":[],"id":"direct-4","name":"interrupt_agent","args":["{\"target\":\"reviewer\"}"]},{"type":"chunk","time":1790429366033,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"direct-4","name":"interrupt_agent","arguments":"{\"target\":\"reviewer\"}"}}},{"type":"chunk","time":1790429366033,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1790429366033,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
@@ -11,7 +11,7 @@ The Team Lead and all teammates share the same working directory and filesystem.
Prefer read/edit/write for file changes. If a file operation returns FS_STALE_VERSION, read the current file, rebase your intended change onto the new content, and retry. Bash, formatters, code generators, and scripts are not fully protected by the filesystem version guard; coordinate them explicitly and have the Lead review the final diff and run tests.
Use the target returned by spawn_teammate or list_agents for send_message and interrupt_agent, or as owner when assigning or filtering shared tasks. send_message steers a running target at its nearest step boundary and starts or resumes an inactive target. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. provisioning means member creation is in progress; failed means member creation failed. A peer message starts with its sender name. A successful send returns the accepted message id; it does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, use list_agents and make sure another required member is running or provisioning; use send_message first when the required member is inactive. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member can produce a change. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer.
Use the target returned by spawn_teammate or list_agents for send_message and interrupt_agent, or as owner when assigning or filtering shared tasks. send_message steers a running target at its nearest step boundary and starts or resumes an inactive target. inactive means no turn is executing; it does not describe task completion, success, failure, or waiting for other agents. provisioning means member creation is in progress; failed means member creation failed. A peer message starts with its sender name. A successful send does not mean the recipient has processed it. Shared-task workflow is list, get, claim with the current revision, perform the work, then complete. Task readiness never starts an owner. Before wait_agent, use list_agents and make sure another required member is running or provisioning; use send_message first when the required member is inactive. wait_agent observes only changes after that call starts, never wakes a member, and returns noProgress immediately when no other member can produce a change. Re-list after wakeup or timeout. The Lead must wait for required teammates before giving the final answer.
Check the [exit code: N] marker on every bash result; investigate failures before moving on.
@@ -293,7 +293,7 @@
},
{
"name": "send_message",
"description": "Send one message to another Team member and return its accepted message id. A running target receives it at the nearest step boundary; an inactive target starts or resumes a turn.",
"description": "Send one message to another Team member. A running target receives it at the nearest step boundary; an inactive target starts or resumes a turn.",
"parameters": {
"type": "object",
"properties": {