* 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
* fix(subagents): apply tool policies to middleware-declared tools
* fix(subagents): address tool-policy review feedback
Normalize declaration conversion errors with middleware context, document
generated discovery exemptions, and add the Unreleased changelog entry.
Audit final explicit and middleware tool sets before graph creation, with
identity-based exemptions for generated discovery helpers and regressions.
* fix(subagents): reuse tool name resolution and clarify policy errors
* fix(client): scope memory management to the named agent's bucket
A named DeerFlowClient stored and read its memory in that agent's bucket:
MemoryMiddleware and the memory tools both pass agent_name. The management
methods on the client did not, so create_memory_fact wrote the fact into the
user's default bucket while the agent's injected context came from its own
bucket — the fact was persisted but never recalled by the same client.
Pass the client's agent_name through get_memory, export_memory,
import_memory, reload_memory, clear_memory and the single-fact CRUD calls.
An unnamed client keeps the default bucket, and a backend that ignores the
scoping keyword keeps raising its own unsupported-operation error.
Fixes#8142
* fix(client): refuse named memory calls a backend cannot scope
DeerFlowClient forwarded the agent name to every memory management call, but
a backend that declares no agent-scoped storage ignores the keyword and
answers normally. A named client then reported success while the call read,
cleared or exported the user's default bucket, the same silent
wrong-bucket behaviour #8142 is about. Check the capability flag before
forwarding the scope and raise an unsupported-operation error, as the
Gateway already does with 501.
Resolve the scope in reload_memory before the get_memory fallback so an
unsupported scope is not mistaken for a backend without reload support and
retried as a user-wide read.
* chore: drop the CHANGELOG entry
The changelog is generated at release time, and the hand-written entry points
at #8146 without a link definition, which breaks
tests/test_changelog_reference_links.py. Removing it keeps the file released-
generated, per the reviewer's request.
* docs(memory): explain scoped noop client behavior
---------
Co-authored-by: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
* fix(browserless): reject timeouts httpx cannot use
`_coerce_timeout` forwarded any int, float or numeric string to
`httpx.AsyncClient(timeout=...)` unchanged, so a configured `timeout_s: 0`
or a negative value failed every fetch and capture with `ConnectTimeout`
against a healthy backend, `nan` raised `ValueError: deadline must not be
NaN`, and `inf` left the request without a deadline so it never completed.
Each of those silently disabled the provider.
Fall back to the documented 30s default with a warning when the number is
not a usable deadline. This matches `crawl4ai._coerce_timeout` after #7481;
`jina_ai._coerce_timeout` still forwards non-positive values.
* fix(browserless): address timeout review and format tests
Explain how HTTPX handles NaN and positive infinity. Document that
Browserless accepts positive finite timeouts without an upper limit,
and pin that behavior with a 1e308 test case.
Crawl4AI and Jina timeout validation are tracked in #7481 and #7823.
Remove the Browserless docstring's claim that Crawl4AI shares its
coercion behavior. Format the test file for the backend lint check.
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
In long-running agent workflows with repeated context compaction, empirical gateway ablations across 132 real sessions revealed that standard boundary compliance remains robust, while semantic drift can occur when model-generated summaries rephrase literal approval phrases and action conditions.
This change converges PR #8114 into a clean built-in summary prompt enhancement:
- Replaces regex extraction, tag elevation, and heuristic revocation with an extended built-in DeerFlow summary prompt (_DEERFLOW_SUMMARY_PROMPT).
- Instructs the summarizer to faithfully record active user requirements, literal approval phrases, permitted actions, and later explicit revisions without paraphrasing.
- Treats summary text strictly as historical data, fully preserving the security boundary and control/data plane separation of DurableContextMiddleware.
- Unifies automatic and manual compaction factory paths to select the built-in template when unconfigured, while preserving explicit summary_prompt precedence.
- Adds comprehensive sync/async unit tests covering built-in and custom templates.
Co-authored-by: Totoro <Totoro-qaq@users.noreply.github.com>
Co-authored-by: Totoro <msy626836554@gmail.com>
* docs: turn README into an index and move reference detail to docs/guide
The README had grown to 393 KB because every change appended its notes there. The text moves verbatim into 18 topic guides under docs/guide/; only headings are re-leveled and relative links re-pointed. The README keeps the introduction, a quick start and a guide index. Contributor test notes move to CONTRIBUTING.md.
* docs: send user docs to topic guides and budget the root README
The rule 'update README.md for user-facing changes' is what grew the README. Guidance now points to docs/guide/, and check_agent_guidance.py fails when the root README exceeds 16 KiB (AG003).
* chore: remove unreferenced screenshots and finished plan notes
pr-build/ holds two PR screenshots (3 MB) from #1986. plans/ holds the plan for the merged #4049. docs/SKILL_NAME_CONFLICT_FIX.md is a February change note. Nothing references them.
* docs: route always-loaded AGENTS.md files to scoped guides
* docs(agents): cut scoped guides to scope, verify, traps, decisions
Each AGENTS.md keeps four kinds of content: the scope of the directory, the
verification commands, traps a local test run does not show, and decisions the
code cannot state. Values, field lists, function inventories, test names, issue
numbers and history are deleted, because the code, the tests and git state them.
Search and fetch providers share one community guide. Four guides with no
remaining content are removed. The size check now allows 6 KiB per file (hard
limit 8 KiB) and 24 KiB per chain (hard limit 32 KiB).
* style: format frontend guide table and guidance test imports
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>