OpenAI has launched Codex Cloud: you describe a task, Codex works on it on its own machine while your computer sleeps, and you come back to review a pull request. The idea is good. It is also tied to one agent (Codex), one account (ChatGPT) and one code host (GitHub).
The building blocks themselves are simple: a fresh machine per task, a ready environment, secrets kept in their place, a result you review. This article puts them together with any command-line agent and any model, on an FFxF machine rented by the hour in Montréal. Two routes: your agent delegates on its own over MCP, or a fifty-line script starts the task without you.
1. What Codex Cloud does
According to its documentation, Codex Cloud rests on five ideas:
- A published environment: repositories, dependencies and tools, prepared once, with an install script.
- An isolated workspace per task, drawn from that environment: two tasks never step on each other.
- Work goes on without you: the computer can sleep.
- Network secrets: a proxy substitutes the real value for allowed domains, the process only sees a placeholder.
- Review: you read the changes, ask for a follow-up, open the PR.
2. The same building blocks, unbundled
None of these ideas needs a closed product. Here is the equivalent of each one with an FFxF machine.
- Published environment → the
coding-agentimage (Ubuntu, Docker, git, gh, Node.js, Python and uv, Claude Code, Codex CLI) plus a.agent/setup.shin the repository. - Isolated workspace per task → one KVM machine per task, with root and Docker, destroyed at the end.
- Goes on without you → the machine runs in Montréal; the task starts from a script, a CI job or your agent.
- Network secrets → narrowly scoped keys, sent over standard input, erased with the machine.
- Review and PR → a pushed branch and a draft PR, opened by
gh. - The agent, Codex → Codex, Claude Code, or any agent that installs from the command line.
A Nano (1 vCPU, 2 GB) costs 0.018 CAD an hour, and a machine created and destroyed within the hour costs exactly that. For a heavy build, pick a bigger plan: prices are in Plans and regions.
3. First route: your agent delegates
If you already work with an agent on your own computer, the shortest route is to
give it the tools to rent its own machine. The FFxF MCP server
(https://ffxf.net/mcp) installs in Claude Code, Codex, Cursor and the
other MCP clients; this sentence is enough for the agent to install it itself:
Install the FFxF MCP server by following https://ffxf.net/agents.md. My API key is in the FFXF_API_KEY environment variable.
Then add the block from Remote mode to the
project's AGENTS.md or CLAUDE.md: when a task needs a real
machine, the agent prices it, orders it, works on it over SSH and destroys it.
The call-by-call demo shows a real
run: delivered in 42 seconds, destroyed in 10, 0.018 CAD.
This route keeps a human in the loop, but it needs an open session. To hand off a task and close the laptop, you need the second one.
4. Second route: one task, one machine, one PR
The script below does for one task what Codex Cloud does: it orders a machine, clones the repository on it, runs the agent unattended, pushes a branch, opens a draft PR, and destroys the machine, whether the task succeeded or not.
It needs, in the environment:
FFXF_API_KEY: an FFxF key limited tovms.read,vms.createandvms.destroy, created under Account → API keys.GH_TOKEN: a fine-grained GitHub token limited to the repository, with Contents and Pull requests write access.ANTHROPIC_API_KEYorOPENAI_API_KEY, depending on the agent.- Your public key
~/.ssh/id_ed25519.pub, already added to the FFxF account.
#!/usr/bin/env bash
# ffxf-task: one agent task on a fresh machine, returned as a PR, then the machine is destroyed.
# Usage: ffxf-task owner/repo "the task" [claude|codex]
set -euo pipefail
REPO=$1 TASK=$2 AGENT=${3:-claude}
API=https://api.ffxf.net/v1
AUTH="Authorization: Bearer $FFXF_API_KEY"
NAME="task-$(date +%s)"
KEY=$(ssh-keygen -lf ~/.ssh/id_ed25519.pub | awk '{print $2}')
# 1. Order: Nano, coding-agent image, hourly, SSH key only.
BODY=$(jq -n --arg h "$NAME" --arg k "$KEY" '{plan: "nano", region: "montreal",
image: "coding-agent", hostname: $h, billing: "hourly",
password_delivery: "none", ssh_keys: [$k]}')
VM=$(curl -sf "$API/vms" -H "$AUTH" -H "Content-Type: application/json" \
-H "Idempotency-Key: $NAME" -d "$BODY" | jq -r .data.vm.id)
# Whatever happens next, the machine is destroyed when the script exits.
trap 'curl -sf -X DELETE "$API/vms/$VM?confirm=$NAME" -H "$AUTH" >/dev/null && echo "$NAME destroyed"' EXIT
# 2. Wait for delivery, then for SSH.
until [ "$(curl -sf "$API/vms/$VM" -H "$AUTH" | jq -r .data.status)" = running ]; do sleep 5; done
IP=$(curl -sf "$API/vms/$VM" -H "$AUTH" | jq -r .data.ipv4)
SSH=(ssh -o StrictHostKeyChecking=accept-new "ubuntu@$IP")
until "${SSH[@]}" true 2>/dev/null; do sleep 3; done
# 3. Secrets go through stdin: not on a command line, not in a history.
"${SSH[@]}" 'umask 077; cat > ~/.agent.env' <<EOF
GH_TOKEN=$GH_TOKEN
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
OPENAI_API_KEY=${OPENAI_API_KEY:-}
EOF
# 4. The work, on the machine.
"${SSH[@]}" "bash -s -- $(printf '%q ' "$REPO" "$NAME" "$AGENT" "$TASK")" <<'EOF'
set -euo pipefail
set -a; . ~/.agent.env; set +a
gh auth setup-git
git config --global user.name "Agent FFxF"
git config --global user.email "agent@users.noreply.github.com"
gh repo clone "$1" ~/work/repo && cd ~/work/repo
git switch -c "agent/$2"
[ -x .agent/setup.sh ] && .agent/setup.sh
case "$3" in
claude) claude -p "$4" --dangerously-skip-permissions ;;
codex) printenv OPENAI_API_KEY | codex login --with-api-key >/dev/null
codex exec --dangerously-bypass-approvals-and-sandbox "$4" ;;
esac
git add -A
git diff --cached --quiet && { echo "No changes."; exit 0; }
git commit -qm "$4"
git push -qu origin HEAD
gh pr create --draft --fill
EOF
Run it like this, and run several at once if you like:
chmod +x ffxf-task
./ffxf-task my-org/my-repo "test_invoice_rounding fails: find the cause and fix it" &
./ffxf-task my-org/my-repo "Add pagination to GET /customers, with tests" codex &
wait
Each call gets its own machine, just as each Codex Cloud task gets its own workspace. The default quota is five machines per account.
5. What matters in this script
- The
trapis set as soon as the machine exists. Agent failure, SSH drop, Ctrl+C: the machine is destroyed when the script exits. A forgotten machine is the only real source of spending. - The FFxF key never leaves your computer. The machine gets a GitHub token limited to one repository and the model key, nothing that lets it order or destroy anything.
- Secrets go through standard input, into a 600 file: they show up neither in the process list nor in a history, and they vanish with the disk. Codex Cloud does better here (the process never sees the real value); in this setup, cap spending at the model provider.
--dangerously-skip-permissionsand its Codex equivalent are reasonable here, and only here: the machine is disposable, reaches nothing else, and will be erased within the hour..agent/setup.sh, if the repository has one, plays the part of Codex Cloud's install script: dependencies, test database, variables. The agent also reads the repository'sAGENTS.mdorCLAUDE.md, as it does locally.- The idempotency key: if the order is resent after a network drop, the API returns the first response instead of creating a second machine.
One difference remains: Codex Cloud keeps the environment ready between tasks, while here each task starts from a clean image and reinstalls its dependencies. For an ordinary Node or Python project that is a minute; for a project that needs ten, keep a monthly machine as the agent's workstation and run the tasks on it.
6. Any agent, any model
The script's case is the only place that depends on the agent. A few
variations:
- Claude Code on a subscription rather than an API key:
claude setup-tokenon your computer gives a long-lived token, to write asCLAUDE_CODE_OAUTH_TOKENin~/.agent.envinstead ofANTHROPIC_API_KEY. - Codex with an open model:
codex exec --oss --local-provider ollamauses an Ollama server instead of the OpenAI API; its address is set in the Codex configuration. - Another agent: install it in
.agent/setup.shand add a line to thecase. It needs a non-interactive mode (instructions as an argument, an exit code); most have one. - Your own model: a Nano will not run an LLM. Host it on a bigger machine that stays up (see Self-host Hermes and Ollama), and point the disposable agents at it. Code and model then stay in Canada.
7. Starting a task from a GitHub issue
Codex Cloud starts from ChatGPT, a phone or Slack. The script starts from anywhere a
command can run: a cron job for a nightly task, or GitHub Actions when someone puts
the agent label on an issue, with the script committed as
tools/ffxf-task.
# .github/workflows/agent.yml
name: agent
on:
issues:
types: [labeled]
jobs:
task:
if: github.event.label.name == 'agent'
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: SSH key
env:
SSH_KEY: ${{ secrets.FFXF_SSH_KEY }}
run: |
install -d -m 700 ~/.ssh
printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
- name: Task
env:
FFXF_API_KEY: ${{ secrets.FFXF_API_KEY }}
GH_TOKEN: ${{ secrets.AGENT_GH_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
NUMBER: ${{ github.event.issue.number }}
run: ./tools/ffxf-task "$GITHUB_REPOSITORY" "Handle issue #$NUMBER (gh issue view $NUMBER) and open a PR."
Two precautions. The issue text never enters the command: only its number does, and
the agent reads the issue itself with gh issue view. And only
repository members can add a label, but the issue text comes from whoever wrote it:
to the agent it is an instruction like any other, hence the token limited to this
one repository and the draft PR, reviewed before merging.
If the runner is killed without warning, the trap has no time to run.
The console's monthly budget bounds the spending, and the
GET /v1/usage?group_by=vm report from Recipes
shows any task-… machine that survived.
8. What each does better
Codex Cloud needs no assembly: the environment is prepared in conversation, review happens in the app, on the phone too, and its network secrets are tighter than a file on the machine. If you already live in ChatGPT, Codex and GitHub, it is the shortest path.
The FFxF assembly is the better fit when you want to choose:
- the agent and the model, including a model you host;
- a real VM: root, Docker, public IPv4 and IPv6, a service that stays reachable while you test it;
- an hourly bill, on prepaid credit, with a monthly budget that stops machines past 120 %;
- where the code runs: Montréal, Canada;
- the trigger: a script, a cron job, any CI, any code host.
The full API is covered in the documentation, and the basic scripts in Recipes.