Skip to content

🐛 Bug: copilotkit_emit_state partial state not merged with current_graph_state in STATE_SNAPSHOT #3138

Description

@Valen-C12

♻️ Reproduction Steps

  1. Create a LangGraph agent with custom state fields (e.g., thinking_steps, run_titles, current_run_id)
  2. Use copilotkit_emit_state to emit partial state updates during node execution:
# In node, emit only specific fields
await copilotkit_emit_state(config, {"current_run_id": run_id, "current_turn_index": turn_index})
# Later, emit different fields
await copilotkit_emit_state(config, {"thinking_steps": new_steps})
# Even later, emit another field
await copilotkit_emit_state(config, {"run_titles": current_titles})
  1. Observe frontend useAgent hook - each STATE_SNAPSHOT event replaces previous state instead of merging

🔎 Observed Behavior

When copilotkit_emit_state emits partial state (e.g., {"run_titles": {...}}):

  • The STATE_SNAPSHOT event contains only {"run_titles": {...}}
  • Frontend loses previously emitted fields like thinking_steps
  • State "flickers" - fields appear, disappear, then reappear

Console logs showing the issue:

[useAgent] state: {current_run_id: '...', current_turn_index: 6}
[useAgent] state: {thinking_steps: Array(43)}  // ✅ has steps
[useAgent] state: {run_titles: {...}}          // ❌ thinking_steps GONE!
[useAgent] state: {thinking_steps: Array(43)}  // ✅ back again

✅ Expected Behavior

According to documentation:

"the underlying mechanism in _emit_state_sync_event will merge this with the current_graph_state before emitting a complete state object to the frontend"

Each STATE_SNAPSHOT should contain the merged state, preserving all fields.

📃 Root Cause Analysis

In langgraph_agent.py (v0.1.76 and v0.1.77), lines 437-447:

if manually_emit_intermediate_state:
    manually_emitted_state = cast(Any, event["data"])
    yield self._emit_state_sync_event(
        thread_id=thread_id,
        run_id=run_id,
        node_name=node_name,
        state=manually_emitted_state,  # ❌ Only partial state!
        running=True,
        active=True
    ) + "\n"
    continue  # ❌ Skips merge with current_graph_state

The continue statement bypasses lines 479-482 where normal state sync merges with current_graph_state.

💡 Suggested Fix

Merge the manually emitted state with current_graph_state before emitting:

if manually_emit_intermediate_state:
    manually_emitted_state = cast(Any, event["data"])
    # Merge with current graph state
    merged_state = {**current_graph_state, **manually_emitted_state}
    current_graph_state.update(manually_emitted_state)
    yield self._emit_state_sync_event(
        thread_id=thread_id,
        run_id=run_id,
        node_name=node_name,
        state=merged_state,  # ✅ Full merged state
        running=True,
        active=True
    ) + "\n"
    continue

📦 Versions

  • copilotkit (Python SDK): 0.1.76 / 0.1.77
  • @copilotkit/react-core: 1.51.2
  • langgraph: 1.0.7+

🔗 Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions