Skip to content

Conversation

@evanliu048
Copy link
Contributor

@evanliu048 evanliu048 commented Sep 16, 2025

Issue #, if available:

Description of changes:
Introduce session-scoped checkpoints for the current repo. Q CLI now snapshots file changes into a shadow bare git repo and lets users list, expand, diff, and restore to any checkpoint. Errors are short and human-readable; empty diffs print “No differences.” The conversation history is also unwind when restoring a checkpoint

Commands

  • /checkpoint init — Manually enable checkpoints if not in a git repo.
    • (Auto): If starting inside a git repo, checkpoints auto-enable (ephemeral, cleaned on session end).
  • /checkpoint list [--limit N] — Turn-level checkpoints with file stats.
  • /checkpoint expand <tag> — Show tool-level checkpoints under a turn, with “(+/~/-) files” badges.
  • /checkpoint diff <tag1> [tag2|HEAD]
    • Prints header then diff, or “No differences.” if none.
    • Unknown tags: Checkpoint with tag '' does not exist! Use /checkpoint list to see available checkpoints
  • /checkpoint restore [<tag>] [--hard]
    • Default: revert tracked changes & deletions; do not delete files created after the checkpoint.
    • --hard: make workspace exactly match the checkpoint; deletes tracked files created after it.
    • If <tag> omitted, shows interactive picker.
  • /checkpoint clean — Delete session shadow repo.
image

Implementation (high-level)

Shadow storage: bare git repo under get_shadow_repo_dir(conversation_id), operating on --work-tree=..
Helper run_git(...) centralizes --git-dir/--work-tree and error handling.
File stats via git diff --name-status; treat R/C as modified; cache per-tag stats.
Previous-tag resolution for X and X.Y forms.
Soft vs hard restore wired through git checkout vs git reset --hard.

Examples

/checkpoint list
[0] 2025-09-18 14:00:00 - Initial checkpoint
[1] 2025-09-18 14:05:31 - add two_sum.py (+1 file)
[2] 2025-09-18 14:07:10 - add tests (modified 1)

/checkpoint expand 2
[2] 2025-09-18 14:07:10 - add tests
 └─ [2.1] fs_write: Add minimal test cases to two_sum.py (modified 1)

/checkpoint diff 2
Comparing current state with checkpoint [2]:
No differences.

/checkpoint restore 1 --hard
Restored checkpoint: 1

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

kiran-garre and others added 16 commits August 13, 2025 16:48
Updates:
- Only works if the user has git installed
- Supports auto initialization if the user is in a git repo, manual
if not
- UI ported over from dedicated file tools implementation
Updates:
- The clean subcommand will delete the shadow repo
- The description for turn-level checkpoints is a truncated version
of the user's last message
Updates:
- Running the clean subcommand now properly deletes the entire
shadow repo for both automatic and manual modes
Updates:
- Users can now view diffs between checkpoints
- Fixed tool-level checkpoint display handling
Updates:
- Checkpoints now (hopefully) correctly display the correct
turn-specific user message
- Added slash command auto completion
@evanliu048 evanliu048 marked this pull request as draft September 16, 2025 21:56
@evanliu048 evanliu048 marked this pull request as ready for review September 22, 2025 22:51
@evanliu048 evanliu048 changed the title feat: Adds checkpointing/Capture functionality using Git CLI commands feat: Adds checkpointing functionality using Git CLI commands Sep 22, 2025
String::new()
}
// Take manager out temporarily to avoid borrow conflicts
else if let Some(mut manager) = self.conversation.checkpoint_manager.take() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any invocation here that requires a mut self for manager? If not we can just do

else if let Some(manager) = self.conversation.checkpoint_manager.as_mut() {
...
}

Copy link
Contributor Author

@evanliu048 evanliu048 Sep 24, 2025

Choose a reason for hiding this comment

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

Thanks for the advise! I do change manager later (tools_in_turn += 1 and create_checkpoint also mutates internal state)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also the take() approach is here because I need to access both manager (mutably) and self.conversation.history() (immutably) in the same scope. Using as_mut() would cause a borrowing conflict when calling self.conversation.history().clone() inside create_checkpoint().

debug!("Failed to create tool checkpoint: {}", e);
String::new()
} else {
manager.tools_in_turn += 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we ever increment this by more than one per turn? I see that we are resetting it to 0 every turn.
If that's the case this can just be a bool then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes we do increase this value
It's used to generate unique checkpoint tags like "3.1", "3.2" for each tool within a turn
Multiple tools can be executed within a single turn.
We need to distinguish between different tool checkpoints in the same turn

impl CheckpointSubcommand {
pub async fn execute(self, os: &Os, session: &mut ChatSession) -> Result<ChatState, ChatError> {
// Check if in tangent mode - captures are disabled during tangent mode
if os
Copy link
Contributor

Choose a reason for hiding this comment

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

This is on me but we should have an experiment helper to query experiments in particular.

session.stderr,
style::SetForegroundColor(Color::Yellow),
style::Print(
"⚠️ Checkpoint is disabled while in tangent mode. Disable tangent mode with: q settings -d chat.enableTangentMode.\n\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why disable checkpoint if tangent mode is enabled? I understand disabling it while in Tangent mode, but not following why we can't have both features enabled at same time.

/// Restore conversation from a checkpoint's history snapshot
pub fn restore_to_checkpoint(&mut self, checkpoint: &Checkpoint) -> Result<(), eyre::Report> {
// 1. Restore history from snapshot
self.history = checkpoint.history_snapshot.clone();
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious? Can a checkpoint corrupt conversation history by any means? Like a tool use without tool results and so on?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The checkpoint is created after the tool executes successfully and in handle_response, so that should be fine. Thanks for the callout — we can test it more thoroughly during the bug bash

@kensave kensave merged commit 1f4f96f into main Sep 25, 2025
14 of 15 checks passed
@kensave kensave deleted the snapshot_revise branch September 25, 2025 23:12
session.stderr,
style::SetForegroundColor(Color::Yellow),
style::Print(
"⚠️ Checkpoint is disabled while in tangent mode. Disable tangent mode with: q settings -d chat.enableTangentMode.\n\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think user just needs to exit tangent mode, right? Doesn't need to disable it completely?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes. thanks for catching this. I will change the word here

xianwwu pushed a commit to xianwwu/amazon-q-developer-cli that referenced this pull request Oct 6, 2025
* (in progress) Implement checkpointing using git CLI commands

* feat: Add new checkpointing functionality using git CLI

Updates:
- Only works if the user has git installed
- Supports auto initialization if the user is in a git repo, manual
if not
- UI ported over from dedicated file tools implementation

* feat: Add user message for turn-level checkpoints, clean command

Updates:
- The clean subcommand will delete the shadow repo
- The description for turn-level checkpoints is a truncated version
of the user's last message

* fix: Fix shadow repo deletion logic

Updates:
- Running the clean subcommand now properly deletes the entire
shadow repo for both automatic and manual modes

* chore: Run formatter and fix clippy warnings

* feat: Add checkpoint diff

Updates:
- Users can now view diffs between checkpoints
- Fixed tool-level checkpoint display handling

* fix: Fix last messsage handling for checkpoints

Updates:
- Checkpoints now (hopefully) correctly display the correct
turn-specific user message
- Added slash command auto completion

* fix: Fix commit message handling again

* chore: Run formatter

* Removed old comment

* define a global capture dirctory

* revise the capture path

* fix cpature clean bug

* add a clean all flag

* add auto drop method for capture feature

* support file details when expand

* add the file summary when list and expand

* revise structure and print no diff msg

* delete all flag, add summry when fs read

* refactor code

* revise ui

* add capture into experiement

* clippy

* rename to checkpoint

* reverse false renaming

* recover history

* disable tangent mode in checkpoint

* fix cr

* nit: keep checkpoint name

* allow both tangent & checkpoint enabled

* ci

---------

Co-authored-by: kiran-garre <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants