How to configure a custom model runner
A custom runner is any local command that reads a rendered prompt and writes model output to stdout. The Rust agent handles claims, temporary prompt files, process timeouts, heartbeats, and result submission.
Prerequisites
- The agent is installed and enrolled.
- The model code is enabled for the project.
- The command works under a POSIX
sh -cshell. On Windows, use Git Bash or Windows Subsystem for Linux.
Add a model binding
Add a [[model]] table to ~/.thespider/config.toml:
[[model]]
code = "sonnet"
command = "claude --print < {prompt_path}"
timeout_seconds = 900
The code must match the server model catalog. timeout_seconds is optional; when omitted, the agent uses the timeout served with the lease.
The command template supports four variables:
| Variable | Value |
|---|---|
{prompt_path} | Private temporary file containing the rendered slice prompt |
{target_path} | Repository checkout root |
{model_code} | Claimed server model code |
{slice_id} | Claimed slice identifier |
The agent shell-quotes substituted values. Unknown variables fail before the process starts.
Follow the stdout contract
The command may write progress or diagnostics before and after the result block. It must write one JSON array between the opening and closing marker from the prompt:
model progress can appear here
<THESPIDER_FINDINGS_JSON>
[
{
"title": "Authorization missing on project update",
"severity": "high",
"confidence": 0.94,
"categories": ["authz"],
"files": [{ "path": "src/http/projects.ts", "line": 88 }],
"evidence": "The update query uses the path id without an organization predicate.",
"impact": "A tenant member can modify another tenant's project.",
"recommendation": "Apply tenant scope before the update."
}
]
</THESPIDER_FINDINGS_JSON>
Write process diagnostics to stderr. The agent uploads only the final 4,000 characters of stderr.
Read the canonical runner contract for marker parsing, exit handling, size limits, and submission dispositions.
Configure OpenCode
The repository ships runners/opencode-runner.sh. It creates a fresh OpenCode session with occtl, sends the prompt, waits for a complete marker-delimited JSON array, and prints the assistant's final text to stdout.
[[model]]
code = "opencode-sonnet"
command = "/opt/thespider/runners/opencode-runner.sh {prompt_path} anthropic/claude-sonnet-4-6"
timeout_seconds = 3600
Requirements:
occtl1.1.0 or newer is onPATH.- An OpenCode server is reachable at
127.0.0.1:4096, orOPENCODE_SERVER_HOSTandOPENCODE_SERVER_PORTpoint to it. OPENCODE_CAPTURE_TIMEOUTis shorter than the model binding timeout.
The runner keeps a failed or empty OpenCode session and writes its identifier beside the prompt as session.id. Recover it with the command printed to stderr. Set OPENCODE_KEEP_SESSION=1 to retain successful sessions too.
Smoke-test without a model provider
The repository's deterministic fake model reads the prompt path and emits a marker-compliant security or bug report without external calls:
[[model]]
code = "fake"
command = "bun /opt/thespider/fake-model.ts {prompt_path}"
timeout_seconds = 60
Run a single batch:
thespider-agent run --once --models fake
The command exits with 0 when every submitted slice was accepted or superseded. Open Slices to inspect status, imported report count, duration, and any runner or import error.
Verify a production binding
- Run the command manually against a saved prompt.
- Confirm stdout contains both marker tags and a JSON array immediately after the opening tag.
- Confirm the process exits before
timeout_seconds. - Run
thespider-agent run --once --models MODEL_CODE. - Confirm the slice reports
acceptedand a nonzero report count.