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) andDevinAgent(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 functionsgetAgent(),getAvailableAgents(), andisCloudAgentProvider(). - Type definitions (
src/lib/cloudAgent/types.ts) – Shared TypeScript contracts includingCloudAgentTask,CloudAgentStatus,CloudAgentResult, andCloudAgentActivity. - Persistence layer –
credentials.tsanddb.tshandle encrypted API-key storage and task lifecycle tracking in SQLite, initialized via migration061_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 identifierexternalId– Provider-returned identifierproviderId– Agent type (e.g.,codex-cloud,devin)status– Initial state (queuedorrunning)prompt,source,options– Input payloadcreatedAt/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
- OmniRoute’s cloud-agent framework provides a unified interface for remote AI providers through
CloudAgentBaseinsrc/lib/cloudAgent/baseAgent.ts. - Task persistence is handled automatically via SQLite in
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, initialized by migration061_cloud_agent_credentials.sql. - Provider implementations for Codex Cloud and Devin live in
src/lib/cloudAgent/agents/codex.tsandsrc/lib/cloudAgent/agents/devin.ts, respectively. - Registry access through
getAgent()andgetAvailableAgents()insrc/lib/cloudAgent/registry.tsenables 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 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →