title: Agent tasks description: Give one of your agents a goal; it runs Claude Code, Codex or your own command in a sandbox with your own model key, then pushes a branch and opens a pull request for review.
An agent task is a goal you give one of your agents, such as "Fix the Contact sales link on the pricing page and add a test". Diffinery starts a sandbox as that agent with the repository checked out, runs a coding agent (Claude Code, Codex or a command of your own) with your own model API key, streams its transcript to the task page, then pushes the work to a branch and opens a pull request as the agent. The pull request needs a person's approval like any agent pull request.
Tasks are optional. Nothing changes for a repository until someone with a model key starts one.
The same machinery reviews pull requests: see Agent review. Reviews show in the repository's task list, labelled Review. The repository's Project tab shows tasks on a board, with a conversation and a memory every task reads: see Projects.
Before you start
You need three things:
- Write access to the repository.
- An agent you manage: one you own, or one of an organization where you are an owner or admin. The agent needs write access to the repository too (agents get at most their owner's access). Create one under Settings → Agents; see Agents.
- Your own model key under Settings → Model access: an Anthropic API key for Claude Code, an OpenAI API key for Codex, or an OpenRouter key for either. See Model access.
Start a task
- Open the repository's Project tab (people with write access see it) and click New task.
- Write the goal, as you would ask a colleague. Markdown works.
- Pick the runtime: Claude Code, Codex or Custom command (see Runtimes).
- Pick the agent and the base branch.
- Tick Plan first to have the agent plan before it builds (see Plan first).
- Click Start task.
The task page shows the transcript live: what the agent says, the commands it runs and their output.
What happens in a run
| Step | What Diffinery does |
|---|---|
| Sandbox | Re-checks that you may run the task, then starts a fresh sandbox as the agent, on behalf of you, with the base branch (or the task's branch, for later runs) checked out. Its git credential is the agent's, at most write, and stops working when the run ends. |
| Install | Installs the runtime from its vendor's official npm package, at a version Diffinery pins (see Runtimes). No model credential exists in the sandbox yet. |
| Agent | Runs the runtime headless with the goal, plus the project's instructions and memory when it has them (see Projects). Its process, and only its process, gets a model token for this run (see How your key is protected). |
| Push | Commits what the agent left uncommitted, as the agent, and pushes agent/<number>-<slug>. Branch protection, push protection and the agent's guardrails apply as to any push. |
| Pull request | Opens a pull request from that branch as the agent, with the goal as its intent, or updates the one the task already has. |
| Close | Closes the sandbox. The run's git credential and model token stop working. |
A run stops after 6 hours. Tasks show on Today under Your agent tasks while they run and for a day after they change.
Task states
| State | Meaning |
|---|---|
| Queued | Waiting for a running task in the repository to finish, or, for a task a coordinator started, for the tasks it waits on: a repository runs at most 3 at once unless its admins change it (see How many at once). |
| Running | A run is in progress. |
| Ready | The last run finished. Its work is on the task's pull request, if it changed anything. |
| Failed | The last run failed: the runtime exited with an error, the push was refused, or you lost access. The transcript says why. |
| Stopped | Someone stopped it. |
A plan-first task also has a plan state: planning while its plan runs, waiting when the plan waits for you (the board shows it under Waiting on you), then approved or split.
Plan first
A plan-first task's first run writes a plan instead of code. The agent gets a read-only sandbox, as an agent review does: it reads the code and runs read-only commands, its git credential can't push, and nothing it changes is kept. The plan is its final message (for a custom command, everything it printed), up to 20,000 characters, and shows on the task page. Then you choose:
- Approve plan: a build run starts with the goal and the plan. It, and every later run of the task, gets the plan in its prompt as the one you approved.
- Send a follow-up to ask for changes: the agent plans again, with its previous plan and your message.
- Split into tasks: your coordinator gets the goal and the plan as a post of yours and starts tasks for its parts. Each says it was split from this task, which is then done with. You need a coordinator in the project for this.
Repository admins can make plans the default, or require them for every task, including ones coordinators start (see Plan first).
Steer, retry and stop
- Send a follow-up from the task page to continue: a new run starts on the task's branch with the goal, how the previous run ended and your message, and its work goes to the same pull request. Sent while a run is active, the message waits and starts when that run ends.
- Retry runs the last run again when the task isn't running.
- Stop stops the agent and closes its sandbox. Work already pushed stays.
Only the person who started a task can send it messages or retry it, because each run spends their model key. That person, the task's agent and repository admins can stop it.
Runtimes
| Runtime | Needs | Runs |
|---|---|---|
| Claude Code | Your Anthropic API key | claude -p "$DIFFINERY_TASK_PROMPT" --output-format stream-json --verbose --permission-mode bypassPermissions |
| Codex | Your OpenAI API key | codex exec --json --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check "$DIFFINERY_TASK_PROMPT" |
| Claude Code via OpenRouter | Your OpenRouter key | The same, with ANTHROPIC_BASE_URL on OpenRouter's Anthropic-compatible endpoint (through the proxy), the token as ANTHROPIC_AUTH_TOKEN, and the model you choose for every model role (ANTHROPIC_MODEL, ANTHROPIC_DEFAULT_OPUS_MODEL, _SONNET_, _HAIKU_, ANTHROPIC_SMALL_FAST_MODEL, CLAUDE_CODE_SUBAGENT_MODEL) |
| Codex via OpenRouter | Your OpenRouter key | The same, with an openrouter model provider on OpenRouter's Responses API (-c model_provider=openrouter -c model_providers.openrouter...) and -m "$DIFFINERY_MODEL" |
| Custom command | Nothing required | Your command, with the goal in $DIFFINERY_TASK_PROMPT, and ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY, OPENAI_BASE_URL / OPENAI_API_KEY and OPENROUTER_BASE_URL / OPENROUTER_API_KEY set for each provider you have a key for |
OpenRouter
With an OpenRouter key, Claude Code and Codex can run on a model you choose there. Pick Claude Code via OpenRouter or Codex via OpenRouter as the runtime, and name the model (an id from openrouter.ai/models). Left empty, Claude Code uses ~anthropic/claude-sonnet-latest and Codex ~openai/gpt-sol-latest. Over the API, send "provider": "openrouter" and "model".
OpenRouter supports Claude Code fully only with Anthropic models served by Anthropic, and Codex works best with OpenAI models. Other models may not handle the runtime's tools well. Usage, including OpenRouter's fees, is billed to your OpenRouter account.
A run through OpenRouter is pinned to the model you chose. The model proxy refuses a request for any other model, and requests that set OpenRouter's routing or paid extras (models, route, provider, plugins, web_search_options, speed, best_of). So code in the repository can't switch the run to a pricier model. A custom command that uses your OpenRouter key isn't pinned, since it picks its own model.
Diffinery names itself to OpenRouter as the app (HTTP-Referer and X-Title). The sandbox's own attribution and X-OpenRouter-* headers are dropped.
The runtimes run unmodified, from the vendors' own packages, at pinned versions: Claude Code 2.1.296 (@anthropic-ai/claude-code) and Codex 0.162.1 (@openai/codex). Diffinery moves to newer versions deliberately rather than installing whatever was published last. The sandbox is its own container, so they run without permission prompts. The goal reaches the runtime in an environment variable, never pasted into a command line.
The prompt tells the agent which task it is working on, that Diffinery pushes and opens the pull request, and not to push or change git credentials itself.
Model access
Settings → Model access holds your own API keys, one per provider:
- Anthropic, for Claude Code. Create a key at console.anthropic.com.
- OpenAI, for Codex. Create a key at platform.openai.com.
- OpenRouter, for Claude Code or Codex on a model you choose there (see OpenRouter). Create a key at openrouter.ai/settings/keys.
Saving a key asks you to confirm that you use that provider through Diffinery under your own account and the provider's terms, with links to them (Anthropic's Commercial Terms and Usage Policy; OpenAI's Terms and Policies; OpenRouter's Terms of Service and Privacy Policy). The provider bills your account. Diffinery only provides the ability; whether and how you use it is your choice and your agreement with the provider. Your confirmation is stored with its date.
- Keys are encrypted at rest and write-only: no page, API call or MCP tool returns one. You see the last four characters.
- Only you, signed in in a browser, can save, replace or delete your keys. No token, agent, job or sandbox can.
- Saving a key sends you a security email ("Your Anthropic API key was added to Diffinery"), which you can't turn off.
- Keys belong to people. Agents never have model keys of their own, and organization keys aren't available yet.
- Subscriptions (Claude Pro or Max, ChatGPT plans) can't be used for tasks: providers allow automated use with API keys.
How your key is protected
Your key never enters a sandbox:
When a run's agent starts, Diffinery creates a model token for that run (
dmt_...) and gives it to the runtime's process only, as its API key, with the provider's base URL pointed at Diffinery's model proxy (https://diffinery.com/-/model/<provider>).The proxy accepts the token only while that run's agent is running. On every request it re-checks that you can still run the task, then forwards the request to the provider with your key added.
The proxy forwards only the model endpoints the runtimes use, and refuses everything else with
403, so an agent can't reach the rest of your provider account (stored files, batches, assistants):Provider Endpoints Anthropic POST /v1/messages,POST /v1/messages/count_tokens,GET /v1/modelsandGET /v1/models/{id}OpenAI POST /v1/responses,POST /v1/responses/compact,GET /v1/responses/{id},POST /v1/chat/completions,GET /v1/modelsandGET /v1/models/{id}OpenRouter POST /v1/messages,POST /v1/messages/count_tokens,POST /v1/responses,POST /v1/chat/completions,POST /v1/completionsandGET /v1/models. Never keys, credits, generation stats or anything else on your OpenRouter account.When the agent exits, is stopped, or the run ends any other way, the token stops working. If you lose access to the repository or the agent, the task stops.
Everything the sandbox returns, including the transcript, masks the model token and the git credential.
What this doesn't protect against. Anything the agent runs in its sandbox (build scripts, tests, dependencies, code from the repository) can read the model token and the git credential while the run lasts, and repository content can try to trick the agent into misusing them ("prompt injection"). Sandboxes have internet access. So:
- Use a dedicated API key with a spending limit for Diffinery, and revoke it with the provider if you suspect misuse.
- Run tasks on repositories whose content you trust.
- A transcript can show a credential in another form (for example encoded) that masking can't recognise. Transcripts are visible only to people with write access.
Task sandboxes are closed to the sandbox API: no call reaches them, for anyone, including the agent's own tokens and repository admins.
Who sees what
- Tasks and transcripts: people with write access to the repository (stricter than Actions logs, because transcripts show an agent's reasoning and command output).
- The task's pull request: everyone who can read the repository, like any pull request.
REST API
| Endpoint | Does | Who |
|---|---|---|
GET /repos/{owner}/{repo}/diffinery/tasks | List tasks | Write access; any token |
GET /repos/{owner}/{repo}/diffinery/tasks/{number} | A task with its runs | Write access; any token |
GET /repos/{owner}/{repo}/diffinery/tasks/{number}/transcript?after=0 | Transcript lines | Write access; any token |
POST /repos/{owner}/{repo}/diffinery/tasks | Start a task; "plan_first": true or false overrides the project's default, unless it requires plans | Browser session only |
POST .../tasks/{number}/messages | Send a follow-up | Browser session only, the person who started it |
POST .../tasks/{number}/retry | Retry | Browser session only, the person who started it |
POST .../tasks/{number}/plan/approve | Approve a waiting plan: the build run starts | Browser session only, the person who started it |
POST .../tasks/{number}/plan/split | Hand a waiting plan to your coordinator to split into tasks | Browser session only, the person who started it |
POST .../tasks/{number}/stop | Stop | The person who started it, its agent or a repository admin |
GET /diffinery/tasks | Tasks you started, across repositories | Any token |
GET /diffinery/model-keys | Your saved keys (provider, last four characters, dates) | Any token |
PUT / DELETE /diffinery/model-keys/{provider} | Save or delete a key | Browser session only |
curl https://diffinery.com/api/v3/repos/acme/website/diffinery/tasks/7 \
-H "Authorization: Bearer dfy_..."
{
"number": 7,
"title": "Fix the Contact sales link on the pricing page",
"goal": "Fix the Contact sales link on the pricing page so it goes to /contact, and add a test.",
"state": "ready",
"runtime": "claude-code",
"agent": { "login": "ayse-bot", "type": "Bot" },
"started_by": { "login": "ayse", "type": "User" },
"plan_first": false,
"base": "main",
"branch": "agent/7-fix-the-contact-sales-link-on-the",
"pull_request": { "number": 42, "html_url": "https://diffinery.com/acme/website/pull/42" },
"queued_message": null,
"error": null,
"html_url": "https://diffinery.com/acme/website/tasks/7",
"runs": [{ "number": 1, "kind": "start", "message": null, "state": "done", "exit_code": 0, "error": null, "model_requests": 42, "tokens": 318204, "started_at": "2026-10-14T07:02:11.000Z", "ended_at": "2026-10-14T07:09:40.000Z" }]
}
A run's model_requests and tokens are what its agent used through the model proxy, counted from the usage the provider reports (cache reads count as a tenth). An answer that reports no usage counts an estimate: a token per 3 bytes of request, plus the output it allows.
Transcript lines have seq, run, at, stream (out and err for the runtime's output, setup for installing and pushing, note for Diffinery's own lines) and text. Claude Code writes stream-json lines and Codex writes --json lines to out.
MCP
Two read-only tools, so your own agent can follow tasks:
| Tool | What it does |
|---|---|
agent_tasks | With owner and repo: that repository's tasks. Without: the tasks you started. |
agent_task | One task with its runs and its transcript after after. |
Starting, steering and retrying aren't available over MCP, because they spend a person's model key.
Troubleshooting
| What you see | Cause and fix |
|---|---|
Claude Code needs your Anthropic API key | Save a key under Settings → Model access. |
you can't run tasks as <agent> | You must own the agent or be an owner or admin of the organization that owns it. |
<agent> needs write access to this repository | Give the agent write access, or a role through its organization. Agents get at most their owner's access. |
starting or steering an agent task needs a browser session | Use the web page; tokens can't start tasks. |
| The run failed at Installing | The npm registry couldn't be reached or the package failed to install. Retry. |
Pushing the work ... failed | The push was refused, usually by branch protection, push protection (a secret in the changes) or the agent's guardrails. The transcript shows git's message. |
| The task stopped with "can no longer run it" | You lost write access to the repository, or can no longer manage the agent. |
Model calls fail with 401 ... its task run has ended | The run is over; that token no longer works. Start a new run. |
A custom command gets 403 ... isn't available to agent tasks | The model proxy forwards only the model endpoints listed under How your key is protected. |