--- title: "Run a coding agent" description: "Fix a failing test and open a pull request, verify a pull request before merging, or upgrade a dependency — in an isolated code environment under Safe writes." --- A coding Worker gets a Linux environment with Node and Python, your repository through your existing GitHub connection, and a permission preset that lets it open a pull request and forbids it merging one. It runs commands in that isolated sandbox: tests, builds, installs. No credential is placed in the sandbox. Three reviewed templates cover most of it: | Template | Job | Preset | |---|---|---| | `bug-to-pull-request` | reproduce a failing test, write the smallest fix, open a PR | Safe writes | | `pull-request-verification` | check out an open PR, run tests and linters, post a review comment | Safe writes | | `dependency-upgrade` | bump one dependency, run the suite, open a PR either way | Safe writes | ## What Safe writes means here Safe writes grants reversible typed writes — create a branch, open a pull request, file an issue, post a comment — and forbids irreversible ones: merging to the default branch, deleting, force-pushing. That is not a naming convention; presets compile to Cedar policy, and default-deny stands underneath. "View policy" on the Worker shows the compiled result. A verified capability such as `github.pull_request.create` is a typed, governed call. It is a different thing from an arbitrary UI action, and the two are never presented as equivalent — see [Take over a browser](/workers/take-over-a-browser). ## Create it ```bash curl -fsS -X POST "$WS/workers" \ -H "Authorization: Bearer $OBOL_SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "slug": "bug-to-pr", "display_name": "Fix a failing test", "agent_passport_id": "agt_release", "template_id": "bug-to-pull-request", "environment_classes": ["code"], "permission_preset": "safe_writes", "execution_mode": "safe", "instructions": "Reproduce the failing test, write the smallest fix that makes it pass, and open a pull request explaining what changed. Do not merge anything." }' ``` Then recover the id, read the draft, and publish v1 — [the quickstart](/workers/quickstart) has that sequence in full, and the SDK does it in two calls: ```ts await obol.workers.create({ /* as above */ }); const worker = await obol.workers.findBySlug("bug-to-pr"); await obol.workers.publishDraft(worker.id); ``` ## Run it ```ts const started = await obol.workers.run("bug-to-pr", { inputs: { repository: "acme/storefront", default_branch: "main", test_command: "pnpm vitest run", }, confirm: (estimate) => estimate.hard_stop.max_total_micros_usd <= 3_000_000, }); ``` The template's defaults are a 15-minute timeout and an allowlist of `github.com`. (GitHub's *API* is deliberately not in the sandbox allowlist — effect-bearing GitHub operations go through the Obol gateway, where they are authorized, scoped, and receipted. `api.github.com` is not reachable from the sandbox.) A dependency upgrade additionally allows the package registries it needs; a code environment denies egress otherwise, so a script that reaches somewhere else fails with `network_destination_denied` and a `widen_network_policy` fix rather than silently succeeding. ## How the sandbox runs The agent gets three tools in the code environment: `env_code_exec` (a `bash -c` string, default working directory `/workspace`), `env_code_file_read`, and `env_code_file_write`. Each command is a fresh shell; files persist, shell variables do not. A command may run for at most 240 seconds. Background processes are not supported. The sandbox is destroyed when the run ends or waits more than a few minutes. A nonzero exit is a tool result, not a failed run. Clone a **public** repository with `git clone https://github.com//`. Never `git push`. Publish a branch through the GitHub tools, in this order: `github.get_ref` → `github.create_tree` (the changed files' full contents) → `github.create_commit` → `github.create_ref` → `github.create_pull`. Those calls go through the gateway, so they are authorized, scoped, and receipted. Private repositories are not supported in this release. A Worker acts in one environment class per run. A Worker that needs both a code sandbox and a browser cannot run yet. Browser Workers on Browserbase are unchanged. Command output shown on the run is an observation, not evidence of a vendor-side effect. What a GitHub tool call proved is on its receipt, and the receipt names the class. ## What you get back Completion is decided by the output contract, never by the model saying it is done. `bug-to-pull-request` requires: - a result object with `fixed` and `explanation`, plus `pull_request_url` when `fixed` is true; - one `patch` artifact — the diff that makes the test pass; - at least one `log` artifact — the test output before and after. If the run finishes without them, it fails with `output_contract_not_satisfied` and `run.output.missing_requirements` names what was absent. That is the point: a Worker that says "done" and produces nothing has not finished. ```ts const view = await obol.workers.getRun(runId); view.run.output.validated; // false until the contract is satisfied view.run.output.artifact_ids; // fetch each with getArtifact() view.run.output.missing_requirements; // what a failure was missing ``` ## Test mode A published Worker can be run with `test_mode: true`: reduced budgets, non-production connections where the workspace has them, and vendor-side writes blocked, with the same output structure as production. Sandboxed commands whose network is none or declared still run. A test run's observations are never evidence of a production effect. Use it after publishing and before pointing the Worker at a repository that matters. ```ts await obol.workers.run("bug-to-pr", { test_mode: true, inputs: { /* … */ } }); ``` ## Approvals In `safe` execution mode a consequential action pauses for a human, and the run sits in `waiting_approval` with `pending_approval_ids` populated — except sandbox commands whose network is none or declared, which run without that hold. Typed non-read tools and computer-use actions still wait. `balanced` lets typed known actions execute within their limits. `autonomous` asks for fewer approvals and still enforces every limit — and it never lifts the restriction on high-impact arbitrary UI actions, which is a semantic boundary rather than a preference. A run may narrow its mode at start time — asking for *more* approvals than the published revision — and never widen it. Asking for fewer than the revision permits is refused. A live E2B agent command run has not been watched to completion as a canary. The tools, admission, and gallery copy describe what the product is built to do. They are not a proof that a canary has completed. Isolation on the E2B fleet remains a vendor-attested microVM, not production isolation — see [Environments](/workers/environments).