# CodexBar Refresh Loop Timing and Error Recovery: A Deep Dive into AdaptiveRefreshPolicy

> Understand CodexBar refresh loop timing with AdaptiveRefreshPolicy and error recovery using exponential back-off. Learn how CodexBar optimizes performance based on usage patterns.

- Repository: [Peter Steinberger/CodexBar](https://github.com/steipete/CodexBar)
- Tags: deep-dive
- Published: 2026-07-08

---

**CodexBar uses a pure `AdaptiveRefreshPolicy` to calculate dynamic refresh intervals based on user interaction patterns and system constraints, while `UsageStore` implements exponential back-off error recovery that starts at 30 seconds and caps at 10 minutes.**

The steipete/CodexBar repository implements a sophisticated refresh architecture that balances real-time data accuracy with system resource conservation. Understanding the **refresh loop timing** and **error recovery mechanisms** requires examining how `AdaptiveRefreshPolicy` makes deterministic scheduling decisions and how `UsageStore` handles transient failures without user intervention.

## Understanding AdaptiveRefreshPolicy

The `AdaptiveRefreshPolicy` struct in [`Sources/CodexBar/AdaptiveRefreshPolicy.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/AdaptiveRefreshPolicy.swift) serves as a pure decision engine that calculates the optimal delay before the next automatic refresh. It accepts an `Input` snapshot containing the current time, last menu interaction timestamp, Low-Power mode status, and thermal state, then returns a `Decision` containing both the delay duration and a human-readable reason tag.

### The Four Interaction-Based Timing Signals

The policy categorizes user engagement into four distinct states, each with specific thresholds and corresponding delays:

- **Recent interaction**: When the menu was opened within the last 5 minutes (`recentInteractionThreshold`), the system waits 120 seconds (`recentInteractionDelay`) before the next refresh, tagged as `.recentInteraction`.
- **Warm**: If the last interaction occurred between 5 minutes and 1 hour ago (`warmThreshold`), the delay extends to 300 seconds (`warmDelay`) with reason `.warm`.
- **Idle**: For interactions between 1 hour and 4 hours ago (`idleThreshold`), the delay increases to 900 seconds (`idleDelay`) marked as `.idle`.
- **Long idle**: When no interaction has occurred for 4 hours or more, or when `lastMenuOpenAt` is nil, the system enters a 1800-second (`longIdleDelay`) cycle tagged `.longIdle`.

### System Constraint Detection

Beyond time-based signals, the policy monitors hardware conditions through **ProcessInfo**. When the device enters Low-Power mode or reports a thermal state of `.serious` or `.critical`, the policy immediately switches to a **constrained delay** of 1800 seconds (`constrainedDelay`), regardless of interaction history. This ensures CodexBar respects system-wide resource limitations.

## UsageStore and Refresh Loop Implementation

The actual refresh orchestration occurs in [`Sources/CodexBar/UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift). When `UsageStore.refresh()` completes—whether successfully or with an error—the store queries `AdaptiveRefreshPolicy` for the next appropriate interval and schedules execution via `DispatchQueue.main.asyncAfter`.

The scheduling process follows this flow:

1. Construct an `AdaptiveRefreshPolicy.Input` snapshot with current system variables.
2. Call `policy.nextDelay(for: input)` to receive a `Decision` containing the delay and reason.
3. Calculate the actual timer interval based on error state (policy delay vs. retry delay).
4. Schedule the next refresh using `DispatchQueue.main.asyncAfter(deadline: .now() + interval)`.

Because the policy is **pure**—containing no clock reads or side effects—it guarantees deterministic results for identical inputs, making the system predictable and testable.

## Error Recovery and Exponential Back-Off

When network failures, credential expiry, or malformed responses occur, `UsageStore` implements a resilient **error recovery mechanism** that prevents aggressive retry storms while ensuring eventual consistency.

The back-off algorithm operates as follows:

- **Initial retry delay**: Starts at 30 seconds (`retryDelay = .seconds(30)`).
- **Exponential growth**: On consecutive failures, the delay doubles (`retryDelay * 2`).
- **Maximum cap**: Hard limit at 10 minutes (`maxRetryDelay = .minutes(10)`).
- **Success reset**: Immediately returns to 30 seconds upon successful refresh.

Errors are stored in `store.lastRefreshError`, which the UI observes via [`MenuCardRefreshMonitor.swift`](https://github.com/steipete/CodexBar/blob/main/MenuCardRefreshMonitor.swift) to display visual indicators such as red badges or status messages.

## Practical Code Examples

**Inspecting the next refresh interval**:

```swift
import Foundation

let policy = AdaptiveRefreshPolicy()
let input = AdaptiveRefreshPolicy.Input(
    now: Date(),
    lastMenuOpenAt: Date().addingTimeInterval(-300), // 5 minutes ago
    lowPowerModeEnabled: false,
    thermalState: .nominal
)

let decision = policy.nextDelay(for: input)
print("Next refresh in \(decision.delay) because of \(decision.reason.rawValue)")
// Output: "Next refresh in 300s because of warm"

```

**Integrating the policy in UsageStore**:

```swift
final class UsageStore {
    private let refreshPolicy = AdaptiveRefreshPolicy()
    private var retryDelay = Duration.seconds(30)

    func startLoop() {
        scheduleRefresh()
    }

    private func scheduleRefresh() {
        let input = AdaptiveRefreshPolicy.Input(
            now: Date(),
            lastMenuOpenAt: lastMenuOpen,
            lowPowerModeEnabled: ProcessInfo.processInfo.isLowPowerModeEnabled,
            thermalState: ProcessInfo.processInfo.thermalState
        )
        let decision = refreshPolicy.nextDelay(for: input)

        let delay = lastRefreshError == nil ? decision.delay : retryDelay
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) { self.refresh() }
    }

    private func refresh() async {
        do {
            try await fetchUsageData()
            lastRefreshError = nil
            retryDelay = .seconds(30)          // Reset back-off
        } catch {
            lastRefreshError = error
            retryDelay = min(retryDelay * 2, .minutes(10))
        }
        scheduleRefresh()                      // Schedule next tick
    }
}

```

**Logging refresh decisions for diagnostics**:

```swift
func logRefreshDecision(_ decision: AdaptiveRefreshPolicy.Decision) {
    os_log("Refresh scheduled: %{public}s (%{public}s)",
           decision.delay.description,
           decision.reason.rawValue)
}

```

## Key Source Files

The refresh loop architecture spans several components in the steipete/CodexBar repository:

- **[`Sources/CodexBar/AdaptiveRefreshPolicy.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/AdaptiveRefreshPolicy.swift)**: Implements the pure decision engine that maps system signals to refresh intervals.
- **[`Sources/CodexBar/UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift)**: Central store managing the refresh timer, error state, and back-off logic.
- **[`Sources/CodexBar/MenuCardRefreshMonitor.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/MenuCardRefreshMonitor.swift)**: UI component observing `UsageStore` state to update visual indicators.
- **[`Sources/CodexBar/MenuDescriptor.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/MenuDescriptor.swift)**: Records `lastMenuOpenAt` timestamps that feed into the adaptive policy.
- **[`Sources/CodexBar/MemoryPressureMonitor.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/MemoryPressureMonitor.swift)**: Detects low-memory conditions that influence the constrained refresh path.

## Summary

- **AdaptiveRefreshPolicy** provides deterministic, pure-function timing decisions based on four interaction states and system constraints.
- Refresh intervals range from **120 seconds** during active use to **1800 seconds** during long idle periods or thermal constraints.
- **UsageStore** implements **exponential back-off** error recovery starting at 30 seconds and capping at 10 minutes.
- The architecture separates policy (when to refresh) from execution (how to recover), enabling testable and observable behavior.
- All timing logic resides in [`Sources/CodexBar/AdaptiveRefreshPolicy.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/AdaptiveRefreshPolicy.swift) while loop orchestration lives in [`Sources/CodexBar/UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift).

## Frequently Asked Questions

### How does CodexBar determine the next refresh interval?

CodexBar calculates the next refresh interval using the `AdaptiveRefreshPolicy.nextDelay(for:)` method, which evaluates four time-based signals (recent interaction, warm, idle, long idle) and system constraints (Low-Power mode, thermal state). The policy returns a delay between 120 and 1800 seconds depending on how recently the user interacted with the menu and the current hardware conditions.

### What happens when a refresh fails in CodexBar?

When `UsageStore.refresh()` encounters an error, the system stores the error in `lastRefreshError` and switches to exponential back-off mode. The retry delay starts at 30 seconds, doubles after each consecutive failure, and caps at 10 minutes. Once a refresh succeeds, the delay immediately resets to 30 seconds and the system returns to policy-driven scheduling.

### How does thermal state affect CodexBar's refresh timing?

If `ProcessInfo.processInfo.thermalState` reports `.serious` or `.critical`, or if Low-Power mode is enabled, `AdaptiveRefreshPolicy` overrides the standard time-based delays and applies a **constrained delay** of 1800 seconds. This ensures the application respects thermal throttling and battery conservation priorities.

### Where is the refresh loop logic implemented in the CodexBar source code?

The refresh loop spans two primary files: [`Sources/CodexBar/AdaptiveRefreshPolicy.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/AdaptiveRefreshPolicy.swift) contains the pure logic for calculating delays, while [`Sources/CodexBar/UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift) manages the active timer, error handling, and back-off state. The UI observes these changes through [`Sources/CodexBar/MenuCardRefreshMonitor.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/MenuCardRefreshMonitor.swift).