Skip to content

Issue #10: resume intent log + compensator preflight - #13

Merged
nutt-adam merged 3 commits into
mainfrom
codex/issue-10-resume-intent-compensator
Mar 14, 2026
Merged

Issue #10: resume intent log + compensator preflight#13
nutt-adam merged 3 commits into
mainfrom
codex/issue-10-resume-intent-compensator

Conversation

@nutt-adam

@nutt-adam nutt-adam commented Mar 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add per-step workflow intent records at .tutti/state/workflow-intents/<run_id>/<step_id>.json
  • persist step outcomes (success/warning/failed) onto intent records after each attempted step
  • add resume compensator preflight planning before replay (tt run --resume)
  • add built-in idempotency guards for resume replays:
    • land: skip replay when branch is already merged and the agent worktree is clean
    • review: skip replay if a review packet already exists since prior attempt
    • ensure_running: keeps existing idempotent no-op behavior
  • bump version to 0.2.2

Tests

  • added coverage for step intent/outcome persistence
  • added coverage for command-step compensator planning
  • added coverage for land compensator behavior (merged branch skip + fresh branch with dirty worktree replay)
  • full suite green (cargo fmt, cargo clippy --all-targets --all-features -- -D warnings, cargo test, cargo build --release)

Closes #10

Summary by CodeRabbit

  • New Features

    • Show a formatted resume plan when resuming workflows
    • Persist per-step intents and outcomes to improve resume/rollback behavior
    • Surface failed steps for clearer resume diagnostics
  • Bug Fixes

    • Prevent resuming when there are no pending steps
    • Idempotency guards to avoid duplicate step execution and unnecessary replays
  • Chores

    • Package version bumped to 0.2.2

@nutt-adam

Copy link
Copy Markdown
Contributor Author

Addressed the review findings in 9b6c59d:\n\n1. is now preserved across re-attempts (only increments).\n2. Control-DAG intent-write failures now return explicit failed outcomes (no silent propagation).\n3. resume idempotency guard now requires merged+divergent history and clean worktree; fresh branches at are no longer treated as already-landed.\n4. Outcome persistence failures now emit explicit warnings instead of being silently discarded.\n\nAlso added regression tests for each behavior and bumped version to . All checks are green locally (, , , ).

@nutt-adam

Copy link
Copy Markdown
Contributor Author

Addressed the review findings in 9b6c59d:

  1. planned_at is now preserved across re-attempts (only attempt increments).
  2. Control-DAG intent-write failures now return explicit failed StepResult outcomes (no silent propagation).
  3. land resume idempotency guard now requires merged+divergent history and clean worktree; fresh branches at HEAD are no longer treated as already-landed.
  4. Outcome persistence failures now emit explicit warnings instead of being silently discarded.

Also added regression tests for each behavior and bumped version to 0.2.2. All checks are green locally (cargo fmt, cargo clippy --all-targets --all-features -- -D warnings, cargo test, cargo build --release).

@coderabbitai

coderabbitai Bot commented Mar 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds persistent per-step intent and outcome logging plus compensator-plan generation for workflow resume; threads run/workflow identifiers through execution paths, introduces idempotency guards for Git/worktree-based steps, and surfaces failed step indices in ResumeContext.

Changes

Cohort / File(s) Summary
Version Bump
Cargo.toml
Bumped package version from 0.2.0 to 0.2.2.
Intent & Outcome Persistence
src/state/mod.rs
Added WorkflowStepIntentRecord and WorkflowStepOutcomeRecord structs, JSON persistence helpers save_workflow_intent and load_workflow_intent, and ensured state dir includes workflow-intents. Tests added for round-trip.
Resume & Compensation Core
src/automation/mod.rs
Large addition: per-step keying/sanitization, intent recording before scheduling, outcome recording after execution, functions to load intents/outcomes, resume guards (branch/worktree/review detection), compensator-plan builder exposed publicly (pub fn build_resume_compensator_plan), propagated run_id and workflow_name, and added pub failed_steps: Vec<usize> to ResumeContext. Many helper routines and idempotency checks for land/review/ensure_running flows.
CLI Resume Plan Integration
src/cli/run.rs
When resuming, call build_resume_compensator_plan and print a resume plan (new print_resume_plan). Added validation to reject resume when no pending steps remain.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as "cli/run.rs"
    participant Executor as "automation/mod.rs"
    participant State as "state/mod.rs"
    participant Git as "Git checks / worktree"

    User->>CLI: request resume run
    CLI->>Executor: resolve workflow + resume context
    CLI->>Executor: request compensator plan -> Executor
    Executor->>State: load intent for step N
    alt intent exists and no successful outcome
        State-->>Executor: WorkflowStepIntentRecord
        Executor->>Git: check idempotency (branch merged? PR exists? worktree changes?)
        alt side-effects present
            Git-->>Executor: detected prior side-effects
            Executor-->>CLI: include compensator actions in plan / skip replay
        else
            Git-->>Executor: no side-effects found
            Executor->>Executor: schedule step, record intent
            Executor->>Executor: execute step
            Executor->>State: record outcome
        end
    else no intent
        Executor->>Executor: record intent, execute step
        Executor->>State: record outcome
    end
    Executor-->>CLI: return compensator plan
    CLI-->>User: print resume plan
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I tucked intents in tiny files,

so when you wake me from my trials,
I'll check the branches, peek the tree,
and plan the hops that set you free. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately references Issue #10 and clearly summarizes the two main features: resume intent log and compensator preflight planning.
Linked Issues check ✅ Passed All primary coding objectives from Issue #10 are implemented: intent/outcome persistence, compensator plan generation, idempotency guards for land/review/ensure_running, and resume executor integration.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue requirements: workflow intent structures, persistence helpers, compensator planning logic, and version bump.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/issue-10-resume-intent-compensator
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
Cargo.toml (1)

3-3: Version discrepancy with PR summary.

The PR summary mentions bumping version to 0.2.1, but the actual version is 0.2.2. Please verify the intended version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Cargo.toml` at line 3, The Cargo.toml entry version = "0.2.2" does not match
the PR summary which states 0.2.1; decide the intended release and make them
consistent by updating the Cargo.toml version field (version = "...") to the
correct value or update the PR summary/release notes to reflect version =
"0.2.2".
src/automation/mod.rs (2)

2662-2691: Duplication of idempotency guard logic.

This land idempotency guard duplicates the logic at lines 1403-1432. Consider extracting a shared helper function (e.g., should_skip_land_replay) to ensure both code paths stay consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/automation/mod.rs` around lines 2662 - 2691, The idempotency guard that
checks prior_unsuccessful_intent duplicates logic elsewhere; extract this into a
shared helper (e.g., should_skip_land_replay) and call it from both locations.
Implement should_skip_land_replay(project_root, agent,
prior_unsuccessful_intent) to encapsulate the agent_branch lookup,
is_branch_merged, branch_has_divergent_commits, and worktree_has_changes checks
and return a bool plus any message needed; then replace the duplicated block in
the current function (the if let Some(prior_unsuccessful_intent) { ... } block)
with a call to that helper and construct the ControlStepOutcome/StepResult only
when the helper indicates skipping. Ensure the helper uses the same
semantics/wrapping of unwrap_or defaults as the original checks to keep behavior
identical.

2163-2170: Consider reusing existing worktree inspection logic.

The worktree_has_changes function duplicates logic from src/worktree/mod.rs::inspect_worktree() (see relevant code snippet 2). While acceptable for this PR, consider consolidating these utilities in a future refactor to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/automation/mod.rs` around lines 2163 - 2170, worktree_has_changes
duplicates logic from src/worktree/mod.rs::inspect_worktree; replace the
duplicated implementation by calling inspect_worktree for the same path and
mapping its result to a bool, instead of re-running git_output_for_automation
here. Import or reference inspect_worktree, compute worktree_path =
project_root.join(".tutti").join("worktrees").join(agent), call
inspect_worktree(&worktree_path) and return Ok(true/false) based on whether it
reports changes, propagating errors as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/automation/mod.rs`:
- Around line 1403-1432: The idempotency guard currently requires divergent ==
true which misses fast-forward merges; update the conditional in the
prior_unsuccessful_intent handling (the block using agent_branch,
is_branch_merged, branch_has_divergent_commits, worktree_has_changes) to skip
replay when the branch is merged and the worktree is clean regardless of
divergence (i.e., remove the divergent check), and adjust/remove the now-unused
branch_has_divergent_commits result or its call accordingly; keep the StepResult
construction (step_results push with message about already merged) and ensure
the message still reports branch and intent.planned_at.

---

Nitpick comments:
In `@Cargo.toml`:
- Line 3: The Cargo.toml entry version = "0.2.2" does not match the PR summary
which states 0.2.1; decide the intended release and make them consistent by
updating the Cargo.toml version field (version = "...") to the correct value or
update the PR summary/release notes to reflect version = "0.2.2".

In `@src/automation/mod.rs`:
- Around line 2662-2691: The idempotency guard that checks
prior_unsuccessful_intent duplicates logic elsewhere; extract this into a shared
helper (e.g., should_skip_land_replay) and call it from both locations.
Implement should_skip_land_replay(project_root, agent,
prior_unsuccessful_intent) to encapsulate the agent_branch lookup,
is_branch_merged, branch_has_divergent_commits, and worktree_has_changes checks
and return a bool plus any message needed; then replace the duplicated block in
the current function (the if let Some(prior_unsuccessful_intent) { ... } block)
with a call to that helper and construct the ControlStepOutcome/StepResult only
when the helper indicates skipping. Ensure the helper uses the same
semantics/wrapping of unwrap_or defaults as the original checks to keep behavior
identical.
- Around line 2163-2170: worktree_has_changes duplicates logic from
src/worktree/mod.rs::inspect_worktree; replace the duplicated implementation by
calling inspect_worktree for the same path and mapping its result to a bool,
instead of re-running git_output_for_automation here. Import or reference
inspect_worktree, compute worktree_path =
project_root.join(".tutti").join("worktrees").join(agent), call
inspect_worktree(&worktree_path) and return Ok(true/false) based on whether it
reports changes, propagating errors as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d2a4e35c-308c-41f9-95cf-01b048692298

📥 Commits

Reviewing files that changed from the base of the PR and between 8fbaacf and 9b6c59d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Cargo.toml
  • src/automation/mod.rs
  • src/cli/run.rs
  • src/state/mod.rs

Comment thread src/automation/mod.rs Outdated
@nutt-adam

Copy link
Copy Markdown
Contributor Author

Addressed the requested changes in ea3ac34:

  1. Refactored duplicated land idempotency logic into shared helper should_skip_land_replay(...) and reused it in both sequential and control-DAG paths.
  2. Switched worktree dirtiness checks to worktree::inspect_worktree(...) via worktree_has_changes(...) (removed ad-hoc git status duplicate logic).
  3. Updated land skip semantics for resume to skip when merged + clean worktree (supports fast-forward merged state too).
  4. Updated PR summary version text to match actual release version (0.2.2).

Validation rerun clean:

  • cargo fmt --all
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test
  • cargo build --release

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
src/automation/mod.rs (1)

2088-2130: Consider using creation time instead of modification time for robustness.

The function uses meta.modified() to determine if a review packet was created after the intent timestamp. While modification time works for most cases, it could theoretically be updated by file operations other than creation (e.g., reads on some filesystems, or accidental touches).

If the platform supports it, meta.created() would be more semantically correct. However, this is a minor concern since modification time is standard practice and the current implementation is acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/automation/mod.rs` around lines 2088 - 2130, The code in
review_packet_exists_since uses file modification time (meta.modified()) to
decide if a review packet is "since" the given timestamp; change this to prefer
creation time when available by calling meta.created() and falling back to
meta.modified() if created() returns Err or is unsupported, update the variable
names (e.g., created_or_modified -> modified_time) and comparisons where
modified is used (the tuple in best: Option<(std::time::SystemTime, PathBuf)>
and the is_none_or closure) so the function prefers the creation timestamp but
retains modification-time behavior on platforms that don't expose creation time.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/automation/mod.rs`:
- Around line 2088-2130: The code in review_packet_exists_since uses file
modification time (meta.modified()) to decide if a review packet is "since" the
given timestamp; change this to prefer creation time when available by calling
meta.created() and falling back to meta.modified() if created() returns Err or
is unsupported, update the variable names (e.g., created_or_modified ->
modified_time) and comparisons where modified is used (the tuple in best:
Option<(std::time::SystemTime, PathBuf)> and the is_none_or closure) so the
function prefers the creation timestamp but retains modification-time behavior
on platforms that don't expose creation time.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0e42cb21-10e3-4ba4-90d4-e41dd8c1c19b

📥 Commits

Reviewing files that changed from the base of the PR and between 9b6c59d and ea3ac34.

📒 Files selected for processing (1)
  • src/automation/mod.rs

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.

Workflow resume: add intent log + compensator plan before step replay

1 participant