feat: /rewind — jump back to a prior user message and re-prompt (#21910)#23445
feat: /rewind — jump back to a prior user message and re-prompt (#21910)#23445SaguaroDev wants to merge 8 commits into
Conversation
|
@xavierchoi — you volunteered to help test on the original issue (#21910). v1 is up here when you have a moment. A few notes specifically on the gateway concerns you raised:
What would help most:
Happy to iterate on anything that feels off. Thanks for the detailed v2 scope on the issue — it shaped how the memory hook signature is laid out so this PR doesn't paint v2 into a corner. |
|
Nice, glad to see this moving. The active-flag soft-delete approach is cleaner than I expected — keeping rewound rows queryable for audit while excluding them from search by default is the right call. Will keep an eye on the v2 gateway scope. |
|
v2 plan is mapped out and committed to a separate branch ( Quick scouting summary in case it's useful for this review:
v2 scope (9 TDD-shaped tasks):
Thread-scoped by design ( Holding the v2 branch until this one lands so I can rebase cleanly if anything shifts in review. |
ec6835c to
3f1f5db
Compare
|
Review in 5 minutes Quick verification recipe if you want to smoke-test before reading the diff: Then in two terminals, one CLI and one TUI: CLI (
TUI (
The one open design call I'd value a second opinion on: the TUI auto-picks the most recent user turn rather than rendering a dedicated Ink picker overlay. My read is this matches the most common use case (single-step undo) and the picker overlay is a clean v1.1 follow-up; happy to block on the overlay if you'd rather ship them together. Scope: CLI + TUI only, as the issue body specifies. Gateway (Discord/Telegram/Slack message deletion) is v2, mapped out separately and reuses the Size note: +1026 / −10 across 13 files but the diff splits cleanly along the 6 commits — the schema/primitives commit is independently mergeable as a no-op-from-user-perspective base if you'd prefer to land it first and layer CLI + TUI as follow-ups. Happy to split. @teknium1 — could I get a look at this when you have a window? Most of the surface area is |
|
Appreciate the ping, and honestly impressed at how fast this came together. Have to be upfront: I can't smoke-test on my end right now — I'm running Hermes gateway-only on Discord, no local CLI/TUI session set up. So no signal from me on (1) and (2). For (3), I'll trust the test coverage — the On the TUI auto-pick vs dedicated picker overlay design call: my vote is ship v1 with auto-pick, track the overlay as a follow-up. Single-step undo covers the muscle-memory case (it's what double-Esc trains you to expect), and the CLI already has the full picker for the multi-step case. Blocking v1 on the overlay feels like scope creep for a marginal UX gain. Also — thanks for mapping the v2 gateway branch already. |
|
@teknium1 @OutThisLife — could I get a review when you have a window? Diff splits cleanly:
253/253 tests green in the blast radius. Acceptance checklist 5/6, with the deferred Esc-Esc keybind explained in the PR body. Happy to split into two PRs if that's easier to review. |
…ch#21910) 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.
Adding rewound as an explicit named kwarg on MemoryManager.on_session_switch broke existing tests that assert exact equality on the extra kwargs dict captured by providers (test_manager_fans_out_to_all_providers, test_manager_reset_flag_preserved). The MemoryProvider base class still documents rewound as a known kwarg, but the manager now passes it through **kwargs like any other forwarded flag. The CLI rewind handler already passes rewound=True keyword-style, so behavior is identical.
…ousResearch#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 NousResearch#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 NousResearch#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.
|
Rebased on main and resolved the conflict in
Drive-by: the new 241/241 in the rewind blast radius after the rebase, plus 67/67 in adjacent session/search/memory tests. @alt-glitch — would you be able to route this to a reviewer? I keep having to rebuild as main moves; a second pair of eyes on the SessionDB half (the |
3f1f5db to
05e33f3
Compare
|
@alt-glitch is this change not wanted? I will stop dev work on it, but I keep having to update it while there are no responses. That's fine but please tell me to drop it so I can move on with my life. Please |
Resolve conflicts in hermes_state.py and tests/test_hermes_state.py with the platform_message_id/observed work that landed on main (PRs NousResearch#27166, #31a01001, #4a91e364). - SCHEMA_VERSION 12 (HEAD) vs 13 (main) -> 14, accommodating both feature bumps (rewind 'active' flag + platform_message_id/observed). - messages table: keep both new columns ('active', 'platform_message_id', 'observed') side by side. - DEFERRED_INDEX_SQL: fold main's idx_messages_platform_msg_id into the same deferred block as idx_messages_session_active (consistent legacy-DB reconciliation path). - _read_messages_for_sessions SELECT: union both column sets and preserve the active-flag filter from the rewind branch. - test_schema_version assertions: switch the three == 12 literals to == SCHEMA_VERSION so the test tracks the constant. Verified with: pytest tests/test_hermes_state.py (227 passed) and pytest -k 'rewind or platform_message or observed or active' (348 passed).
# Conflicts: # hermes_state.py # tests/test_hermes_state.py
b73fc67 to
a737a3b
Compare
|
@teknium1 This PR is rebased clean on latest Once CI is unblocked it should go green: the Would appreciate the workflow approval and a look when you have a moment. Thanks! |
|
Superseded by #36229. We folded your /rewind work into the existing Your substantive infrastructure was cherry-picked onto current main with your authorship preserved in git log — the SessionDB rewind primitives ( |
Closes #21910
Adds a first-class
/rewindaffordance — jump back to a prior user message in the current session, re-prompt from there with the original text pre-filled and editable, while keeping the truncated rows on disk for audit. Equivalent to Claude Code's double-Esc behavior, scoped per the issue to CLI + TUI only (gateway is a v2 follow-up).What ships
Schema (v11 → v12)
messages.active INTEGER NOT NULL DEFAULT 1— soft-delete flagsessions.rewind_count INTEGER NOT NULL DEFAULT 0— audit counteridx_messages_session_activeindex (created after_reconcile_columnsso legacy DBs don't trip on the missing column)Declarative reconcile + a belt-and-suspenders
UPDATE messages SET active = 1 WHERE active IS NULLcovers existing databases.SessionDB API
rewind_to_message(session_id, target_message_id) → {rewound_count, target_message, new_head_id}— refuses non-user targets and cross-session targets withValueError; idempotentrestore_rewound(session_id, since_message_id) → int— reverse op for a future/unrewindlist_recent_user_messages(session_id, limit=20)— picker sourceget_messages,get_messages_as_conversation,search_messagesgaininclude_inactive: bool = False. By default rewound rows are excluded everywhere — includingsession_search— so re-prompts and search hits see a clean transcript. Opt in for audit / debug tooling.CLI (
/rewind)Slash command registered in
hermes_cli/commands.py; handler incli.pymirroring_handle_branch_command:_run_curses_picker) shows the last 10 user messagesSessionDB.rewind_to_messageconversation_historyreset_session_state,_invalidate_system_prompt,_last_flushed_db_idx)_memory_manager.on_session_switch(..., rewound=True)so per-turn caches invalidate (Hindsight etc.)TUI (Ink + tui_gateway)
command.dispatchgets aname == "rewind"branch returning the new{"type": "prefill", "message": <text>, "notice": "↶ Rewound N message(s)…"}payload/rewindadded to_PENDING_INPUT_COMMANDSso slash.exec routes it correctlyprefillvariant onCommandDispatchResponse+asCommandDispatchvalidator;createSlashHandlerhandles it by callingctx.composer.setInput(d.message)(no auto-submit, unlikesend)Memory hook (#6672 extension)
MemoryProvider.on_session_switchdocumentsrewound: bool = False.MemoryManager.on_session_switchkeeps it in**kwargs(not an explicit named kwarg) so existing tests that assert exactextradict equality stay green. Existing callers (/branch, compression, etc.) are untouched.Test coverage
tests/test_hermes_state.pytests/cli/test_rewind_command.pytests/tui_gateway/test_rewind_command.pyui-tui/src/__tests__/asCommandDispatch.test.tsAll existing suites in the blast radius (memory, cli, branch, hermes_state, tui_gateway) — 253 / 253 passing post-rebase.
Pre-existing failures on
origin/mainthat are unrelated and unaffected by this PR:test_anthropic_adapter.py::test_returns_token_from_credential_files— mock setup issuetest_cli_save_config_value.py::TestSaveConfigValueAtomic::*— missingruamelmodule in CI envAcceptance checklist (from #21910)
/rewindcommand in CLI and TUIget_messages_as_conversation)active=0), not deletedon_session_switch(rewound=True))Esckeybind — deferred per design discussion (/rewindslash command is the canonical entry; keybind can land in a follow-up after we auditEsc Escfor conflicts with prompt_toolkit's existing keymap)Out of scope for v1 (tracked as follow-ups to #21910)
resolve_resume_session_idextended; not addressed here.