External Workers
Have your own AI agent — like Claude Code — execute a worker's tasks instead of CreateWorker's built-in models. CreateWorker holds the work, your agent polls for it, does it, and returns a deliverable you review and approve.
How it works
A worker marked as an External Worker doesn't run on CreateWorker's models. Instead its tasks are held for an outside runner (your AI agent) to pick up:
create task ─▶ ASSIGNED ──(runner claims)──▶ IN_PROGRESS
│
submit deliverable ◀──────┘
│
IN_REVIEW ──(you approve in CreateWorker)──▶ COMPLETED
└──(you reject/edit)────────────▶ ASSIGNED (re-queued)The runner drives it with a handful of tools: poll tasks_list(status="ASSIGNED"), task_claim, task_progress, and deliverable_submit. You stay in control — nothing completes until you approve it in CreateWorker.
1. Create an External Worker
Any worker can be external. Three ways to set it:
- In the dashboard — open a worker → Settings → General → External Worker and turn on "Run tasks with an external runner". Any queued tasks are immediately held for the runner.
- New worker under the External Worker role — create a worker with the reserved External Worker role and it's external from the start.
- Via the API / an agent — see below; your agent can create or flip a worker itself.
Create or update through the REST API:
# Create an external worker
curl -X POST https://www.createworker.com/api/v1/workers \
-H "Authorization: Bearer cw_live_…" -H "Content-Type: application/json" \
-d '{ "name": "Claude Code", "roleKey": "GENERAL_PURPOSE", "externalExecutor": true }'
# Or flip an existing worker
curl -X PATCH https://www.createworker.com/api/v1/workers/{workerId} \
-H "Authorization: Bearer cw_live_…" -H "Content-Type: application/json" \
-d '{ "externalExecutor": true }'SDK: cw.workers.create({ name, roleKey, externalExecutor: true }) or cw.workers.update(id, { externalExecutor: true }). MCP: worker_create / worker_update with externalExecutor.
2. Create a scoped API key
Your runner authenticates with a CreateWorker API key. In your dashboard, open Developer → Create API key (org admin) and grant just what a runner needs:
tasks:read— poll for assigned taskstasks:write— claim and post progressdeliverables:write— submit the resultchat:read,chat:write— answer the worker's dashboard chat (see below)- (optional)
workers:write— let the agent create/flip workers itself
The secret is shown once — copy it. See Authentication for details. The key is the trust boundary: it only reaches tasks in your org.
3. Connect Claude Code
Add the CreateWorker MCP server to Claude Code with your key:
claude mcp add createworker \
--env CREATEWORKER_API_KEY=cw_live_… \
-- npx -y @createworker/mcpOther clients (Claude Desktop, Cursor) and the hosted transport are on the MCP Server page.
4. Run the loop
With the server connected, paste this prompt into Claude Code (in the repo it should work in) to make it your worker's runner:
You're connected to CreateWorker via the `createworker` MCP server and you're the runner
for my External Worker. Do this loop:
1. tasks_list(status="ASSIGNED") — the tasks assigned to me. If none, stop and tell me.
2. Take the oldest. task_claim(id). If it returns a 409 conflict, skip it and try the next.
3. task_progress(id, note="picked up").
4. task_get(id), then do the work in this repo following its conventions and my CLAUDE.md.
Code change -> open a PR (never merge or force-push). Research -> gather findings.
5. deliverable_submit(id, { title, summary, content: <markdown of what you did>,
links: [{ label: "PR", url: "<pr url>" }] }). This moves the task to IN_REVIEW.
6. Tell me it's ready to review in CreateWorker, then repeat from step 1.
If you get blocked, call task_progress(id, state="blocked", note="<why>") and stop.
Also answer chat: chat_pending() returns dashboard chat messages waiting for me. For each, read
chat_history(sessionId) for context and reply with chat_reply(sessionId, content). Keep chat
replies short and conversational.Make it recurring
409 on task_claim just means another runner already has it — skip and move on.Prefer push over polling? Subscribe a webhook endpoint to the task.assigned event — it fires whenever a task becomes claimable (created, executor turned on, returned by a reviewer, or reclaimed after a stall).
Claims expire after 4 hours of silence
Chat with your external worker
The worker's Ask Worker chat in the dashboard can be answered by your runner too — so when you type a message, Claude Code replies, not CreateWorker's model. It works just like tasks: your message is held, the runner picks it up, and its reply shows up in the chat.
chat_pending— chat messages waiting for a reply (each with itssessionIdand recent context)chat_history— a session's full messages, to build contextchat_reply— post your reply; it appears in the dashboard chat
The quickstart prompt above already handles this. Over REST it's GET /v1/chat/pending, GET /v1/chat/sessions/{id}/messages, and POST /v1/chat/sessions/{id}/messages (needs chat:read / chat:write).
It's a poll, so there's a small delay
5. Review and manage in CreateWorker
When the runner submits a deliverable, the task moves to IN_REVIEW and appears in your Inbox. Open it to read the deliverable and any linked PR, then Approve (→ COMPLETED) or Reject (sends it back to ASSIGNED with your reason, so the runner picks it up again). Progress notes the runner posts show on the task's timeline.
Turning it off