# How the Action Simulation Model Works in Cloudflare OS Gatekeepers

> Discover how the action simulation model in Cloudflare OS Gatekeepers allows agents to read external service state, anticipating writes before human approval for efficient operations.

- Repository: [Cloudflare/cloudflare-os](https://github.com/cloudflare/cloudflare-os)
- Tags: internals
- Published: 2026-09-04

---

**Cloudflare OS gatekeepers use an action simulation model that lets agents read external service state as if all pending writes have already been applied, even while those writes await human approval.**

The **action simulation model** enables a seamless developer experience within the `cloudflare/cloudflare-os` repository. By separating durable action storage from pure-functional state overlays, gatekeepers can queue destructive operations without blocking execution, presenting a consistent "what-if" view to calling code immediately after a write request.

## Core Components of the Simulation Architecture

The simulation layer is implemented across three primary modules in the **gatekeeper-kit** package, each responsible for a distinct phase of the action lifecycle.

### Action Journal (action-journal.ts)

The **Action Journal** serves as the durable storage layer for every queued operation. Located at [`packages/gatekeeper-kit/src/action-journal.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/src/action-journal.ts), this module records the action ID, target resource, payload, and current status (pending, approved, failed). It provides the authoritative source of truth for all changes that have been requested but not yet committed to the external API.

### Simulation Records (simulation.ts)

The **Simulation** module at [`packages/gatekeeper-kit/src/simulation.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/src/simulation.ts) constructs immutable, ordered snapshots derived from the journal. These **simulation records** are indexed by their target identifier—such as a GitHub issue number, Notion page ID, or Spotify playlist—and contain the partial payload representing the pending change. The records are sorted by journal ID to ensure deterministic ordering when multiple actions target the same resource.

### Overlay Helper

The overlay helper provides pure-functional utilities to merge simulation records onto live API data. The primary entry point, `applySimulationOverlay`, accepts the real external data and the current simulation records, then returns a frozen object representing the combined state. This design prevents side effects and accidental mutation across concurrent read operations.

## Execution Flow of the Action Simulation Model

The simulation lifecycle follows five distinct stages, from initial request through final resolution.

### 1. Queueing Actions with Simulation Delivery

When a gatekeeper receives a write request—such as adding a track to a Spotify playlist—it creates an action and persists it to the Action Journal. The action’s `delivery` field is explicitly set to `"continue-with-simulation"`, allowing the calling agent to proceed immediately without waiting for human approval.

```typescript
import { queueAction } from "@gatekeeper-kit/actions";

async function addTrack(playlistId: string, track: Track) {
  await queueAction({
    target: { type: "playlist", id: playlistId },
    payload: { op: "add", track },
    delivery: "continue-with-simulation",
  });
  // Caller can immediately read the playlist and see the new track
}

```

### 2. Building the Simulation View

The `getSimulationView` function in [`packages/gatekeeper-kit/src/simulation.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/src/simulation.ts) reads all pending actions from the journal, sorts them by their unique ID, and constructs the immutable **SimulationRecords** collection. Each record maps a target identifier to its pending transformation, creating a complete overlay dataset without modifying external services.

### 3. Overlaying Pending Changes on Reads

When the gatekeeper performs a subsequent read operation, it first fetches the real data from the external API, then invokes the overlay helper to apply pending changes. The `applySimulationOverlay` function walks the simulation records, applies adds, updates, and deletes to a fresh copy of the real data, and returns the simulated view.

```typescript
import { getSimulationView, applySimulationOverlay } from "@gatekeeper-kit/simulation";
import { fetchPlaylist } from "./spotify-api";

async function listTracks(playlistId: string) {
  const real = await fetchPlaylist(playlistId);          // Live API call
  const sim  = await getSimulationView(playlistId);      // Pending actions
  const view = applySimulationOverlay(real, sim);        // Merge changes
  return view.tracks;                                    // Includes simulated additions
}

```

### 4. Human Approval and State Resolution

Queued actions remain in the journal until an administrator approves or rejects them via the `approveAction` utility. Upon approval, the gatekeeper executes the real request against the external API and discards the corresponding simulation record. If rejected, the simulation record is removed without executing the external call, causing subsequent reads to revert to the original state.

```typescript
import { approveAction } from "@gatekeeper-kit/actions";

async function approvePending(actionId: string) {
  await approveAction(actionId);   // Executes real request, removes simulation entry
}

```

## Consistency Guarantees and Safety

The action simulation model in `cloudflare-os` provides three critical safety properties:

- **Deterministic Ordering**: Actions are sorted by journal ID before overlay application, ensuring repeatable results regardless of retrieval timing.
- **Immutability**: The overlay helper returns frozen objects, preventing accidental mutations that could leak simulated state into production data.
- **Side-Effect Isolation**: Simulation logic remains pure-functional; no network requests or state changes occur during the overlay phase.

## Summary

- The **action simulation model** enables immediate read-after-write consistency without waiting for human approval.
- The **Action Journal** at [`packages/gatekeeper-kit/src/action-journal.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/src/action-journal.ts) provides durable storage for pending operations.
- **Simulation records** in [`packages/gatekeeper-kit/src/simulation.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/src/simulation.ts) create immutable, ordered snapshots of pending changes indexed by target resource.
- The `applySimulationOverlay` function merges pending changes onto live API data using pure-functional transformations.
- Actions specify `delivery: "continue-with-simulation"` to opt into the simulation workflow.
- Approval via `approveAction` triggers the real external API call and removes the simulation overlay.

## Frequently Asked Questions

### What is the primary purpose of the action simulation model in Cloudflare OS?

The action simulation model allows gatekeepers to queue sensitive write operations for human approval while immediately presenting the expected state to calling agents. This bridges the gap between asynchronous approval workflows and synchronous developer expectations, ensuring code can read back changes it just wrote without encountering stale data.

### How does the `applySimulationOverlay` function handle conflicting actions?

The function processes simulation records in strict journal ID order, applying each pending change sequentially to a fresh copy of the real data. Because the records are immutable and the overlay is deterministic, conflicts resolve predictably based on action ordering, with later actions overriding earlier ones when targeting the same resource field.

### Where are the simulation records stored and how long do they persist?

Simulation records exist only in memory during the overlay computation; they are derived on-demand from the **Action Journal** stored in [`packages/gatekeeper-kit/src/action-journal.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/src/action-journal.ts). Records persist in the journal until explicitly removed—either through the `approveAction` flow (which commits the change externally) or via rejection (which discards the pending operation).

### Which files should developers reference when implementing custom gatekeepers with simulation support?

Developers should examine [`packages/gatekeeper-kit/src/simulation.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/src/simulation.ts) for the overlay logic and record types, [`packages/gatekeeper-kit/src/action-journal.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/src/action-journal.ts) for durable action storage, and [`packages/gatekeeper-kit/__tests__/simulation.test.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/gatekeeper-kit/__tests__/simulation.test.ts) for unit tests demonstrating expected overlay behavior. Gatekeeper-specific implementations in packages like `gatekeeper-spotify` and `gatekeeper-github` provide concrete usage patterns.