通过消除 status 轮询和启用并行工具调用减少 token 浪费 - #48
Open
hufeide wants to merge 7 commits into
Open
Conversation
…arallel tool calls - captain persona: replace status polling with sleep+wait-for-message - member persona: encourage parallel claim+in_progress and completed+send_message - scheduler: pre-disclose attempt_id to enable parallel calls - tools: discourage status polling in description and create_task output
- create_task: allow forward dependency references (t3 can depend on t1/t2 before they are created in the same parallel batch); validate id format instead of requiring pre-existence - captain persona: instruct to create ALL tasks in one step (parallel); reduce sleep from 60s to 5s since member messages are delivered live via steerCaptainReport (no need for long waits)
- captain persona: allow only ONE sleep 5, then wait for messages; forbid sleep+status cycles and status after sleep - member persona: forbid reading team.json/inbox files; prerequisite results are delivered inline; explicitly ban in_progress after completed - scheduler: inject completed dependency outputs into assignmentPrompt so review/evaluation tasks get prerequisite results without file reads - scheduler: add dependencyOutputs to DispatchTicket, collected from completed dependency tasks at dispatch time
Root cause (from session logs): captain slept 5s, woke up, and even though both poet completion messages were already in its inbox, it slept again for 15s — then again for 15s. Captain did not understand that inbox messages = members are done. Fix: persona now explicitly tells captain that after sleep, the next step automatically contains member messages — if you see 'AgentTeams message from member' in inbox, those members are DONE; process results immediately, do NOT sleep again. Allow at most 2 sleeps total (in case some members haven't reported yet).
Root cause: captain used sleep to 'wait for members', but sleep is unnecessary because steerCaptainReport() calls captain.steer(), which wakes an idle captain automatically (per agent-loop README: 'An idle agent starts a turn synchronously' on steer). Fix: persona now tells captain to simply END its turn after creating tasks (stop calling tools). When members complete and call send_message(to=captain), the steer() wake mechanism automatically starts a new captain turn with the member's message in inbox. This eliminates all sleep steps (~45K cache_read saved) and all status polling.
Problems found in latest run (486K tokens, worse than v4): 1. captain manually called send_message to dispatch tasks after create_task already auto-notified members (redundant, +1 step each) 2. captain still called agent_teams_status 3 times despite ban 3. poet-yi split claim+in_progress into 2 steps instead of parallel 4. poet-jia sent duplicate completion report (re-waking captain) 5. two empty captain steps from spurious wake-ups Fixes: - index.ts: forbid send_message for task dispatch; only for guidance; tell captain to output brief text and end turn after create_task - tools.ts: create_task output now says 'do NOT send_message to dispatch'; status description strengthened with WARNING + ONCE limit - members.ts: claim+in_progress now mandatory parallel (not optional); ban duplicate completion reports; strengthen status ban - scheduler.ts: assignmentPrompt makes parallel claim+in_progress mandatory instead of 'MAY'
- members.ts: unify claim+in_progress wording (remove conflicting MAY/ALWAYS, use mandatory phrasing only) - scheduler.ts: truncate dependency outputs at 2000 chars to prevent oversized assignment prompts - index.ts: split captain rule 4 into two rules (delegation vs post-create behavior) for clarity - tools.ts: add TODO comment for unvalidated forward dependency refs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
通过对 AgentTeams 一次完整的"两首五言绝句 + 评审"任务的会话日志分析,发现 ReAct 循环中存在大量不必要的 step,每个 step 都要重读 ~15K 的 system prompt + 工具 schema(命中 cache_read),导致 token 消耗远超预期。
问题分析
会话日志实测数据(优化前)
88% 的 token 是 cache_read(每步重读固定前缀),而真正的工作(input+output)只占 12%。
三类浪费
1. status 轮询(captain 侧,~97K token)
captain 在创建任务后连续调用了 6 次 `agent_teams_status`,框架的 `repeat-tool-reminder` 插件已注入"Repeated tool call detected: consecutive_calls: 5"警告,但 captain 无视了:
```
s5: status ← 查一次(合理)
s6: status ← 没新消息,再查
s7: status ← 继续查
s8: status ← 框架警告:repeating
s9: status ← 仍然继续
s11: status ← sleep 之后又查
```
每次 cache_read ~15K,6 次共浪费 ~97K token。
根因:captain persona 写了"monitor with agent_teams_status"和"Poll status until every required task is terminal",直接鼓励轮询。
2. 串行工具调用(member 侧,~84K token)
member 把本可并行的工具调用拆成了串行步骤:
```
s3: claim_task ← 拿 attempt_id
s4: update_task(in_progress) ← 等 s3 返回后才调
s5: update_task(completed) ← 单独一步
s6: send_message ← 等 s5 返回后才调
```
实际上 `claim_task` 和 `update_task(in_progress)` 可以并行(DeepSeek API 支持单次返回多个 tool_calls,框架的 `runGroup` 也支持并行执行)。同理 `update_task(completed)` 和 `send_message` 也可以并行。
每个多余的 step 浪费 ~14K token,3 个 member 共浪费 ~84K。
根因:persona 用"Then mark in_progress"的措辞暗示了顺序执行;assignment prompt 说"call claim_task; it will return this same attempt_id"暗示需要先拿返回值。
3. 收尾总结(双端,~37K token)
captain 在 `delete` 后输出了两步纯文本总结;member 在 `send_message` 后也输出了一步总结。这些步骤没有工具调用,纯粹是模型"礼貌收尾"。
```
captain s13: (无工具) cache=18,432 output=364 ← 总结
captain s14: (无工具) cache=19,200 output=267 ← 二次总结
poet-a s7: (无工具) cache=14,336 output=286 ← 收尾总结
```
共浪费 ~37K token。
改动内容
4 个文件,10 行改动,分两层防御:
captain 侧
`src/index.ts` — captain persona(3 处)
`src/tools.ts` — 工具描述 + 输出(2 处)
member 侧
`src/members.ts` — member persona(4 处)
`src/scheduler.ts` — assignment prompt(1 处)
关键点:assignment prompt 里已包含 `attempt_id`,member 不需要等 `claim_task` 返回就知道 attempt_id,因此可以并行调用。
防御层次
```
captain 侧 member 侧
┌───────────────────────┐ ┌───────────────────────┐
│ index.ts persona │ │ members.ts persona │
│ "不要轮询 status" │ │ "不要查 status" │
│ "用 sleep 等待" │ │ "并行 claim+progress" │
│ "delete 后不总结" │ │ "并行 completed+msg" │
└──────────┬────────────┘ │ "发完消息就结束" │
│ └──────────┬────────────┘
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ tools.ts 描述/输出 │ │ scheduler.ts prompt │
│ status: "不要循环" │ │ "已知 attempt_id" │
│ create_task: "已通知" │ │ "可并行调用" │
└───────────────────────┘ └───────────────────────┘
```
每个角色都有两层防御:persona 层 + 工具/prompt 层。即使模型忽略了某一层,另一层仍能阻止浪费行为。
实测效果
member 侧(已验证)
优化后重新运行了相同任务,对比日志:
三个优化全部生效:
captain 侧(预期)
未重新测试,但根据改动逻辑预期:
影响范围
`index.ts` 的 persona 是全局注入的,但只在用户主动要求用 AgentTeams 时生效(persona 开头有"When the user asks to run something with AgentTeams"守卫)。
测试
```bash
npm test
npm run lint
```