Skip to main content

Execution state

Reqon can persist execution state so missions can resume after a crash or restart, and so you can inspect what happened on a previous run.

State persistence is opt-in. Pass persistState: true (or a custom executionStore) to the executor, and Reqon writes an execution record for each run.

State storage​

By default, state lives under .reqon-data/:

.reqon-data/
├── executions/ # Execution records (one JSON file per run)
├── sync/ # Incremental-sync checkpoints, one per mission
├── traces/ # Trace snapshots (when trace is enabled)
├── pauses/ # Pause state (for long pauses)
└── execution-log/ # Durable event log (FileExecutionLog default)

The base directory is configurable with the dataDir option.

Execution record​

Each run produces an ExecutionState record:

{
"id": "exec_abc123",
"mission": "CustomerSync",
"status": "completed",
"startedAt": "2024-01-20T09:00:00Z",
"completedAt": "2024-01-20T09:02:30Z",
"duration": 150000,
"stages": [
{ "action": "FetchCustomers", "status": "completed", "itemsProcessed": 1523, "attempt": 0 },
{ "action": "TransformCustomers", "status": "completed", "attempt": 0 }
],
"errors": []
}

State fields​

FieldDescription
idUnique execution identifier
missionMission name
statuspending, running, completed, failed, or paused
startedAtStart timestamp
completedAtCompletion timestamp (if finished)
durationTotal duration in milliseconds
stagesPer-stage state (action, status, itemsProcessed, attempt, …)
checkpointLatest checkpoint for resume (if any)
errorsErrors encountered during the run
metadataUser-provided context passed via the metadata option

Checkpoints​

Sync checkpoints​

Incremental sync writes a checkpoint per mission to .reqon-data/sync/{mission}.json, recording the last sync point so the next run only fetches what changed. See incremental sync for the since: lastSync syntax.

Resume checkpoints​

When a run is interrupted, the execution record carries a checkpoint describing where to pick up:

{
"stageIndex": 1,
"stepIndex": 3,
"itemIndex": 523,
"createdAt": "2024-01-20T09:01:00Z"
}

itemIndex is set when the interruption happened inside a for loop, so the resume starts from the next unprocessed item.

Resumable execution​

If a run is interrupted (status paused or failed), resume it by execution ID:

reqon ./missions/customer-sync/ --resume exec_abc123

When a run pauses or fails, the CLI prints the execution ID and the exact resume command to use.

Accessing state​

Programmatically​

State is read through an execution store, not standalone helper functions:

import { FileExecutionStore } from 'reqon-dsl';

const store = new FileExecutionStore('.reqon-data/executions');

// Latest run for a mission
const latest = await store.findLatest('CustomerSync');

// A specific run
const state = await store.load('exec_abc123');

// Recent runs
const recent = await store.listRecent(10);

// Runs that can be resumed
const resumable = await store.findResumable('CustomerSync');

MemoryExecutionStore offers the same interface for tests and ephemeral runs.

Via the control server​

When a mission runs with --control, the control server exposes live status:

curl http://localhost:3001/status

State cleanup​

There's no built-in cleanup command. Remove old records directly, or via the store:

import { FileExecutionStore } from 'reqon-dsl';

const store = new FileExecutionStore('.reqon-data/executions');
await store.delete('exec_abc123');

Old files can also be deleted from .reqon-data/executions/ directly.

Monitoring​

Progress callbacks​

Pass progress callbacks to observe a run in real time:

import { execute } from 'reqon-dsl';

const result = await execute(source, {
progress: {
onExecutionStart: (event) => console.log(`Started: ${event.mission}`),
onStageStart: (event) => console.log(`Stage: ${event.stageName}`),
onStageComplete: (event) => console.log(`Done: ${event.stageName}`),
onExecutionComplete: (event) => console.log(`Finished in ${event.duration}ms`),
},
});

For fine-grained, per-step monitoring, use the observability event system.

State persistence​

Enabling persistence​

State is only written when you opt in:

import { execute } from 'reqon-dsl';

const result = await execute(source, {
persistState: true, // write execution records under .reqon-data/executions/
dataDir: '.reqon-data',
});

Custom execution store​

Provide your own executionStore to persist state somewhere other than the filesystem. Implement the ExecutionStore interface (save, load, listByMission, listRecent, delete, findLatest, findResumable):

import { execute, type ExecutionStore } from 'reqon-dsl';

const myStore: ExecutionStore = {
async save(state) { /* persist to a database */ },
async load(id) { /* ... */ return null; },
async listByMission(mission) { return []; },
async listRecent(limit) { return []; },
async delete(id) { /* ... */ },
async findLatest(mission) { return null; },
async findResumable(mission) { return []; },
};

await execute(source, { executionStore: myStore, persistState: true });

Event log​

For replay-based durable execution, Reqon can also emit an append-only event log via the executionLog option. Pause, sync, and trace are all views over this log. See durability for details.

Troubleshooting​

State not persisting​

Confirm you passed persistState: true (state isn't written otherwise), then check directory permissions:

ls -la .reqon-data/executions/

Resume not working​

Verify the execution record exists and is resumable:

cat .reqon-data/executions/CustomerSync-*.json | jq '.status'

Only runs with status paused or failed can be resumed.