diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs index 3e2da6553f..119380327b 100644 --- a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs +++ b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs @@ -189,9 +189,9 @@ class TeamFixtureAdapter extends LlmAdapter { } const chunks = identity === 'TEAM_WORKFLOW_CHILD' ? textChunks('Fresh workflow child complete.') - : identity === '\nYou are teammate "researcher".\n' + : identity === '\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: "", message: "..." }).\n' ? researcher(options.messages) - : identity === '\nYou are teammate "implementer".\n' + : identity === '\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: "", message: "..." }).\n' ? implementer(options.messages) : lead(options.messages) for (const chunk of chunks) { diff --git a/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts index 93e6e27ad4..33752b7812 100644 --- a/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts @@ -862,9 +862,17 @@ describe('headless stream-json snapshots', () => { "identityReminders": [ " 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: "", message: "..." }). ", " 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: "", message: "..." }). ", ], "memberEdges": 4, diff --git a/packages/experimental/tool-agent-team/README.i18n.yaml b/packages/experimental/tool-agent-team/README.i18n.yaml index 2b30aa79fc..4e2ee828f7 100644 --- a/packages/experimental/tool-agent-team/README.i18n.yaml +++ b/packages/experimental/tool-agent-team/README.i18n.yaml @@ -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 diff --git a/packages/experimental/tool-agent-team/README.md b/packages/experimental/tool-agent-team/README.md index bfa3fc7f46..0918686d28 100644 --- a/packages/experimental/tool-agent-team/README.md +++ b/packages/experimental/tool-agent-team/README.md @@ -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 `\nYou are teammate "".\n`, 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 `\nYou are teammate "".\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: "", message: "..." }).\n`, 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 diff --git a/packages/experimental/tool-agent-team/README.zh.md b/packages/experimental/tool-agent-team/README.zh.md index 990e786fd6..8243f8471a 100644 --- a/packages/experimental/tool-agent-team/README.zh.md +++ b/packages/experimental/tool-agent-team/README.zh.md @@ -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 消息前加上 `\nYou are teammate "".\n`,接着是一个空行和任务。该前缀不含 Team id,禁用运行时上下文时也能生效。fork 继承历史,不额外添加 Lead 身份消息。 +一段共享 system 策略会说明显式 delegation 要求、共享 cwd 行为、文件陈旧版本恢复、Bash/formatter/codegen 风险、task/write-scope 协调、Steer 投递、mailbox 不重试规则,以及 Lead 必须在回答前等待。Lead 与 teammate 的全部九个 Team schema 相同;执行时检查仅限 Lead 的操作权限。`spawn_teammate` 在初始 user 消息前加上 `\nYou are teammate "".\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: "", message: "..." }).\n`,接着是一个空行和任务。该前缀不含 Team id,禁用运行时上下文时也能生效。fork 继承历史,不额外添加 Lead 身份消息。 #### Token 影响 diff --git a/packages/experimental/tool-agent-team/src/index.ts b/packages/experimental/tool-agent-team/src/index.ts index 3ab4b77235..75dee88c70 100644 --- a/packages/experimental/tool-agent-team/src/index.ts +++ b/packages/experimental/tool-agent-team/src/index.ts @@ -193,7 +193,15 @@ function install(agent: Agent, ctx: Context, config: Required): () => vo name: args.name, description: args.description, prompt: [ - { type: 'text', text: `\nYou are teammate "${args.name.trim()}".\n\n\n` }, + { type: 'text', text: ` +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: "", message: "..." }). + + +` }, { type: 'text', text: args.prompt }, ], context, diff --git a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts index 198e39f35f..c1c98c19e2 100644 --- a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts +++ b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts @@ -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 === '\nYou are teammate "tool-worker".\n\n\n'))).toBe(true) + block.type === 'text' && block.text === '\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: "", message: "..." }).\n\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(['\nYou are teammate "tool-worker".\n\n\n', 'stay available']) + : []).toEqual(['\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: "", message: "..." }).\n\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: '\nYou are teammate "reviewer".\n\n\n' }, + { type: 'text', text: '\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: "", message: "..." }).\n\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: '\nYou are teammate "reviewer".\n\n\n' }, + { type: 'text', text: '\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: "", message: "..." }).\n\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 = '\nYou are teammate "reviewer".\n\n\n' + const 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: "", message: "..." }).\n\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: '\nYou are teammate "reviewer".\n\n\n' }, + { type: 'text', text: '\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: "", message: "..." }).\n\n\n' }, { type: 'text', text: 'Review the work' }, ]) })