# How Scheduled Prompts Work with Prime Agent Worker Recovery: Complete Technical Guide

> Discover how Prime Agent uses a durable cron job store and recovery journal to persist scheduled prompts through worker crashes. Learn about seamless worker recovery.

- Repository: [Prime Intellect/prime-agent](https://github.com/PrimeIntellect-ai/prime-agent)
- Tags: technical-guide
- Published: 2026-08-15

---

**Prime Agent persists scheduled prompts across worker crashes by combining a durable cron job store with a JSON-L recovery journal that records in-flight operations and replays them on restart.**

The Prime Agent codebase implements a robust daemon mode that handles recurring AI prompts (often called *heartbeat* or *cron* jobs) with full crash recovery. When running as a supervised worker, the system guarantees that scheduled prompts survive process restarts without duplication or data loss. This article examines the exact mechanism using source code from the `PrimeIntellect-ai/prime-agent` repository.

## Architecture Overview: Three Layers of Durability

Prime Agent's scheduled prompt system operates across three coordinated layers:

| Layer | Component | Persistence Strategy |
|-------|-----------|-------------------|
| **Schedule Definition** | `AgentCronJobStore` | JSON file on disk (`cron-jobs-path`) |
| **Execution State** | `WorkerRecoveryJournal` | Append-only JSON-L file (`worker.recovery.jsonl`) |
| **Runtime Coordination** | `AgentCronScheduler` | In-memory queue with disk-backed recovery |

This design separates *what should run* (the schedule) from *what is currently running* (the recovery state).

## How Scheduled Prompts Are Created

Scheduled prompts enter the system through RPC commands. In [`packages/coding-agent/src/modes/daemon/daemon-mode.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/modes/daemon/daemon-mode.ts), the `createCronJobForState` method handles the conversion from client request to durable job.

The method signature and core logic appear around lines 1844–1856:

```typescript
private createCronJobForState(
  state: ActiveSessionState,
  schedule: string,
  prompt: string,
): AgentCronJob {
  const job: AgentCronJob = {
    id: randomUUID(),
    schedule: parseAgentCronSchedule(schedule),
    prompt,
    createdAt: Date.now(),
  };
  
  this.cronJobStore.add(job);
  this.recordWorkerRecoveryState(state, "create_job");
  
  return job;
}

```

The `AgentCronJob` interface is defined in [`packages/coding-agent/src/core/cron-jobs.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/cron-jobs.ts) (lines 45–62). It stores:

- `id`: UUID for deduplication
- `schedule`: Parsed cron expression with interval calculation
- `prompt`: The actual text sent to the AI session
- `createdAt`: Timestamp for debugging

## The Cron Scheduler: Driving Execution

The `AgentCronScheduler` class in [`packages/coding-agent/src/core/cron-jobs.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/cron-jobs.ts) (lines 228–371) evaluates all registered jobs every second. When a job becomes due, it invokes `runCronJob` via callback.

The scheduler maintains no internal persistence—it relies entirely on the `AgentCronJobStore` for job definitions and the `WorkerRecoveryJournal` for execution state.

```typescript
// From cron-jobs.ts - simplified scheduler loop
public start(): void {
  this.interval = setInterval(() => {
    const now = Date.now();
    for (const job of this.store.getAll()) {
      if (this.isDue(job, now)) {
        this.onRunJob(job); // Callback to daemon-mode.ts
      }
    }
  }, 1000);
}

```

## Recording Worker Recovery State

The critical bridge between scheduled prompts and crash recovery is `recordWorkerRecoveryState`. This method in [`daemon-mode.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/daemon-mode.ts) (around lines 7271–7285) creates a durable record before any state-changing operation.

```typescript
private recordWorkerRecoveryState(
  state: ActiveSessionState,
  operation: string,
): void {
  if (!this.recoveryJournal) return;
  
  const record: WorkerRecoveryRecord = {
    timestamp: Date.now(),
    activeSessionId: state.activeSessionId,
    sessionId: state.runtime.session.sessionId,
    sessionFile: state.runtime.session.sessionFile,
    busy: true,
    operation,
  };
  
  this.recoveryJournal.record(record);
}

```

**Key parameters in `WorkerRecoveryRecord`:**

- **`busy`**: `true` when work is in-progress, `false` when complete
- **`operation`**: String identifier like `"run_job"`, `"session_end"`, `"create_job"`
- **`sessionId`**: Links the record to a specific AI session
- **`sessionFile`**: Path to session state for full reconstruction

## The Recovery Journal: JSON-L Implementation

The `WorkerRecoveryJournal` class in [`packages/coding-agent/src/modes/daemon/worker-recovery-journal.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/modes/daemon/worker-recovery-journal.ts) implements an append-only log with automatic compaction.

**Recording entries** (lines 66–84):

```typescript
public record(entry: WorkerRecoveryRecord): void {
  const line = JSON.stringify(entry) + '\n';
  fs.appendFileSync(this.path, line);
  
  // Track latest state per session
  this.latestBySession.set(entry.sessionId, entry);
  
  // Compact if all entries are idle
  if (this.allEntriesIdle()) {
    this.compact();
  }
}

```

**Reading latest state** (lines 91–93):

```typescript
public readLatest(): Map<string, WorkerRecoveryRecord> {
  return this.latestBySession;
}

```

The `readLatest` method returns a map from `sessionId` to most recent record, enabling O(1) lookup of recovery state on restart.

## Complete Recovery Flow: From Crash to Resume

When a worker process crashes and restarts, this sequence ensures scheduled prompts resume correctly:

1. **Supervisor restarts worker** with `DAEMON_WORKER_RECOVERY_JOURNAL_ENV` pointing to the journal file

2. **Daemon constructor** ([`daemon-mode.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/daemon-mode.ts) lines 40–44) initializes:
   ```typescript
   const journalPath = process.env.DAEMON_WORKER_RECOVERY_JOURNAL_ENV;
   if (journalPath) {
     this.recoveryJournal = new WorkerRecoveryJournal(journalPath);
     this.recoveryJournal.open();
   }
   ```

3. **Load cron jobs** from `AgentCronJobStore` disk file—schedules are fully restored

4. **Replay recovery journal** by iterating `recoveryJournal.readLatest()`:
   - Jobs with `busy: false` are considered complete
   - Jobs with `busy: true` are re-queued for execution

5. **Scheduler starts**, picking up where execution left off

## Exactly-Once Semantics for In-Flight Prompts

The `busy` flag prevents duplicate execution of scheduled prompts that were mid-flight during a crash. Consider this scenario:

| Time | Event | Journal State |
|------|-------|---------------|
| T0 | Cron job becomes due | — |
| T1 | `recordWorkerRecoveryState(..., "run_job")` called | `busy: true` |
| T2 | Prompt sent to AI session | `busy: true` |
| T3 | **CRASH** — process terminates | `busy: true` (persisted) |
| T4 | Worker restarts, reads journal | `busy: true` detected |
| T5 | Job re-queued, executes again | `busy: true` (new record) |
| T6 | Completion recorded | `busy: false` |

The compacted journal would show only the final `busy: false` record after successful completion.

## Code Example: End-to-End Scheduled Prompt with Recovery

```typescript
// Client code: Request a recurring prompt
import { RPCClient } from 'prime-agent';

const client = new RPCClient({ socketPath: '/tmp/prime-agent.sock' });
await client.addSchedule('*/5 * * * *', 'Review code changes and suggest improvements');

// Daemon-mode.ts: Handling the schedule creation
class DaemonMode {
  public async handleAddSchedule(
    state: ActiveSessionState,
    scheduleExpr: string,
    promptText: string,
  ): Promise<void> {
    // 1. Record intent to create job
    this.recordWorkerRecoveryState(state, "create_job");
    
    // 2. Create durable cron job
    const job = this.createCronJobForState(state, scheduleExpr, promptText);
    
    // 3. Schedule persists to disk
    await this.cronJobStore.flush();
    
    // 4. Confirm success
    return { jobId: job.id };
  }
  
  private async runCronJob(job: AgentCronJob): Promise<void> {
    const state = this.getOrCreateSessionState();
    
    // Critical: Record before execution
    this.recordWorkerRecoveryState(state, "run_job");
    
    try {
      await this.sendPromptToSession(state, job.prompt);
      
      // Record completion
      this.recordWorkerRecoveryState(state, "job_complete");
    } catch (err) {
      // Busy remains true; will be retried on recovery
      throw err;
    }
  }
}

```

## Key Files and Their Responsibilities

| File Path | Core Responsibility |
|-----------|---------------------|
| [`packages/coding-agent/src/modes/daemon/daemon-mode.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/modes/daemon/daemon-mode.ts) | RPC handling, cron job lifecycle, recovery coordination |
| [`packages/coding-agent/src/core/cron-jobs.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/cron-jobs.ts) | `AgentCronJob` definition, `AgentCronScheduler`, schedule parsing |
| [`packages/coding-agent/src/modes/daemon/worker-recovery-journal.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/modes/daemon/worker-recovery-journal.ts) | JSON-L persistence, compaction, replay logic |
| [`packages/coding-agent/src/core/cron-job-store.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/cron-job-store.ts) | On-disk storage for job definitions |
| [`packages/coding-agent/src/modes/rpc/rpc-client.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/modes/rpc/rpc-client.ts) | Client-side API for `addSchedule`, `setHeartbeat` |

## Performance Characteristics

- **Journal append**: O(1) — single `fs.appendFileSync` per state change
- **Compaction**: O(n) where n = number of sessions, triggered only when all idle
- **Recovery read**: O(n) on startup, results cached in `Map` for O(1) lookups
- **Scheduler evaluation**: O(m) per second where m = number of scheduled jobs

The journal file size remains bounded because compaction collapses completed operations.

## Summary

- **Scheduled prompts in Prime Agent** use `AgentCronJob` definitions stored in `AgentCronJobStore` for durability across restarts.
- **Worker recovery** relies on `WorkerRecoveryJournal`, an append-only JSON-L log that records `busy` state for every operation.
- The `recordWorkerRecoveryState` method in [`daemon-mode.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/daemon-mode.ts) creates recovery records before any state-changing operation, ensuring crash consistency.
- On restart, the daemon reads the latest recovery records via `readLatest()` and replays any `busy: true` operations, providing exactly-once execution for in-flight prompts.
- Automatic compaction keeps the recovery journal small while preserving necessary state.

## Frequently Asked Questions

### What happens to scheduled prompts if the worker crashes during execution?

The partially-executed prompt is recorded with `busy: true` in the recovery journal. When the worker restarts, `readLatest()` returns this record, and the daemon re-queues the job for execution. The `busy` flag ensures the prompt runs to completion without being lost.

### How does Prime Agent prevent duplicate scheduled prompts after recovery?

The recovery journal uses the `busy` flag to distinguish between completed and in-flight work. Only records with `busy: true` are replayed. Additionally, each `AgentCronJob` has a unique `id`, and the scheduler deduplicates jobs during initialization.

### Where is the recovery journal stored and how is it configured?

The journal path is passed via the `DAEMON_WORKER_RECOVERY_JOURNAL_ENV` environment variable, typically set to a file like `/var/run/prime-agent/worker.recovery.jsonl`. The `WorkerRecoveryJournal` constructor opens this path for append-only writing.

### Can scheduled prompts survive a full system reboot?

Yes. The `AgentCronJobStore` persists job definitions to a JSON file on disk (configured via `cron-jobs-path`). The recovery journal is also file-based. Both survive process termination and system reboots, though the reboot itself must not corrupt the filesystem.