Skip to content

Python: [Bug]: AG-UI workflow resume does not persist user HITL text into Thread Snapshot #8160

Description

@likebean

Summary

For workflow AG-UI runs with Thread Snapshot persistence enabled, a HITL resume that carries the user’s reply only in resume.interrupts[].value (with messages: []) continues execution correctly, but the persisted Thread Snapshot never records that user turn. After refresh/hydrate, the transcript jumps from one assistant message to the next and the user’s replies (e.g. “I want a refund”, an order id) are missing.

This matches how official samples resume (messages: [] + resume only), so demos that only pushMessage locally look fine until hydrate.

Current behavior (Python AG-UI workflow path)

  1. Client sends the documented resume shape (same as ag_ui_workflow_handoff):
{
  "threadId": "...",
  "messages": [],
  "resume": {
    "interrupts": [
      {
        "id": "<request_info id>",
        "value": [
          { "role": "user", "contents": [{ "type": "text", "text": "我要退货" }] }
        ]
      }
    ]
  }
}
  1. Framework comments explicitly treat resume as interrupt-response-only and seed the snapshot builder via resume_seeded_messages(stored + incoming) (_workflow.py, _snapshot_session.py):

Resume requests carry only the synthesized interrupt response; seeding with stored history keeps the persisted thread from being truncated.

  1. With incoming == [], the builder seed is only prior snapshot messages. _WorkflowSnapshotBuilder.observe then appends newly streamed assistant/tool events. The human text inside resume.value is used as a workflow responses payload, not folded into snapshot messages as a role: "user" (or equivalent) turn.

  2. Hydrate (messages: [], no resume) correctly replays whatever was stored — so the gap becomes visible after refresh.

Contrast: agent path

On the agent AG-UI path, resume is synthesized into messages that are also appended to snapshot_seed_messages (e.g. _resume_to_tool_messages / approval resume messages in _agent_run.py). The workflow path has no equivalent synthesis for handoff-style user text resumes.

Expected behavior

When snapshot persistence is enabled and a workflow resume resolves a pending request_info with user-visible content (message list / text), the saved Thread Snapshot should include a replayable representation of that user turn (at least for hydrate UX), e.g.:

  • synthesize a role: "user" message from resume.value into the snapshot seed (preferred for chat UIs), and/or
  • document a required client contract that resume must also send those turns in messages (if that is the intended design)

Today neither is done for workflow handoff HITL: samples use messages: [], and the server drops the user text from the snapshot.

Repro (conceptual)

  1. Enable snapshot_store on an AG-UI workflow endpoint (e.g. handoff + request_info after agent reply).
  2. Run until RUN_FINISHED with an interrupt whose value is a user-input request (not only tool approval).
  3. Resume with messages: [] and resume.interrupts[0].value = [{ role: "user", ... }].
  4. Confirm the run continues (assistant asks for order id, etc.).
  5. Hydrate the same threadId with messages: [] and no resume.
  6. Observe: stored messages contain assistant turns after the resume, but not the user’s resume text.

Proposal

In AgentFrameworkWorkflow.run / _WorkflowSnapshotBuilder (workflow path), when resume_payload is present and snapshot persistence is enabled:

  1. Extract user-visible content from each resolved interrupt value (message list / plain text / known handoff shapes).
  2. Append synthesized snapshot messages to builder_seed_messages after resume_seeded_messages(...), before observing the new run’s stream (mirroring agent-path snapshot_seed_messages.extend(...)).
  3. Keep tool-approval resumes as tool/system-style entries if that is preferred, but do not leave conversational HITL replies out of the hydrate transcript.

Happy to adjust if the intended contract is “clients must put HITL user text in messages on resume”; in that case samples + README hydrate/resume docs should state it explicitly, because they currently demonstrate messages: [].

Environment

  • Package: agent-framework Python AG-UI + orchestrations handoff
  • Path: workflow AG-UI + Thread Snapshot store (SQLite/in-memory)
  • Related: samples python/samples/05-end-to-end/ag_ui_workflow_handoff*

Activity

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

Metadata

Metadata

Labels

ag-uiUsage: [Issues, PRs], Target: AG-UI protocol integrationpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions