Run coding agents from your own tools: plan, then implement
Updated
Start Claude Code or Codex on a Prized box from a script, a Slack bot or an orchestrator over HTTP, one git worktree per plan item, and get a signed webhook when each run ends: a replacement for Conductor Cloud's API, with a Python example.
On this page6
Your tool calls the Prized API, each item of a plan becomes a Claude Code or Codex run in its own git worktree on a box, and a signed webhook tells your tool when each one ends. The runs live on the box, so nothing waits on your laptop or your bot staying up.
You need a box with your repo on it, here at ~/code/myproject on the box mango (Moving from Conductor Cloud), the agent signed in there (Sign in first), and an API key with Boxes: Write from API keys. The calls below read the key and the edge's address like this (API reference):
export PRIZED_API_KEY=dcp_…
API=https://api.prized.dev/api/v1
AUTH="Authorization: Bearer $PRIZED_API_KEY"
EDGE=$(curl -s "$API/me" -H @<(echo "$AUTH") | jq -r .edge.url)1. Make a worktree for the item
A run starts in a folder that already exists (400 invalid_path otherwise), so add the worktree first with POST /exec. Append your setup script to the same command if the repo has one.
curl -s -X POST "$EDGE/v1/box/mango/exec" -H @<(echo "$AUTH") -H "Content-Type: application/json" -d '{"command":"git -C ~/code/myproject worktree add -b plan-1 ~/code/myproject-plan-1","timeoutSeconds":120}' | jq '{exitCode, stderr}'2. Start a run in it
curl -s -X POST "$EDGE/v1/box/mango/prompts" -H @<(echo "$AUTH") -H "Content-Type: application/json" -d '{"provider":"claude","model":"opus","prompt":"Add rate limiting to the login endpoint. Run the tests, then commit.","cwd":"code/myproject-plan-1","auto":true}' | jq '.run | {id, status}'The answer is 202 prompt.started with the run's id; a paused box wakes first.
| Field | What it does |
|---|---|
provider | claude or codex (opencode and pi work too) |
model | The agent's own model id or alias, such as opus or gpt-5.5; prized prompt models lists them |
reasoningEffort | low, medium or high |
prompt | The task, up to 512 KiB |
cwd | The worktree, relative to your home on the box |
auto | No approvals, so the agent can run the tests unattended (Approvals) |
notify | An https URL that gets one signed POST when the run ends (step 3) |
From a shell script, prized prompt starts the same run, reading the task from a file:
prized prompt mango --provider claude --model opus --cwd code/myproject-plan-1 --auto --detach - < item.md3. Get told when it ends
- The workspace owner creates the webhook signing secret once, under Webhook signing secret on API keys. Until then, a prompt with
notifyanswers409 webhook_secret_missingand nothing starts. - Add
"notify": "https:/to the prompt (/ hooks.example.com/ prized" --notifyon the CLI). Only publichttpsaddresses on port 443 are sent to. - Verify each request with any Standard Webhooks library and answer
2xxquickly. Retries carry the samewebhook-id, so dedupe on it.
The event's data.run.status is done, failed or interrupted, and data.links.events reads what the agent did, with the same key, while the box runs. The full body and the retries are on API reference: Webhooks.
Fan out: one run per plan item
Repeat steps 1 and 2 for each item, with its own worktree and branch. A prompt without conversationId, continue or queue starts a new conversation, so the items run side by side.
- On one box: up to one run in progress per 2 GB of memory, at most 16 (an Extra Small runs 4); one more answers
409 prompt_limit_reached(Limits). The runs share the box's disk, CPU and memory, which is why each gets a worktree: two runs in one folder edit the same files. - A box per item: fork a box you set up with
POST /api/v1/boxes/{id}/forkand prompt each copy once it runs (API reference: Boxes). This needs a plan, since the free credit runs one box at a time.
Plan, then implement, in Python
The plan can be any list you keep. To have the agent write it, prompt it in the main checkout and copy the file back; without --detach, prized prompt returns when the run ends:
prized prompt mango --provider claude --model opus --cwd code/myproject "Plan this change as one pull request per item. Write the plan to plan.md as a Markdown list, one line per item starting with '- '. Change nothing else."prized cp mango:code/myproject/plan.md plan.mdorchestrate.py start makes a worktree and starts a run for each line of the plan that begins with - ; orchestrate.py serve is the webhook receiver, which prints each run's status and the agent's last message.
import json, os, re, sys, threading, urllib.error, urllib.request
from http.server import BaseHTTPRequestHandler, HTTPServer
from standardwebhooks import Webhook
AUTH = {"Authorization": "Bearer " + os.environ["PRIZED_API_KEY"], "Content-Type": "application/json"}
REPO = os.environ.get("REPO", "code/myproject") # the repo's folder on the box, under your home
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args): # urllib would resend the key to wherever a redirect points
return None # so a 3xx raises HTTPError instead
def call(url, body=None):
req = urllib.request.Request(url, json.dumps(body).encode() if body else None, AUTH)
try: # a long wait: a box in deep sleep takes minutes to wake
with urllib.request.build_opener(NoRedirect).open(req, timeout=600) as r:
return json.load(r)
except urllib.error.HTTPError as e:
raise RuntimeError(f"{url}: {e.code} {e.read().decode()}") from None
def start(plan_file, notify_url, first=1):
edge = call("https://api.prized.dev/api/v1/me")["edge"]["url"] + "/v1/box/" + os.environ["BOX"]
items = [line[2:].strip() for line in open(plan_file) if line.startswith("- ")]
for n, item in list(enumerate(items, 1))[first - 1:]:
name = f"plan-{n}-" + re.sub(r"[^a-z0-9]+", "-", item.lower())[:30].strip("-") # branch and folder
tree = f"{REPO}-{name}"
made = call(f"{edge}/exec", {"command": f"test -d ~/{tree} || git -C ~/{REPO} worktree add -b {name} ~/{tree}", "timeoutSeconds": 120})
if made["exitCode"] != 0:
raise RuntimeError(made["stderr"])
prompt = f"{item}\n\nYou are in a git worktree on branch {name}. Run the tests, then commit."
run = call(f"{edge}/prompts", {"provider": "claude", "model": "opus", "prompt": prompt,
"cwd": tree, "auto": True, "notify": notify_url})["run"]
print(f"{name}: {run['id']}")
class Receiver(BaseHTTPRequestHandler):
seen = set() # a retried delivery keeps its event id
def do_POST(self):
body = self.rfile.read(int(self.headers["Content-Length"]))
try:
data = self.hook.verify(body, dict(self.headers))["data"]
except Exception:
return self.send_error(401)
self.send_response(204)
self.end_headers()
if data["run"]["id"] and data["eventId"] not in self.seen: # id is null for a queued prompt that never ran
self.seen.add(data["eventId"])
threading.Thread(target=report, args=(data["run"], data["links"])).start()
def report(run, links):
try: # a run's events can be read while its box runs
count = call(links["run"])["run"]["eventCount"]
events = call(f"{links['events']}?after={max(count - 20, 0)}")["events"]
said = [e["text"] for e in events if e.get("text")][-1:] # the agent's last words
except Exception as e:
said = [f"(could not read the run: {e})"]
print(run["id"], run["status"], *said, flush=True)
if __name__ == "__main__":
try:
if sys.argv[1] == "start":
start(sys.argv[2], sys.argv[3], int(sys.argv[4]) if len(sys.argv) > 4 else 1)
else:
Receiver.hook = Webhook(os.environ["PRIZED_WEBHOOK_SECRET"])
HTTPServer(("", int(sys.argv[2])), Receiver).serve_forever()
except RuntimeError as e:
sys.exit(str(e))- Add a subtract(a, b) function to calc.py, with a test.
- Add a multiply(a, b) function to calc.py, with a test.
- Add a README.md that says what calc.py does and how to run the tests.Both halves read PRIZED_API_KEY, exported as above, and need one library beyond Python's own:
pip install standardwebhooksThe receiver speaks plain HTTP, so run it behind your https address on port 443, with the signing secret from step 3:
PRIZED_WEBHOOK_SECRET=whsec_… python orchestrate.py serve 8080Then, from anywhere:
BOX=mango python orchestrate.py start plan.md https://hooks.example.com/prizedIt starts every item at once, so use a box whose limit covers the plan, and prints a run id per item; the receiver prints a line as each run ends. If it stops partway (a missing secret, the box's limit), fix that and start again from the first item it did not print, such as python orchestrate.py start plan.md https:/. Each run commits on its own branch; to have it open a pull request too, add that to the prompt (gh on the box is signed in if it was on your machine at setup).
Once a branch merges, remove its worktree and branch on the box:
git worktree remove ../myproject-plan-1-add-a-subtract-a-b-function-to
git branch -D plan-1-add-a-subtract-a-b-function-toComing from Conductor's API
| Conductor Cloud | Prized |
|---|---|
POST /v0/workspaces, or conductor workspace create | POST /exec to add a worktree (and run your setup script), then POST /prompts with cwd set to it |
projectId or repositoryUrl | The repo, cloned on the box once (prized setup . --clone, or an environment's repos) |
branch | The branch git worktree add checks out or starts from |
agent: claude or codex | provider (--provider on the CLI): claude or codex |
model, effort | model, in the agent's own ids (opus, gpt-5.5), and reasoningEffort (--effort): low, medium or high |
message, or conductor workspace create --message-file item.md | prompt; prized prompt … - < item.md reads it from a file |
env | env on POST /api/v1/boxes, or the box's Settings tab: the box's variables, for every run on it |
Polling GET /v0/sessions/{id}/status | The notify webhook, or GET /prompts/{runId} |
GET / | GET /, one run's events after a seq; follow=1 streams them |
POST /, a follow-up | POST /prompts with the run's conversationId and the same cwd; add queue: true while a turn is running |
POST /v0/sessions/{id}/cancel | POST /; prompts queued behind it wait for the conversation's next turn |
POST / | git worktree remove through POST /exec; it deletes the folder but keeps the branch, so commit first |
POST / | POST /, which pauses the whole box; on a box that stops clean when it pauses, that ends its runs (Webhooks) |
- Shared box: each Conductor workspace was isolated; the runs on a box share it, up to the limit above.
- Agents and efforts: Conductor's
cursorandacpagents, and its efforts other thanlow,mediumandhigh, have no match in the prompt API. - No routines: nothing on Prized starts a saved prompt from an incoming URL, so have the endpoint you already run call
POST /prompts. - No chat view: follow a run with
prized events --run pr_… --follow. Runs don't message each other; your orchestrator passes results along. - A task per request: for a Slack bot or a queue that sends one task at a time rather than a plan, Run Conductor-style agent tasks from code takes each from request to pull request.