How to configure a custom model runner

Bind a local model command to TheSpider's prompt and result contract

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 -c shell. 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:

VariableValue
{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:

  • occtl 1.1.0 or newer is on PATH.
  • An OpenCode server is reachable at 127.0.0.1:4096, or OPENCODE_SERVER_HOST and OPENCODE_SERVER_PORT point to it.
  • OPENCODE_CAPTURE_TIMEOUT is 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

  1. Run the command manually against a saved prompt.
  2. Confirm stdout contains both marker tags and a JSON array immediately after the opening tag.
  3. Confirm the process exits before timeout_seconds.
  4. Run thespider-agent run --once --models MODEL_CODE.
  5. Confirm the slice reports accepted and a nonzero report count.