Codex Cloud gives every coding task its own workspace. Build a background coding agent with the OpenAI Agents SDK
#How Codex Cloud tasks work
Codex Cloud is OpenAI's way to hand Codex a coding task and walk away. Tasks run in the cloud, so the work carries on while your computer is asleep, and you check on them from ChatGPT on the web, on mobile or in the desktop app (Codex Cloud). OpenAI's cloud environments guide describes how it fits together:
- A published environment. You pick GitHub repositories, and Codex inspects them, installs dependencies and tools, and tests the workflow. You review the setup and publish it. Publishing captures the prepared filesystem for new tasks.
- One workspace per task. Each new task starts from that prepared filesystem in its own isolated workspace. An existing task continues with its own saved files, including uncommitted changes and installed tools. OpenAI adds that saved state doesn't replace source control.
- Credentials kept out of processes. A network secret reaches programs as a placeholder, and a proxy swaps in the real value only for allowed HTTPS destinations. Internet access is set per environment: package registries only, custom domains, or unrestricted.
- Review, then ship. You inspect the changed files, ask for follow-ups, and commit or open a pull request when you're ready.
Burrowbox isn't involved with OpenAI or Codex.
#The pattern
You hand off a task, close the laptop, and come back later to a workspace that still holds the half-finished branch. A follow-up picks up from there, with the dependencies already installed. That loop needs three things:
- A prepared starting point, so a task doesn't spend its first ten minutes installing things.
- A workspace per task that keeps uncommitted work between turns.
- Credentials that the agent's processes never hold.
The Manus post gave a general-purpose agent a computer per task, run as a durable workflow on Vercel. This post is about code: a repository checked out per task, an uncommitted branch that waits between follow-ups, and a push that happens where the agent can't reach it.
#Build it with the OpenAI Agents SDK and Burrowbox
A Burrowbox machine is a Linux computer with its own disk. Stopping it snapshots the entire filesystem, so an uncommitted branch is still there at the next start. Memory isn't kept, so running programs are relaunched. The sandbox vs persistent machine post covers what else survives a stop.
The agent runs on your server with the OpenAI Agents SDK. MCPServerStreamableHttp connects it to the machine's MCP endpoint, and a static tool filter limits it to three tools: shell_run, file_read and file_write. A SQLiteSession keeps the conversation history between turns, so a follow-up a day later still has the context (Sessions).
pip install openai-agents httpx, then set OPENAI_API_KEY and BURROWBOX_KEY:
# task.py
# python task.py new 42 https://github.com/acme/app.git "Fix the flaky date test in utils"
# python task.py follow 42 "Also cover the leap-year case"
# python task.py ship 42
import asyncio, os, sys, httpx
from agents import Agent, Runner, SQLiteSession
from agents.mcp import MCPServerStreamableHttp, create_static_tool_filter
api = httpx.AsyncClient(base_url="https://burrowbox.dev", timeout=60,
headers={"Authorization": f"Bearer {os.environ['BURROWBOX_KEY']}"})
def workspace(m, tools=None):
return MCPServerStreamableHttp(
name="workspace",
params={"url": m["mcpUrl"], "headers": {"Authorization": f"Bearer {m['mcpToken']}"}},
client_session_timeout_seconds=920, # shell_run can take up to 900 s
cache_tools_list=True,
tool_filter=create_static_tool_filter(allowed_tool_names=tools) if tools else None,
)
async def wake(task_id):
[m] = (await api.get("/api/machines", params={"label.task": task_id})).json()
await api.post(f"/api/machines/{m['id']}/start", json={"ttlMinutes": 60})
while True:
m = (await api.get(f"/api/machines/{m['id']}")).json() # includes mcpToken
if m["status"] == "running":
return m
if m["status"] == "error":
raise RuntimeError(m["lastError"])
await asyncio.sleep(2)
async def turn(task_id, prompt):
m = await wake(task_id)
try:
async with workspace(m, ["shell_run", "file_read", "file_write"]) as mcp:
agent = Agent(name="Coder", mcp_servers=[mcp], instructions=(
"The repository is in ~/repo. Use shell_run to read code and run tests. "
"Don't commit or push. Finish with what you changed and the test results."))
result = await Runner.run(agent, prompt, max_turns=80,
session=SQLiteSession(task_id, "tasks.db"))
print(result.final_output)
finally:
await api.post(f"/api/machines/{m['id']}/stop") # keeps the uncommitted work
async def new(task_id, repo, prompt):
await api.post("/api/machines", json={
"name": f"task-{task_id}", "size": "small", "ttlMinutes": 60, "labels": {"task": task_id}})
m = await wake(task_id)
async with workspace(m) as mcp: # your code runs setup, not the model
await mcp.call_tool("apps_install", {"kind": "apt", "packages": ["git", "nodejs", "npm"]})
await mcp.call_tool("shell_run", {"command": f"git clone {repo} ~/repo && cd ~/repo && npm ci",
"timeout_seconds": 900})
await turn(task_id, prompt)
async def ship(task_id):
m = await wake(task_id)
try:
async with workspace(m) as mcp:
await mcp.call_tool("shell_run", {"command": "cd ~/repo && git add -A && git diff --cached > /tmp/task.patch"})
patch = await mcp.call_tool("file_read", {"path": "/tmp/task.patch"})
with open(f"task-{task_id}.patch", "w") as f:
f.write(patch.content[0].text)
finally:
await api.post(f"/api/machines/{m['id']}/stop")
cmd, *args = sys.argv[1:]
asyncio.run({"new": new, "follow": turn, "ship": ship}[cmd](*args))
How the pieces map to Codex Cloud:
- One machine per task. The
tasklabel is the lookup key:GET /api/machines?label.task=42finds the task's machine, so you don't need a table for it. AddexternalIdif the task belongs to a customer, and usage is reported per customer. - Setup by your code.
newinstalls packages and clones the repo before the model gets any tools. For many tasks on the same stack, a warm pool template can do that setup ahead of time. - Saved state between turns. Every turn ends in
stop. The clone, the installed dependencies and the uncommitted edits sit on the stopped disk untilfollowstarts the machine again.ttlMinutes: 60stops it anyway if your process dies mid-turn. - A narrow token. The agent gets only the machine token (
tmm_…), which works on this machine's MCP endpoint and its live-view links. It can't list, create, stop or bill anything (Authentication). The MCP server per user post goes into that split.
#Ship the change from your side
Burrowbox's docs don't describe a network-secret proxy like Codex's, so this design keeps the push token off the machine entirely. ship copies the staged diff to your server, and you open the pull request from a checkout you control:
git switch -c task-42 && git apply task-42.patch
git add -A && git commit -m "Fix the flaky date test in utils" && gh pr create --fill
For a private repository, clone with a read-only deploy key for that one repo. The agent can read anything on the machine through shell_run, so don't put anything there that can write to your repo.
#Where it differs from Codex Cloud
- No domain allowlist or secret substitution. Burrowbox's docs don't describe either. If a task needs a credential that must never sit in a process, keep that step on your server, as
shipdoes. - A desktop, not only a shell. A machine also has a persistent browser and a desktop. The same agent could open your app in the browser after the tests pass, with logins from the vault, and you can watch through a live-view link.
- You run the agent. Model, prompt, tool list and turn limit are yours, and so is the review screen. Codex Cloud gives you the review UI and the GitHub integration ready-made.
#What it costs
A small machine costs $0.11 an hour while running, billed by the minute, so a 20-minute turn is under four cents of machine time. A stopped machine costs $0.001 an hour, $0.72 per 30 days, while it waits for your follow-up. Model usage is billed separately by OpenAI. Billing has the rates and the per-customer usage report. Once the pull request merges, delete the machine with DELETE /api/machines/{id}.
Create an account to try it.
#Key takeaways
- Codex Cloud gives each task its own workspace, keeps its uncommitted changes between follow-ups, and holds credentials outside the agent's processes.
- One stopped Burrowbox machine per task gives you the same kind of workspace: files, dependencies and uncommitted work survive the stop.
- Give the agent only the machine token and a short tool list, and push from your own server.
#FAQ
#Does a Codex Cloud task keep its files between follow-ups?
Yes. According to OpenAI's docs, an existing task continues with its own saved files, including uncommitted changes and installed tools. New tasks start from the published environment instead.
#Does a stopped Burrowbox machine keep uncommitted changes?
Yes. A stop snapshots the whole filesystem, including files that git hasn't committed. Memory isn't kept, so a dev server or watcher has to be started again on the next turn.
#Can the agent see my GitHub token?
Not in this setup. The machine only has a read-only clone, and the commit and push happen on your server from the copied patch.
#Can I run several tasks at once?
Yes. Each task gets its own machine with its own disk and token, so tasks don't share files. Run new once per task.
#Sources
- OpenAI, Codex Cloud: https://learn.chatgpt.com/docs/cloud
- OpenAI, Cloud environments: https://learn.chatgpt.com/docs/environments/cloud-environments
- OpenAI Agents SDK, Model context protocol (MCP): https://openai.github.io/openai-agents-python/mcp/
- OpenAI Agents SDK, Sessions: https://openai.github.io/openai-agents-python/sessions/
- Burrowbox docs, Machines: https://burrowbox.dev/docs/machines
- Burrowbox docs, MCP: https://burrowbox.dev/docs/mcp
- Burrowbox docs, Authentication: https://burrowbox.dev/docs/authentication
- Burrowbox docs, Billing: https://burrowbox.dev/docs/billing