Runner contract
A runner is a command thespider-agent runs with sh -c in the checkout. It gets a prompt file and must write findings on stdout. The agent posts the raw output; the server parses it.
Command binding
A [[model]] block names the MODEL (code) and the AGENT that runs it (agent). Agent-bound
names one of the four built-in CLIs and lets TheSpider render the command:
[[model]]
code = "xai/grok-4.6"
agent = "grok"
timeout_seconds = 1200
Freeform supplies the shell command directly, with or without an agent label. Any agent
TheSpider cannot render requires this form:
[[model]]
code = "MODEL_CODE"
agent = "pi" # optional label; required to be paired with `command`
command = "COMMAND {prompt_path} {target_path} {model_code} {slice_id}"
timeout_seconds = 900
The containing .thespider/config.toml also stores project and profile. The profile selects the
campaign methodology; model command bindings remain project-wide.
| Field | Required | Behavior |
|---|---|---|
code | Yes | A canonical models.dev code, <lab>/<model> — or an mcp-* host-agent family code. Must match a code enabled for the project. |
agent | No | The local CLI that runs the model. grok, claude, codex and opencode are built in, and command may then be omitted. Any other name is accepted and requires command. |
harness | No | Deprecated alias of agent. Loads with a warning; an error if it disagrees with agent. |
model_id | No | Vendor model string handed to the agent CLI. Derived from code (the <model> half; the whole provider/model code for OpenCode). Characters A-Z a-z 0-9 . _ / : - only. |
variant | No | OpenCode --variant only; an error for any other agent. Same character restriction as model_id. |
command | No | POSIX-shell command template. Required unless agent is a built-in one. Always wins when both are present. |
timeout_seconds | No | Local wall-clock timeout. Falls back to the built-in agent's default (Grok 1,200, Claude 900, Codex 1,200, OpenCode 1,200), then to the advisory lease timeout. Minimum effective value is one second. |
Supported template variables are {prompt_path}, {target_path}, {model_code}, and {slice_id}. Values are shell-quoted. Unknown identifier-shaped variables fail substitution. The legacy standalone variables {thespider_dir} and {workspace_path} are not supported.
Validity
agent | command | Verdict | What executes |
|---|---|---|---|
| absent | present, non-blank | Valid (freeform) | the command string (after template substitution) |
| a built-in agent | absent | Valid (agent-bound) | that agent's rendered audit template |
| a built-in agent | present, non-blank | Valid, warns | the command string (after template substitution; the agent is not used to render) |
| any other agent | present, non-blank | Valid | the command string; the agent name is an identity label |
| any other agent | absent | Error | — TheSpider cannot render a command for an agent it does not know |
| absent | absent | Error | — |
| any | present but blank | Error | — a blank command never falls back to the agent |
grok, claude or codex with variant | any | Error | — variant is OpenCode-only |
a code that is not <lab>/<model> or mcp-* | any | Error | — with the models.dev URL and the expected shape |
| any | any, with model_id/variant outside A-Z a-z 0-9 . _ / : -, empty, or over 200 characters | Error | — |
Precedence
When a block carries both agent and command, command always wins. The agent value is
then used only to identify which CLI to probe for auth status, never to render a command. Every
command that runs models — run, daemon, gate, setup, status, models, check-models —
emits a warning when it loads such a block, not only setup, because these files are hand-edited.
Provenance
code is what the server records and what reports group by. agent and model_id are local: which CLI actually ran. One enabled code points at one agent at a time. A second concurrent agent needs a second enabled code.
Process contract
- Working directory: target checkout root.
- Shell:
sh -c. - Stdin: closed.
- Stdout: captured in full unless
--redactis enabled. - Stderr: captured; only the final 4,000 characters are submitted.
- Timeout: the agent stops the child process and reports
timeoutwith no exit code. - Source tree: read locally by the runner; TheSpider does not upload it.
Because stdin is closed, every command must reach the prompt through the {prompt_path} file rather
than expecting it on standard input. Each built-in agent template does this itself: Grok reads it
with --prompt-file, Claude and Codex use a shell redirect from {prompt_path}, and OpenCode
attaches it with -f after the positional instruction (OpenCode's -f is variadic).
The first-party agent itself exits 0 when every submitted result is valid coverage and 1 when one or more slices fail. A model process's nonzero exit is included in the result payload and stored as an artifact, but it is not valid coverage. The server still attempts to parse the marker so the warning is useful; the slice stays retryable.
Marker protocol
The claim response contains marker, and the rendered prompt states the corresponding output schema. The default marker is THESPIDER_FINDINGS_JSON.
A valid result block has this shape:
<THESPIDER_FINDINGS_JSON>
[
{
"title": "Concrete issue title",
"severity": "high",
"categories": ["authz"],
"files": [{ "path": "src/http.ts", "line": 42 }],
"evidence": "Exact evidence",
"impact": "User-visible or security consequence",
"recommendation": "Specific remediation"
}
]
</THESPIDER_FINDINGS_JSON>
Rules:
- Use the marker from the prompt; do not hardcode the default when custom methodologies can change it.
- Include both opening and closing tags.
- The first non-whitespace character after the opening tag must be
[. - The enclosed value must be valid JSON.
- Write an empty array when there are no findings.
- Text outside the block is retained in the raw output artifact but is not a finding.
Security reports can include confidence, exploitScenario, cvssV4Score, cvssV4Vector, and cweCodes. Bug reports can include reproductionSteps, expectedBehavior, actualBehavior, rootCause, and testSuggestion. The server normalizes aliases and computes identity.
Truncation and size limits
| Boundary | Default | Behavior |
|---|---|---|
| Submitted stderr tail | 4,000 characters | Older stderr is dropped by the agent. |
| Server artifact | 10 MiB | Controlled by MAX_ARTIFACT_BYTES; oversized stdout or stderr returns 413. |
Agent --redact stdout | 1 MiB | Secret-shaped strings are masked first, then the tail is dropped and a visible truncation marker is appended. |
The result block should appear well before these limits. A truncation that removes the closing marker prevents parsing.
Claim and result fields
The host receives:
{
"lease_id": 9001,
"slice_id": 501,
"model_code": "anthropic/claude-sonnet-5",
"pass_code": "http",
"bucket_code": "auth",
"prompt": "FULL_RENDERED_PROMPT",
"prompt_hash": "SHA256_HEX",
"marker": "THESPIDER_FINDINGS_JSON",
"timeout_seconds": 900,
"lease_expires_at": "2026-07-22T12:00:00Z"
}
The host submits:
{
"exit_code": 0,
"duration_ms": 18422,
"model_code": "anthropic/claude-sonnet-5",
"client_version": "thespider-agent/0.1.0",
"prompt_hash": "SHA256_HEX_FROM_CLAIM",
"stdout": "RAW_STDOUT",
"stderr_tail": "TAIL"
}
prompt_hash protects prompt integrity. The result endpoint is idempotent on (lease_id, prompt_hash).
Submission dispositions
| HTTP status | Agent text | Meaning |
|---|---|---|
200 | accepted | This lease won. The runner exited 0 and the output contained a valid marked JSON array (including []). Reports were imported. The slice is valid coverage. |
202 | accepted-as-artifact (superseded) | Another lease already won; this output is retained but not imported. |
409 | rejected (prompt_hash mismatch) | The echoed hash does not match the claimed prompt. The slice is unchanged. |
413 | rejected (output too large) | An artifact exceeds the configured size cap. The slice is unchanged. |
422 | rejected (parse error) / rejected (runner exit) | Artifact received and stored. The marker was missing, unclosed, malformed, or not an array, or the runner did not exit 0. The slice is failed and claimable again. Campaign completion does not increase. |
An artifact can be received successfully while the audit execution fails. accepted means valid coverage, not merely that the POST arrived.
Repair historic invalid completions with thespider-agent repair --campaign ID. --dry-run lists valid / invalid / unverified without changing anything.
See custom runners for setup and external agents for implementing the lease protocol.