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

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) – 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) and DevinAgent (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) – 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) – Shared TypeScript contracts including CloudAgentTask, CloudAgentStatus, CloudAgentResult, and CloudAgentActivity.
  • Persistence layer – credentials.ts and db.ts handle encrypted API-key storage and task lifecycle tracking in SQLite, initialized via migration 061_cloud_agent_credentials.sql.

The Agent Contract

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

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 and 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, which lives inside OmniRoute’s core database (src/lib/db/). The table schema is established by migration 061_cloud_agent_credentials.sql.

Secure Credential Storage

API keys are encrypted and stored via 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:

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:

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:

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:

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:

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

Frequently Asked Questions

How does OmniRoute handle authentication for cloud agents?

OmniRoute stores encrypted API keys in SQLite via the 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. 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 and 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →