Merge pull request #4951 from deepseek-harness/fix/teammate-lead-guidance

fix(agent-team): explain teammate discovery and messaging
This commit is contained in:
Dudu-0223 authored and GitHub committed 2026-09-22 17:36:31 +08:00
commit 7c296b3f08
7 files changed
+29 -13

No files matched your search

@@ -189,9 +189,9 @@ class TeamFixtureAdapter extends LlmAdapter {
}
const chunks = identity === 'TEAM_WORKFLOW_CHILD'
? textChunks('Fresh workflow child complete.')
: identity === '<system-reminder>\nYou are teammate "researcher".\n</system-reminder>'
: identity === '<system-reminder>\nYou are teammate "researcher".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>'
? researcher(options.messages)
: identity === '<system-reminder>\nYou are teammate "implementer".\n</system-reminder>'
: identity === '<system-reminder>\nYou are teammate "implementer".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>'
? implementer(options.messages)
: lead(options.messages)
for (const chunk of chunks) {
@@ -862,9 +862,17 @@ describe('headless stream-json snapshots', () => {
"identityReminders": [
"<system-reminder>
You are teammate "implementer".
Your Team Lead is named "lead".
Use list_agents({}) to find your teammates and their names.
To message your Team Lead, use send_message({ target: "lead", message: "..." }).
To message another teammate, use send_message({ target: "<teammate name>", message: "..." }).
</system-reminder>",
"<system-reminder>
You are teammate "researcher".
Your Team Lead is named "lead".
Use list_agents({}) to find your teammates and their names.
To message your Team Lead, use send_message({ target: "lead", message: "..." }).
To message another teammate, use send_message({ target: "<teammate name>", message: "..." }).
</system-reminder>",
],
"memberEdges": 4,
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/experimental/tool-agent-team/README.md
README.md: bfa3fc7f466d4b7f405231de3de4cf648fc690de
README.zh.md: 990e786fd69877af17cef339a696bb5e93d5925c
README.md: 0918686d28f7212558aafa988a4b968c18aa8664
README.zh.md: 8243f8471a67fe55d5a5d368d98e20c5519f42f3
@@ -125,7 +125,7 @@ Read these pages when the package-level contract is not enough. They move from t
#### What the model sees
One shared system policy states the explicit-delegation requirement, shared-cwd behavior, filesystem stale-version recovery, Bash/formatter/codegen risk, task and write-scope coordination, Steer delivery, the no-retry mailbox rule, and the Lead's duty to wait before answering. All nine Team schemas are identical for Leads and teammates; execution enforces Lead-only operations. `spawn_teammate` prefixes its initial user message with `<system-reminder>\nYou are teammate "<name>".\n</system-reminder>`, followed by a blank line and the task. The prefix contains no Team id and works when runtime context is disabled. Forks inherit history without an additional Lead identity message.
One shared system policy states the explicit-delegation requirement, shared-cwd behavior, filesystem stale-version recovery, Bash/formatter/codegen risk, task and write-scope coordination, Steer delivery, the no-retry mailbox rule, and the Lead's duty to wait before answering. All nine Team schemas are identical for Leads and teammates; execution enforces Lead-only operations. `spawn_teammate` prefixes its initial user message with `<system-reminder>\nYou are teammate "<name>".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>`, followed by a blank line and the task. The prefix contains no Team id and works when runtime context is disabled. Forks inherit history without an additional Lead identity message.
#### Token effect
@@ -125,7 +125,7 @@ member scope 上的一个 `team:policy` 段落说明共享的协作规则;固
#### 模型看到什么
一段共享 system 策略会说明显式 delegation 要求、共享 cwd 行为、文件陈旧版本恢复、Bash/formatter/codegen 风险、task/write-scope 协调、Steer 投递、mailbox 不重试规则,以及 Lead 必须在回答前等待。Lead 与 teammate 的全部九个 Team schema 相同;执行时检查仅限 Lead 的操作权限。`spawn_teammate` 在初始 user 消息前加上 `<system-reminder>\nYou are teammate "<name>".\n</system-reminder>`,接着是一个空行和任务。该前缀不含 Team id,禁用运行时上下文时也能生效。fork 继承历史,不额外添加 Lead 身份消息。
一段共享 system 策略会说明显式 delegation 要求、共享 cwd 行为、文件陈旧版本恢复、Bash/formatter/codegen 风险、task/write-scope 协调、Steer 投递、mailbox 不重试规则,以及 Lead 必须在回答前等待。Lead 与 teammate 的全部九个 Team schema 相同;执行时检查仅限 Lead 的操作权限。`spawn_teammate` 在初始 user 消息前加上 `<system-reminder>\nYou are teammate "<name>".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>`,接着是一个空行和任务。该前缀不含 Team id,禁用运行时上下文时也能生效。fork 继承历史,不额外添加 Lead 身份消息。
#### Token 影响
@@ -193,7 +193,15 @@ function install(agent: Agent, ctx: Context, config: Required<Config>): () => vo
name: args.name,
description: args.description,
prompt: [
{ type: 'text', text: `<system-reminder>\nYou are teammate "${args.name.trim()}".\n</system-reminder>\n\n` },
{ type: 'text', text: `<system-reminder>
You are teammate "${args.name.trim()}".
Your Team Lead is named "lead".
Use list_agents({}) to find your teammates and their names.
To message your Team Lead, use send_message({ target: "lead", message: "..." }).
To message another teammate, use send_message({ target: "<teammate name>", message: "..." }).
</system-reminder>
` },
{ type: 'text', text: args.prompt },
],
context,
@@ -238,12 +238,12 @@ describe('dsh-tool-team', () => {
expect(renderPrompt(childAssembly)).toBe(leadPrompt)
expect(renderContextSnapshot(childAssembly)).not.toContain('team:identity')
expect(child.session.deriveMessages().some(message => message.content.some(block =>
block.type === 'text' && block.text === '<system-reminder>\nYou are teammate "tool-worker".\n</system-reminder>\n\n'))).toBe(true)
block.type === 'text' && block.text === '<system-reminder>\nYou are teammate "tool-worker".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>\n\n'))).toBe(true)
const initialPrompt = child.session.snapshotEvents().find(event => event.type === 'user/message'
&& event.data.source.kind === 'user')
expect(initialPrompt?.type === 'user/message'
? initialPrompt.data.content.flatMap(block => block.type === 'text' ? [block.text] : [])
: []).toEqual(['<system-reminder>\nYou are teammate "tool-worker".\n</system-reminder>\n\n', 'stay available'])
: []).toEqual(['<system-reminder>\nYou are teammate "tool-worker".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>\n\n', 'stay available'])
const denied = await execute(ctx, child, 'spawn_teammate', {
name: 'nested', description: 'not allowed', prompt: 'no',
@@ -279,14 +279,14 @@ describe('dsh-tool-team', () => {
expect(JSON.stringify(childRequest.messages)).not.toContain('Parent task')
}
expect(childRequest.messages.at(-1)?.content.slice(0, 2)).toEqual([
{ type: 'text', text: '<system-reminder>\nYou are teammate "reviewer".\n</system-reminder>\n\n' },
{ type: 'text', text: '<system-reminder>\nYou are teammate "reviewer".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>\n\n' },
{ type: 'text', text: 'Review the work' },
])
await using persisted = await ctx.sessionPersistence.open(childId, 'read')
const { events } = await persisted.read()
const initial = events.findLast(event => event.type === 'user/message' && event.data.source.kind === 'user')
expect(initial?.type === 'user/message' ? initial.data.content : []).toEqual([
{ type: 'text', text: '<system-reminder>\nYou are teammate "reviewer".\n</system-reminder>\n\n' },
{ type: 'text', text: '<system-reminder>\nYou are teammate "reviewer".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>\n\n' },
{ type: 'text', text: 'Review the work' },
])
})
@@ -377,7 +377,7 @@ describe('dsh-tool-team', () => {
})
const childId = spawnedChildId(ctx, lead, spawned)
await waitNoAgent(ctx, childId)
const reminder = '<system-reminder>\nYou are teammate "reviewer".\n</system-reminder>\n\n'
const reminder = '<system-reminder>\nYou are teammate "reviewer".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>\n\n'
const first = serializeRequest(adapter.requests[0]!).messages
expect(first.at(-1)?.content.slice(0, 2)).toEqual([{ type: 'text', text: reminder }, { type: 'text', text: 'Review the work' }])
expect(adapter.requests[1]!.messages.filter(message => message.content.some(block =>
@@ -419,7 +419,7 @@ describe('dsh-tool-team', () => {
expect(spawned.isError, text(spawned)).toBe(false)
await waitNoAgent(ctx, spawnedChildId(ctx, lead, spawned))
expect(serializeRequest(adapter.requests[0]!).messages.at(-1)?.content).toEqual([
{ type: 'text', text: '<system-reminder>\nYou are teammate "reviewer".\n</system-reminder>\n\n' },
{ type: 'text', text: '<system-reminder>\nYou are teammate "reviewer".\nYour Team Lead is named "lead".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: "lead", message: "..." }).\nTo message another teammate, use send_message({ target: "<teammate name>", message: "..." }).\n</system-reminder>\n\n' },
{ type: 'text', text: 'Review the work' },
])
})