From 6934145897be09cfdd8621ec94f8ef37b910ff28 Mon Sep 17 00:00:00 2001 From: Jack Rothrock Date: Thu, 3 Sep 2026 12:02:52 -0600 Subject: [PATCH] feat(conductor): route code tasks through a sandbox project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The conductor architecture is Conductor → Worker → Sandbox project (each sandbox has its own Claude). A worker runs on the HOST and is NOT sandboxed, so it must not write/run/test a repo's code directly — but it was: e.g. the "add + scan core-agent" chat cloned the repo to /tmp and explored the code on the host instead of spinning up a sandbox. Make the routing explicit in both places a conductor/worker reads: - corral-api SKILL.md: a new "CODE TASKS RUN IN A SANDBOX" section — for any task that edits or executes a repo's code (feature, bug, issue, tests, get an app running, or exploring code to plan such a change), the worker creates a project via POST /projects/create with the coding task as the sandbox `prompt`, then supervises it; non-code work (inspect state, logs/PRs, flows, analysis) stays on the host. - workerContractPreamble: the same rule injected into every worker prompt, so it holds even without the skill. Prompt/skill text only; the skill ships via the asset bundle. No code build needed; worker preamble tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- host/skills/corral-api/SKILL.md | 37 +++++++++++++++++++++++++ internal/dashboard/conductor_workers.go | 11 ++++++++ 2 files changed, 48 insertions(+) diff --git a/host/skills/corral-api/SKILL.md b/host/skills/corral-api/SKILL.md index 01eed76..9974438 100644 --- a/host/skills/corral-api/SKILL.md +++ b/host/skills/corral-api/SKILL.md @@ -73,6 +73,43 @@ break the ask into tasks, kick off a worker per task, and keep going. running it inline. - Put ALL context the worker needs in `prompt` (it starts fresh in a neutral dir). - Give a short, human `title` — it's the worker's tab label. + +### CODE TASKS RUN IN A SANDBOX — the worker creates a project, it does NOT edit code on the host + +The architecture is **Conductor → Worker → Sandbox project** (which has its own +Claude). A worker runs **on the host and is NOT sandboxed** — so a worker must +**never write, run, or test a repo's code directly**. That would touch the +operator's real checkout with no isolation. Instead, a worker whose task is code +work is an *orchestrator*: it **creates a sandbox project and hands the actual +work to that project's own Claude** via the auto-submitted `prompt`. + +**If the task is a code task** — implement a feature, fix a bug, work a GitHub +issue, write/run/debug tests, get an app running, anything that edits or executes +a repo's code — the worker's job is to spin up the sandbox, not to do the coding: + +``` +# The worker (or you, when kicking one off) creates the sandbox and passes the +# real task as the sandbox's first prompt — that sandbox's Claude does the work. +corral api POST /projects/create -d '{ + "repoId": "", # from GET /repos (or "repos":[…] for multi-repo) + "branch": "", # optional; defaults to the repo default branch + "prompt": "" +}' +# → { "id": "", ... } — a sandbox is now running Claude on the task +``` + +So the usual shape for a code request is: **conductor** spawns a **worker** whose +prompt says "create a sandbox project for repo X on this task and drive it to +done"; the **worker** calls `POST /projects/create` (with the coding task as the +project `prompt`); the **sandbox project's Claude** does the isolated code work. +The worker then supervises — polls `GET /status` for that project, reads its +conversation (`GET /api/conversations?origin=sandbox&project=`), steers or +restarts it if needed — but the edits/builds/tests happen **inside the sandbox**. + +**Only skip the sandbox for NON-code work** — inspecting Corral state, reading +logs/PRs, running flows, triage/analysis, orchestration. Those are fine to do on +the host (in the worker or inline). When in doubt about whether a task will touch +a repo's code: route it through a sandbox project. - Workers, like merge jobs, are listed by `GET /merge-jobs`, streamed at `/merge-jobs//ws`, and removed with `DELETE /merge-jobs/`. - Workers run on the HOST and are **not sandboxed**; they use the operator's diff --git a/internal/dashboard/conductor_workers.go b/internal/dashboard/conductor_workers.go index 6783eff..e0697d1 100644 --- a/internal/dashboard/conductor_workers.go +++ b/internal/dashboard/conductor_workers.go @@ -45,6 +45,17 @@ func workerContractPreamble(jobID string) string { "no human at you to answer one. Use ONLY your granted tools: Read/Grep/Glob, plus Bash and " + "Monitor when you have act capability. Those cover waiting/polling; do not reach for an " + "ungranted tool, which would block on approval and strand you.\n" + + "CODE WORK GOES IN A SANDBOX, NOT ON THE HOST: if your task involves writing, running, " + + "building, or testing a repo's code (implementing a feature, fixing a bug, working an issue, " + + "getting an app running, or exploring code to plan such a change), do NOT edit or run that code " + + "here on the host. Instead CREATE A SANDBOX PROJECT and hand the actual work to its own Claude: " + + "`corral api POST /projects/create -d '{\"repoId\":\"\",\"prompt\":\"\"}'` " + + "(the project `prompt` is auto-submitted to the sandbox's Claude). Then supervise it — poll " + + "`corral api GET /status` and read its conversation " + + "(`corral api GET \"/api/conversations?origin=sandbox&project=\"`), steering or restarting if " + + "needed. You are the orchestrator; the edits/builds/tests happen inside the sandbox. Only pure " + + "host/orchestration work (inspecting Corral state, reading logs/PRs, running flows, analysis) " + + "stays on the host.\n" + "Two valid ways to handle a long step (image pull/transfer, build, install):\n" + " (a) BLOCK on it in-turn — run it in the foreground, or poll with Bash " + "(`until …; do sleep N; done`) / Monitor, then proceed once it's done.\n" +