Reliability Architecture

Reliability Architecture

Visual diagrams and architecture for the reliability design.

System Architecture with Reliability Components

┌─────────────────────────────────────────────────────────────────────────────┐
│                         Management Server (Rust)                             │
│                                                                               │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                         Orchestrator                                 │    │
│  │  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐           │    │
│  │  │   Task        │  │   Executor    │  │   Monitor     │           │    │
│  │  │   Registry    │  │               │  │               │           │    │
│  │  │  (in-memory)  │  │  - Stage      │  │  - Tail logs  │           │    │
│  │  │               │  │  - Provision  │  │  - Broadcast  │           │    │
│  │  │               │  │  - Execute    │  │               │           │    │
│  │  └───────┬───────┘  └───────┬───────┘  └───────────────┘           │    │
│  │          │                   │                                       │    │
│  │          │                   │                                       │    │
│  │  ┌───────▼───────────────────▼──────────────────────────────────┐   │    │
│  │  │              Reliability Layer (NEW)                         │   │    │
│  │  │                                                               │   │    │
│  │  │  ┌─────────────┐  ┌──────────────┐  ┌──────────────┐       │   │    │
│  │  │  │ Checkpoint  │  │   Timeout    │  │     Hang     │       │   │    │
│  │  │  │   Store     │  │  Enforcer    │  │   Detector   │       │   │    │
│  │  │  │             │  │              │  │              │       │   │    │
│  │  │  │ - Save on   │  │ - Per-op     │  │ - No output  │       │   │    │
│  │  │  │   state Δ   │  │ - Per-stage  │  │ - No Δ state │       │   │    │
│  │  │  │ - Load on   │  │ - Task total │  │ - Auto-kill  │       │   │    │
│  │  │  │   startup   │  │              │  │              │       │   │    │
│  │  │  └─────────────┘  └──────────────┘  └──────────────┘       │   │    │
│  │  │                                                               │   │    │
│  │  │  ┌─────────────┐  ┌──────────────┐  ┌──────────────┐       │   │    │
│  │  │  │   Retry     │  │  Resource    │  │ Degradation  │       │   │    │
│  │  │  │   Policy    │  │   Monitor    │  │   Manager    │       │   │    │
│  │  │  │             │  │              │  │              │       │   │    │
│  │  │  │ - Exp B/O   │  │ - Storage    │  │ - Admission  │       │   │    │
│  │  │  │ - Jitter    │  │ - Memory     │  │   control    │       │   │    │
│  │  │  │ - Max tries │  │ - VMs        │  │ - Graceful   │       │   │    │
│  │  │  │             │  │              │  │   shutdown   │       │   │    │
│  │  │  └─────────────┘  └──────────────┘  └──────────────┘       │   │    │
│  │  └───────────────────────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────────────┘    │
│                                                                           │
│  ┌─────────────────────────────────────────────────────────────────┐    │
│  │                    Observability Layer (NEW)                     │    │
│  │                                                                   │    │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │    │
│  │  │   Metrics    │  │   Logging    │  │   Tracing    │          │    │
│  │  │              │  │              │  │              │          │    │
│  │  │ - Counters   │  │ - Trace IDs  │  │ - Spans      │          │    │
│  │  │ - Gauges     │  │ - JSON       │  │ - OTel       │          │    │
│  │  │ - Histograms │  │ - Structured │  │ - Jaeger     │          │    │
│  │  │              │  │              │  │              │          │    │
│  │  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘          │    │
│  └─────────┼──────────────────┼──────────────────┼─────────────────┘    │
│            │                  │                  │                       │
└────────────┼──────────────────┼──────────────────┼───────────────────────┘
             │                  │                  │
             ▼                  ▼                  ▼
    ┌─────────────────┐  ┌──────────────┐  ┌──────────────┐
    │   Prometheus    │  │     Loki     │  │    Jaeger    │
    │   + Grafana     │  │  (optional)  │  │  (optional)  │
    └─────────────────┘  └──────────────┘  └──────────────┘

Task Lifecycle State Machine with Failure Handling

┌──────────┐
│ Pending  │
└────┬─────┘
     │ submit_task()
     ▼
┌──────────┐ ─── Timeout: 15m ────────────────┐
│ Staging  │                                   │
└────┬─────┘                                   │
     │ stage_task()                            │
     │  - Git clone (retry: 3x, timeout: 10m) │
     │  - Write TASK.md                        │
     ▼                                         │
┌──────────────┐ ─── Timeout: 10m ─────────┐  │
│ Provisioning │                            │  │
└────┬─────────┘                            │  │
     │ provision_vm()                        │  │
     │  - Create VM (retry: 2x)              │  │
     │  - Wait for SSH (retry: 5x, 2s)       │  │
     │  - Health check                       │  │
     ▼                                        │  │
┌─────────┐                                  │  │
│  Ready  │ ─── Checkpoint saved ───────────┼──┼─► Restore on
└────┬────┘                                  │  │   crash recovery
     │                                        │  │
     ▼                                        │  │
┌──────────┐ ─── Timeout: 24h (manifest) ───┼──┤
│ Running  │ ─── Hang: 30m (no output) ─────┼──┤
└────┬─────┘ ─── Hang: 1h (no progress) ────┼──┤
     │ execute_claude()                      │  │
     │  - SSH command                        │  │
     │  - Stream output                      │  │
     │  - Monitor activity                   │  │
     │                                        │  │
     │  Success (exit=0)                     │  │
     ▼                                        │  │
┌────────────┐ ─── Timeout: 15m ────────────┼──┤
│ Completing │                               │  │
└────┬───────┘                               │  │
     │ collect_artifacts()                   │  │
     │  - SCP from VM (retry: 3x)            │  │
     │  - Verify checksums                   │  │
     ▼                                        │  │
┌───────────┐                                │  │
│ Completed │◄───────────────────────────────┘  │
└───────────┘                                   │
                                                │
     ┌──────────────────────────────────────────┘
     │ Any failure or timeout
     ▼
┌──────────────────┐
│      Failed      │
│        or        │
│ FailedPreserved  │◄──── failure_action: preserve
└──────────────────┘      (VM kept for debug)

     ▲
     │ user cancellation
     │
┌──────────┐
│Cancelled │
└──────────┘

Checkpoint and Recovery Flow

┌─────────────────────────────────────────────────────────────────┐
│                    Normal Operation                              │
└─────────────────────────────────────────────────────────────────┘

Task.transition_to(Staging)
      │
      ▼
CheckpointStore.save(task)
      │
      ├─► Write /srv/tasks/{id}/checkpoint.json (atomic)
      │   {
      │     "task": {...},
      │     "checkpointed_at": "2026-01-29T12:00:00Z",
      │     "version": 1
      │   }
      │
      └─► Success

Task.transition_to(Running)
      │
      ▼
CheckpointStore.save(task)
      │
      └─► Update checkpoint.json

... Server Crashes ...

┌─────────────────────────────────────────────────────────────────┐
│                    Recovery Flow                                 │
└─────────────────────────────────────────────────────────────────┘

systemctl start management-server
      │
      ▼
Orchestrator::new()
      │
      ├─► CheckpointStore::recover_tasks()
      │        │
      │        ├─► Scan /srv/tasks/*/checkpoint.json
      │        ├─► Load non-terminal tasks
      │        └─► Return Vec<Task>
      │
      ▼
Orchestrator::recover_from_crash(tasks)
      │
      ├─► For each task:
      │     │
      │     ├─► Check task.state
      │     │
      │     ├─► Pending/Staging → resubmit_task()
      │     │
      │     ├─► Provisioning → check if VM exists
      │     │     │
      │     │     ├─► VM exists → resume from Ready
      │     │     └─► VM missing → resume from Provisioning
      │     │
      │     ├─► Ready/Running → check if VM running
      │     │     │
      │     │     ├─► VM running → resume_task_running()
      │     │     │                 (restart monitoring)
      │     │     └─► VM stopped → fail_task()
      │     │
      │     └─► Completing → resume_artifact_collection()
      │
      └─► Reconcile with VM registry
            │
            ├─► virsh list --all
            ├─► For each VM: check if task exists
            └─► Orphaned VM → handle_orphaned_vm()
                  │
                  ├─► created_at < 24h ago → preserve
                  └─► created_at > 24h ago → destroy

Timeout Enforcement Architecture

┌─────────────────────────────────────────────────────────────────┐
│                 Timeout Hierarchy                                │
└─────────────────────────────────────────────────────────────────┘

TimeoutEnforcer
      │
      ├─► Operation Timeouts (wrapped in tokio::timeout)
      │     │
      │     ├─► git_clone: 10m
      │     ├─► vm_provision: 5m
      │     ├─► ssh_connect: 30s
      │     └─► artifact_collect: 10m
      │
      └─► Stage Timeouts (background monitoring task)
            │
            ├─► staging: 15m
            ├─► provisioning: 10m
            ├─► running: 24h (from manifest)
            └─► completing: 15m


Example: Git Clone with Timeout

executor.stage_task(task)
      │
      ▼
timeout_enforcer.with_timeout("git_clone", async {
      │
      ├─► Set deadline: now + 10m
      │
      ├─► tokio::timeout(10m, git_clone())
      │     │
      │     ├─► Success before 10m → Ok(result)
      │     └─► Timeout after 10m → Err(TimeoutError)
      │
      └─► On timeout:
            │
            ├─► Log error with task_id, operation
            ├─► Emit metric: operation_timeout_total
            └─► Return ExecutorError::Timeout
})

If timeout → Task transitions to Failed


Example: Stage Timeout Monitoring

timeout_enforcer.enforce_stage_timeout(task, Staging)
      │
      ├─► Loop every 10s:
      │     │
      │     ├─► Check elapsed = now - task.state_changed_at
      │     ├─► If elapsed > 15m:
      │     │     │
      │     │     ├─► Log warning
      │     │     ├─► Emit metric: stage_timeout_total
      │     │     └─► Cancel task
      │     │
      │     └─► If task.state != Staging:
      │           └─► Exit loop (stage changed)
      │
      └─► Success (stage completed before timeout)

Hang Detection Flow

┌─────────────────────────────────────────────────────────────────┐
│                 Hang Detection Loop                              │
└─────────────────────────────────────────────────────────────────┘

HangDetector::run_detection_loop(orchestrator)
      │
      └─► Loop every 60s:
            │
            ├─► Get active tasks (Running, Staging, Provisioning)
            │
            ├─► For each task:
            │     │
            │     ├─► Check last_activity_at
            │     │     │
            │     │     └─► If now - last_activity > 30m:
            │     │           └─► HangType::NoOutput
            │     │
            │     ├─► Check state_changed_at
            │     │     │
            │     │     └─► If now - state_changed_at > 1h:
            │     │           └─► HangType::NoProgress
            │     │
            │     └─► If hang detected:
            │           │
            │           ├─► Log warning
            │           ├─► Emit metric: hangs_detected_total
            │           │
            │           └─► If elapsed > critical_threshold (2h):
            │                 │
            │                 ├─► Log error
            │                 ├─► Cancel task
            │                 └─► Emit alert


Activity Updates (keep task alive):

TaskMonitor::monitor_task()
      │
      ├─► Tail stdout.log
      │     │
      │     └─► On new data:
      │           └─► task.update_progress(bytes, None, None)
      │                 └─► Sets last_activity_at = now
      │
      ├─► Tail stderr.log (same)
      │
      └─► Parse events.jsonl
            │
            └─► On tool call:
                  └─► task.update_progress(0, tool_calls, current_tool)
                        └─► Sets last_activity_at = now

Graceful Degradation Flow

┌─────────────────────────────────────────────────────────────────┐
│             Degradation Levels and Actions                       │
└─────────────────────────────────────────────────────────────────┘

ResourceMonitor (runs every 60s)
      │
      ├─► Check storage usage
      ├─► Check memory available
      └─► Determine degradation level


Normal (storage <80%, memory >2GB)
      │
      └─► All features enabled
            - Accept all tasks
            - Normal cleanup frequency


Warning (storage 80-90%, memory 1-2GB)
      │
      └─► Log warnings
            - Emit metrics with severity=warning
            - Continue accepting tasks


Degraded (storage 90-95%, memory 500MB-1GB)
      │
      └─► Reduce functionality
            - Reject tasks with disk >40GB
            - Increase cleanup frequency (every 10m)
            - Emit metrics with severity=degraded
            - Log degradation events


Critical (storage >95%, memory <500MB)
      │
      └─► Emergency mode
            - Reject ALL new task submissions
            - Force cleanup of completed tasks
            - Emit critical alerts
            - Consider graceful shutdown


Emergency (OOM imminent, crash likely)
      │
      └─► Graceful shutdown
            - Stop accepting tasks
            - Save all checkpoints
            - Drain active tasks (with timeout)
            - Shutdown server


Task Submission Check:

POST /api/v1/tasks
      │
      ▼
degradation_manager.can_accept_task(manifest)
      │
      ├─► Normal/Warning → Ok(())
      │
      ├─► Degraded → Check manifest.vm.disk
      │                 │
      │                 ├─► disk >40GB → Err(RejectionReason::DegradedMode)
      │                 └─► disk ≤40GB → Ok(())
      │
      └─► Critical/Emergency → Err(RejectionReason::CriticalMode)

Retry Strategy with Exponential Backoff

┌─────────────────────────────────────────────────────────────────┐
│              Retry Flow Example: Git Clone                       │
└─────────────────────────────────────────────────────────────────┘

retry_policy.execute(|| git_clone(url, branch))

Attempt 1:
      │
      ├─► git clone ... (timeout: 10m)
      │     │
      │     └─► Error: "Connection timeout"
      │
      ├─► Log: "Git clone failed (attempt 1/3): Connection timeout"
      ├─► Emit metric: retries_total{operation="git_clone"}
      │
      └─► Sleep: 5s + jitter (4.25s to 5.75s)

Attempt 2:
      │
      ├─► git clone ... (timeout: 10m)
      │     │
      │     └─► Error: "Repository not found"
      │
      ├─► This is NOT retryable (permanent error)
      │
      └─► Return Err(GitCloneError::NotFound)

vs.

Attempt 2:
      │
      ├─► git clone ... (timeout: 10m)
      │     │
      │     └─► Error: "Rate limit exceeded"
      │
      ├─► Log: "Git clone failed (attempt 2/3): Rate limit"
      ├─► Emit metric: retries_total{operation="git_clone"}
      │
      └─► Sleep: 10s + jitter (8.5s to 11.5s)

Attempt 3:
      │
      ├─► git clone ... (timeout: 10m)
      │     │
      │     └─► Success!
      │
      ├─► Log: "Git clone succeeded after 3 attempts"
      └─► Return Ok(repo_path)


Retry Configuration per Operation:

┌──────────────────┬──────────┬─────────┬─────────┬──────────┐
│ Operation        │ Max Tries│ Initial │ Max     │ Jitter   │
│                  │          │ Delay   │ Delay   │          │
├──────────────────┼──────────┼─────────┼─────────┼──────────┤
│ git_clone        │    3     │   5s    │  60s    │  ±15%    │
│ vm_provision     │    2     │  10s    │  30s    │  ±15%    │
│ ssh_connect      │    5     │   2s    │  30s    │  ±15%    │
│ artifact_scp     │    3     │   5s    │  60s    │  ±15%    │
│ storage_write    │    2     │   1s    │   5s    │  ±15%    │
└──────────────────┴──────────┴─────────┴─────────┴──────────┘

Delay calculation:
  next_delay = min(current_delay * 2.0, max_delay)
  actual_delay = next_delay * (1.0 + random(-0.15, 0.15))

Metrics Collection and Export

┌─────────────────────────────────────────────────────────────────┐
│              Metrics Pipeline                                    │
└─────────────────────────────────────────────────────────────────┘

Instrumentation Points:

Task Lifecycle:
  ├─► submit_task()
  │     └─► counter("tasks_submitted_total")
  │
  ├─► transition_to(Completed)
  │     ├─► counter("tasks_completed_total", status="success")
  │     └─► histogram("task_duration_seconds", duration)
  │
  └─► transition_to(Failed)
        ├─► counter("tasks_failed_total", stage=stage, reason=reason)
        └─► histogram("task_duration_seconds", duration, status="failure")

Operations:
  ├─► git_clone()
  │     ├─► Start timer
  │     ├─► Execute
  │     └─► histogram("git_clone_duration_seconds", elapsed, status)
  │
  ├─► provision_vm()
  │     └─► histogram("vm_provision_duration_seconds", elapsed)
  │
  └─► collect_artifacts()
        └─► histogram("artifact_collection_duration_seconds", elapsed)

Resources:
  └─► Every 60s:
        ├─► gauge("tasks_active", count)
        ├─► gauge("storage_usage_percent", usage, path="/srv/tasks")
        ├─► gauge("memory_available_bytes", bytes)
        └─► gauge("vm_pool_available", count)

Container Runtime (Docker):
  └─► Every 30s:
        ├─► gauge("agentic_containers_by_status", status="running")
        └─► gauge("agentic_containers_by_status", status="stopped")


Export Format (Prometheus):

GET /metrics

# HELP tasks_submitted_total Total tasks submitted
# TYPE tasks_submitted_total counter
tasks_submitted_total 1234

# HELP tasks_completed_total Total tasks completed
# TYPE tasks_completed_total counter
tasks_completed_total{status="success"} 1100
tasks_completed_total{status="failure"} 50

# HELP task_duration_seconds Task execution duration
# TYPE task_duration_seconds histogram
task_duration_seconds_bucket{status="success",le="60"} 200
task_duration_seconds_bucket{status="success",le="300"} 500
task_duration_seconds_bucket{status="success",le="600"} 800
task_duration_seconds_bucket{status="success",le="+Inf"} 1100
task_duration_seconds_sum{status="success"} 250000
task_duration_seconds_count{status="success"} 1100

# HELP storage_usage_percent Storage utilization
# TYPE storage_usage_percent gauge
storage_usage_percent{path="/srv/tasks"} 75.2
storage_usage_percent{path="/srv/agentshare"} 42.1

Distributed Tracing Example

┌─────────────────────────────────────────────────────────────────┐
│           Trace Hierarchy for Task Execution                     │
└─────────────────────────────────────────────────────────────────┘

Trace ID: task-a1b2c3d4-e5f6-4789-abcd-ef0123456789

Span: task:lifecycle
├── duration: 3m 42s
├── status: ok
├── attributes:
│   ├── task.id = "task-a1b2c3d4"
│   ├── task.name = "Fix bug in parser"
│   └── task.state = "completed"
└── children:
    │
    ├── Span: task:staging
    │   ├── duration: 8.2s
    │   ├── status: ok
    │   └── children:
    │       │
    │       ├── Span: git_clone
    │       │   ├── duration: 7.5s
    │       │   ├── attributes:
    │       │   │   ├── repo.url = "https://github.com/..."
    │       │   │   ├── repo.branch = "main"
    │       │   │   └── repo.size_mb = 125
    │       │   └── status: ok
    │       │
    │       └── Span: write_prompt
    │           ├── duration: 0.2s
    │           └── status: ok
    │
    ├── Span: task:provisioning
    │   ├── duration: 45s
    │   ├── status: ok
    │   └── children:
    │       │
    │       ├── Span: provision_script
    │       │   ├── duration: 40s
    │       │   ├── attributes:
    │       │   │   ├── vm.name = "task-a1b2c3d4"
    │       │   │   ├── vm.cpus = 4
    │       │   │   └── vm.memory = "8G"
    │       │   └── status: ok
    │       │
    │       └── Span: vm_health_check
    │           ├── duration: 2s
    │           └── status: ok
    │
    ├── Span: task:running
    │   ├── duration: 3m 25s
    │   ├── status: ok
    │   └── children:
    │       │
    │       ├── Span: ssh_connect
    │       │   ├── duration: 1.2s
    │       │   └── status: ok
    │       │
    │       ├── Span: claude_execution
    │       │   ├── duration: 3m 20s
    │       │   ├── attributes:
    │       │   │   ├── claude.model = "sonnet-4.5"
    │       │   │   ├── claude.turns = 12
    │       │   │   └── claude.tool_calls = 45
    │       │   └── children:
    │       │       ├── Span: claude_turn_1
    │       │       │   ├── duration: 8s
    │       │       │   └── attributes:
    │       │       │       └── tool = "Read"
    │       │       │
    │       │       ├── Span: claude_turn_2
    │       │       │   ├── duration: 15s
    │       │       │   └── attributes:
    │       │       │       └── tool = "Write"
    │       │       │
    │       │       └── ... (10 more turns)
    │       │
    │       └── Span: output_monitoring
    │           ├── duration: 3m 20s
    │           └── attributes:
    │               ├── output.stdout_bytes = 1024000
    │               └── output.stderr_bytes = 2048
    │
    └── Span: task:completing
        ├── duration: 12s
        ├── status: ok
        └── children:
            │
            └── Span: artifact_collection
                ├── duration: 11s
                ├── attributes:
                │   ├── artifacts.count = 3
                │   ├── artifacts.total_bytes = 524288
                │   └── artifacts.patterns = "*.patch,*.json"
                └── status: ok


Visualization in Jaeger:

[============================================] task:lifecycle (3m 42s)
  [=====] staging (8.2s)
    [====] git_clone (7.5s)
    [.] write_prompt (0.2s)
  [============] provisioning (45s)
    [===========] provision_script (40s)
    [.] vm_health_check (2s)
  [=============================] running (3m 25s)
    [.] ssh_connect (1.2s)
    [============================] claude_execution (3m 20s)
      [.] claude_turn_1 (8s)
      [.] claude_turn_2 (15s)
      ...
    [============================] output_monitoring (3m 20s)
  [===] completing (12s)
    [==] artifact_collection (11s)

Alert Workflow

┌─────────────────────────────────────────────────────────────────┐
│           Alert Processing Flow                                  │
└─────────────────────────────────────────────────────────────────┘

Prometheus Scrapes /metrics Every 15s
      │
      └─► Evaluates Alert Rules Every 1m
            │
            ├─► Rule: HighTaskFailureRate
            │     │
            │     ├─► Query: rate(tasks_failed_total[5m]) /
            │     │          rate(tasks_submitted_total[5m]) > 0.10
            │     │
            │     ├─► Evaluation: True (15% failure rate)
            │     │
            │     └─► Alert Status:
            │           ├─► Pending (wait for "for: 5m")
            │           ├─► Firing (after 5m of continuous failure)
            │           └─► Send to AlertManager
            │
            └─► AlertManager Receives Alert
                  │
                  ├─► Check routing rules:
                  │     │
                  │     └─► severity=warning → Slack channel
                  │
                  ├─► Check inhibition rules:
                  │     │
                  │     └─► If ManagementServerDown firing,
                  │          inhibit all other alerts
                  │
                  ├─► Check silences:
                  │     │
                  │     └─► No active silence → Proceed
                  │
                  └─► Notify:
                        │
                        ├─► Slack: #agentic-sandbox-alerts
                        │     │
                        │     └─► Message:
                        │          [WARNING] High Task Failure Rate
                        │          15% of tasks are failing (threshold: 10%)
                        │          Runbook: https://docs/runbooks/high-failure-rate
                        │
                        └─► If severity=critical:
                              └─► PagerDuty: Page on-call engineer


On-Call Engineer Response:
      │
      ├─► Open runbook
      │
      ├─► Run diagnosis commands
      │     │
      │     ├─► curl /metrics | grep failure
      │     ├─► curl /api/v1/tasks?state=failed | jq
      │     └─► df -h /srv/tasks
      │
      ├─► Identify root cause (e.g., storage full)
      │
      ├─► Execute fix (cleanup-tasks.sh)
      │
      ├─► Verify resolution
      │     │
      │     └─► Failure rate drops below 10%
      │
      └─► Alert auto-resolves after 5m

Failure Recovery Decision Tree

                      Task Failed
                          │
                          ▼
                  What stage failed?
                          │
        ┌─────────────────┼─────────────────┐
        │                 │                 │
        ▼                 ▼                 ▼
    Staging         Provisioning       Running
        │                 │                 │
        ▼                 ▼                 ▼
  Git clone       VM creation       Claude execution
  failed?         failed?           failed?
        │                 │                 │
        │                 │                 │
    Network         libvirt down      Exit code?
    timeout?        Storage full?         │
        │                 │           ┌───┴───┐
        │                 │           │       │
        ▼                 ▼           ▼       ▼
    RETRY           RETRY if       124     Non-zero
    (3 attempts)    transient    (timeout)  (error)
        │                 │           │       │
        │                 │           │       │
    Success?          Success?    Timeout  Check logs
        │                 │        detected  for error
        ▼                 ▼           │       │
    Continue          Continue        │       ▼
                                      │   Is retryable?
                                      │       │
                                      │   ┌───┴────┐
                                      │   │        │
                                      │   ▼        ▼
                                      │  Yes      No
                                      │   │        │
                                      │ RETRY   FAIL
                                      │           │
                                      └───────────┼────► failure_action?
                                                  │            │
                                                  │        ┌───┴────┐
                                                  │        │        │
                                                  │        ▼        ▼
                                                  │    preserve  destroy
                                                  │        │        │
                                                  │        ▼        ▼
                                                  │   Keep VM   Cleanup VM
                                                  │   for debug
                                                  │
                                                  └────► State: Failed
                                                         or FailedPreserved

This architecture provides the visual framework for understanding the reliability design. See the main reliability-design.md for detailed implementation specifications.