# Cloud Agent Architecture for Codex, Cursor, Devin, and Jules: A Technical Deep Dive

> Explore the cloud agent architecture of Codex, Cursor, Devin, and Jules. Discover how OmniRoute uses thin adapters to unify provider APIs via abstract classes and a runtime registry.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: deep-dive
- Published: 2026-07-03

---

**The cloud agent architecture in OmniRoute implements thin adapters that normalize provider-specific REST APIs into a unified interface through an abstract base class and runtime registry.**

OmniRoute unifies AI coding assistants through a pluggable cloud agent architecture that abstracts the disparate APIs of Codex, Cursor, Devin, and Jules into a consistent TypeScript interface. Located in `src/lib/cloudAgent/`, this architecture enables seamless routing and task orchestration across multiple cloud-based AI agents without provider-specific implementation details leaking into the core application logic.

## Core Architecture Components

### CloudAgentBase Abstract Class

The foundation resides in [`src/lib/cloudAgent/baseAgent.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/baseAgent.ts), where `CloudAgentBase` defines the common contract all agents must implement. This abstract class standardizes methods including `createTask`, `getStatus`, `approvePlan`, `sendMessage`, and `listSources`, while providing shared utilities for ID generation, generic status mapping, and standardized error handling. Each concrete agent extends this base to inherit consistent behavior while implementing provider-specific HTTP logic.

### Shared Type Definitions

Type safety across the architecture comes from [`src/lib/cloudAgent/types.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/types.ts), which exports canonical interfaces including `CloudAgentTask`, `CloudAgentActivity`, `CloudAgentStatus`, and `CloudAgentCredentials`. These types ensure that regardless of which provider handles a task, the data structures remain predictable for upstream consumers like the router and UI components.

### Provider Registry Pattern

Runtime selection of agents occurs through [`src/lib/cloudAgent/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/registry.ts), which maintains a lightweight mapping between provider IDs (e.g., `"codex-cloud"`, `"cursor-cloud"`) and their concrete implementations. The `getAgentByProviderId()` function resolves these strings to instantiated classes, enabling the router to dynamically load the correct agent without hardcoding provider-specific logic.

### Credential Management

Persistence and retrieval of API keys and optional custom base URLs are handled by [`src/lib/cloudAgent/credentials.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/credentials.ts) and [`src/lib/cloudAgent/db.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/db.ts). These utilities load per-user credentials from the database, ensuring each agent instance receives authenticated access to its respective service endpoint.

## Cloud Agent Lifecycle

### Task Creation

When the router initiates work, it calls `createTask(params, credentials)` on the resolved agent. The implementation builds a provider-specific payload—such as including `prompt` and `repository_context` for Codex or constructing a `source` object with `repository` and `ref` for Cursor—and POSTs to the provider's creation endpoint. The method returns a normalized `CloudAgentTask` containing an internal ID, external provider ID, initial status, and metadata.

### Status Polling and Normalization

OmniRoute periodically polls agent status via `getStatus(externalId, credentials)`. Each concrete class maps its provider's proprietary status enums to the canonical `CLOUD_AGENT_STATUS` set (`QUEUED`, `RUNNING`, `COMPLETED`, `FAILED`, `CANCELLED`). For example, `CursorCloudAgent` in [`src/lib/cloudAgent/agents/cursor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/cursor.ts) utilizes a custom `CURSOR_STATUS_MAP` to handle uppercase enums specific to Cursor's API, while extracting result data like PR URLs and conversation activities.

### Message Interaction

For providers supporting follow-up conversations, the `sendMessage(externalId, message, credentials)` method POSTs user messages to provider-specific follow-up endpoints. This returns a `CloudAgentActivity` representing the sent message, enabling real-time chat interfaces with agents that support iterative refinement.

### Repository Source Enumeration

Some agents implement `listSources(credentials)` to enumerate available repositories, populating UI dropdowns for repository selection. This method is particularly useful when targeting specific codebases across different Git integrations.

## Provider-Specific Implementations

### CodexCloudAgent

Located in [`src/lib/cloudAgent/agents/codex.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/codex.ts), the Codex agent uses provider ID `"codex-cloud"` and targets `https://api.openai.com/v1`. It sends prompts with optional repository context and relies on the base class's `mapStatus()` for status normalization. The implementation focuses on OpenAI's Codex-specific payload structures while maintaining the standard interface contract.

### CursorCloudAgent

The Cursor implementation in [`src/lib/cloudAgent/agents/cursor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/cursor.ts) uses provider ID `"cursor-cloud"` with a default base URL of `https://api.cursor.com/v0` (overridable via credentials). It implements custom status mapping through `CURSOR_STATUS_MAP` and handles Cursor's full conversation log returned as `data.conversation`, making it distinct from simpler single-response agents.

### DevinCloudAgent and JulesCloudAgent

`DevinCloudAgent` ([`src/lib/cloudAgent/agents/devin.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/devin.ts)) and `JulesCloudAgent` ([`src/lib/cloudAgent/agents/jules.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/agents/jules.ts)) follow the pattern established by Cursor, targeting their respective Devin and Jules REST APIs. Both provide `createTask`, `getStatus`, `sendMessage`, and `listSources` implementations, adapting the common lifecycle to their provider-specific endpoint structures and authentication schemes.

## Integration Examples

### Instantiating an Agent via the Registry

```typescript
import { getAgentByProviderId } from '@/lib/cloudAgent/registry';
import { loadCredentialsForUser } from '@/lib/cloudAgent/credentials';

// Resolve a concrete agent for "cursor-cloud"
const cursorAgent = getAgentByProviderId('cursor-cloud');

// Load credentials (e.g. from the DB)
const credentials = await loadCredentialsForUser(userId, 'cursor-cloud');

// Create a new task
const task = await cursorAgent.createTask(
  {
    prompt: 'Refactor this function to use async/await',
    source: { repoUrl: 'https://github.com/example/repo', branch: 'main' },
    options: { autoCreatePr: true },
  },
  credentials
);

// Poll status later
const status = await cursorAgent.getStatus(task.externalId, credentials);

```

### Creating a Codex Task Directly

```typescript
import { CodexCloudAgent } from '@/lib/cloudAgent/agents/codex';
import { loadCredentialsForUser } from '@/lib/cloudAgent/credentials';

const codex = new CodexCloudAgent();
const creds = await loadCredentialsForUser(userId, 'codex-cloud');

const task = await codex.createTask(
  {
    prompt: 'Write unit tests for the UserService class',
    source: { repoUrl: 'https://github.com/example/app' },
    options: { environment: 'node14' },
  },
  creds
);

console.log('Created Codex task with external ID:', task.externalId);

```

### Polling a Cursor Task and Extracting Activities

```typescript
import { CursorCloudAgent } from '@/lib/cloudAgent/agents/cursor';
import { loadCredentialsForUser } from '@/lib/cloudAgent/credentials';
import { CLOUD_AGENT_STATUS } from '@/lib/cloudAgent/types';

const cursor = new CursorCloudAgent();
const creds = await loadCredentialsForUser(userId, 'cursor-cloud');

const statusResult = await cursor.getStatus('external-id-123', creds);

if (statusResult.status === CLOUD_AGENT_STATUS.COMPLETED) {
  console.log('Result PR URL:', statusResult.result?.prUrl);
}

// Log conversation messages
statusResult.activities.forEach(act => {
  console.log(`[${act.timestamp}] ${act.type}: ${act.content}`);
});

```

### Listing Available Repositories for Jules

```typescript
import { JulesCloudAgent } from '@/lib/cloudAgent/agents/jules';
import { loadCredentialsForUser } from '@/lib/cloudAgent/credentials';

const jules = new JulesCloudAgent();
const creds = await loadCredentialsForUser(userId, 'jules-cloud');

const repos = await jules.listSources(creds);
repos.forEach(r => console.log(`- ${r.name}: ${r.url}`));

```

## Summary

- OmniRoute implements cloud agents as thin adapters in `src/lib/cloudAgent/`, translating proprietary REST APIs into a unified TypeScript interface.
- The `CloudAgentBase` abstract class defines the common contract for `createTask`, `getStatus`, `sendMessage`, and `listSources`, ensuring consistent behavior across Codex, Cursor, Devin, and Jules.
- [`registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/registry.ts) provides runtime resolution of provider IDs to concrete implementations, enabling dynamic agent selection without hardcoded dependencies.
- Each provider-specific agent handles custom payload construction, status code mapping (such as Cursor's `CURSOR_STATUS_MAP`), and endpoint authentication while normalizing outputs to shared types like `CloudAgentTask` and `CloudAgentStatus`.
- Credentials and per-user configuration are abstracted through [`credentials.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/credentials.ts) and [`db.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/db.ts), allowing flexible deployment across different user environments.

## Frequently Asked Questions

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

OmniRoute centralizes authentication through [`src/lib/cloudAgent/credentials.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/credentials.ts), which loads per-user API keys and optional custom base URLs from the database. Each agent receives these credentials as parameters to its methods, allowing the concrete implementations to attach the appropriate headers or tokens required by their specific provider endpoints without exposing authentication complexity to the router.

### What is the difference between the external ID and internal ID in OmniRoute cloud agents?

The internal ID is generated by OmniRoute's `CloudAgentBase` for tracking within the application, while the external ID represents the provider's native task identifier (such as OpenAI's or Cursor's task ID). When `createTask` returns a `CloudAgentTask`, it contains both identifiers, allowing OmniRoute to correlate its internal records with status queries made to the external provider's API.

### Can I add custom cloud agents to OmniRoute?

Yes, the architecture supports plugin-style extensions. Create a new class extending `CloudAgentBase` in `src/lib/cloudAgent/agents/`, implement the required methods (`createTask`, `getStatus`, etc.), and register the provider ID in [`src/lib/cloudAgent/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/registry.ts). The agent immediately becomes available through the public API façade in [`src/lib/cloudAgent/api.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudAgent/api.ts) without modifying upstream routing logic.

### How does error handling work across different cloud agent providers?

All agents inherit standardized error handling from `CloudAgentBase`, which throws concise `Error` objects for non-2xx HTTP responses. These are caught upstream and converted into standardized error responses for clients, ensuring that network failures or API errors from Codex, Cursor, Devin, or Jules surface consistently regardless of the underlying provider's specific error format.