mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-12 22:20:33 +00:00
* fix(agents): count each delegation once when a provider reuses its call id Tool-call ids are unique per response, not per conversation. Some providers number every response's calls from call_0, so two task calls in one run can share an id. The delegation ledger used (run_id, tool_call_id) as a delegation's identity: - extract_delegations built one id -> entry map over the whole run, so a later call_0 replaced the earlier delegation's description and took its result while it was still running. - merge_delegations replaced the same (run_id, id), keeping one entry. - SubagentLimitMiddleware counted distinct ids, so the per-run cap (subagents.max_total_per_run) never advanced past the first call_0 and a run could launch any number of subagents one response at a time. Each entry now also records dispatch_message_id, the AIMessage that issued the call, and results pair through pair_tool_call_results, which answers a call only from its own response. The reducer, DurableContext's change filter and the cap use the extended identity. An entry without a dispatch id still updates its run's entry for that call id, and a dispatch-scoped entry adopts a ledger entry written before the field existed, so runs in flight across the upgrade are not counted twice. The steer path names the dispatching AIMessage from the tool request's state, so its delegation update and the graph's own capture stay one entry. * fix(agents): close an earlier run's dangling dispatch by its own response _close_delegations_left_by_earlier_runs decided "answered" by (run_id, tool_call_id). When a stopped run had dispatched call_0 twice, the first reply kept the second, dangling entry in_progress, so the lead kept hearing "already delegated" for work nothing would finish. Entries that carry dispatch_message_id are now decided by pairing that response's calls through pair_tool_call_results. Legacy entries and dispatches no longer in history keep the id-based inference. Also pin that two runs reusing call_0 stay two ledger entries, and cross-reference the three delegation identity helpers. * fix(agents): settle an approval-gated task delegation in the ledger With `task` in `hitl_approval.tools`, the paused run records the dispatch as in_progress and the resume run executes it. The resume run opens with no HumanMessage, so its capture window starts after the pre-existing messages and never contains the dispatching AIMessage: the result was dropped and the entry stayed in_progress with no result_brief, forever. DurableContext now pairs results the resumed window receives with the last pre-existing dispatch and settles the issuing run's entry, keeping its run_id instead of re-tagging it. HITL denials of `task` carry `cancelled` subagent status, so a rejected or denied delegation settles too. * fix(agents): address review on the delegation ledger - Steer path: hold each wait's dispatch AIMessage locally and pass it to the observer, so a later response reusing the call id while the first call is still observed cannot take over its args or dispatch id. - New-turn close: a dispatch whose reply carries subagent status settles from that reply instead of staying in_progress, covering a crash between the tools checkpoint and the next capture. - Reuse thread_state.delegation_key instead of a duplicate identity helper in DurableContext. - CHANGELOG: reference #8128. * docs(changelog): drop the #8128 entry per review