Skip to content

fix(agents): preserve todos state across node updates#3180

Open
Huixin615 wants to merge 1 commit into
bytedance:mainfrom
Huixin615:feature/3123-fix-todos-state-reducer
Open

fix(agents): preserve todos state across node updates#3180
Huixin615 wants to merge 1 commit into
bytedance:mainfrom
Huixin615:feature/3123-fix-todos-state-reducer

Conversation

@Huixin615
Copy link
Copy Markdown

What

Adds a merge_todos reducer to ThreadState.todos so that a downstream node returning a partial state without todos no longer wipes the previously streamed value.

Why

Closes #3123.

ThreadState.todos was declared as NotRequired[list | None] with no reducer. In LangGraph, fields without a reducer use last-write-wins, and a missing key in a partial state update is materialized as None, which overwrites the previous value. This is exactly the root cause @WillemJiang identified in the issue: the to-do list renders correctly during streaming, then disappears once a later node finishes with no todos in its return.

How

  • Add merge_todos(existing, new) reducer in backend/packages/harness/deerflow/agents/thread_state.py. Semantics:
  • Annotate todos: Annotated[list | None, merge_todos], mirroring the existing artifacts: Annotated[list[str], merge_artifacts] pattern in the same file.

Tests

New backend/tests/test_thread_state_reducers.py with 10 cases:

All 69 thread-related tests pass locally:

PYTHONPATH=. uv run pytest \
  tests/test_thread_state_reducers.py \
  tests/test_thread_meta_repo.py \
  tests/test_memory_thread_meta_isolation.py \
  tests/test_thread_data_middleware.py -v
# 69 passed in 1.96s

make lint and make format both pass cleanly (only the 2 files in this PR are touched).

Checklist

  • make lint passes (ruff check + ruff format --check)
  • make format applied
  • New unit tests added and passing
  • No public API changes
  • Scoped change: only 2 files touched

ThreadState.todos had no reducer, so any downstream node returning a
partial state without todos was implicitly setting it to None, which
LangGraph then used to overwrite the previously streamed value. This
caused the to-do list to render correctly during streaming but vanish
once streaming completed.

Add a merge_todos reducer that keeps the last non-None value, mirroring
the merge_artifacts pattern already used in the same file. An explicit
empty list is still respected so that 'user cleared todos' works.

Tests: 10 new unit tests in tests/test_thread_state_reducers.py covering
merge_todos plus regression coverage for merge_artifacts and
merge_viewed_images. All 69 thread-related tests pass locally.

Closes bytedance#3123
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 23, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a reducer for ThreadState.todos so LangGraph partial state updates that omit todos do not clear previously streamed todo state, addressing issue #3123.

Changes:

  • Adds merge_todos(existing, new) with “preserve on None, replace otherwise” semantics.
  • Annotates ThreadState.todos with the new reducer.
  • Adds reducer unit tests for todos plus sanity checks for existing artifact/image reducers.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
backend/packages/harness/deerflow/agents/thread_state.py Defines and wires the merge_todos reducer into ThreadState.todos.
backend/tests/test_thread_state_reducers.py Adds unit coverage for reducer behavior and existing reducer sanity checks.

title: NotRequired[str | None]
artifacts: Annotated[list[str], merge_artifacts]
todos: NotRequired[list | None]
todos: Annotated[list | None, merge_todos]
Comment on lines +8 to +12
from packages.harness.deerflow.agents.thread_state import (
merge_artifacts,
merge_todos,
merge_viewed_images,
)
@WillemJiang
Copy link
Copy Markdown
Collaborator

@Huixin615, thanks for your contribution. Please take a look at the review comment of Copilot.

@WillemJiang WillemJiang added the reviewing The PR is in reviewing status label May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reviewing The PR is in reviewing status

Projects

None yet

Development

Successfully merging this pull request may close these issues.

To-dos 列表在流式输出过程中显示,但输出完成后消失

4 participants