Skip to content

feat(undo): /undo [N] backs up N user turns with prefill + soft-delete (#21910)#36229

Merged
teknium1 merged 5 commits into
mainfrom
hermes/hermes-615d9e33
Jun 1, 2026
Merged

feat(undo): /undo [N] backs up N user turns with prefill + soft-delete (#21910)#36229
teknium1 merged 5 commits into
mainfrom
hermes/hermes-615d9e33

Conversation

@teknium1

@teknium1 teknium1 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

/undo [N] now backs up N user turns (default 1), soft-deletes the truncated rows on disk, and prefills the composer with the backed-up message for editing — the in-place "undo and re-prompt" primitive requested in #21910, folded into the existing /undo command instead of a separate /rewind.

Salvages @SaguaroDev's #21910 infrastructure (SessionDB rewind primitives, on_session_switch(rewound=True) memory hook, TUI prefill payload) onto current main, wired to /undo [N].

Changes

  • cli.py: undo_last(n, prefill) — in-memory truncate + SQLite soft-delete (active=0) + agent surgery (system-prompt invalidate, flush-index reset) + memory notify + editable buffer prefill. /undo dispatch parses optional count; checkpoint-rollback caller passes prefill=False.
  • tui_gateway/server.py: command.dispatch undo branch parses count, picks Nth-from-last user turn, clamps to oldest.
  • hermes_state.py / agent/memory_*.py: SessionDB rewind primitives + rewound kwarg (SaguaroDev's commits, rebased onto v14 schema).
  • commands.py: /undo gains [N] args_hint.
  • tests: renamed + expanded TUI suite (multi-turn, clamp, invalid-count).
  • release.py: AUTHOR_MAP entry for SaguaroDev.

Behavior

Input Effect
/undo back up 1 user turn, prefill its text
/undo 3 back up 3 user turns at once
/undo 99 clamps to the oldest user turn

Rewound rows stay on disk (active=0) for audit, hidden from re-prompts and session_search; rewind_count is bumped per operation.

Validation

  • 243 targeted tests pass (full test_hermes_state.py + 11 new TUI /undo tests).
  • E2E on real isolated SQLite: soft-delete sync, search exclusion, audit counter, ValueError on non-user target.
  • E2E on the real CLI undo_last path: in-memory + disk stay in sync across sequential undos.

Replaces #21910 / #23445 (separate /rewind command). Closes #21910.

Co-authored-by: SaguaroDev 74339271+SaguaroDev@users.noreply.github.com

Infographic

undo-akira-neotokyo

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-615d9e33 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9575 on HEAD, 9570 on base (🆕 +5)

🆕 New issues (1):

Rule Count
unresolved-import 1
First entries
tests/tui_gateway/test_undo_command.py:21: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`

✅ Fixed issues: none

Unchanged: 4959 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 1, 2026
SaguaroDev and others added 5 commits May 31, 2026 21:19
Schema v12 adds:
- messages.active (default 1) — soft-delete flag for /rewind
- sessions.rewind_count (default 0) — audit counter
- idx_messages_session_active deferred index

New SessionDB methods:
- rewind_to_message(session_id, target_message_id) — soft-deletes rows
  >= target_id, refuses non-user targets, increments rewind_count
- restore_rewound(session_id, since_message_id) — undo for stretch goal
- list_recent_user_messages — picker source

Existing methods get include_inactive kwarg (default False):
- get_messages, get_messages_as_conversation, search_messages.
  Rewound rows excluded from session_search by default — opt-in for audit.

The deferred index pattern (DEFERRED_INDEX_SQL run after _reconcile_columns)
avoids 'no such column: active' on legacy pre-v12 databases, since
executescript(SCHEMA_SQL) runs before column reconciliation.
…21910)

Adds the TUI half of the /rewind feature so the Ink terminal UI gets
the same affordance as the prompt_toolkit CLI.

Python side (tui_gateway/server.py):
- /rewind added to _PENDING_INPUT_COMMANDS so slash.exec rejects it
  and the TUI falls through to command.dispatch (the only path with
  access to live session state + memory hooks).
- New command.dispatch branch for name == "rewind":
  v1 auto-picks the most recent user turn (Claude-Code-style single-
  step undo), calls SessionDB.rewind_to_message, refreshes the
  in-memory history, fires _memory_manager.on_session_switch with
  rewound=True, and returns the new "prefill" payload.
- A dedicated picker overlay (multi-step rewind) is tracked as a
  follow-up to #21910.

TS side (ui-tui/src/):
- New "prefill" variant on CommandDispatchResponse + asCommandDispatch
  validator. Mirrors "send" but does NOT auto-submit; the client drops
  the message into the composer for editing.
- createSlashHandler renders the optional notice via sys() and calls
  ctx.composer.setInput(d.message), letting the user edit-and-resubmit
  the rewound turn — the core UX promised by the issue.

Tests:
- 7 new tui_gateway tests covering prefill payload shape, in-memory
  history truncation, DB soft-delete, memory-provider notification
  (rewound=True), busy-session refusal, missing-session error, and
  registry placement in _PENDING_INPUT_COMMANDS.
- Extended asCommandDispatch vitest covering the new prefill variant
  (with + without notice, and rejection of malformed payloads).

Out of scope for v1 (tracked as #21910 follow-up):
- Dedicated picker overlay in Ink (the multi-step rewind UI). v1 auto-
  picks the most recent user turn, matching the most common case.
- Gateway platforms (Telegram, Discord, etc.) — issue scopes v1 to
  CLI + TUI only.
Extends the existing /undo command from a single in-memory exchange
removal into a full rewind: back up N user turns (default 1), soft-delete
the truncated rows in SessionDB (active=0, kept for audit, hidden from
re-prompts and search), notify memory providers, and prefill the composer
with the backed-up message text for editing — CLI and TUI.

Reuses the SessionDB rewind primitives, the on_session_switch(rewound=True)
memory hook, and the TUI command.dispatch prefill payload from SaguaroDev's
#21910 work, wired to /undo [N] instead of a separate /rewind picker.

- cli.py: undo_last(n, prefill) — in-memory truncate + SQLite soft-delete
  + agent surgery (system-prompt invalidate, flush-index reset) + memory
  notify + editable buffer prefill; /undo dispatch parses optional count;
  checkpoint-rollback caller passes prefill=False
- tui_gateway/server.py: command.dispatch undo branch (was rewind) parses
  count, picks Nth-from-last user turn, clamps to oldest
- commands.py: /undo gains [N] args_hint
- tests: rename + expand TUI suite (multi-turn, clamp, invalid-count)
- release.py: AUTHOR_MAP entry for SaguaroDev

Co-authored-by: SaguaroDev <74339271+SaguaroDev@users.noreply.github.com>
The on_session_switch fan-out passed rewound=rewound unconditionally,
injecting rewound=False into every provider's **kwargs on the common
/resume, /branch, /new, and compression paths. Providers that capture
extra kwargs into an 'extra' dict (and the exact-dict-equality tests
guarding them) broke. Forward rewound only when truthy; /undo sets it
explicitly, everyone else stays clean.
@teknium1 teknium1 force-pushed the hermes/hermes-615d9e33 branch from b3016a8 to 6f80976 Compare June 1, 2026 04:21
@SaguaroDev

SaguaroDev commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Thank you @teknium1, this honestly made my week!! Seeing the rewind primitives land on main with authorship preserved means a lot, and folding it into /undo [N] is cleaner than the separate command + picker I had. Glad the schema reconcile and deferred-index ordering made the rebase painless, that was the part I was most worried about.

One thing in case it's useful, not a blocker: #25074 (stacked on this) carries the messaging platform gateway half of the #21910 v2 scope that /undo doesn't touch yet, best-effort cross-platform message deletion with per-platform outbound-id tracking (schema v13 columns + partial index) and thread-scoped rewind behind a permission probe. If gateway-side rewind is ever on the roadmap, the outbound-tracking groundwork is already there to cherry-pick. Either way, thank you so much for taking a look and a screenshot worthy visual. I'm touched

@teknium1 teknium1 merged commit e1951ce into main Jun 1, 2026
21 of 23 checks passed
@teknium1 teknium1 deleted the hermes/hermes-615d9e33 branch June 1, 2026 08:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: rewind/edit-and-resubmit (like Claude Code's double-Esc)

3 participants