Skip to content

update_state on fresh DeepAgentState thread drops first messages write #8012

@ic-zl

Description

@ic-zl

Submission checklist

  • This is a bug, not a usage question.
  • I added a clear and descriptive title.
  • I searched existing issues and didn't find this.
  • I can reproduce this with the latest released version.
  • I included a minimal reproducible example and steps to reproduce.

Area (Required)

  • deepagents (SDK)
  • cli
  • code
  • acp
  • evals
  • harbor
  • daytona
  • modal
  • quickjs
  • runloop
  • langsmith-sandbox
  • Other / not sure / general

Related Issues / PRs

No response

Reproduction Steps / Example Code (Python)

import asyncio

from deepagents.graph import DeepAgentState
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph


async def model(state: DeepAgentState) -> dict:
    return {}


async def main() -> None:
    graph = StateGraph(DeepAgentState)
    graph.add_node("model", model)
    graph.set_entry_point("model")
    graph.set_finish_point("model")

    agent = graph.compile(checkpointer=InMemorySaver())

    config = {"configurable": {"thread_id": "t"}}
    message = HumanMessage(content="hello", id="m1")

    await agent.aupdate_state(
        config,
        {"messages": [message]},
        as_node="model",
    )

    snapshot = await agent.aget_state(config)
    print("snapshot.values:", snapshot.values)


asyncio.run(main())

Error Message and Stack Trace (if applicable)

snapshot.values: {'messages': []}

Description

I am trying to write a message into a fresh DeepAgentState thread using LangGraph's aupdate_state.

Expected behavior:

await agent.aupdate_state(
    config,
    {"messages": [HumanMessage(content="hello", id="m1")]},
    as_node="model",
)

snapshot = await agent.aget_state(config)
assert snapshot.values["messages"]

Actual behavior:

aupdate_state creates a checkpoint, but reading the state back returns:

{"messages": []}

Control cases:

  • agent.ainvoke({"messages": [message]}, config) persists the same message correctly.
  • A normal MessagesState graph with aupdate_state({"messages": [message]}) persists correctly.
  • If a DeepAgentState thread is first seeded through normal ainvoke, later aupdate_state appends appear to work.

This seems specific to the first out-of-band write into DeepAgentState.messages, which is backed by DeltaChannel.

Possible cause:

In the aupdate_state path, channel writes appear to be persisted only when a previous checkpoint exists:

if saved and channel_writes:
    await checkpointer.aput_writes(checkpoint_config, channel_writes, task_id)

On a fresh thread, saved is None, so the first messages write is applied to the live channel but not persisted as checkpoint writes. Then create_checkpoint(...) omits non-snapshot DeltaChannel values from channel_values, leaving a checkpoint with a bumped messages channel version but no readable value or replayable writes.

Question:

Is update_state / aupdate_state expected to support writing to DeepAgentState.messages on a fresh thread? If not, is there a recommended API for appending a message to a DeepAgents thread without invoking the model?

Environment / System Info

OS: macOS 26.5.1 (25F80)
Python: 3.12.9
deepagents: 0.6.8
langgraph: 1.2.4
langgraph-checkpoint: 4.1.1
langgraph-checkpoint-sqlite: 3.1.0
langchain-core: 1.4.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingexternal

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions