2690 Commits

Author SHA1 Message Date
Tanishq
2a16b26960
fix(compaction): build the manual-compaction summarization models off the event loop (#7806)
compact_thread_context built the summarization middleware inline on the
Gateway event loop, and with it the run-profile model and any separately
configured summary model. When the provider reads local credential files
(the Codex provider reads the Codex CLI auth.json while it is constructed),
each POST /api/threads/{thread_id}/compact did synchronous file IO on the
shared loop, once for every model it built, so other requests and streams
waited for it. The route passes no app_config, so the call also read the
live config on the loop first, which stats and hashes config.yaml on every
call. #7767 listed this site in the utils guidance as not covered.

Build the middleware through run_assembly, as #7484 and #7767 do for the
one-shot helpers, skill moderation and the goal evaluator, and read the live
config in that pool too when no app_config is passed. The lead agent already
builds the same middleware there through run_agent's agent factory call.
Model resolution, the memory-flush policy, the disabled, failed and
pending-work outcomes and the written checkpoint are unchanged, and
cancellation during construction neither starts the summary call nor writes
a checkpoint.

tests/blocking_io/test_compaction_model_offloop.py builds real
CodexChatModels under the strict Blockbuster gate. On main, compaction
reported itself disabled because every model build hit BlockingError
(os.stat), and the /compact handler answered 500 because the gate rejected
its config read first.

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 21:46:14 +08:00
lilei
d8b0d24a84
fix(summarization): preserve native document text during compaction (#7825)
* fix(summarization): preserve native document text during compaction

* fix(summarization): retain metadata for opaque documents

* docs: keep agent guidance within size limits

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 21:42:13 +08:00
liyinlong
8d7e48e5e7
fix(memory): restore facts from older backups (#7921)
* fix(memory): restore facts from older backups

Use the current snapshot revision for existing facts during backup import so saved contents can be restored after later edits. Preserve manifest and fact preconditions, scope isolation, and caller-owned input data.

* refactor(memory): share import revision preconditions

* docs(memory): fit import guidance within chain budget

* fix(memory): validate backup revisions before restoration

---------

Co-authored-by: liyinlong <173714821+lilong555@users.noreply.github.com>
2026-11-06 21:39:18 +08:00
Hyeonsang Cho
61d76d9a6a
fix(channels): key WeCom group conversations by chatid, not the sender (#7794)
* fix(channels): key WeCom group conversations by chatid, not the sender

Group messages used the sender's userid as chat_id and topic_id, the same
key as the sender's private chat. Wherever no group connection kept them
apart (auth-disabled mode, require_bound_identity: false, legacy group
bindings), a member's private chat and every group they wrote in ran in one
DeerFlow thread. Groups are now keyed by chatid with one thread per member.
A group-scoped connection carries its sender-keyed thread over once; a
legacy binding, which spans every group of the bot, does not.

* docs(changelog): reference #7794 for the WeCom group conversation fix

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 21:36:19 +08:00
lilei
7e64330dd1
fix(memory): avoid recursive tool-argument admission (#7928)
* fix(memory): avoid recursive tool-argument admission

* fix(memory): track argument redaction changes without deep equality
2026-11-06 21:24:57 +08:00
yetuge
a41d0fcec1
fix(parsing): keep literal think tags inside JSON string values (#7907)
* fix(parsing): keep literal think tags inside JSON string values

Structured parsers ran strip_think_blocks over the whole response before
json.loads. A model answer that quotes the tag -- "Explain <think>example
</think>" -- lost the quoted text, and an unclosed literal tag inside a
string cut the JSON document mid-string, so suggestions returned None and
goal evaluation failed to parse.

Strip reasoning only outside JSON string literals: leading blocks are still
removed whole (a JSON example inside reasoning is never taken as the
answer), and blocks after the payload starts are removed only outside
quotes. Suggestions and the goal evaluator both use the shared helper.

* fix(parsing): keep think-tag stripping complete outside the JSON payload

The string-aware walker only covered the payload itself: a reasoning block
after prose and before the payload was copied verbatim, so brackets inside
it became the parsers' find/rfind targets, and an unclosed block in
trailing prose was kept where strip_think_blocks used to truncate.

Find the payload start while stepping over think blocks, strip the head with
strip_think_blocks, keep strip/truncate semantics after the top-level
payload closes, and truncate at an unclosed block outside a string. Adds a
CHANGELOG entry for the user-visible parsing change.
2026-11-06 21:19:52 +08:00
Chau Vu
856cde25b1
fix(gateway): strip server-owned metadata from run input todos and goal (#7803 follow-up) (#7904)
* fix(gateway): strip server-owned metadata from run input todos and goal (#7803 follow-up)

Commit 459fc8c introduced shape validation for caller-written todos in
both strip_server_owned_state_metadata and normalize_input, but only
applied _strip_external_metadata_from_message_like in
strip_server_owned_state_metadata.

External callers submitting POST /runs or POST /threads/{id}/runs with
input.todos or input.goal could supply forged server-owned metadata
(such as untrusted_input: False, original_user_content, or artifact
knowledge_sources) directly into the agent graph.

Apply _strip_external_metadata_from_message_like to both normalized
todos and goal in normalize_input when trusted_internal=False.

* fix(gateway): mirror normalized goal and todos into canonical run record input

* fix(gateway): replace non-dict additional_kwargs and cover edge cases

- Ensure non-dict additional_kwargs in message-like entries is replaced with an empty dict instead of surviving the strip
- Remove redundant None-check on normalized_goal in normalize_input
- Add regression tests covering non-dict additional_kwargs replacement and untrusted_input stamping on human-like entries

* test(gateway): format todo input validation cases

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 21:14:28 +08:00
Hoouse
f9e05c24ea
docs(tutorials): add required tool names and groups (#7922)
* docs: complete tool configuration examples in tutorials

Signed-off-by: ZKDplz <2646995705@qq.com>

* test(docs): validate tutorial tool configuration examples

Signed-off-by: ZKDplz <2646995705@qq.com>

* docs: complete tool configuration examples in related guides

---------

Signed-off-by: ZKDplz <2646995705@qq.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 21:10:30 +08:00
baolong lin
07b4e06697
fix(skills): pair a tool result with its own response's calls (#7929)
Tool-call ids are unique per assistant response, not per conversation.
extract_skills built a single id -> path map over the whole message
list, so a later read that reused an id overwrote the earlier one: the
earlier skill silently dropped out of the ledger and its correct
ToolMessage logged "mismatched skill read metadata".

Rebuild the map from each AIMessage's own calls and resolve a
ToolMessage against the nearest preceding response, matching the
pairing rule tool_call_args.pair_tool_call_results already applies.

Fixes #7858

Signed-off-by: Batianxieshen1 <280706568+Batianxieshen1@users.noreply.github.com>
Co-authored-by: Batianxieshen1 <280706568+Batianxieshen1@users.noreply.github.com>
2026-11-06 19:53:32 +08:00
lisry-1
c0b0ac3489
fix(feishu): clear msg.files after receive_file downloads them (#7804)
receive_file downloaded each image/file descriptor and replaced the
[image]/[file] placeholders in msg.text, but left msg.files intact. The
generic URL-based _ingest_inbound_files pass then re-walked the residual
{"image_key":...}/{"file_key":...} descriptors, found no url, and logged a
spurious 'reader returned no data' warning per file.

Clear msg.files after materializing the paths, matching DingTalk (msg.files =
[]) and the Slack/Telegram/Discord providers (which materialize them).

Co-authored-by: lisry-1 <249596828+lisry-1@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 11:27:36 +08:00
hataa
e163b02bea
fix(provisioner): keep POSIX hostPath values POSIX on Windows hosts (#7905)
join_host_path joined POSIX-styled bases with the running OS's native
path module, so a provisioner running on Windows emitted backslash
hostPath values into pod specs — paths a Linux kubelet cannot resolve.
The same leak existed in _host_base_dir_for_extra_mounts,
_is_path_under_base, extra-mount validation, and PVC subPath
computation, all of which used os.path on paths that are POSIX by
contract. Route every non-Windows-styled path through posixpath and
keep the PureWindowsPath branch only for genuinely Windows bases.

Also re-spell the memory guide probe layout with forward slashes so
backend/tests/test_memory_documentation.py compares against the
documented layout instead of the test host's local separator.
2026-11-06 11:19:13 +08:00
Rei
097fa6dca7
fix(frontend): fence a confirmed background-task cancellation against stale polls (#7914)
A background-task detail or list read issued before the user cancels can
resolve after the mutation's success handler and overwrite the confirmed
"cancelled" snapshot with the pre-cancel "working" one, so the expanded card
shows the task as active again (and polling resumes) until a later response
corrects it.

useCancelBackgroundTask now awaits cancelQueries for the thread's
["background-tasks", threadId] prefix before seeding the confirmed detail and
list entry, so a read that started before the cancel is dropped instead of
landing afterwards. The following list invalidation is unchanged, so a mounted
card still refreshes from the server. This is the same ordering contract as the
memory mutations fixed in #6959.

Regression: frontend/tests/unit/core/background-tasks/hooks.dom.test.tsx uses a
real QueryClient with deferred detail and list reads; it ends on "working"
without the fix and keeps "cancelled" with it.

Co-authored-by: REI-330 <173389910+REI-330@users.noreply.github.com>
Co-authored-by: AochenShen99 <sac1063467113@hotmail.com>
2026-11-06 10:29:57 +08:00
哈基米
ee006e0c86
fix(community): initialize InfoQuest TLS clients off the event loop (#7899)
* fix(community): initialize InfoQuest TLS clients off the event loop

InfoQuest's fetch, web search and image search constructed
httpx.AsyncClient inline in their coroutines, so the default transport
loaded the TLS trust store synchronously on the agent event loop
(~125 ms per call in a local probe). RAGFlow, LightRAG, You.com,
GroundRoute and Webz already construct their clients with
asyncio.to_thread; InfoQuest was the missed sibling.

Move the three constructions to asyncio.to_thread and cover all three
methods in the blocking-I/O TLS test. Requests, cleanup, timeouts and
responses stay on the event loop.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* docs: qualify the off-loop HTTPX provider list

The list is not exhaustive; several community clients still construct
their client inline. Use non-exhaustive wording until the follow-up
sweep lands.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* docs: correct the off-loop provider list after merging main

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-06 10:26:42 +08:00
hdu_zhou
2feec9cb09
fix(mcp): degrade a blob without mimeType instead of failing the tool call (#7800)
* fix(mcp): degrade a blob without mimeType instead of failing the tool call

A resource link or embedded resource without mimeType made the blob guard
raise, so the whole tool call failed instead of degrading the attachment.
Keep the guard for genuinely unusable payloads and let a missing media type
fall through to the caller's default.

* Modify test for text blocks and mime type validation

Update test to check for text blocks and their mime types.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* test(mcp): remove MIME assertion from text content block

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-11-05 21:40:32 +08:00
Chau Vu
4a53a36e32
fix(extensions): roll back shared state mutations on budget check failure (#7902)
* fix(extensions): roll back shared state mutations on budget check failure

In deerflow_extension_agent_teams.shared_state, publish() and update()
mutated team['public_state'] in-memory before invoking check_budget().
When check_budget() raised a budget-exceeded ValueError (e.g. 16,000 JSON
bytes or 24,000 shared context bytes), the state mutation was not rolled
back, causing:
1. Permanent state lockout on rejected publish (the oversized entry
   remained in state['entries']).
2. False optimistic concurrency conflicts on update retry (the entry
   revision was prematurely bumped and content altered).

Wrap check_budget() in try-except in both functions:
- On publish failure: pop the appended entry and decrement revision.
- On update failure: restore original entry dict and decrement revision.
- Added regression test covering both rollback scenarios.

* test(extensions): request plugin fixture for isolated regression run

* fix(extensions): add rollback identity guard and pin retry assertions
2026-11-05 16:06:20 +08:00
inchang-ing
9731bcf28a
fix(browserless): construct the fetch and screenshot clients off the event loop (#7913)
* fix(browserless): construct the fetch and screenshot clients off the event loop

* fix(browserless): close the client a cancelled construction still builds

Keeping the constructor off the loop is right, but awaiting
`asyncio.to_thread(httpx.AsyncClient, ...)` directly is not cancellation-safe:
the await can be cancelled while the worker thread is still inside the
constructor. The await is abandoned, the thread runs on and hands back a live
client, and execution never enters the `async with` that would have closed it,
so `is_closed` stays False and the connection pool leaks on that cancellation.

Own the construction as a task instead. When the caller is cancelled, wait for
the task with the shield loop this package already uses for cleanup, close
whatever client it produced, and only then let the cancellation propagate. A
failure while closing is logged rather than raised, so it cannot mask the
cancellation the caller asked for.

The new regression test gates the constructor on a thread event, cancels the
operation while it is parked there, and releases the worker afterwards. Against
the previous commit it fails for both fetch_html_with_status and
capture_screenshot with "the client built after the cancellation was leaked".

---------

Co-authored-by: inchang-ing <197932532+inchang-ing@users.noreply.github.com>
2026-11-05 16:04:45 +08:00
Qiu
7f9e0bf611
fix(sandbox): defer fresh AIO skill-policy acquisition (#7782)
* fix(sandbox): defer fresh AIO skill-policy acquisition

Prepare the authorized thread skill view before the model runs without
acquiring an AIO container for a fresh lazy run. Keep existing sandbox
bindings and upload-based providers on the eager rebind/sync path.

Add sync/async regressions for first-tool authorization, strict rebinding,
filtered read-only AIO mounts, and policy-scoped warm reuse.

AI assistance: Codex; the account owner confirmed human review of the
complete seven-file patch and responsibility for the change.

* docs(sandbox): keep lazy-isolation guidance within inherited budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 15:37:13 +08:00
Totoro
593ba49458
feat(subagents): continue requested batch work and schedule explicit prerequisites (#7839)
* feat(subagents): continue native batches and honor item dependencies

* docs: link native batch follow-up changelog entries

* test(migrations): pin isolated history-seed upgrade to 0050

* fix(subagents): preserve follow-up scope, blockers and fair scheduling

* test(memory): freeze capacity evaluation time

* fix(subagents): guard follow-up admission and retain run controls

* fix(subagents): enforce runs:cancel on tool cancellation of follow-up batches

* style(tests): format test_batch_followup_host with ruff

* fix(subagents): drop the admission checkpoint check and keep SDK cancel compatible

The checkpoint check inside run admission waited on the checkpoint saver's
lock while holding the SQLite write lock, which could make another chat's
checkpoint write fail. Remove it; dispatch still checks the conversation
before admission.

A follow-up waiting for an unanswered question now keeps its claim until the
lease expires instead of rereading the conversation every poll. Cancel passes
the caller's run authority only to submitters that take it, so existing SDK
submitters keep the two-argument call. Document that retrying a prerequisite
by hand does not requeue its blocked items, and list 0052 as the migration head.

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 15:10:21 +08:00
dengzi
24d93399fb
fix(lark): keep Windows SID lookup working with non-ASCII account names (#7845)
* Keep the Windows SID lookup alive on non-UTF-8 console output

`_resolve_current_user_sid` calls `whoami /user /fo csv /nh` in text mode
without an encoding. `make dev` and `make gateway` start the Gateway with
PYTHONUTF8=1, so on a Chinese Windows host the interpreter decodes as UTF-8
while whoami writes the account name in the console's ANSI code page. The
strict decode raises inside subprocess's reader thread; the exception is not
re-raised, so stdout comes back as None and the credential-tree hardening
walk dies with `AttributeError: 'NoneType' object has no attribute 'strip'`
before any directory is created or hardened.

Pin the encoding and keep an errors policy. The SID is the second CSV field
and is pure ASCII, as are the delimiters, so replacing the undecodable
account name is harmless. #6590 pinned the seven lark-cli/npm captures in
this file; this is the eighth text-mode capture and, unlike those, does not
emit UTF-8, so the errors policy is the part that matters here.

Add a cross-platform regression test next to the #6590 encoding tests.

* docs(lark): correct the SID capture wording

`undecodable` is the spelling used elsewhere in the repository, and the
comment about the console code page reads better as "which does not need to
match". No behaviour change.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 15:09:13 +08:00
lukyturnflow
bef8c9c2e3
fix(skills): keep disabled skills disabled when the extensions config cannot be read (#7705)
* fix(skills): fail closed when the extensions config cannot be read

* test(skills): cover a disabled skill when the extensions config cannot be read

* style(tests): format the fail-closed regression for ruff

* style(tests): fit the docstring and signature inside the 240-column limit

* fix(skills): fail closed in the user-scoped listing when the config cannot be read

* test(skills): cover the user-scoped listing with an unreadable extensions config

* fix(gateway): keep skill read surfaces loud about an unreadable extensions config

Folding the user-scoped degraded mode in (#7704) also stopped the skill
read endpoints from failing on a config that cannot be read: the storage
now answers with an all-disabled listing, which regressed the #7155
contract (a generic 500 with the host path only in the server log) into
a 200 that presents a broken deployment as a legitimately empty one.

Keep both properties: the storage still fails closed for the runtime
gate, and the read surfaces re-validate the config explicitly so the
raised error reaches each handler's generic 500 branch unchanged.
First-run behavior is untouched: no config file resolves to an empty
config without raising, so default-enabled still applies.

* fix(skills): redact config-load failures and keep the read preflight off the event loop

Review follow-ups:

- Log the failure shape instead of the exception text in both degraded
  modes: from_file resolves $VAR placeholders before validation, so its
  message can embed resolved credentials. Reuse _describe_load_failure,
  the loader's own redaction helper.
- Run the gateway read preflight through asyncio.to_thread in the
  capability adapter, matching the scan it precedes.
- Cover the user-scoped degraded branch with a CUSTOM skill that has
  per-user state on disk, so the second read's guard is directly
  regression-tested (verified red with the guard removed).

---------

Co-authored-by: Jay0130-a <309071492+Jay0130-a@users.noreply.github.com>
2026-11-05 15:07:07 +08:00
Wenchao An
b6ed47778d
feat(extensions): add versioned team file handoffs (#7817)
* feat(extensions): add versioned team file handoffs

* fix(extensions): fence team artifact execution and snapshots

* fix(agent-teams): retain resumed keys and bound artifact transfers

* ci: reclaim runner space before sandbox image smoke

* ci: budget sandbox image preparation separately

* fix(extensions): version artifact contracts and document snapshots
2026-11-05 14:50:09 +08:00
Wenchao An
c1f9064e9b
fix(models): route managed OpenAI GPT-6 presets through Responses (#7870)
* fix(models): route managed OpenAI GPT-6 presets through Responses

* test(memory): pin capacity scenarios to a stable clock

* fix(models): honor Flash mode for managed OpenAI GPT-6

* fix(models): reconcile preset effort with explicit reasoning contracts

* test(memory): share the fixed clock with updater capacity tests

* fix(models): normalize GPT-6 reasoning overrides and sampling

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 14:30:42 +08:00
asts
59beff5799
fix(mcp): load personal transport TLS trust off the event loop (#7909) 2026-11-05 14:27:52 +08:00
yetuge
bebbd2cb35
fix(crawl4ai): construct the fetch client off the event loop (#7906)
* fix(crawl4ai): construct the fetch client off the event loop

Crawl4AiClient builds an httpx.AsyncClient per fetch, and AsyncClient
construction loads the TLS trust store synchronously: on the agent event
loop that blocks every other request for the CA bundle read. Construct it
through asyncio.to_thread, the same rule the RAGFlow/LightRAG and
You.com/GroundRoute/Webz clients already follow; the request and cleanup
stay on the loop.

* docs(agents): list Crawl4AI among the off-loop HTTPX clients

#7889 extended the same sentence for its providers; keep the named list in
the same change set as the client, and record the clients that still
construct on the loop so the next migration has a checklist.
2026-11-05 14:24:21 +08:00
Rei
bb7958886d
fix(mcp): fence notification claims on SQLite (#7859)
* fix(mcp): fence notification claims on SQLite

claim_notification_work() selects due generations with FOR UPDATE SKIP LOCKED, which SQLite ignores. Two workers could read the same due generation before either writes, both returned it with different lease tokens, and a stale flush could overwrite the winning owner's token or phase.

On SQLite, claim each candidate with a conditional UPDATE fenced on the event version, notification status, dispatch version and the absent-or-expired notification lease the read saw, so a peer claim or a newer recorded event wins and the losing claim has no side effects. Pin the dispatch version on the empty-snapshot consumption fence too. PostgreSQL keeps its existing row-locking path.

Coverage: a paused claim that loses a race to a second worker returns no row and leaves the winner's owner, token and phase stored.

* fix(mcp): honor deferred notification backoff in SQLite claims

claim_notification_work() fenced the observed event version, phase, dispatch version and lease, but not next_notification_at. A peer can claim a due row, then release the claim (release_notification_lease) or defer a dispatched one (defer_dispatched_notification) without changing phase, moving the next attempt into the future and clearing the lease. A paused claim still matched and returned the notification immediately, bypassing retry/defer backoff.

Fence the claim on the observed next_notification_at value too, and cover the interleaving with a regression test.

---------

Co-authored-by: REI-330 <173389910+REI-330@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 14:19:05 +08:00
Harvey DU
b561242ba3
fix(harness): reject Windows reserved device names in thread ID validation (#7892)
* fix(harness): reject Windows reserved device names in thread_id validation

A caller-supplied thread ID of NUL (any casing) passes
validate_thread_id() but Paths.ensure_thread_dirs() then fails on
Windows hosts with FileNotFoundError (WinError 3), because NUL is a
reserved device name in every directory.

Reject the reserved device names (CON, PRN, AUX, NUL, COM1-9, LPT1-9)
case-insensitively in the shared validator, including the ".ext"
form kept legal by recent Windows versions, so thread IDs stay safe
for every filesystem backend regardless of host OS.

* style(harness): apply ruff format to thread_id validation

* refactor(harness): reuse windows_incompatible_segment for thread ID check

Address review feedback on #7892:

- Drop the private _WINDOWS_RESERVED_DEVICE_NAMES copy and route the
  thread-ID check through the canonical windows_incompatible_segment()
  from deerflow.utils.host_paths, which uploads, sandbox writes and
  skill support files already use. The canonical list also covers
  CONIN$/CONOUT$ and the superscript-digit COM/LPT variants.
- Remove the unreachable stem-split: the thread-ID pattern cannot
  contain a dot, so test whole IDs and pin the reserved-name branch
  with the dedicated 'reserved Windows device name' message; the
  unreachable 'NUL.txt' case is removed from the parametrization.
- Document the default-behavior change (Linux-host threads with
  reserved-name IDs will fail validation after upgrade; deletion is
  unaffected) under ### Fixed in CHANGELOG.md.
2026-11-05 14:13:23 +08:00
huyengiang101086-spec
8331afa759
fix(agents): pair null call IDs with empty result IDs (#7837)
* fix(agents): pair null call IDs with empty result IDs

* fix(agents): treat producer sentinel IDs as absent when pairing

Middleware producers spell a missing provider ID three ways: "" is canonical,
str(tool_call.get("id", "")) yields "None" once the key is present with a
null value, and the artifact/deferred/knowledge middlewares fall back to
"missing_tool_call_id". Only "" reached the pairing pass, so a result carrying
either sentinel was dropped as an orphan and the middleware's guidance was
replaced by the interrupted placeholder — the exact outcome this PR set out to
avoid, just through a different spelling.

Route every absent spelling through one canonicalisation used by the pairing
gate, the candidate comparison and the turn-completeness count.

---------

Co-authored-by: huyengiang101086-spec <270684046+huyengiang101086-spec@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 14:07:15 +08:00
hdu_zhou
9e2cf685b0
fix(memory): treat a content-less deermem message as empty, not None (#7854)
extract_message_text falls back to str(content), so a message whose content is None becomes the truthy literal 'None'. That string then reaches signal detection and the memory-update prompt as if the user had written it. The canonical helper (utils/messages.py:message_content_to_text) and the mem0 copy both return '' for this shape and the canonical one documents exactly this failure mode.

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 12:25:51 +08:00
哈基米
713a861e75
fix(channels): ignore non-finite WeChat server longpoll timeouts (#7898)
* fix(channels): ignore non-finite WeChat server longpoll timeouts

The getupdates response's longpolling_timeout_ms was passed through
float() with only TypeError/ValueError handled. A huge JSON integer
raised OverflowError before the message loop and cursor write, so the
same batch was re-polled forever, while Infinity/NaN were stored and
turned the next request timeout into inf/nan.

Apply the same positive-finite rule the channel already uses for
configured timing values: catch OverflowError and reject non-finite
results, keeping the previous valid timeout.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* fix(channels): bound WeChat server long-poll hints

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 12:07:31 +08:00
一片一片
459fc8ca43
fix(todos): keep an unusable stored todo list from failing every model call (#7803)
* fix(todos): keep an unusable stored todo list from failing every model call

`todos` is a reducer channel that `POST /threads/{id}/state` and run input can
Overwrite directly, but neither boundary validated its shape. The todo
middleware rendered it with `entry.get(...)` in both `before_model` and
`after_model`, so a non-object entry — or a value that is not a list — raised
AttributeError on every model call, permanently for that thread.

Validate caller-written todos beside `goal` in `_normalize_external_todos`: a
list of objects with string content and a known status, or HTTP 400. The
middleware now reads the channel through `_usable_todos` and ignores entries of
any other stored shape, so checkpoints and direct integrations written before
this boundary still run.

* fix(todos): normalize stored todos in token-usage attribution

TokenUsageMiddleware reads the same stored `todos` channel through
`_build_attribution` whenever the last AIMessage calls write_todos, and
`token_usage.enabled` defaults on, so a value the todo middleware now tolerates
still aborted the step here. Route the stored list through `_normalize_todos`
before reading entries.

Document that a caller-written todo without a status is accepted as pending and
cover that shape at both external boundaries.

* docs: keep backend agent guidance within size budgets

* fix(todos): keep the channel metadata strip on caller-written todos

The dedicated todos branch in strip_server_owned_state_metadata replaced the
generic list branch, which stripped server-owned metadata from every entry. A
caller could therefore persist additional_kwargs.original_user_content,
untrusted_input, or artifact.knowledge_sources on a todo entry. Re-apply the
per-entry strip after normalization, keeping None and empty lists untouched.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 07:28:33 +08:00
Cordis798
6538496dbc
fix(memory): apply Honcho read policies to malformed search metadata (#7890)
Co-authored-by: Cordis798 <284918518+Cordis798@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 07:04:15 +08:00
Wenchao An
2145c9ab1f
feat(models): discover provider catalogs and search chat models (#7871)
* feat(models): discover provider catalogs and search chat models

* docs(models): scope catalog guidance to owning directories

* test(guidance): register model catalog scoped instructions

* test(memory): pin capacity scenarios to a stable clock

* fix(models): guard catalog imports and align picker interactions

* test(memory): share the fixed clock with updater capacity tests

* fix(models): clear provider filters removed by catalog refresh

* refactor(models): share connection fields across model forms

* fix(models): keep active provider filters clearable

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-05 06:43:01 +08:00
yetuge
a46d9221f8
fix(summarization): build the summary model off the Gateway event loop (#7874)
Context compaction runs from abefore_model/aafter_model on the shared loop,
and its async candidate loop constructed the summary provider inline.
create_chat_model can read local CLI credentials while it is constructed
(Codex auth.json, Claude Code credential files), so an automatic compaction
stalled every other request for that read.

Split construction out of _model_for and add _amodel_for, which builds the
provider on the assembly pool; the sync path keeps building inline. Anchor
and failure-cache semantics are unchanged, so a broken candidate still falls
through to the next one, and inference still awaits on the loop.

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 21:54:47 +08:00
MasterZRC
2ceda05bf9
fix(sandbox): release ports after definite CLI spawn failures (#7887)
* fix(sandbox): release ports after definite CLI spawn failures

* test(memory): pin hybrid capacity evaluation clock
2026-11-04 21:44:45 +08:00
aduoge
26bf78f2b9
fix(github): apply allow_authors to the webhook sender (#7868)
_author_login returned the issue/PR creator for issues and pull_request
events. On edited/labeled/reopened actions the actor is sender, so an
allow_authors user editing someone else's issue was gated on mention,
while anyone editing an allow_authors user's issue bypassed it. Prefer
sender.login and keep the object author as a fallback.

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 21:35:55 +08:00
Cordis798
4c4a4e9858
fix(community): initialize search TLS clients off the event loop (#7889)
Co-authored-by: Cordis798 <284918518+Cordis798@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 20:54:43 +08:00
easT-T
3dd9e8841f
fix(frontend): preserve live subtask steps during history backfill (#7864)
* fix(frontend): preserve live subtask steps during history backfill

* test(deermem): keep updater eviction fixtures fresh

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 20:16:09 +08:00
ZhiLin Zhang
e5969fd8b9
feat(harness): introduce HarnessGuardMiddleware for pre-write syntax validation and anti-doom-loop (#7624)
* feat(harness): introduce HarnessGuardMiddleware for pre-write syntax validation and anti-doom-loop

- Add HarnessGuardMiddleware to intercept tool writes and validate .py (AST) and .json syntax before disk write
- Add anti-doom-loop state machine detecting repeated error signatures (>=2) and A -> B -> A -> B oscillating loops
- Inject targeted remediation hints directly into tool results
- Add DynamicSkillRouter for zero-LLM JIT skill intent activation (<1ms)
- Fully unit-tested with 25 passing test cases

* fix(harness): address review findings on write tools, scoping, and content handling

* fix(harness): offload async validation off-thread, support Command results, and harden parsing

* fix(harness): address reviewer 1 feedback on scoping, assembly count, and non-blocking verification

- Revert .gitignore to exclude out-of-scope graphify-out entry
- Short-circuit syntax validation when block_on_syntax_error is false
- Trim AGENTS.md rule chain to fit hard guidance budget (AG002)
- Fix input_sanitization mock and update subagent middleware count expectation to 23

* fix(harness): preserve chunked write flow, normalize CRLF line endings, and guard error classification

- Preserve append-in-chunks flow: use codeop to distinguish incomplete Python fragments from syntax errors, and handle premature EOF on chunked JSON arrays/objects
- Reuse native _match_line_endings for str_replace to prevent CRLF line ending bypass
- Check existing tool failure status before classifying error text to avoid misclassifying successful tool outputs
- Add 4 regression tests covering chunked writes, CRLF replacement, and successful error-text reads

* fix(harness): scope pre-write syntax validation to str_replace and preserve write_file semantics

* fix(harness): make str_replace syntax check opt-in, allow progressive repair of invalid files, and clean up RFC docs

* chore: align subagent middleware count and satisfy AG002 guidance budget

---------

Co-authored-by: Yakyou7 <Yakyou7@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 20:13:19 +08:00
golemon
5371343f69
fix(gateway): continue dispatching queued inputs after cancellation (#7746)
* fix(gateway): continue queued inputs after cancellation

* docs(runtime): clarify queued input cancellation handling

* fix(gateway): clarify cancelled-input error and log race-window skips

* docs(changelog): document queued input cancellation fix

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 10:39:20 +08:00
leom1919
fcbb14e83f
docs: replace dead pull/5224 links with squash-merge commit permalinks in subagents reference (en/zh) (#7849)
* docs: replace dead pull/5224 links with squash-merge commit permalinks in subagents reference (en/zh)

The "Related changes" list in harness/subagents/reference.mdx cites PR #5224
("tool assembly off the event loop"), but the PR and its issue counterpart were
deleted by repository administrators, so both URLs return 404. The PR itself was
merged: squash commit 5727449 matches the cited description verbatim.

Same approach as #7689 (changelog deleted-PR references): keep the [#5224] text
and entry description, replace the URL with the squash-merge commit permalink.
1 line changed in each of the en and zh pages.

* test(deermem): keep eviction fixtures fresh

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-04 06:28:20 +08:00
yetuge
6f24bc7c36
fix(title): build the title model off the Gateway event loop (#7813)
Thread title generation runs inside the agent graph (aafter_model), and it
built its configured model inline. create_chat_model can read local CLI
credentials while it is constructed (Codex auth.json, Claude Code credential
files), so the first turn of every new thread read them on the shared loop
and other requests waited for it.

Build the provider through run_assembly, the dedicated assembly pool the
one-shot helpers, skill moderation, the goal evaluator and start_run already
use. Inference still awaits on the loop, and the sync path is unchanged.

A Blockbuster regression builds the real Codex provider: on main the gate
rejects the credential read, the broad fallback swallows it, and the local
fallback title replaces the model's title; with the change the provider is
constructed on a worker thread and the model's title is kept.
2026-11-04 04:31:57 +08:00
Nan Gao
1dd6d05627
fix(extensions): hand compaction observers the live task store and name the task in CompactionEvent (#7831)
* feat(extension-api): add task identity fields to CompactionEvent (0.2.10)

CompactionEvent carried the transform, its hashes and its counts, but
nothing that names the task, run or thread the compaction happened in.
An observer that wants to relate a compaction to the run that produced it
had to match hashes across sessions, which is unreliable.

Append three optional fields, task_id, run_id and thread_id (str | None,
default None). Existing keyword and positional construction is unchanged,
so already-released extensions keep working. The host fills them in the
next commit.

Bump deerflow-extension-api to 0.2.10 and the harness's exact pin with it.

* fix(extensions): hand compaction observers the live task store and its identity

The summarization middleware already recovers the live task store from
the runtime to observe its summary system call, and the registry
allocates that store precisely because a compaction observer is
registered. notify_context_compacted() then discarded it and handed every
observer a detached store, with a docstring claiming there was no live
task.

- The lead run worker and the subagent executor now seed every task store
  they allocate with the task's TaskInfo, not only when a lifecycle
  contributor is registered; the lifecycle notification reuses the seeded
  object. A subagent without a parent run_id still gets no TaskInfo.
- compact_state() and acompact_state() pass the store from
  task_store_from_runtime() to _record_compaction().
- notify_context_compacted() takes an optional task_store. A live store
  goes to the observers and fills the event's task_id/run_id/thread_id
  from its TaskInfo; without one (manual /compact runs outside any run)
  observers still receive a detached store and the ids stay None.

For a background subagent the store keeps its public scope id (the
provider tool_call_id) while TaskInfo.task_id stays the execution id, as
lifecycle contributors already see it; the event follows TaskInfo.

* docs(extensions): document compaction event identity and seeded TaskInfo

The extensions guide and the developer manual still said compaction
observers always receive a detached store because no task is live at the
call site. Describe what the host does now: lead runs and subagents with a
parent run seed every task store with TaskInfo, compaction observers get
the live store, and CompactionEvent's task_id/run_id/thread_id come from
it; only manual /compact falls back to a detached store with None ids.
Adds the 0.2.10 row to the contract version table (PR number pending).

* docs(changelog): note compaction observers receive the live task identity

* docs(extensions): list compaction observers among task-store triggers

* docs: reference #7831 for the compaction event identity change

* docs(extensions): backfill contract history rows 0.2.5 to 0.2.9

The version table jumped from 0.2.4 to 0.2.10 although 0.2.5-0.2.9
shipped. Add those rows, attributing names merged without a bump to
the first release that carried them (#5796 in 0.2.5, #6920 and #5645
in 0.2.8, #7729 in 0.2.10). The page intro now says it documents
0.2.4 plus the 0.2.10 CompactionEvent fields, and points to the
history for the rest.
2026-11-03 15:06:52 +08:00
Xiao Hou
14abd9a28e
fix(subagents): terminalize a background cancel that beat the loop (#7788)
* fix(subagents): terminalize a background cancel that beat the loop

request_cancel_background_task() calls Future.cancel() on the future handed
back by _submit_to_isolated_loop_in_context(). That call only wins while the
coroutine has not been picked up yet; when it does win, run_with_timeout never
starts, so no one writes a terminal status and no one opens the cleanup fence.
The registry entry then stayed PENDING forever: task_tool polled its full
budget for a result that could not arrive, and cleanup_background_task()
refused to drop a non-terminal entry.

Publish CANCELLED from the future's own completion callback when the coroutine
body never ran. A cancellation that did reach a running coroutine keeps its
existing owner -- the future reports cancelled() for that case as well, which
is why the callback checks a start marker instead of cancelled() alone.

* fix(subagents): publish a pre-start cancel from the isolated loop

`Future.cancel()` on the future returned by
`_submit_to_isolated_loop_in_context()` fires `forget_future` in the calling
thread, but the loop keeps owning the coroutine until its first step runs. A
cancel that lands in that window read the start marker as clear and published
CANCELLED — opening the cleanup fence — while the worker could still take its
capacity slot.

Hand the decision to the isolated loop and re-check the marker there, which is
the only place serialized against that first step. A loop that is gone cannot
start the coroutine, so the inline fallback keeps the original guarantee.

* test(subagents): accept both terminal owners in the task-created cancel window

Task.__init__ queues the first step inside the task factory, so the step can
run before the deferred pre-start decision that was handed to the loop. When
it does, the coroutine's own CancelledError branch legitimately publishes
"Cancelled by user"; the harness had pinned the pre-start message instead.
Assert the invariants both owners share -- terminal cancelled, fence opened
only after the loop settled the body, cleanup removes the entry -- and let
either error message pass.

* fix(subagents): refuse to start a cancelled execution before taking a slot

A cancel that wins the race against the loop is decided on the loop, one turn
after the submission callback queues the wrapper's first step. That turn is not
a guarantee: the wrapper could still start, create the execution task, and take
a capacity slot after the terminal status and the cleanup fence had already been
published from that decision.

request_cancel_background_task sets cancel_event before it cancels the
future, so the wrapper can treat that event as the startup half of the handshake
and decline to create the execution task. The fence then cannot be outrun by a
worker that acquires capacity afterwards.

* test(subagents): hold the cancel window open with an event barrier

The factory blocked the loop for a fixed 200 ms and then resumed on its own. A
caller delayed longer than that -- a loaded CI worker is enough -- let execution
start before the cancel landed, and `assert not execution_ran.is_set()` failed
even though cancellation was handled correctly.

Block the factory on an event instead and release it from the test's `finally`
after the cancel request and the closed-fence assertions, so the window stays
open exactly as long as the test needs it.

---------

Co-authored-by: gouyu-hou <213296172+gouyu-hou@users.noreply.github.com>
2026-11-02 21:36:59 +08:00
dependabot[bot]
12c304f12c
chore(deps): bump pyasn1 from 0.6.4 to 0.6.5 in /backend (#7796)
Bumps [pyasn1](https://github.com/pyasn1/pyasn1) from 0.6.4 to 0.6.5.
- [Release notes](https://github.com/pyasn1/pyasn1/releases)
- [Changelog](https://github.com/pyasn1/pyasn1/blob/main/CHANGES.rst)
- [Commits](https://github.com/pyasn1/pyasn1/compare/v0.6.4...v0.6.5)

---
updated-dependencies:
- dependency-name: pyasn1
  dependency-version: 0.6.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-02 15:56:14 +08:00
spud
21175ba46f
fix(runtime): persistently fence stale run event producers (#7619)
* fix(runtime): fence revoked run event producers

* fix(runtime): reduce off-mode producer guard locking

* docs: keep inherited guidance within size budget

* chore(runtime): remove development phase labels

* fix(persistence): normalize libpq SSL options for asyncpg connections

* docs(runtime): clarify producer fencing behavior in off mode

* test(persistence): align migration TLS probes with channel-binding rejection

* fix(runtime): authorize fenced Gateway history seeding

* fix(scheduler): preserve takeover identity in recovery publication

* fix(scheduler): report unconfirmed recovery publication

* fix(runtime): preserve history messages before the first human turn

* fix(runtime): clarify run creation failure diagnostic

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-02 15:26:42 +08:00
Willem Jiang
01e1ed778b
docs: restore agent guidance size budgets (#7810) 2026-11-02 15:20:54 +08:00
linkangjun
3f86f071f3
fix(middlewares): prevent todo retries after text-only output caps (#7605)
* fix(middlewares): prevent todo retries after text-only output caps

* docs(middlewares): clarify text-only output cap markers

* test(subagents): isolate cleanup test from shared assembly pool

* docs(middlewares): keep cap guidance within chain budget

---------

Co-authored-by: linkangjun <331227811+linkangjun@users.noreply.github.com>
2026-11-02 10:46:59 +08:00
王一帆
d0ec8dcc5d
feat(knowledge): add optional ingestion and status center (#7518)
* feat(knowledge): add optional ingestion and status center

* docs(knowledge): record upstream CI budget fix and regression results

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-02 10:42:21 +08:00
Cordis798
92325bd126
fix(community): load retrieval TLS trust off the event loop (#7738)
Co-authored-by: Cordis798 <284918518+Cordis798@users.noreply.github.com>
2026-11-02 10:35:15 +08:00
Hyeonsang Cho
2c8892c810
fix(channels): deliver the DingTalk reply when an AI card request fails (#7781)
* fix(channels): deliver the DingTalk reply when an AI card request fails

dingtalk-stream's AICardReplier logs a failed card request and returns
as if it succeeded; its create call returns the card ID it generated
before the request. The channel stored that ID, so neither the running
reply's text fallback nor send()'s sampleMarkdown fallback could run,
every streamed update failed silently, and the final answer never
reached DingTalk.

Send createAndDeliver and the streaming update through the channel's
httpx client with its token, using the bodies the SDK sends, and raise
on HTTP and transport failures so both fallbacks run. The SDK replier,
its per-card map and the token shim it needed are removed.

* docs(changelog): reference #7781 for the DingTalk AI card fallback

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-02 10:34:03 +08:00