Smart Agent Teams

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

AdapterStatusNeedsCost source
claude_codeImplementedclaude on PATHtotal_cost_usd reported by Claude Code
codexImplementedcodex on PATHComputed from tokens with SAT_CODEX_PRICE_IN and SAT_CODEX_PRICE_OUT
echoImplemented, used by --simulateNothingFixed at 1 cent
cursor, gemini, opencode, httpNot 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 flagPassed asNotes
--claude-permission-mode--permission-modeDefault 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--settingsA 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-turnsCaps agentic turns per run. The runner records any Claude Code result whose subtype is not success as a failed run.
--claude-model--modelAny 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:

LineBecomes
assistant message, text partAn agent transcript entry
assistant message, tool_use partA 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 partA tool entry starting with ← (or ✗ for an error result), cut to 800 characters
result messageUsage, cost and the final result (below)
A line that is not JSONA 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 no total_cost_usd, the run records 0 cents.
  • Result: success when is_error is false and subtype is success. The result text is the agent's final message: it becomes the run summary and the task comment. On failure, the result text (or claude finished with <subtype>) becomes the run error.

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>] \
  -
stdin
<system prompt>

---

<task prompt>

Options

Runner flagPassed asValues
--codex-sandbox--sandboxread-only, workspace-write (default) or danger-full-access. danger-full-access removes Codex's sandbox; use it only on an isolated machine.
--codex-model--modelAny 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 start

For 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

EventBecomes
item.completed with agent_messageAn agent entry. The last one is the final message
item.completed with reasoningA system entry, cut to 800 characters
item.completed with command_executionA tool entry: → <command> (exit <code>) and the output, cut to 800 characters
item.completed with file_changeA tool entry: → edit and the changes, cut to 300 characters
item.completed with error, or a top-level errorA system entry: error: <message>
turn.completed with usageAdds input_tokens + cached_input_tokens and output_tokens to the run totals
turn.failedMarks 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:

  1. You are Grace, Senior Engineer at NoteFlow. (the agent's title, or its role, or "an agent").
  2. Company mission: ...
  3. You report to Linus (CTO).
  4. Your capabilities: ...
  5. A note that it works autonomously inside SAT and that the board (humans) reviews its work.
  6. The sat commands 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, and sat tasks move with the task statuses.
  7. "Do not mark your own task done unless it is fully finished and verified; prefer in_review."
  8. "Finish with a short plain-text summary of what you did and what is left."
  9. The blocked convention: if it cannot proceed, its final message must start with BLOCKED: followed by what it needs.
  10. Standing instructions from the board: followed by the agent's instructions, 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 trigger and the first system transcript 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:

VariableValue
SAT_RUN_IDThe run id. sat adds it as run_id to task writes, so they are attributed to the agent
SAT_AGENT_IDThe agent id
SAT_AGENT_NAMEThe agent name, for example Grace
SAT_COMPANYThe company id, so sat acts on the right company
SAT_TASKThe task key, for example NF-12. Not set for heartbeats without a task
SAT_API_KEYThe runner's API key
SAT_API_URLThe API origin the runner uses
SAT_EVENTS_URLThe live events origin the runner uses
SAT_CONFIG_DIR<workdir>/.sat-agent-config, an empty directory created with mode 0700
SAT_CREDENTIAL_STOREfile, so sat never reads the machine's keychain
SAT_TOKEN, SAT_PROFILEEmpty, 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.

On this page