Adapters
How sat runner executes an agent with Claude Code, Codex or the echo adapter, and what the agent receives.
An agent's adapter field names the tool that executes it. sat runner implements three adapters: claude_code, codex and echo. Each adapter starts a process, turns its output into transcript entries, and reports a result with tokens and cost. This page lists the exact commands, how output is parsed, and what the agent receives: its prompts and its environment.
Adapters at a glance
| Adapter | Status | Needs | Cost source |
|---|---|---|---|
claude_code | Implemented | claude on PATH | total_cost_usd reported by Claude Code |
codex | Implemented | codex on PATH | Computed from tokens with SAT_CODEX_PRICE_IN and SAT_CODEX_PRICE_OUT |
echo | Implemented, used by --simulate | Nothing | Fixed at 1 cent |
cursor, gemini, opencode, http | Not implemented | — | — |
New agents default to claude_code. The API accepts any adapter string up to 50 characters; it does not check that a runner can execute it.
How a runner picks adapters
Two decisions are involved: which runs a runner claims, and which adapter executes a claimed run.
Which runs it claims. At start, unless you pass --adapters or --simulate, the runner looks for claude and codex on PATH (and reads --version) and sends the ones it finds as the adapters filter on every claim. Runs for other agents stay queued for another runner. See Which runs a runner takes.
Which adapter executes a run. For each claimed run, the runner uses the adapter named by the agent's adapter field. With --simulate, it uses echo for every run. An adapter name the runner does not know (or one of the unsupported names) fails the run with The <name> adapter is not implemented in sat runner yet. Unsupported adapters fail loudly on purpose, rather than pretending to succeed.
sat runner adapters shows what a machine can run.
Claude Code
The runner starts Claude Code in print mode, in the run's workspace, and writes the task prompt to its standard input:
claude -p \
--output-format stream-json \
--verbose \
--permission-mode acceptEdits \
--append-system-prompt "<system prompt>" \
[--model <--claude-model>] \
[--max-turns <--max-turns>] \
[--settings <--claude-settings>]Options
| Runner flag | Passed as | Notes |
|---|---|---|
--claude-permission-mode | --permission-mode | Default acceptEdits: the agent can edit files in its workspace; other tools follow Claude Code's own permission rules. bypassPermissions lets the agent run any command without asking; use it only on an isolated machine or container. plan makes the agent plan without changing anything. default uses Claude Code's default permission rules; nobody is present to approve a tool call during a run. |
--claude-settings | --settings | A settings file path or a JSON string. Use it, for example, to let Claude Code's sandbox reach the SAT API host so the agent can use sat. |
--max-turns | --max-turns | Caps agentic turns per run. The runner records any Claude Code result whose subtype is not success as a failed run. |
--claude-model | --model | Any model name Claude Code accepts. |
Claude Code uses its own sign-in or ANTHROPIC_API_KEY. The agent process inherits the runner's environment, so set credentials there.
Output parsing
The runner reads stream-json line by line:
| Line | Becomes |
|---|---|
assistant message, text part | An agent transcript entry |
assistant message, tool_use part | A tool entry: → <tool name> <input>. The input shown is the first of command, file_path, path, pattern, url, description or prompt, otherwise the JSON input, cut to 200 characters |
user message, tool_result part | A tool entry starting with ← (or ✗ for an error result), cut to 800 characters |
result message | Usage, cost and the final result (below) |
| A line that is not JSON | A system entry, cut to 500 characters |
From the result message:
- Input tokens =
usage.input_tokens+usage.cache_creation_input_tokens+usage.cache_read_input_tokens. - Output tokens =
usage.output_tokens. - Cost =
total_cost_usd× 100, rounded up to whole cents. If Claude Code reports nototal_cost_usd, the run records 0 cents. - Result: success when
is_erroris false andsubtypeissuccess. Theresulttext is the agent's final message: it becomes the runsummaryand the task comment. On failure, theresulttext (orclaude finished with <subtype>) becomes the runerror.
If the process exits without a result message, the run succeeds with no summary when the exit code is 0, fails with claude exited with <code>: <stderr> (last 1,500 characters) otherwise, or fails with claude was stopped when the runner killed it.
Codex
The runner starts Codex in exec mode. Codex has no separate system prompt option, so the runner sends both prompts on standard input, separated by a rule:
codex exec \
--json \
--skip-git-repo-check \
--sandbox workspace-write \
-C "<workspace>" \
[--model <--codex-model>] \
-<system prompt>
---
<task prompt>Options
| Runner flag | Passed as | Values |
|---|---|---|
--codex-sandbox | --sandbox | read-only, workspace-write (default) or danger-full-access. danger-full-access removes Codex's sandbox; use it only on an isolated machine. |
--codex-model | --model | Any model Codex accepts. |
Cost
Codex reports tokens but not cost. To record cost, set both variables in the runner's environment, in US dollars per million tokens:
SAT_CODEX_PRICE_IN=1.25 SAT_CODEX_PRICE_OUT=10 sat runner startFor each turn the runner computes (input × SAT_CODEX_PRICE_IN + output × SAT_CODEX_PRICE_OUT) / 1,000,000 dollars, rounds it up to whole cents, and adds it to the run's cost. Cached input tokens are priced at the full input price. If either variable is missing, Codex runs record 0 cents and never trigger a budget pause.
Output parsing
| Event | Becomes |
|---|---|
item.completed with agent_message | An agent entry. The last one is the final message |
item.completed with reasoning | A system entry, cut to 800 characters |
item.completed with command_execution | A tool entry: → <command> (exit <code>) and the output, cut to 800 characters |
item.completed with file_change | A tool entry: → edit and the changes, cut to 300 characters |
item.completed with error, or a top-level error | A system entry: error: <message> |
turn.completed with usage | Adds input_tokens + cached_input_tokens and output_tokens to the run totals |
turn.failed | Marks the run failed with the error message |
The older codex exec --json format, which wraps events in {"msg": {...}}, is also understood (agent_message, exec_command_begin, token_count).
The run succeeds when Codex exits with 0 and no turn failed; the last agent message is the summary. Otherwise it fails with the turn's error, or codex exited with <code>: <stderr>, or codex was stopped.
Echo
echo makes no model calls. It writes three steps over --echo-delay milliseconds (default 1,500), reports 100 input tokens, 50 output tokens and 1 cent, then succeeds with Echo: <task key> <task title> done. (or Echo: heartbeat done.). With --echo-fail-rate, that fraction of runs fails with echo failure (--echo-fail-rate). Use it through --simulate; see Simulate.
The prompt the agent receives
Each run gets two prompts built from the run context. Claude Code receives the system prompt through --append-system-prompt, so it is added to Claude Code's own system prompt. Codex receives both on standard input.
System prompt: who the agent is
In this order, skipping lines whose data is missing:
You are Grace, Senior Engineer at NoteFlow.(the agent's title, or its role, or "an agent").Company mission: ...You report to Linus (CTO).Your capabilities: ...- A note that it works autonomously inside SAT and that the board (humans) reviews its work.
- The
satcommands it can use, which act as the agent for this run:sat tasks show,sat tasks comment(with--kind question|plan),sat tasks create ... --assignee ... --parent ...to delegate or split work, andsat tasks movewith the task statuses. - "Do not mark your own task done unless it is fully finished and verified; prefer in_review."
- "Finish with a short plain-text summary of what you did and what is left."
- The blocked convention: if it cannot proceed, its final message must start with
BLOCKED:followed by what it needs. Standing instructions from the board:followed by the agent'sinstructions, when set.
Task prompt: what to do now
For a run with a task:
Trigger: assignment (Assigned NF-12)
Task NF-12: Fix login redirect
Status: todo · Priority: high
Due: 2026-10-09
Project: Web app (github.com/noteflow/web)
Goal: Ship v1 login ← Launch v1
Users land on /404 after signing in with Google.
Discussion (3 comments):
- Linus [plan]: Check the OAuth callback URL first.
- Grace [comment]: Reproduced on staging.
- Ada [question]: Can this ship by Friday?- The trigger line shows the run's
triggerand the firstsystemtranscript entry (the queue reason). - The goal line lists the task's goal and then its parents.
- The discussion shows the last 20 comments, with a note when there are more.
(no description)replaces an empty description.
For a heartbeat (a run without a task), the prompt says there is no specific task and asks the agent to review its open work and the company goals, pick the most valuable next step and do it, with sat tasks list --assignee "Grace" --open to see its queue.
The BLOCKED convention
If the agent's final message starts with BLOCKED: (any case), the runner posts it as a question comment and moves the task to blocked, where it shows up in the board's inbox. If you write standing instructions for an agent, keep this convention.
Environment variables given to the agent
The agent process inherits the runner's whole environment, then these values are set on top:
| Variable | Value |
|---|---|
SAT_RUN_ID | The run id. sat adds it as run_id to task writes, so they are attributed to the agent |
SAT_AGENT_ID | The agent id |
SAT_AGENT_NAME | The agent name, for example Grace |
SAT_COMPANY | The company id, so sat acts on the right company |
SAT_TASK | The task key, for example NF-12. Not set for heartbeats without a task |
SAT_API_KEY | The runner's API key |
SAT_API_URL | The API origin the runner uses |
SAT_EVENTS_URL | The live events origin the runner uses |
SAT_CONFIG_DIR | <workdir>/.sat-agent-config, an empty directory created with mode 0700 |
SAT_CREDENTIAL_STORE | file, so sat never reads the machine's keychain |
SAT_TOKEN, SAT_PROFILE | Empty, so no other credential or profile applies |
PATH | <workdir>/.bin first, then the runner's PATH |
<workdir>/.bin/sat is a small shell script that runs the same sat the runner runs, so the agent's sat always matches the runner's version and configuration.
SAT_CONFIG_DIR points the agent's sat at an empty configuration, so it cannot read the profiles and credentials of the person who started the runner. The directory is shared by all agents of one runner. See Runner security for what the agent can still reach.