Sign in

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.

FieldWhat it does
providerclaude or codex (opencode and pi work too)
modelThe agent's own model id or alias, such as opus or gpt-5.5; prized prompt models lists them
reasoningEffortlow, medium or high
promptThe task, up to 512 KiB
cwdThe worktree, relative to your home on the box
autoNo approvals, so the agent can run the tests unattended (Approvals)
notifyAn 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.md

3. Get told when it ends

  1. The workspace owner creates the webhook signing secret once, under Webhook signing secret on API keys. Until then, a prompt with notify answers 409 webhook_secret_missing and nothing starts.
  2. Add "notify": "https://hooks.example.com/prized" to the prompt (--notify on the CLI). Only public https addresses on port 443 are sent to.
  3. Verify each request with any Standard Webhooks library and answer 2xx quickly. Retries carry the same webhook-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}/fork and 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.md

orchestrate.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.

orchestrate.py
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))
plan.md
- 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 standardwebhooks

The 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 8080

Then, from anywhere:

BOX=mango python orchestrate.py start plan.md https://hooks.example.com/prized

It 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://hooks.example.com/prized 4. 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:

you@box ~/code/myproject
git worktree remove ../myproject-plan-1-add-a-subtract-a-b-function-to
git branch -D plan-1-add-a-subtract-a-b-function-to

Coming from Conductor's API

Conductor CloudPrized
POST /v0/workspaces, or conductor workspace createPOST /exec to add a worktree (and run your setup script), then POST /prompts with cwd set to it
projectId or repositoryUrlThe repo, cloned on the box once (prized setup . --clone, or an environment's repos)
branchThe branch git worktree add checks out or starts from
agent: claude or codexprovider (--provider on the CLI): claude or codex
model, effortmodel, 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.mdprompt; prized prompt … - < item.md reads it from a file
envenv 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}/statusThe notify webhook, or GET /prompts/{runId}
GET /v0/sessions/{id}/messages?after=…GET /prompts/{runId}/events?after=…, one run's events after a seq; follow=1 streams them
POST /v0/sessions/{id}/messages, a follow-upPOST /prompts with the run's conversationId and the same cwd; add queue: true while a turn is running
POST /v0/sessions/{id}/cancelPOST /prompts/{runId}/interrupt; prompts queued behind it wait for the conversation's next turn
POST /v0/workspaces/{id}/archivegit worktree remove through POST /exec; it deletes the folder but keeps the branch, so commit first
POST /v0/workspaces/{id}/sleepPOST /api/v1/boxes/{id}/suspend, 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 cursor and acp agents, and its efforts other than low, medium and high, 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.