# How to Set Up Cloud Agents Like Codex Cloud and Devin with Task Persistence in OmniRoute

> Learn to set up cloud agents like Codex Cloud and Devin with task persistence in OmniRoute. Run long AI tasks remotely with state and credential persistence via SQLite.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-07-05

---

**OmniRoute provides a built-in cloud-agent framework that lets you run long-running AI tasks on remote providers like Codex Cloud and Devin while automatically persisting task state and credentials in SQLite.**

OmniRoute ships with a modular cloud-agent architecture that abstracts provider-specific APIs into a consistent interface. The framework handles task lifecycle management, secure credential storage, and durable state persistence, allowing you to orchestrate remote AI agents without managing infrastructure complexity.

## Understanding the Cloud Agent Framework

OmniRoute’s cloud-agent framework is built on reusable abstractions that separate provider-specific HTTP logic from task orchestration.

### Core Components

The framework consists of five primary components:

- **`CloudAgentBase`** ([`src/lib/cloudAgent/baseAgent.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/baseAgent.ts)) – An abstract base class defining the contract for all agents, including task creation, status polling, plan approval, messaging, and source listing. It also provides helpers for ID generation and status mapping.
- **Agent implementations** – Concrete classes like `CodexCloudAgent` ([`src/lib/cloudAgent/agents/codex.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/codex.ts)) and `DevinAgent` ([`src/lib/cloudAgent/agents/devin.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/devin.ts)) that translate OmniRoute-internal parameters into provider HTTP calls and map responses to common types.
- **Registry** ([`src/lib/cloudAgent/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/registry.ts)) – Maintains a singleton map of available agents (`jules`, `devin`, `codex-cloud`, `cursor-cloud`) and exposes helper functions `getAgent()`, `getAvailableAgents()`, and `isCloudAgentProvider()`.
- **Type definitions** ([`src/lib/cloudAgent/types.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/types.ts)) – Shared TypeScript contracts including `CloudAgentTask`, `CloudAgentStatus`, `CloudAgentResult`, and `CloudAgentActivity`.
- **Persistence layer** – [`credentials.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/credentials.ts) and [`db.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/db.ts) handle encrypted API-key storage and task lifecycle tracking in SQLite, initialized via migration [`061_cloud_agent_credentials.sql`](https://github.com/diegosouzapw/OmniRoute/blob/main/061_cloud_agent_credentials.sql).

### The Agent Contract

All cloud agents inherit from `CloudAgentBase` and implement the following abstract methods:

```typescript
export abstract class CloudAgentBase {
  abstract readonly providerId: string;
  abstract readonly baseUrl: string;

  // Create a new task on the remote provider
  abstract createTask(
    params: CreateTaskParams,
    credentials: AgentCredentials
  ): Promise<CloudAgentTask>;

  // Retrieve the latest status of a previously-created task
  abstract getStatus(
    externalId: string,
    credentials: AgentCredentials
  ): Promise<GetStatusResult>;

  // Optional: approve a generated plan
  abstract approvePlan(
    externalId: string,
    credentials: AgentCredentials
  ): Promise<void>;

  // Send a follow-up message to a running task
  abstract sendMessage(
    externalId: string,
    message: string,
    credentials: AgentCredentials
  ): Promise<CloudAgentActivity>;

  // List source repositories the agent can access
  abstract listSources(
    credentials: AgentCredentials
  ): Promise<{ name: string; url: string; branch?: string }[]>;
}

```

Concrete implementations in [`src/lib/cloudAgent/agents/codex.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/codex.ts) and [`src/lib/cloudAgent/agents/devin.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/devin.ts) handle provider-specific authentication and endpoint mapping while returning normalized data structures.

## Task Persistence Architecture

OmniRoute guarantees task durability through a SQLite-backed persistence layer that survives server restarts and enables cross-session task monitoring.

### Database Schema and Migrations

When `createTask` is invoked, the agent generates an internal task ID using the pattern `task_<timestamp>_<random>`. The resulting `CloudAgentTask` object contains:

- `id` – OmniRoute-local identifier
- `externalId` – Provider-returned identifier
- `providerId` – Agent type (e.g., `codex-cloud`, `devin`)
- `status` – Initial state (`queued` or `running`)
- `prompt`, `source`, `options` – Input payload
- `createdAt` / `updatedAt` – Timestamps

The framework persists these records in the `cloudAgentTask` table defined in [`src/lib/cloudAgent/db.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/db.ts), which lives inside OmniRoute’s core database (`src/lib/db/`). The table schema is established by migration [`061_cloud_agent_credentials.sql`](https://github.com/diegosouzapw/OmniRoute/blob/main/061_cloud_agent_credentials.sql).

### Secure Credential Storage

API keys are encrypted and stored via [`src/lib/cloudAgent/credentials.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/credentials.ts), linked to their respective providers. The `getCredentialsForProvider()` function retrieves these credentials at runtime, ensuring each HTTP request can authenticate without exposing secrets in code.

Because both task state and credentials reside in SQLite, you can query task history from any UI component—including the dashboard’s *Cloud Agents* page—after process restarts.

## Implementation Guide

Follow these steps to integrate Codex Cloud and Devin with full task persistence.

### Listing Available Cloud Agents

Before creating tasks, discover which providers are registered:

```typescript
import { getAvailableAgents } from '@/lib/cloudAgent/registry.ts';

const agents = getAvailableAgents(); 
// → ["jules", "devin", "codex-cloud", "cursor-cloud"]

console.log('Available cloud agents:', agents);

```

### Creating a Codex Cloud Task

Instantiate the agent via the registry and invoke `createTask` with your parameters:

```typescript
import { getAgent } from '@/lib/cloudAgent/registry.ts';
import type { CreateTaskParams } from '@/lib/cloudAgent/baseAgent.ts';

async function runCodexTask() {
  const agent = getAgent('codex-cloud');
  if (!agent) throw new Error('Codex Cloud agent not found');

  const creds = { 
    apiKey: process.env.CODEX_API_KEY! 
  }; // Retrieved from encrypted DB in production

  const params: CreateTaskParams = {
    prompt: 'Write a TypeScript utility that formats dates',
    source: { repoUrl: 'https://github.com/example/repo' },
    options: { environment: { NODE_ENV: 'production' } },
  };

  const task = await agent.createTask(params, creds);
  console.log('Created task', task.id, '(remote-id:', task.externalId, ')');
  return task;
}

```

The `createTask` method persists the task record immediately, returning a `CloudAgentTask` with both local and remote identifiers.

### Polling for Task Status

To monitor long-running tasks, re-instantiate the agent and call `getStatus` in a loop:

```typescript
import { getAgent } from '@/lib/cloudAgent/registry.ts';
import type { GetStatusResult } from '@/lib/cloudAgent/baseAgent.ts';

async function pollTask(taskId: string, externalId: string) {
  const agent = getAgent('codex-cloud')!;
  const creds = { apiKey: process.env.CODEX_API_KEY! };

  while (true) {
    const status: GetStatusResult = await agent.getStatus(externalId, creds);
    console.log(`[${taskId}] status →`, status.status);

    if (status.status === 'completed' || status.status === 'failed') {
      console.log('Final result:', status.result);
      break;
    }
    await new Promise((r) => setTimeout(r, 5_000)); // 5 second delay
  }
}

```

The `GetStatusResult` includes a normalized `status` field (`queued`, `running`, `completed`, `failed`) and an `activities` array for UI rendering.

### Sending Follow-up Messages

For interactive agents like Devin, use `sendMessage` to communicate with running tasks:

```typescript
import { getAgent } from '@/lib/cloudAgent/registry.ts';

async function devinFollowup(externalId: string, message: string) {
  const agent = getAgent('devin')!;
  const creds = { apiKey: process.env.DEVIN_API_KEY! };

  const activity = await agent.sendMessage(externalId, message, creds);
  console.log('Sent message, activity id:', activity.id);
}

```

### Retrieving Stored Credentials

For internal tooling or admin interfaces, access encrypted credentials directly:

```typescript
import { getCredentialsForProvider } from '@/lib/cloudAgent/credentials.ts';

async function getCreds() {
  const devinCreds = await getCredentialsForProvider('devin');
  // Returns { apiKey: 'xxxx', baseUrl?: string }
  console.log('Devin API key loaded from DB');
}

```

## Summary

- **OmniRoute’s cloud-agent framework** provides a unified interface for remote AI providers through `CloudAgentBase` in [`src/lib/cloudAgent/baseAgent.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/baseAgent.ts).
- **Task persistence** is handled automatically via SQLite in [`src/lib/cloudAgent/db.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/db.ts), ensuring tasks survive server restarts and maintain state between polling cycles.
- **Credential security** relies on encrypted storage in [`src/lib/cloudAgent/credentials.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/credentials.ts), initialized by migration [`061_cloud_agent_credentials.sql`](https://github.com/diegosouzapw/OmniRoute/blob/main/061_cloud_agent_credentials.sql).
- **Provider implementations** for Codex Cloud and Devin live in [`src/lib/cloudAgent/agents/codex.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/codex.ts) and [`src/lib/cloudAgent/agents/devin.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/devin.ts), respectively.
- **Registry access** through `getAgent()` and `getAvailableAgents()` in [`src/lib/cloudAgent/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/registry.ts) enables stateless, durable task orchestration.

## Frequently Asked Questions

### How does OmniRoute handle authentication for cloud agents?

OmniRoute stores encrypted API keys in SQLite via the [`credentials.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/credentials.ts) module. When you call `getCredentialsForProvider()`, the system retrieves and decrypts the appropriate key for the requested provider (e.g., `codex-cloud` or `devin`). These credentials are then passed to the agent’s methods (`createTask`, `getStatus`, etc.) as the `AgentCredentials` parameter.

### What happens to tasks when the OmniRoute server restarts?

Tasks persist in the `cloudAgentTask` table defined in [`src/lib/cloudAgent/db.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/db.ts). Because this table resides in OmniRoute’s core SQLite database, all task metadata—including `externalId`, `status`, and `providerId`—survives process restarts. You can resume polling or query task history immediately after restart without losing context.

### Why do some agents throw errors when calling `approvePlan`?

The `approvePlan` method in `CloudAgentBase` is optional and provider-dependent. According to the source code in [`src/lib/cloudAgent/agents/codex.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/codex.ts) and [`src/lib/cloudAgent/agents/devin.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/devin.ts), these specific providers auto-generate and execute plans without manual approval, so their implementations throw errors or return immediately. Future providers (like a potential Cursor Cloud integration) may implement interactive plan approval.

### Can I run multiple cloud agents simultaneously with different credentials?

Yes. The registry in [`src/lib/cloudAgent/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/registry.ts) returns singleton agent instances, but each method call accepts distinct `AgentCredentials`. You can instantiate tasks across multiple providers (e.g., Codex Cloud and Devin) concurrently by calling `getAgent()` for each provider ID and passing the appropriate credentials to each `createTask` invocation. The persistence layer tracks each task separately by its internal `task_<timestamp>_<random>` ID.