How to host an external execution agent
Use this guide when a third-party execution host must run TheSpider slices without the first-party Rust agent. The host remains thin: it executes the rendered prompt and returns raw process output. Parsing, scrubbing, fingerprinting, deduplication, and persistence remain server-side.
Prerequisites
- A project-scoped bearer token.
- The project identifier and model codes enabled for that project.
- A checkout containing the campaign's target commit.
- A runner that follows the runner contract.
Claim work
Send a claim request:
POST /v1/campaigns/42/claim
Authorization: Bearer TOKEN
Content-Type: application/json
{
"client_id": "runner-east-1",
"models": ["sonnet"],
"max_slices": 2,
"lease_seconds": 3600
}
A successful response contains leases[]. An empty array means no matching slice is claimable.
Persist these fields until submission:
lease_idslice_idmodel_codepass_codeandbucket_codepromptprompt_hashmarkertimeout_secondslease_expires_at
Do not reconstruct the prompt. Write the exact UTF-8 prompt bytes to the runner input.
Execute the runner
- Verify that SHA-256 of the prompt bytes equals
prompt_hash. - Run the model in the checkout for the campaign target commit.
- Enforce the smaller of your host policy and the lease's
timeout_secondsunless your claim requested a longer lease for a known model timeout. - Capture stdout, process exit code, duration, and a bounded stderr tail.
- Keep model-derived output opaque. Do not parse or rewrite findings on the host.
Extend a live lease
While the process runs, send best-effort heartbeats:
POST /v1/leases/9001/heartbeat
Authorization: Bearer TOKEN
A heartbeat extends an active lease and returns its new expiry. Heartbeat failure does not require stopping the model. Lease expiry requeues the slice, but a late result can still win if no other lease completed it.
Submit raw results
POST /v1/leases/9001/results
Authorization: Bearer TOKEN
Content-Type: application/json
{
"exit_code": 0,
"duration_ms": 182345,
"model_code": "sonnet",
"client_version": "external-host/1.0.0",
"prompt_hash": "HASH_FROM_CLAIM",
"stdout": "RAW_PROCESS_STDOUT",
"stderr_tail": "LAST_4000_CHARACTERS"
}
Interpret the response by status:
| Status | Meaning | Action |
|---|---|---|
200 | This lease won and the result was imported | Record accepted and parsed_report_count |
202 | Another lease won; this output was retained as a superseded artifact | Stop retrying this result |
409 | prompt_hash does not match | Discard the result and claim again |
413 | An artifact exceeds the server size limit | Reduce output, then claim and rerun |
429 | Rate limited | Retry with exponential backoff and jitter |
5xx | Server failure | Retry with exponential backoff and jitter |
Results are idempotent on (lease_id, prompt_hash). Retrying the same accepted submission does not ingest reports twice.
Recover after host failure
- Before execution, persist the lease payload and runner process identifier.
- After a process finishes, persist the result body before sending it.
- On restart, submit an unsent result even if the lease has expired.
- If no result exists, do not fabricate one. Let the lease expire and requeue.
- Stop retrying after
202,409, or413until a new claim or rerun changes the payload.
Protect the boundary
- Store the bearer token outside command output and model prompts.
- Scope tokens to one project when possible.
- Use HTTPS.
- Do not upload the source tree to TheSpider. The host may separately invoke a cloud model provider; review that provider's data policy.
- Treat prompt and model output as sensitive tenant data.
See the API reference for the canonical route summary and runner troubleshooting for failure diagnosis.