GitHub user sshiv012 created a discussion: Revert & Redo for AI Agent turns
(rewind the workflow to before an agent edit)
> **Status:** Draft : seeking feedback
> **Area:** AI Agent (copilot) · `agent-service` · frontend workspace
> **Type:** Feature proposal
## Summary
The Texera AI agent edits the user's **live workflow canvas**, its tools add,
modify, and delete operators (and the changes auto-persist). Today there is no
agent-aware way to back out a turn that went wrong: you fix it by hand. This
proposal adds a **per-turn "Revert"** control (rewind the workflow to the state
*before* an agent turn) and a **"Redo"** control (undo the revert), implemented
almost entirely on top of machinery the agent already has.
I have a working prototype (running locally) and would like feedback on the
design, especially the UX granularity and the protocol additions, before
opening a PR.
## Motivation
- The agent mutates real state. A bad suggestion ("restructure this", a wrong
delete) currently leaves the user to manually undo a multi-operator change.
- This raises the cost of *letting the agent try things*, which is exactly the
behavior we want to encourage. A cheap, reliable "step back" makes the agent
feel safe to experiment with.
- A frontend-only "visually undo" is **not** enough: the agent-service keeps
its own notion of the current workflow (its HEAD), so the next message would
silently clobber a cosmetic revert. We want a **true rewind** that keeps the
agent's context consistent.
## Background — what already exists
The agent-service already models a conversation as a **version tree of ReAct
steps**:
- Each `ReActStep` has an `id` and a `parentId` (the tree), plus
`beforeWorkflowContent` and `afterWorkflowContent` snapshots.
- A **`HEAD`** pointer marks the current step; the visible chat is the ancestor
path from root to `HEAD`.
- The frontend is already HEAD-aware: it recomputes the visible steps from
`HEAD` and applies any workflow snapshot to the canvas via
`WorkflowActionService.reloadWorkflow(...)`.
The **only missing primitive** was the ability to move `HEAD` *backward*. There
was no client command for it. That is the gap this proposal fills.
## Proposal
### Revert (per turn)
A **Revert** control on each agent turn. Clicking it:
1. Asks the agent-service to move `HEAD` to that turn's **parent** step.
2. The agent-service restores its working workflow to the turn's
`beforeWorkflowContent` and replies with the new HEAD + workflow content.
3. The frontend advances its HEAD pointer (which recomputes the visible chat)
and reloads the canvas from the snapshot.
### Redo
A single **Redo** control in the chat toolbar, shown only when a redo is
available. The agent-service keeps a **redo stack**: a revert pushes the
pre-revert HEAD; redo pops it and moves HEAD forward, restoring that step's
`afterWorkflowContent`. Sending a **new prompt clears the redo stack** (a new
branch invalidates redo). Redo is repeatable for multiple consecutive reverts.
## How it works
### Revert + Redo flow
```mermaid
sequenceDiagram
actor U as User
participant FE as Frontend (agent panel)
participant AS as agent-service
participant WA as WorkflowActionService (canvas)
U->>FE: Click "Revert" on turn T
FE->>AS: WsClientRevertCommand { messageId: T }
Note over AS: HEAD ← parent(T)<br/>push old HEAD to redo stack<br/>restore
workflow = T.beforeWorkflowContent
AS-->>FE: WsServerHeadChangeEvent { headId, workflowContent, canRedo: true }
FE->>FE: headId → recompute visible steps
FE->>WA: reloadWorkflow(workflowContent)
Note over FE: "Redo" button appears
U->>FE: Click "Redo"
FE->>AS: WsClientRedoCommand
Note over AS: pop redo stack → HEAD ← oldHead<br/>restore workflow =
step.afterWorkflowContent
AS-->>FE: WsServerHeadChangeEvent { headId, workflowContent, canRedo: false
}
FE->>WA: reloadWorkflow(workflowContent)
```
### HEAD movement over the step tree
```mermaid
graph LR
I[INITIAL] --> U1[turn 1 user] --> A1[turn 1 agent]
A1 --> U2[turn 2 user] --> A2[turn 2 agent]
classDef head fill:#2da44e,color:#fff,stroke:#2da44e;
%% Revert(turn 2): HEAD moves A2 -> A1 ; Redo: HEAD moves A1 -> A2
```
`Revert(turn 2)` moves `HEAD` from `A2` to `A1` (turn 2's parent). The turn-2
steps stay in the
tree, so `Redo` can move `HEAD` back to `A2`. A new prompt after a revert
branches from `A1` and
clears the redo stack.
## New WebSocket frames
Client → server:
- `WsClientRevertCommand { messageId }` — rewind to before that turn.
- `WsClientRedoCommand` — undo the most recent revert.
Server → client:
- `WsServerHeadChangeEvent { headId, workflowContent, canRedo }` — HEAD moved
without a new step.
- `canRedo` added to `WsServerSnapshotEvent` so a reconnecting client can
render the Redo state.
Both commands are rejected with an error frame while the agent is `GENERATING`
(a mid-run rewind would race the loop's own HEAD/workflow mutations).
## Design decisions & alternatives considered
1. **True rewind vs. frontend-only revert.** A frontend-only revert (just
reload a cached snapshot onto the canvas) is ~half the work but leaves the
agent-service's HEAD out of sync, so the next message clobbers the revert. We
chose the **full rewind** for correctness.
2. **Granularity: per turn vs. per step.** We chose **per turn** (one control
per user message) it matches "undo this change" and keeps the UI quiet.
Per-step revert is possible (the tree already supports it) but noisier; could
be a follow-up.
3. **Redo: stack vs. single-level.** A **stack** supports multiple consecutive
undos→redos for little extra code. New prompt clears it.
4. **Not reusing the canvas undo/redo (yjs `UndoManager`).** Agent turns
produce many fine-grained operator edits; routing through undo/redo would
create dozens of entries per turn and tangle with manual edits.
`reloadWorkflow` already clears the undo stack, so snapshot-replay is a cleaner
primitive here.
## Open questions (feedback welcome)
1. **Granularity** — is per-turn the right default, or do people want per-step
revert too?
2. **Button placement** — Revert lives on each turn's header; Redo is a single
toolbar button. Reasonable, or should both be in the toolbar (operating on the
latest turn)?
3. **Branch UX** — after a revert + new prompt, the old branch is dropped from
view but still in the tree. Should we surface branch navigation, or keep it
hidden (current proposal)?
4. **Persistence** — the step tree is in-memory in the agent-service. Should
revert history survive an agent-service restart, or is "session-scoped"
acceptable?
5. **Confirm dialog** — Revert currently asks for confirmation (it discards
later turns). Keep it, or make it instant and rely on Redo?
<img width="1149" height="340" alt="image"
src="https://github.com/user-attachments/assets/27f83c73-4b41-418f-90f5-b0091aa111fa"
/>
## Out of scope (for the initial PR)
- Per-individual-step revert.
- Forward/branch navigation UI beyond single-level Redo.
- Persisting revert history across restarts.
---
Happy to adjust the design based on feedback before opening the PR. Prototype
branch and tests are ready.
GitHub link: https://github.com/apache/texera/discussions/6039
----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: [email protected]