Recovery Execution Specification¶
Document Date: 2026-06-24 Framework Version: 0.3.0 Status: IMPLEMENTATION SPECIFICATION
1. Overview¶
This document specifies the recovery execution system for the Loop Engineering Framework. Recovery must not stop at generating a RecoveryAction object—it must perform actual state-changing operations.
2. Recovery Architecture¶
Recovery separates four concerns: 1. Decision: Select appropriate recovery strategy 2. Execution: Perform the recovery operation 3. Validation: Verify postconditions 4. State Update: Update runtime state based on outcome
3. Recovery Strategies¶
3.1 RETRY¶
Purpose: Re-execute the failed step with same parameters.
Preconditions: - Step is in EVALUATION_FAILED or VERIFICATION_FAILED state - Retry count < max_retry_attempts
Execution: 1. Mark step as RETRY_PENDING 2. Increment retry counter 3. Reset step execution fields (clear output, timestamps) 4. Transition step to READY 5. Schedule for re-execution
Postconditions: - Step.status == READY - Step.retry_count incremented - Step.output is None - Step.start_time is None
Success Criteria: - Step successfully transitioned to READY - Step is picked up by next planning phase
Failure Criteria: - Max retries exceeded - Step cannot be reset
3.2 RETRY_WITH_BACKOFF¶
Purpose: Retry with delay policy to avoid rapid failure loops.
Preconditions: - Same as RETRY - Injectable clock available for testing
Execution:
1. Calculate delay: delay = backoff_base * (2 ** retry_count)
2. Record delay policy in step metadata
3. Wait via injectable clock (NOT asyncio.sleep in production)
4. Execute RETRY logic
Postconditions: - Delay recorded in step.metadata['backoff_delay'] - Retry only after delay condition satisfied
Testing: - Use mock clock that advances time explicitly - Do NOT use real time delays in tests
3.3 REPLAN_STEP¶
Purpose: Replace failed step with corrected version.
Preconditions: - Planner component available - Step has failed (EVALUATION_FAILED or VERIFICATION_FAILED)
Execution: 1. Preserve failure evidence in step.metadata['failure_evidence'] 2. Call planner.revise_step() with failure context 3. Create new step version with: - New step.id - Same dependencies - Corrected description/action - Version incremented 4. Replace failed step in plan.steps (preserve order) 5. Mark unaffected completed steps as unchanged
Postconditions: - Failed step replaced with corrected version - New step.status == PENDING - Plan.version incremented - Completed steps remain completed
Success Criteria: - New step created with corrected parameters - Plan updated
3.4 REPLAN_REMAINING_TASK¶
Purpose: Replan remaining uncompleted steps.
Preconditions: - Planner component available - Some steps completed, some failed/pending
Execution: 1. Preserve completed steps 2. Identify remaining task scope 3. Call planner.revise_plan() with: - Current plan - Observations - Evaluations - Failure context 4. Merge new plan with completed steps
Postconditions: - Completed steps unchanged - Remaining steps revised - Plan.version incremented
3.5 ROLLBACK¶
Purpose: Restore previous artifact snapshot.
Preconditions: - Snapshot system available - Previous valid state exists
Execution: 1. Identify rollback target (last VERIFIED_COMPLETED step) 2. Restore artifact snapshot 3. Verify rollback success (checksum/state hash) 4. Mark rollback in trace 5. Reset subsequent steps to PENDING
Postconditions: - Artifacts restored to previous state - Subsequent steps reset - Rollback recorded in trace
3.6 CHANGE_TOOL¶
Purpose: Use alternative tool for same task.
Preconditions: - Alternative tools available - Tool registry accessible
Execution: 1. Select alternative tool from registry 2. Update step.tool field 3. Reset step to READY 4. Mark tool change in metadata
3.7 CHANGE_MODEL¶
Purpose: Use different model for same task.
Preconditions: - Multiple models available - Model switcher accessible
Execution: 1. Select alternative model 2. Update step.model field 3. Reset step to READY 4. Mark model change in metadata
3.8 REDUCE_SCOPE¶
Purpose: Reduce task scope to achievable subset.
Preconditions: - Scope reduction policy defined - Partial completion acceptable
Execution: 1. Identify minimum viable scope 2. Mark optional steps as SKIPPED 3. Replan remaining required steps 4. Update goal description
Postconditions: - Some steps marked SKIPPED - Reduced scope documented
3.9 REQUEST_HUMAN¶
Purpose: Escalate to human operator.
Preconditions: - Human escalation configured - Escalation channel available
Execution: 1. Transition to WAITING_FOR_HUMAN state 2. Include decision context: - Failed step details - Recovery attempts made - Options for human 3. Wait for human response 4. Resume based on human instruction
Postconditions: - State == WAITING_FOR_HUMAN - Human context prepared
3.10 ABSTAIN¶
Purpose: Intentionally stop without completing.
Preconditions: - Safe abstention policy allows - No critical requirements violated
Execution: 1. Transition to ABSTAINED state 2. Document abstention reason 3. Preserve partial results 4. Terminate gracefully
3.11 TERMINATE¶
Purpose: Stop with failure status.
Preconditions: - Non-recoverable failure - Max attempts exceeded
Execution: 1. Mark failure as TERMINAL 2. Transition to FAILED state 3. Preserve failure evidence 4. Terminate with error result
4. Recovery Handler Interface¶
class RecoveryHandler(ABC):
"""Base class for recovery strategy handlers."""
@abstractmethod
async def execute(
self,
failure: Failure,
step: Step,
state: LoopState,
context: LoopContext
) -> RecoveryResult:
"""
Execute recovery strategy.
Returns:
RecoveryResult with success/failure and state changes
"""
pass
@abstractmethod
def validate_postconditions(
self,
step: Step,
state: LoopState
) -> bool:
"""Verify recovery produced valid state."""
pass
@dataclass
class RecoveryResult:
"""Result of recovery execution."""
success: bool
new_state: Optional[ExecutionState] = None
step_status: Optional[StepStatus] = None
message: str = ""
evidence: Dict[str, Any] = field(default_factory=dict)
5. Recovery Registry¶
class RecoveryRegistry:
"""Registry of recovery strategy handlers."""
def __init__(self):
self._handlers: Dict[RecoveryStrategy, RecoveryHandler] = {}
def register(self, strategy: RecoveryStrategy, handler: RecoveryHandler):
self._handlers[strategy] = handler
async def execute(
self,
strategy: RecoveryStrategy,
failure: Failure,
step: Step,
state: LoopState,
context: LoopContext
) -> RecoveryResult:
handler = self._handlers.get(strategy)
if not handler:
return RecoveryResult(
success=False,
message=f"No handler for strategy {strategy}"
)
# Execute recovery
result = await handler.execute(failure, step, state, context)
# Validate postconditions
if result.success:
valid = handler.validate_postconditions(step, state)
if not valid:
result.success = False
result.message = "Recovery postconditions not met"
return result
6. Integration with Loop Engine¶
6.1 Recovery Execution Flow¶
async def _execute_recovery(self, context: LoopContext, evaluation: Optional[Evaluation] = None):
"""Execute recovery with real handlers."""
recovery = self.components.get(ComponentType.RECOVERY)
if not recovery:
return
# Get unhandled failures
recent_failures = [
f for f in self.state.failures
if f.status == FailureStatus.UNHANDLED and f.can_recover()
]
if not recent_failures:
return
failure = recent_failures[-1]
failure.status = FailureStatus.RECOVERY_PLANNED
# Get recovery action (decision)
recovery_action = await recovery.recover(
failure,
self.state,
context
)
# Execute recovery strategy (real execution)
result = await self._recovery_registry.execute(
strategy=recovery_action.strategy,
failure=failure,
step=self.state.current_step,
state=self.state,
context=context
)
# Update based on result
recovery_action.executed = True
recovery_action.success = result.success
failure.record_recovery_attempt(recovery_action.action_id)
if result.success:
failure.mark_recovered()
# Transition to appropriate state
if result.new_state:
self._transition_to(result.new_state)
else:
if not failure.can_recover():
failure.mark_terminal()
self.state.execution_state = ExecutionState.FAILED
6.2 State Transitions After Recovery¶
| Recovery Result | Next State | Conditions |
|---|---|---|
| RETRY success | ITERATION_COMPLETE | Step reset to READY |
| REPLAN success | REPLANNING | Plan revised |
| ROLLBACK success | REPLANNING | State restored |
| HUMAN requested | WAITING_FOR_HUMAN | Escalation needed |
| Failure | FAILED | Max attempts or unrecoverable |
7. Required Tests¶
7.1 Retry Tests¶
- First retry succeeds
- Retry increments counter
- Step fields reset properly
- Step returns to READY
7.2 Replan Tests¶
- Failed step replaced
- Completed steps preserved
- Plan version incremented
- New step has correct dependencies
7.3 Rollback Tests¶
- Snapshot restored
- Verification passes
- Subsequent steps reset
- Rollback recorded in trace
7.4 Limit Tests¶
- Max attempts enforced
- New failure creates new ID
- Recovered failures not reprocessed
- Terminal failures stop recovery
7.5 State Transition Tests¶
- Recovery success → valid state
- Recovery failure → FAILED
- Postcondition validation works
8. Implementation Plan¶
8.1 Files to Create¶
loop_engine/recovery/handlers.py- Recovery handlersloop_engine/recovery/registry.py- Recovery registryloop_engine/recovery/__init__.py- Package init
8.2 Files to Modify¶
loop_engine/core.py- Integrate recovery registryloop_engine/components.py- Update recovery interfacetests/unit/test_recovery.py- Recovery tests
8.3 Default Handlers¶
Implement handlers for: - RETRY (essential) - REPLAN_STEP (essential) - REQUEST_HUMAN (essential) - TERMINATE (essential)
Optional handlers (future): - RETRY_WITH_BACKOFF - ROLLBACK - CHANGE_TOOL - CHANGE_MODEL - REDUCE_SCOPE