Skip to content

feat(state): Hermes-style human rewind for shadow checkpoints - #98

Merged
divo12 merged 8 commits into
mainfrom
feat/human-rewind-surface
Aug 16, 2026
Merged

divo12 merged 8 commits into
mainfrom
feat/human-rewind-surface

Conversation

@divo12

@divo12 divo12 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

Operator rewind that restores the worktree and truncates the matching transcript turns (Hermes /rollback), closing the Bex-vs-Hermes gap left after shadow FS snaps. build_harness auto-wires ShadowCheckpointHook + a shared store under DreamPaths.checkpoints_dir; Session.list_checkpoints / Session.restore_checkpoint are the human surface, not an LLM tool.

Turn boundaries come from the Session, not from message inspection. Transcripts contain engine-generated user messages (compaction summaries, attachments, delegation completions, STOP nudges), so classifying "which user message is a human turn" is unreliable — instead Session records the transcript index at each real prompt submit:

self._transcript.append(user_msg)
self._prompt_indices.append(len(self._transcript) - 1)
...
rewind_transcript(messages, prompt_indices=..., turns=n)  # truncates at prompt_indices[-n]

Compaction rewrites the transcript and invalidates those boundaries, so it clears them and a later rewind returns a failed result (requested rewind boundary is unavailable) rather than truncating at a guessed position. FS restore is validated first, so a failed snap never desyncs chat from disk, and rewind_turns < 0 is rejected before any filesystem mutation.

Checkpoints are default-on, with the per-turn cost bounded. Measured here: the first mutating tool call costs ~0.02 s at 1k files but ~0.31–0.38 s at 50k, and it is not incremental. So ShadowCheckpointConfig.max_files (default 10,000) skips oversized worktrees with a distinct CheckpointOutcome.DIRECTORY_TOO_LARGE before the shadow store is touched; the probe uses git ls-files -co --exclude-standard (bounded os.walk fallback for non-git dirs) and is cached per working dir so it runs once, not per call. Override via build_harness(..., shadow_checkpoint_config=...).

Per-session state: the per-turn dedupe set and the checkpoint bookkeeping are keyed by session_id (bounded to the 64 most recent sessions), so concurrent sessions sharing one manager can't clear each other's state.

Type

  • Bug fix
  • Feature
  • Refactor / docs / chore

Checklist

  • Tests added or updated for behaviour changes
  • tests/test_public_api.py updated if the public API changed
  • CHANGELOG.md updated for user-visible changes
  • No secrets, .env.local, or credentials in the diff
  • Scope stays within dream (org/company features belong in sibling repos)

Test plan

  • pytest tests/test_state/test_transcript_rewind.py tests/test_state/test_combined_restore.py
  • pytest tests/test_session/test_checkpoint_rewind.py tests/test_state/test_shadow_checkpoint.py — 31 passed
  • New coverage: session isolation, negative rewind_turns leaves the FS untouched, missing boundary after compaction, oversized tree skips without touching the store, size probe runs once per dir
  • uv run ruff check src tests / uv run mypy
  • CI: 4/4 green (3.11–3.14)
  • Smoke: mutate → list checkpoints → restore → confirm FS + transcript align

Link to Devin session: https://app.devin.ai/sessions/4f7deb4af0914056a24a4d660a661713
Requested by: @divo12


CodeAnt-AI Description

Add safe operator rewind for worktrees and conversation history

What Changed

  • Sessions can list checkpoints and restore the worktree to a selected checkpoint, removing the matching prompt turns from the transcript.
  • Checkpoints are enabled by default and stored under the configured Dream home; checkpoint tracking is isolated between sessions.
  • Large worktrees are skipped before checkpoint creation when they exceed the configurable file limit, avoiding checkpoint overhead.
  • Rewind refuses unsafe requests, including active sends, unavailable prompt boundaries, and negative turn counts; filesystem restore completes before transcript changes are applied.
  • Transcript boundaries are cleared after compaction or session resume so rewind cannot remove the wrong conversation history.

Impact

✅ Restore files and chat together after an unwanted change
✅ Fewer checkpoint delays on large worktrees
✅ Safer operator recovery without transcript desynchronization

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Expose Session.list_checkpoints / restore_checkpoint so operators can
restore the worktree and truncate the matching transcript turns — the
gap vs Hermes /rollback after auto pre-mutate snaps landed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codeant-ai

codeant-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed 9516606 Aug 16, 2026 · 18:03 18:03
✅ Reviewed your PR 4a273b6 Aug 07, 2026 · 10:11 10:15

@codeant-ai

codeant-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Aug 7, 2026
Comment thread src/dream/_factory.py
Comment thread src/dream/_factory.py
Comment thread src/dream/config/paths.py
Comment thread src/dream/state/shadow/_manager.py Outdated
Comment thread src/dream/state/shadow/_rewind.py Outdated
divo12 and others added 4 commits August 7, 2026 11:05
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@divo12

divo12 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai please review

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds default-on shadow checkpoints and an operator-facing combined filesystem/transcript rewind.

  • Wires a shared checkpoint manager and hook into harness-created sessions.
  • Tracks real prompt boundaries for transcript truncation and invalidates them after compaction.
  • Adds worktree-size limits, per-session checkpoint bookkeeping, public restore types, and coverage for restore behavior.

Confidence Score: 2/5

The PR is not safe to merge until checkpoint session propagation, session ownership during restore, and cross-session worktree synchronization are corrected.

PRE_TOOL_USE loses the session identifier and disables subsequent checkpoints, while shared checkpoint selection and unsynchronized restore operations can restore unrelated state or overwrite another active session's filesystem changes.

Files Needing Attention: src/dream/state/shadow/_hook.py, src/dream/state/shadow/_manager.py, src/dream/session.py

Important Files Changed

Filename Overview
src/dream/state/shadow/_hook.py The new session-scoped deduplication is not propagated through PRE_TOOL_USE, causing checkpoints to stop after the first conclusive snapshot.
src/dream/state/shadow/_manager.py Adds size probing and combined restore, but shared worktree operations and checkpoint ownership remain unsynchronized across sessions.
src/dream/session.py Adds prompt-boundary tracking and rewind APIs, but default restore selection and the active-send guard are only locally session-aware.
src/dream/_factory.py Correctly wires the feature by default, while making the manager and worktree shared across all harness sessions.

Sequence Diagram

sequenceDiagram
  participant A as Session A
  participant B as Session B
  participant H as Shared checkpoint hook
  participant M as Shared manager
  participant W as Shared worktree
  A->>H: USER_PROMPT_SUBMIT(session A)
  H->>M: begin_turn("A")
  A->>H: PRE_TOOL_USE(no session_id)
  H->>M: "ensure(session_id=None)"
  M->>W: Create checkpoint
  B->>H: USER_PROMPT_SUBMIT(session B)
  H->>M: begin_turn("B")
  B->>H: PRE_TOOL_USE(no session_id)
  H->>M: "ensure(session_id=None)"
  M-->>B: ALREADY_THIS_TURN
  A->>M: restore newest worktree checkpoint
  M->>W: Reset shared worktree
  Note over A,B: Only A's transcript is rewound
Loading
Prompt To Fix All With AI
### Issue 1
src/dream/state/shadow/_hook.py:53-57
**Session dedup key is lost**

`PRE_TOOL_USE` payloads do not contain `session_id`, so this call passes `None` and all sessions share a deduplication set that real-session `begin_turn` calls never clear. After the first conclusive checkpoint, subsequent mutations return `ALREADY_THIS_TURN` and no longer receive recoverable pre-mutation checkpoints.

### Issue 2
src/dream/session.py:231-237
**Restore selects cross-session checkpoints**

When two sessions on the harness create interleaved checkpoints, `list_for` returns their shared working-directory history and this branch selects the globally newest snapshot. The filesystem can therefore be restored to the other session's state while only the calling session's transcript is rewound, leaving chat and disk unrelated.

### Issue 3
src/dream/session.py:207-208
**Restore lacks worktree synchronization**

If another session is executing a mutating tool in the same harness worktree, this per-session `_active` check still permits a restore. Because the shared manager has no worktree-level lock, the reset can overwrite or interleave with that session's writes, leaving its transcript inconsistent with the resulting filesystem.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "Skip checkpoints for oversized worktrees" | Re-trigger Greptile

Comment on lines +53 to +57
self._manager.ensure(
self._working_dir,
reason=reason,
session_id=self._session_id(payload),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Session dedup key is lost

PRE_TOOL_USE payloads do not contain session_id, so this call passes None and all sessions share a deduplication set that real-session begin_turn calls never clear. After the first conclusive checkpoint, subsequent mutations return ALREADY_THIS_TURN and no longer receive recoverable pre-mutation checkpoints.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/dream/state/shadow/_hook.py
Line: 53-57

Comment:
**Session dedup key is lost**

`PRE_TOOL_USE` payloads do not contain `session_id`, so this call passes `None` and all sessions share a deduplication set that real-session `begin_turn` calls never clear. After the first conclusive checkpoint, subsequent mutations return `ALREADY_THIS_TURN` and no longer receive recoverable pre-mutation checkpoints.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread src/dream/session.py
Comment on lines +231 to +237
listed = manager.list_for(engine.working_dir)
if not listed:
return CombinedRestoreResult(
fs=RestoreResult(
outcome=RestoreOutcome.NOT_FOUND,
detail="no checkpoints for working directory",
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Restore selects cross-session checkpoints

When two sessions on the harness create interleaved checkpoints, list_for returns their shared working-directory history and this branch selects the globally newest snapshot. The filesystem can therefore be restored to the other session's state while only the calling session's transcript is rewound, leaving chat and disk unrelated.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/dream/session.py
Line: 231-237

Comment:
**Restore selects cross-session checkpoints**

When two sessions on the harness create interleaved checkpoints, `list_for` returns their shared working-directory history and this branch selects the globally newest snapshot. The filesystem can therefore be restored to the other session's state while only the calling session's transcript is rewound, leaving chat and disk unrelated.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread src/dream/session.py
Comment on lines +207 to +208
) -> CombinedRestoreResult:
"""Hermes-style human rewind: restore FS and truncate the transcript.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Restore lacks worktree synchronization

If another session is executing a mutating tool in the same harness worktree, this per-session _active check still permits a restore. Because the shared manager has no worktree-level lock, the reset can overwrite or interleave with that session's writes, leaving its transcript inconsistent with the resulting filesystem.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/dream/session.py
Line: 207-208

Comment:
**Restore lacks worktree synchronization**

If another session is executing a mutating tool in the same harness worktree, this per-session `_active` check still permits a restore. Because the shared manager has no worktree-level lock, the reset can overwrite or interleave with that session's writes, leaving its transcript inconsistent with the resulting filesystem.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

divo12 and others added 3 commits August 16, 2026 23:29
Restack operator rewind onto current main so Session.list_checkpoints /
restore_checkpoint restore the worktree and truncate Session-owned prompt
turns, with build_harness auto-wiring the shared shadow store.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the reviewed current-main implementation while joining the original PR branch without rewriting remote history.
@codeant-ai

codeant-ai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@divo12
divo12 merged commit e4c8440 into main Aug 16, 2026
4 checks passed

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

divo12 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@divo12
divo12 deleted the feat/human-rewind-surface branch August 16, 2026 18:03
@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:XL This PR changes 500-999 lines, ignoring generated files labels Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant