280 Commits

Author SHA1 Message Date
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
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
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
Tanishq
bf9b031127
fix(skills,goal): build the skill-moderation and goal-evaluator models off the event loop (#7767)
scan_skill_content and the goal evaluator built their chat model inline on
the Gateway event loop. When the provider reads local credential files (the
Codex provider reads the Codex CLI auth.json while it is constructed), each
scanned skill edit, rollback, install or agent-managed skill write, and the
evaluator construction in each goal run, did synchronous file IO on the
shared loop, so other requests and streams waited for it. #7484 removed the
same stall for the one-shot helpers and listed these two constructors in the
utils guidance as not covered.

Build both providers through run_assembly, as the one-shot helpers do: the
moderation model in scan_skill_content, the evaluator in
evaluate_goal_completion when no model is injected, and the run worker's
cached evaluator factory. A scan without app_config (the in-graph
skill_manage call) also reads the live config in that pool, and its
fail-closed fallback reuses that config instead of reading config.yaml again
on the loop; an unreadable config still fails closed. Verdicts, the
fail-open/fail-closed policy, error propagation and tracing are unchanged,
and cancellation during construction never starts inference.

tests/blocking_io/test_system_model_offloop.py builds a real CodexChatModel
under the strict Blockbuster gate. On main the scan fell back to its
fail-closed "unavailable" block, the evaluator raised BlockingError
(os.stat), and the worker stood the goal down as evaluator_failed.
2026-11-02 09:24:31 +08:00
Evan
02df37c5de
fix(runtime): charge a steered subagent's usage to the token budget (#7776)
* fix(runtime): charge a steered subagent's usage to the token budget

A task that finishes after a Steer reports through a receipt HumanMessage, so its usage never reached the dispatching AIMessage the token budget reads. Carry the usage over when the receipt is delivered.

* fix(runtime): report deferred usage with no AIMessage to charge

Log the rare case instead of dropping the usage silently, and scan for AIMessages once per delivery.
2026-11-01 23:28:04 +08:00
Evan
be39a8a879
fix(runtime): resume children paused for a withdrawn steer (#7753)
* fix(runtime): resume children paused for a withdrawn steer

wait_tool pauses every child as soon as a steer is pending, but only a
delivered steer sets steering_active, the flag finish_graph needs before it
resumes them. Removing the steer before delivery left the children parked
and the run waiting on them.

Release the children when deliver finds no input and no steer is in
progress. They stay paused during later model turns of a delivered steer.

* fix(runtime): release children paused by a steer withdrawn mid-turn

The steering_active guard skipped the release whenever another steer was
being handled. A child started during that turn is outside its
interrupted_children snapshot, so nothing resumed it after a second steer
was withdrawn and finish_graph waited forever.

Release every child a delivered steer does not hold. Each deliver re-takes
that snapshot, so a paused child outside it can only belong to a withdrawn
steer.

* fix(runtime): keep a replacement reusing a call ID out of the held steer

interrupted_children stores call IDs, and child() can replace a closed gate under the same ID. The replacement then looked held by an earlier delivered steer, so a withdrawn steer left it paused forever in a replaced turn. Drop the ID when its gate is replaced.
2026-11-01 23:18:49 +08:00
Totoro
a17e5694cc
fix(sandbox): stop owned command processes when a run is cancelled (#7772)
* fix(sandbox): stop owned command processes when a run is cancelled

* fix(sandbox): handle incomplete AIO output responses

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-01 23:09:25 +08:00
Evan
c6a3993695
fix(runtime): keep each parallel task's gate across a steer (#7755)
wait_tool reused the name gate for its pause loop, so every parallel task
returned the last child's result id and its done callback closed that
child's gate. The message reducer then dropped all but one pending result,
and the model was told that a running subagent had been interrupted.

Rename the loop variable so each call keeps its own gate.
2026-10-31 21:40:59 +08:00
Totoro
44e372dd7f
feat(runtime): support Queue and Steer during active Web runs (#7729)
* feat(runtime): add Queue and Steer for active Web runs

* style(frontend): format Queue and Steer controls

* fix(runtime): retain user Steer controls and order active inputs

* docs: add Chinese changelog entry for Queue and Steer

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-31 11:35:36 +08:00
Lucien
178a9ab458
fix(web): preserve tool-result identity when provider call IDs repeat (#7672)
* fix(web): preserve independent results when tool call IDs repeat

* fix(web): preserve leading results and idempotent result replay
2026-10-31 08:41:07 +08:00
yinx65588-boop
cdc3e92d6b
fix(streaming): isolate in-memory replay payloads (#7644)
* fix(streaming): isolate in-memory replay payloads

* docs(streaming): explain sentinel identity during replay

* docs(streaming): document replay payload ownership

---------

Co-authored-by: yinx65588-boop <yinx65588-boop@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-31 08:15:24 +08:00
RongJie G
9e6ce2f98e
fix(runtime): normalize non-finite floats in canonical serializer (#7670)
serialize_lc_object() passed floats through unchanged, so a non-finite
value (NaN/inf/-inf) survived serialization and broke the two display
paths that share it with opposite JSON policies: the SSE wire
(format_sse / Redis bridge use the lenient default and emit bare
NaN/Infinity tokens the langgraph-sdk/useStream decoder rejects,
breaking the live stream) and the REST surface (Starlette JSONResponse
uses allow_nan=False and raises, turning a thread read into HTTP 500).

Non-finite floats reach the serializer from ordinary runtime data:
json.loads("1e400") yields inf for a tool parsing external JSON, a
provider logprob of -inf for a zero-probability token, or a divide-by-
zero ratio. They round-trip inside Python and checkpoint persistence and
only fail at the JSON boundary.

Normalize non-finite floats to None and recurse into model_dump()/dict()
output so nested values (e.g. a message's response_metadata) are
normalized too. This is a display/wire normalization only; persisted
checkpoint state is never routed through this serializer. Finite floats
are unchanged.

Signed-off-by: CorgiBoyG <111257566+CorgiBoyG@users.noreply.github.com>
2026-10-30 08:49:30 +08:00
LingYi
30aa601f6a
fix(runtime): isolate run admission from unrelated heartbeats (#7664)
Signed-off-by: LingYi-Liang <270316896+LingYi-Liang@users.noreply.github.com>
Co-authored-by: LingYi-Liang <270316896+LingYi-Liang@users.noreply.github.com>
2026-10-29 22:19:10 +08:00
NanPan
f82f9b2829
fix(events): rebase RunEvent snapshot isolation onto current main (#7423) 2026-10-28 20:59:10 +08:00
SumEn
c58c71f3fa
fix(runs): move thread index membership on snapshot replacement (#7622) 2026-10-28 20:47:01 +08:00
Totoro
c48447a4ae
fix(runtime): present outputs before a loop wrap-up and explain step-limit stops (#7588)
* fix(runtime): present outputs before a loop wrap-up and explain step-limit stops

Loop-detection warnings now ask the agent to present the files it already
created in /mnt/user-data/outputs before its final answer, when
present_files is available.

A run that exhausts recursion_limit now ends with stop_reason
recursion_capped and a plain message that lists outputs it created but
did not present. An active goal stands down without an evaluator call.
The /wait error envelope keeps the GraphRecursionError name, and IM
channels reply with the step-limit message instead of a generic failure.

* docs(changelog): note run-end delivery and step-limit stops (#7588)

* test(runtime): stop pinning LangGraph's step count in the step-limit test

The produced paths and the message already follow the observed model
calls, so the exact count only pinned LangGraph's per-step accounting.
Also note on the step-limit goal branch that its caller passes no
evaluator factory, so it has to stay ahead of the evaluator section.

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-27 10:34:34 +08:00
SumEn
f7d02a7515
fix(checkpoints): validate cached delta history before replay (#7548)
* fix(checkpoints): reject malformed Redis history write records

Validate pending-write tuples before optional Redis history entries reach
delta materialization. Treat the whole malformed entry as a miss so the
authoritative saver rebuilds it without dropping or inventing state.

Cover actual checkpoint reads, whole-entry replacement, valid tuple/list
records, neighbor preservation and warm-cache rereads.

* fix(checkpoints): bind cached history writes to their channel

* fix(checkpoints): keep cache warning failures best-effort

Guard warning handlers across decode, ingestion, serialization, Redis outages and purge. Preserve authoritative rebuilds and valid neighboring writes, with native graph regression coverage.

* docs(gateway): keep guidance below the hard size budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-27 00:28:03 +08:00
spud
aabfdeebc9
fix(streaming): make Redis stream END publication idempotent (#7572)
* fix(streaming): make Redis stream END publication idempotent

* fix(streaming): align Redis test fake with Lua END checks

Remove the unreachable owned missing-stream branch, mirror tail-only data checks in both fake script paths, and pin legacy END and owned missing-buffer behavior against real Redis.

* fix(streaming): gate legacy END scans after writer rollout

* fix(helm): synchronize embedded config version with schema

* test(runtime): use real stream bridge config in close coverage

Keep new configuration defaults in lifecycle fixtures and surface owner-task failures instead of masking them as event timeouts. Drain every helper task and cover context entry/close failures.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-27 00:16:53 +08:00
haotianqi666
4f6ee59535
fix(gateway): preserve null input for static continuation (#7578)
* fix(gateway): preserve null input for static continuation

* fix(runtime): preserve continuation inputs during MCP projection

---------

Co-authored-by: haotianqi666 <250384991+haotianqi666@users.noreply.github.com>
2026-10-26 21:32:25 +08:00
Willem Jiang
3d5107dc0d
fix(ci): keep runtime guidance within the chain budget (#7522)
Move checkpoint modes, replay, module maps and terminal-ownership details intact into the existing runtime reference. Keep concise invariants and explicit links in AGENTS.md, reducing the effective chain from 99,004 to 80,817 bytes. The existing guidance regression now passes without changing budget thresholds.
2026-10-26 10:19:30 +08:00
spud
33f7b43400
fix(runtime): backfill run.terminal after confirmed recovery takeover (#7500)
* fix(runtime): backfill run.terminal on confirmed recovery takeover

* fix(runtime): isolate recovery terminal publication failures

* fix(runtime): fence recovery terminal publication lifecycle

* fix(runtime): unify recovery receipt publication

* docs(runtime): document lifecycle guard lock constraints

* fix(runtime): reuse event transaction for recovery guards

Read Run lifecycle through the active event session when SQL stores share an engine, preserving deletion fencing without a second pool checkout. Keep custom lookups and different engines on their ordinary read path. Scope memory recovery guards and deletions to per-thread locks. Add SQLite/PostgreSQL pool pressure, stale identity, fallback, and independent-thread regressions, and document the contracts.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-25 23:22:02 +08:00
SumEn
d9e6341e5b
fix(checkpoints): isolate cached history payloads from state readers (#7259)
* fix(checkpoints): isolate cached history payloads from state readers

Copy nested snapshot seeds and pending-write payloads at both cache boundaries.
Keep user copy callbacks outside the registry lock so readers cannot poison
later checkpoint materialization or deadlock by re-entering the cache.

Cover sync/async state accessor reads, nested message payloads, disabled-cache
controls and callback lock ownership.

* docs(checkpoints): document synchronous history copy costs

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-25 22:00:14 +08:00
lilei
ccb5fbfa8c
fix(runtime): reject delta resumes that discard pending tools (#7441)
* fix(runtime): reject delta resumes that discard pending tools

* fix(runtime): preserve pending head work on rejected delta replay

* fix(runtime): preserve non-Send checkpoint tasks on resume

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-25 14:44:32 +08:00
spud
e9074b7126
feat(runtime): publish owner-confirmed run.terminal evidence (#7134)
* feat(runtime): publish authoritative run terminal events

* fix(runtime): preserve terminal attribution diagnostics

* docs(runtime): consolidate terminal event docs and trim agent guidance

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-25 00:12:26 +08:00
Gusanity
d318a89b80
fix: honor checkpoint object in thread state updates (#7115)
* fix: honor checkpoint object in thread state updates

Prefer the checkpoint object over the deprecated checkpoint_id, validate
its thread_id, and preserve checkpoint namespace when updating state.

Add regression coverage for checkpoint-object forks and thread mismatch.

* fix: preserve client context handling in checkpoint PR

* fix: support state updates in checkpoint namespaces

* fix: reject unknown checkpoint namespaces

* fix(gateway): isolate subgraph titles from thread metadata

Only synchronize state titles into conversation metadata for the root checkpoint namespace. Cover persisted child updates with and without checkpoint IDs in full and delta modes, and retain root title synchronization and branch sequence cleanup.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-24 07:17:27 +08:00
Xuehao Xu
cfe2bec371
feat(workflows): add multi-Gateway coordination and session-authorized failover (#7138)
* feat(workflows): add leased multi-Gateway coordination and fenced dispatch

* test(workflows): validate deployed failover and canonicalize config revisions

* fix(workflows): share namespace across registration and lease fencing

* fix(workflows): recover interrupted cancellation without admission caps

* fix(workflows): retain execution lease through child cleanup

* docs: trim guidance padding to fit merged instruction budget

* fix(workflows): release rejected admission slots and normalize gateway routing

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-23 23:31:43 +08:00
NanPan
1700ac2c82
fix(events): honor empty event type filters in DB store (#7352)
* fix(events): treat empty event type list as an empty filter

* test(events): cover empty event-type filters across stores
2026-10-23 22:34:23 +08:00
Totoro
fc4f719664
fix(goal): retain supplied evidence and retry one malformed evaluator verdict (#7345)
* fix(goal): use spare evidence budget for long user input

* fix(goal): retry one malformed evaluator verdict

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-23 20:29:35 +08:00
Totoro
2f0cd407fc
fix(goal): base no-progress counting on successful operations (#7271)
* fix(goal): track successful operations for no-progress checks

* fix(goal): reject failed exits and narration-only progress

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-23 00:14:32 +08:00
TAI-YUAN GUO
f5907405a0
fix(workspace): don't infer created or deleted files from truncated snapshots (#7172)
* fix(workspace): don't infer created or deleted files from truncated snapshots

A bounded workspace scan stops at max_scanned_files and marks the snapshot
truncated. Absence from such a snapshot is unknown, so creation is only
inferred from a complete before snapshot and deletion only from a complete
after snapshot. get_changed_paths() is the single filter for the summary,
text capture and output-delivery detection.

The scanner now walks subdirectories in sorted order so the truncation
boundary is reproducible across the before and after scans, and the docs
note that the budget is consumed root by root (workspace before outputs).

Supersedes #5231.

Co-authored-by: liutian <91778998+ImcLiuQian@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(runtime): record truncated delivery verification skips

Record verification_skipped=truncated_pre_run_snapshot in run.delivery
receipts when a truncated pre-run scan leaves no provable output changes.
Carry the snapshot into both normal and exceptional receipt finalization.

Keep observed-output verification and terminal statuses unchanged. Cover
success, failure, cancellation and capture failures through the real worker,
and document the per-run receipt marker.

---------

Co-authored-by: liutian <91778998+ImcLiuQian@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 16:52:53 +08:00
PeaceMaker-best
60940ae9c5
fix(runtime): retain interrupted assistant output (#7153)
Signed-off-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>
Co-authored-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 11:47:53 +08:00
Stella-xixi
5cef1a11ef
fix(checkpoints): run conservative retention after completion (#6051)
* fix(checkpoints): wire conservative retention after runs

* fix(checkpoints): fence retention before checkpoint reads

* fix(checkpoints): address retention review feedback

* fix(checkpoints): retry delta reads after retention race

* fix(checkpoints): bound admission retention to the root tail

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-20 22:23:47 +08:00
trent87
0d73798beb
feature(skills): keep allowed-tools as compatibility metadata (#6935)
* refactor(skills): keep allowed-tools as compatibility metadata

* docs(skills): align current guides with agent tool authority

* test(skills): simplify authority regressions and sync Korean docs
2026-10-20 19:34:28 +08:00
Totoro
059758f321
fix(runtime): stop Goal continuations after loop hard stops (#7167)
* fix(runtime): stop Goal continuations after loop hard stops

* chore: remove redundant SVG sources from Goal fix

* test(goal): remove PR-only screenshot generation

---------

Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-20 16:55:13 +08:00
Nan Gao
085924a3d0
fix(channels): run each IM message once across instances, restarts and late redeliveries (#7104)
* fix(channels): run each IM message once across instances, restarts and late redeliveries

Inbound IM messages were deduplicated only by the 10-minute inbound dedupe
store (#7103). Discord direct messages had no key at all, a Discord thread
reply got an instance-dependent key, a Discord thread was created before
dedupe ran, a redelivery past the window started a second run, and two
instances racing on a conversation's first messages bound it to two
threads.

- Discord messages are keyed (discord, guild_id or "dm", posting channel,
  message_id); the posting channel is the same on every instance.
- MessageBus.claim_inbound lets an adapter record the manager's dedupe key
  before a side effect; Discord claims it on the Gateway loop before
  _create_thread, marks the published message dedupe_claimed, and gives the
  claim back when the message is never committed.
- runs.wait / runs.stream / runs.create send an Idempotency-Key derived from
  the dedupe key, and keyed human message ids are uuid5 of it (a uuid4 per
  attempt made every keyed retry a 409). A reused run's status-only /wait or
  stream gap is resolved to this message's own turn via threads.get_state;
  409s about the message's own keyed run keep the key instead of reading as
  a busy thread; an ended reused GitHub run gets no follow-up watcher.
- A conversation's first thread binding is claimed insert-if-absent
  (claim_thread_id on channel_conversations and channel_thread_bindings);
  the loser adopts the winner and deletes the empty thread it minted.
  /new and /agent use still rebind.

Docs: IM_CHANNEL_CONNECTIONS.md, CONFIGURATION.md, channels AGENTS.md,
CHANGELOG (en/zh).

* fix(channels): give back Discord dedupe claims for messages never processed

Review round 1 on #7104. A Discord claim records the manager's dedupe key
before the message is queued, so every path that drops the message unrun
must release it, or a peer refuses the redelivery for the rest of the TTL:

- The hand-off task releases the claim on cancellation too (stop() drains it
  while it resolves the connection identity), via await_drained so the
  cancellation still propagates.
- ChannelManager._discard_pending_inbound releases the key of a claimed
  message that shutdown drops from the queue, after the grace period and on
  a cancelled shutdown.
- The adapter tracks claims until they are committed. A release refused
  because stop() already closed cross-loop intake, or a hand-off cancelled
  before it ran, leaves the claim tracked, and stop() releases it last.

* fix(gateway): keep IM run idempotency stable when /new rebinds the conversation

Review round 2 on #7104. The Gateway scoped every Idempotency-Key by owner
and thread. /new rebinds an IM conversation to a fresh thread, so a late
redelivery of a message admitted on the old thread was requested on the new
one, got a different scoped key and started a second run.

- Keys from trusted internal callers (the IM channel manager) are scoped by
  owner only (`internal-run:` prefix); browser and API keys keep the
  owner-and-thread scope (`http-run:`).
- A key that resolves to a run of another thread or user raises
  RunIdempotencyKeyMismatch, a RuntimeError subclass so in-process launchers
  see the same type, and start_run answers it with 409 "Idempotency-Key
  already used with a different request" instead of a 500. The channel
  manager already treats that 409 as the message's admitted run.

Docs: API.md, IM_CHANNEL_CONNECTIONS.md, gateway AGENTS.md (reworded to stay
under its budget), CHANGELOG (en/zh).

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-19 22:57:19 +08:00
RongJie G
bf2cb0e1df
fix(runtime): recover terminal Redis streams after producer loss (#7093)
Signed-off-by: CorgiBoyG <111257566+CorgiBoyG@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-19 15:53:54 +08:00
ChengFa
5a9a8042b4
fix(streaming): avoid false replay gaps after cursor eviction (#6812)
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-19 15:27:56 +08:00
spud
850b3b2547
fix(runtime): attribute terminal commits and isolate lease renewals (#6919)
* fix(runtime): confirm terminal commits and isolate lease renewal

* fix(runtime): diagnose unsupported terminal store contracts

* fix(runtime): attribute atomic claims and close lease lifecycle gaps

* fix(runtime): require explicit terminal completion capability

* fix(runtime): retry renewals that race rollback admission
2026-10-19 15:06:35 +08:00
Totoro
92e9afe6ea
feat(context): support client context capabilities (#7051)
* feat: support client context capabilities

(cherry picked from commit b7c223f9415dcd545cbe05a5f5a6e47073a42d68)
[Totoro-qaq: resolved onto main. The persisted-reminder middleware hunks and their tests are dropped (delivery is redesigned in a follow-up); the RunCreateRequest description moved to app/gateway/run_models.py.]

* fix: address client context review comments

(cherry picked from commit bce6da6b819228eda58e887872720b6f9b5fab69)
[Totoro-qaq: middleware hunks dropped with the redesigned delivery.]

* feat(agents): deliver client context as a request-scoped data block

The cherry-picked #3106 put context.client into the persisted first-turn
reminder, with change detection and a "not provided" clear marker. Main
has since moved the date reminder to a SystemMessage, made request-scoped
context (the project block, #5443) request-only, and agents/AGENTS.md now
requires user-influenced values to ride the HumanMessage data channel,
never system text.

Render runtime.context["client"] on every model call in
DynamicContextMiddleware.wrap_model_call/awrap_model_call as one hidden
HumanMessage right after the leading SystemMessages, where DurableContext
puts its data. It is never a state update and never flagged
dynamic_context_reminder, so nothing is checkpointed, summarized or
replayed, and a run without a client gets no block. An unchanged client
keeps the previous request as the next request's prefix.

The renderer sanitizes again (direct graph callers bypass the Gateway),
html-escapes values and frames them with frame_untrusted_text: the same
--- BEGIN/END USER INPUT --- boundary the user's own turn and the MCP task
notification get. The lead prompt treats everything outside those markers
as framework data, so unframed values would rank above the user's own
text; framed, context.client can say nothing the same caller could not
already say in input.messages. client_context joins _BLOCKED_TAG_NAMES so
a forged block in user input or a tool result is escaped, and one static,
value-free sentence in the lead prompt names the block as untrusted hints,
never instructions, permissions or identity. The now unused empty-context
marker is removed.

* fix(gateway): log dropped context.client fields without values

A run request is never rejected over context.client: unknown fields,
nested values and malformed entries are silently dropped, the same
leniency as the other context keys. That left no signal when a custom
client sent something the Gateway ignored (review on #3106).

Route both the config.context.client and the body context.client paths
through one helper that emits a debug line whenever sanitizing changed
the value. Like the malformed client_timezone branch, the line carries no
values; unlike client_timezone, client.name and preference strings are
free caller-controlled text, so they are never logged, not even
sanitized. A clean client, an explicit null or an absent key logs
nothing.

Tests also pin the config-only path (sanitized before the early return)
and the end-to-end start_run admission: the sanitized client reaches
config["context"] and never configurable.

* fix(runtime): bound numeric client context preferences

String preferences are capped at 200 characters, but integers were kept
at any size. A JSON body may carry integers of up to 4300 digits, so 32 of
them rendered a block of about 138,000 characters, re-sent on every model
call of the run. In-process callers could also pass an int past the str()
digit limit, which failed the model call, or a number subclass with its
own __str__.

Keep integers within JavaScript's Number.MAX_SAFE_INTEGER, the range a
browser client can send exactly, and coerce numbers to plain int and
float.

* docs(api): describe client context as advisory request-scoped hints

The cherry-picked section described a hidden reminder that no longer
exists. Document what a custom client can rely on: the allow-list and its
limits (strings, the integer range, and only the first 32 entries of each
map), debug-level logging of dropped fields, runtime-only forwarding, and
delivery as a per-request user-role block, framed like user input, rather
than system text or thread history.

Only top-level context.client is documented. An explicit
config.context.client is sanitized for the run as well, but the run record
keeps the caller's config as sent, so it is not offered as an alternative.

It also answers the #3106 review: capabilities and preferences are
advisory hints, not enforcement, which belongs outside the model; name is
informational and never for authorization; the same object should be
sent on every run to keep the cached prefix stable; subagents and
embedded DeerFlowClient runs do not receive it, and server-started runs
(schedules, IM channels, GitHub agents, MCP notifications, extensions
unless the plugin passes one) carry none.

* fix(context): declare client delivery and keep client out of configurable

The request-only <client_context> block is model-visible, so
DynamicContextMiddleware now declares its delivery and the sanitizer's
limits in release_policy_parameters(); runs before and after this change
no longer share a policy fingerprint.

build_run_config copies body.config['configurable'] verbatim and
configurable is persisted with checkpoints, so a client placed there is
dropped instead of being stored raw. Client context stays runtime-only.

Document context.client in the README and in the middleware guide.

* fix(context): apply the PII redaction policy to the client block

DynamicContextMiddleware adds the <client_context> message inside the
outer PiiRedactionMiddleware, and the hidden message is not re-sanitized,
so with pii_redaction enabled a preference such as an email address
reached the model verbatim while the same address in the user turn was
redacted. Pass the rendered block through the shared redact_text seam
with this middleware's app_config.pii_redaction, and declare the setting
in release_policy_parameters().

* fix(context): keep client hints out of stored run config

---------

Co-authored-by: Blair Leng <25675774+BlairLeng@users.noreply.github.com>
Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com>
2026-10-19 10:41:18 +08:00
Ryker_Feng
d1d0958bea
feat(skills): add guarded evolution host APIs (#5645)
* feat(skills): add guarded evolution host APIs

* docs(agents): avoid growing saturated guidance chains

* fix(skills): address mutation host review findings

* fix(skills): order name fence before projection lock

* fix(skills): preserve composite toggle lock order

* fix(skills): address catalog and pagination review feedback

* docs: keep subagent guidance within merged instruction budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 21:17:02 +08:00
TAI-YUAN GUO
a1349a1fe1
fix(runtime): persist the effective model name for every run (#6980)
run_agent() only refreshed runs.model_name when the request named a model,
and it read the effective model from agent.metadata, which compiled
LangGraph graphs do not have. The default model (no model_name requested)
and fallback models were therefore never persisted. Use the effective
model that lead-agent assembly already resolves, and write only when it
differs from the stored value.

Supersedes #3110. Fixes #3109.

Co-authored-by: Yi Tang <6054101+yitang@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:10:38 +08:00
NanPan
f96aca5059
fix(checkpoints): skip unserializable Redis history cache entries (#7014)
* fix(checkpoints): skip Redis cache entries that cannot be serialized

* style(checkpoints): format Redis serialization regression

* fix(test): assert Redis serialization payload bytes

* test(checkpoints): format Redis cache serialization regression

* chore: sync frontend agent guidance with upstream size-compliant main

* fix(checkpoints): track Redis cache serialization failures

* test(checkpoints): consolidate Redis serialization fallback regression

* style(tests): format Redis cache regression signature

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 10:30:14 +08:00
Guo-Yixin
1c433c0273
fix(streaming): retire memory buffers without orphaning subscribers (#6992)
* fix(streaming): retire memory buffers and wake existing subscribers

* fix(streaming): preserve end for fully consumed retired streams

* docs: keep frontend guidance within its hard budget

* fix(streaming): report gaps when retirement wins initial attachment
2026-10-18 09:51:31 +08:00
RongJie G
8941fbe872
fix(runtime): bound RunJournal progress teardown (#6990)
Cancel event and progress writes before force-close waits, bound progress settlement, and supervise cancellation-resistant reporters until their late result is observed.

Fixes #6986

Signed-off-by: CorgiBoyG <111257566+CorgiBoyG@users.noreply.github.com>
2026-10-18 09:39:08 +08:00
NanPan
e6a3c9ffb7
fix(checkpoints): serialize shared memory history cache operations (#6708)
* fix(checkpoints): serialize shared memory history cache operations

* fix(checkpoints): align memory cache contract with locking

* test(checkpoints): make cache race regression deterministic

* docs: remove overlapping cache note from memory-cache PR
2026-10-18 08:13:27 +08:00
Totoro
16d36ccd21
feat(sandbox): add bounded AIO prewarming and lifecycle benchmarks (#6998)
* fix(benchmark): validate sandbox lifecycle and concurrent measurements

* feat(sandbox): prewarm likely AIO tool turns

(cherry picked from commit 3a1017ed032ed9331987bc3dc61f23dcdac4e905)

* fix(sandbox): bind bounded prewarming to execution leases

* docs: trim frontend guidance below its existing size limit

* fix(benchmark): separate failed lifecycle stage timings

* test(sandbox): measure prewarm overlap and unused resources through agent graphs

* fix(sandbox): finish lease and sampler cleanup after cancellation or errors

* docs(benchmark): distinguish initialization wait from model overlap

* docs(helm): align the configuration example with version 61

* fix(sandbox): preserve synchronous agent graphs when prewarming is enabled

* test(cargo): isolate native fixtures from inherited make jobserver flags

* fix(sandbox): require managed cleanup before prewarming

* fix(sandbox): log denied prewarming as an expected skip

* fix(benchmark): filter provider summary input records

---------

Co-authored-by: nothingyuancando <1579035512@qq.com>
2026-10-18 08:10:56 +08:00
ranjianghong526-hash
b731d979f1
fix(streaming): validate numeric reconnect cursors before subscribing (#6690)
* fix(streaming): validate numeric reconnect cursors before subscribing

* fix(streaming): address reconnect cursor review feedback

---------

Co-authored-by: ranjianghong526-hash <228174768+ranjianghong526-hash@users.noreply.github.com>
2026-10-18 07:51:24 +08:00
Xuehao Xu
169002a4c9
feat(workflows): add Phase 2 approvals, immutable artifacts and configuration evidence (#6920)
* feat(workflows): add versioned approvals and run configuration evidence

* docs: trim repeated setup guidance to respect inherited size limit

* docs: leave room in inherited agent guidance

* test(channels): align Buzz capability expectation with upstream registry

* fix(workflows): fence file consumers and preserve review access

* fix(runtime): reuse assembly evidence key at run admission
2026-10-17 09:41:15 +08:00
ranjianghong526-hash
2adc667fec
fix(streaming): report gaps when Redis retention disappears (#6951)
Co-authored-by: ranjianghong526-hash <228174768+ranjianghong526-hash@users.noreply.github.com>
2026-10-16 17:03:25 +08:00
CSXizhang
3fd03ec2eb
fix(compaction): reject manual compaction of threads with pending execution (#6929)
* fix(compaction): reject manual compaction of threads with pending execution

A thread paused at a static breakpoint (interrupt_before=["tools"]) keeps its
pending node send in the checkpoint's __pregel_tasks channel. The compaction
write goes through the state-mutation graph, which schedules no tasks, so
LangGraph drops the pending send ('Ignoring unknown node name tools in pending
sends') and a resumed run finishes success without ever executing the tool.

Fail closed: detect a non-empty __pregel_tasks in the raw checkpoint tuple
inside compact_thread_context and raise ContextCompactionPendingWork, mapped
to HTTP 409 by the compact route. The reservation (reserve_checkpoint_write)
already serializes this check against runs and other checkpoint writers, and
the write still forks from the exact head that was read.

Adds dual-mode (full/delta) regression tests: the rejected compaction leaves
checkpoint id, next and pending task ids untouched, and resuming afterwards
executes the pending tool exactly once; idle threads still compact.

* fix(compaction): reject unavailable checkpoints before summarizing

* docs: keep compaction guidance within merged size budget
2026-10-16 08:59:44 +08:00