How to host an external execution agent

Implement the claim, lease, heartbeat, and result lifecycle on a third-party runner host

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_id
  • slice_id
  • model_code
  • pass_code and bucket_code
  • prompt
  • prompt_hash
  • marker
  • timeout_seconds
  • lease_expires_at

Do not reconstruct the prompt. Write the exact UTF-8 prompt bytes to the runner input.

Execute the runner

  1. Verify that SHA-256 of the prompt bytes equals prompt_hash.
  2. Run the model in the checkout for the campaign target commit.
  3. Enforce the smaller of your host policy and the lease's timeout_seconds unless your claim requested a longer lease for a known model timeout.
  4. Capture stdout, process exit code, duration, and a bounded stderr tail.
  5. 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:

StatusMeaningAction
200This lease won and the result was importedRecord accepted and parsed_report_count
202Another lease won; this output was retained as a superseded artifactStop retrying this result
409prompt_hash does not matchDiscard the result and claim again
413An artifact exceeds the server size limitReduce output, then claim and rerun
429Rate limitedRetry with exponential backoff and jitter
5xxServer failureRetry 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, or 413 until 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.