Skip to content

Cloud Tasks

源码版本rust-v0.145.0

codex cloud is the subcommand that drops a prompt onto the OpenAI cloud to run: locally it does not call the model or run the Agent loop; it only submits a task, pulls status, fetches the diff, and applies the patch. cloud-tasks is the TUI + CLI entry; cloud-tasks-client provides the CloudBackend trait + HTTP impl; cloud-tasks-mock-client plugs a fake backend for debug mode; backend-client is the underlying HTTP (also reused by other backend APIs). Unlike local inference in Model Provider — here "model inference" happens inside a cloud container.

Responsibilities

  1. Submit tasks: run_exec_command packages prompt + git ref + environment id and POST /wham/tasks (or /api/codex/tasks), returning a task id (codex-rs/cloud-tasks/src/lib.rs:161-184).
  2. List / detail / diff: run_list_command, run_status_command, run_diff_command are three CLI subcommands mapping to list_tasks / get_task_summary / get_task_diff (codex-rs/cloud-tasks/src/cli.rs:16-27).
  3. Apply patches: apply_task runs a dry-run preflight before the real apply; diff_override lets the user pick one attempt from best-of-N (codex-rs/cloud-tasks-client/src/http.rs:99-121).
  4. Abstract trait: CloudBackend collapses all backend-interaction methods into a trait; debug builds use MockClient, release uses HttpClient (codex-rs/cloud-tasks-client/src/api.rs:136-176).
  5. Initialize the backend: init_backend assembles base URL, UA, auth provider, ChatGPT-Account-Id, and switches between WHAM / Codex API path style based on CODEX_CLOUD_TASKS_BASE_URL (codex-rs/cloud-tasks/src/lib.rs:43-107).

Design motivation

It's its own crate rather than stuffed into core, because this path does not run the Agent main loop. The client in core sends stream requests to the model, parses SSE, maintains turn state; a cloud task is "submit a long-running task to the cloud, wait for results asynchronously". The two concurrency models are completely different: the former is a long-lived streaming connection, the latter is poll + fetch. The CloudBackend trait exists so the TUI can run in debug builds — when init_backend detects CODEX_CLOUD_TASKS_MODE=mock, it swaps in MockClient, so the UI state machine can be exercised locally without really hitting the ChatGPT backend. HttpClient internally reuses backend-client::Client rather than spinning up its own HTTP, because backend-api's auth headers, Cloudflare cookie store, and path style (wham vs codex-api) are consistent with other backend API calls.

best-of-N is the signature of this path: create_task accepts attempts: usize (1–4); the cloud runs multiple attempts; list_sibling_attempts fetches them all back; ApplyCommand lets the user specify --attempt N to pick one. The diff_override field flows through apply_task_preflight and apply_task, so the apply stage does not re-fetch the diff but uses the user-selected one.

Key files

codex-rs/cloud-tasks/src/lib.rs:735-744run_main, subcommand dispatch + TUI mode entry.codex-rs/cloud-tasks/src/lib.rs:43-107BackendContext and init_backend, deciding mock vs http by env var and assembling auth.codex-rs/cloud-tasks/src/cli.rs:29-50ExecCommand, defining the three core args --env, --attempts, --branch.codex-rs/cloud-tasks-client/src/api.rs:136-176 — the CloudBackend trait, 11 methods covering list / get / apply / create.codex-rs/cloud-tasks-client/src/http.rs:25-63HttpClient, wrapping backend-client::Client and implementing CloudBackend.codex-rs/backend-client/src/client.rs:451-482create_task impl in the base client, extracting the task id from JSON.codex-rs/cloud-tasks-mock-client/src/mock.rs:163-189MockClient implements the trait, delegating to internal mock data generators.

The factory init_backend checks env vars in debug builds, deciding between mock and real HTTP:

rust
// cloud-tasks/src/lib.rs:43-60 — 后端选择
async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext> {
    #[cfg(debug_assertions)]
    let use_mock = matches!(
        std::env::var("CODEX_CLOUD_TASKS_MODE").ok().as_deref(),
        Some("mock") | Some("MOCK")
    );
    let base_url = std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
        .unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string());
    #[cfg(debug_assertions)]
    if use_mock {
        return Ok(BackendContext {
            backend: Arc::new(codex_cloud_tasks_mock_client::MockClient),
            base_url,
        });
    }

The CloudBackend trait uses CloudBackendFuture to unify return types as boxed futures; mock and http impls share the same signature:

rust
// cloud-tasks-client/src/api.rs:136-176 — 后端抽象
pub trait CloudBackend: Send + Sync {
    fn list_tasks<'a>(
        &'a self,
        env: Option<&'a str>,
        limit: Option<i64>,
        cursor: Option<&'a str>,
    ) -> CloudBackendFuture<'a, TaskListPage>;
    fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary>;
    fn get_task_diff(&self, id: TaskId) -> CloudBackendFuture<'_, Option<String>>;
    // ...messages / sibling attempts / apply preflight / apply / create
    fn create_task<'a>(
        &'a self,
        env_id: &'a str,
        prompt: &'a str,
        git_ref: &'a str,
        qa_mode: bool,
        best_of_n: usize,
    ) -> CloudBackendFuture<'a, CreatedTask>;
}

create_task POSTs from the base client and then extracts the task id from JSON (preferring task.id, falling back to the top-level id):

rust
// backend-client/src/client.rs:451-482 — POST /wham/tasks 并提取 task id
pub async fn create_task(&self, request_body: serde_json::Value) -> Result<String> {
    let url = match self.path_style {
        PathStyle::CodexApi => format!("{}/api/codex/tasks", self.base_url),
        PathStyle::ChatGptApi => format!("{}/wham/tasks", self.base_url),
    };
    // ...发请求,解析 body
    match serde_json::from_str::<serde_json::Value>(&body) {
        Ok(v) => {
            if let Some(id) = v.get("task").and_then(|t| t.get("id")).and_then(|s| s.as_str()) {
                Ok(id.to_string())
            } else if let Some(id) = v.get("id").and_then(|s| s.as_str()) {
                Ok(id.to_string())
            } else {
                anyhow::bail!("POST {url} succeeded but no task id found; ...");
            }
        }
        Err(e) => anyhow::bail!("Decode error for {url}: {e}; ..."),
    }
}

Data flow

Boundaries and failures

Summary

Cloud Tasks compresses "submit, query, apply" into the CloudBackend trait; HTTP and mock share the same trait, making TUI debugging possible without a local backend. The underlying HTTP reuses backend-client::Client, sharing auth and path-style decisions with Model Provider. Cloud-task-specific config (allowed environments, enterprise policies) is provided by the cloud config bundle in config system, fetched and applied at startup.