Skip to content

[BUG] updateState(..., asNode) duplicates APPEND state before resume #155

Closed

Description

@Aias00

背景

这是上游遗留问题 alibaba/spring-ai-alibaba#4886。在当前 ARGI main(7a0bf414b)中仍可离线复现。

当前行为

当状态键使用 AppendStrategy 时,调用三参数 CompiledGraph.updateState(config, values, asNode) 会把已有状态再次通过 APPEND 策略合并,导致列表内容翻倍。

例如已有 messages = ["seed"],仅更新 human_feedback 并指定 asNode 后,状态变为:

messages = ["seed", "seed"]

两参数 updateState(config, values) 不经过该分支,因此不会触发同样的重复。

最小复现

MemorySaver saver = new MemorySaver();
CompiledGraph graph = new StateGraph(() -> Map.of("messages", KeyStrategy.APPEND))
        .addNode("resume", node_async(state -> Map.of()))
        .addEdge(StateGraph.START, "resume")
        .addEdge("resume", StateGraph.END)
        .compile(CompileConfig.builder()
                .saverConfig(SaverConfig.builder().register(saver).build())
                .build());

RunnableConfig config = RunnableConfig.builder().threadId("upstream-4886").build();
RunnableConfig checkpointConfig = saver.put(config, Checkpoint.builder()
        .state(Map.of("messages", List.of("seed")))
        .nodeId(StateGraph.START)
        .nextNodeId("resume")
        .build());

RunnableConfig updated = graph.updateState(
        checkpointConfig, Map.of("human_feedback", "next"), "resume");

assertThat(graph.getState(updated).state().value("messages", List.class).orElseThrow())
        .containsExactly("seed", "seed"); // 当前实际结果

本地验证结果:JDK 17,argi-graph-core,测试通过并确认重复;无需模型、API Key 或外部服务。

根因

CompiledGraph#updateState 先在 349 行合并用户提供的 values,随后:

  1. nextNodeId(asNode, branchCheckpoint.getState(), ...) 对直接边返回 new Command(route.id(), state)(441-443 行),Command.update() 携带的是完整 state;
  2. 357 行又执行 branchCheckpoint.updateState(nextNodeCommand.update(), keyStrategyMap);
  3. 完整 state 被当成增量再次套用 AppendStrategy,已有列表因此重复。

条件边路径也会在 464-465 行返回已合并后的完整 state,存在相同的二次合并风险。

影响

  • HITL 中断后使用 asNode 更新状态并恢复执行时,消息历史可能指数式膨胀或出现重复消息。
  • 所有非幂等 KeyStrategy(不仅是 APPEND)都可能产生错误语义。
  • asNode 的路由选择与状态增量被混在同一个 Command.update() 中,容易造成恢复路径与普通执行路径不一致。

期望

指定 asNode 只应决定后续路由,并只合并边动作真正返回的增量;不应把完整 checkpoint state 重新应用一遍。

验收建议

  • 增加直接边与条件边的回归测试。
  • 覆盖 AppendStrategy,并至少增加一个自定义非幂等策略用例。
  • 验证两参数/三参数 updateState 在相同更新下保持一致的状态内容,仅 nextNode 不同。
  • 覆盖 HITL interruption → updateState → resume 的真实链路。

Activity

  1. added
    area/graph图引擎:图工作流、路由、节点与嵌套图
    kind/bug缺陷:实际行为与预期不符
    area/persistence持久化:状态存储、检查点与中断恢复
    area/human-in-the-loop人工介入:审批、暂停与人工反馈
    needs-triage待分诊:尚需确认类型、领域或优先级
    on Oct 13, 2026
  2. logicwu0 commented on Oct 13, 2026

    @logicwu0
    Contributor

    I can take this. I reproduced the duplication on current main (e10f9ab): 12 of the 14 regression cases fail before the fix, while the two no-asNode controls pass. The fix keeps routing commands delta-only and persists the resolved next node in the checkpoint, so resume does not evaluate the same conditional edge and apply its delta again. Both MemorySaver and VersionedMemoryCheckpointSaver are covered, including APPEND, a custom additive strategy, parallel conditional routing, and interruption -> updateState -> resume. All 14 cases now pass locally, along with the offline graph-core suite and 12 related Agent tests. I will submit the PR shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/graph图引擎:图工作流、路由、节点与嵌套图area/human-in-the-loop人工介入:审批、暂停与人工反馈area/persistence持久化:状态存储、检查点与中断恢复kind/bug缺陷:实际行为与预期不符needs-triage待分诊:尚需确认类型、领域或优先级

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions