Skip to content

Adapters

An adapter turns a prompt into the model’s text answer. Each CLI adapter tries to isolate the call from your environment (settings, plugins, hooks, MCP servers, skills, context files, tools, session transcripts), so that a run measures the prompt and not your setup. What each one does, and does not, isolate is listed below.

All of them pass the prompt on stdin, so a 20 KB SKILL.md is no problem on Windows. A call that exits non-zero or runs past timeoutSeconds is an error for that output; it counts toward gate.blockOnErrors. A CLI that cannot start (not installed, a wrong bin) says nothing about the skill: it stops the command with exit code 2 instead of recording a verdict. Every CLI adapter takes an optional bin setting for the executable (default claude, pi, opencode). The .cmd shims that npm installs on Windows work; for those, an argument containing a percent sign or a line break cannot start the CLI, because cmd.exe would expand or split it.

Runs claude -p --model <id>, with the prompt on stdin and the answer read from stdout.

Setting
id The model id, passed as --model
apiKeyEnv Optional, see Credentials
bin Optional, default claude

Isolation flags:

Flag Effect
--setting-sources '' No user, project or local settings (plugins, hooks, effort, permissions), and so no CLAUDE.md, CLAUDE.local.md or rules files
--strict-mcp-config No MCP servers, claude.ai connectors included
--disable-slash-commands No skills or slash commands in the system prompt
--tools '' No tools: the model answers in text and cannot touch files
--append-system-prompt "No tools are available in this session. Answer directly in your reply; do not try to call tools, read files or explore a codebase." Says so; without it the model writes fake tool calls as text
--no-session-persistence No session transcript per call

Auto memory (~/.claude/projects/<project>/memory/) loads whatever the setting sources say, so skill-be also sets CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 in claude’s environment, as Claude Code’s docs advise for isolated runs.

Each call runs in a fresh, empty temp directory, removed afterwards. In a git repository, claude puts the branch and the recent commit messages in its system prompt, and a commit message such as “accept a bind-based lookup in the option eval” would reach the model under test and the grader. A relative bin (starting with ./ or ../) is resolved against the config file’s directory.

Other environment variables are inherited as they are. Provider variables set in your shell, such as ANTHROPIC_API_KEY or ANTHROPIC_BASE_URL, are not scrubbed: they reach claude and apply to the run.

Runs pi -p --provider <provider> --model <id> with the prompt on stdin.

Setting
provider Required. pi would otherwise use the default provider in its own settings
id The model id, passed as --model
thinking Optional: off, minimal, low, medium, high, xhigh or max. Default off. Any other value is a config error (exit code 2)
apiKeyEnv Optional, see Credentials
bin Optional, default pi

thinking is always passed as --thinking, with off unless you set it, so the thinking level in your ~/.pi/agent/settings.json does not leak into the run.

Isolation flags: --no-tools, --no-mcp, --no-skills, --no-extensions, --no-prompt-templates, --no-themes, --no-context-files (no AGENTS.md or CLAUDE.md), --no-approve (ignores project-local .pi/ files in the working directory) and --no-session (no session file per call).

Prompts: --system-prompt replaces pi’s coding prompt, and any global SYSTEM.md, with a short note saying no tools are available. --append-system-prompt '' is passed too; given at all, it stops pi from discovering APPEND_SYSTEM.md. Your global SYSTEM.md and APPEND_SYSTEM.md therefore have no effect on a run.

API key: a key from apiKeyEnv (Credentials) is passed to pi as --api-key, so it is visible in the process list while pi runs. pi prefers a stored login over the provider’s environment variable, so the flag is the only way to make it use the key. Without a key, pi uses its stored login (~/.pi/agent/auth.json).

model:
adapter: pi-cli
provider: anthropic
id: claude-sonnet-5-5
thinking: low
apiKeyEnv: ANTHROPIC_API_KEY

Runs opencode run -m <provider>/<id> --agent skill-be --title skill-be --pure --format json --dir <temp dir> with the prompt on stdin (opencode appends piped stdin to the message). The answer is read from the --format json events.

Setting
provider Required; the model is passed as <provider>/<id>
id The model id
apiKeyEnv Optional, see Credentials
bin Optional, default opencode

API key: a key from apiKeyEnv (Credentials) goes in OPENCODE_AUTH_CONTENT as the only login for that call, since opencode ignores a provider variable such as ANTHROPIC_API_KEY while a login is stored. Without a key, opencode uses its stored login (opencode auth list).

model:
adapter: opencode-cli
provider: google
id: gemini-2.5-flash
apiKeyEnv: GEMINI_API_KEY

opencode run has no flags that turn off tools, settings or context files, so skill-be isolates each call through the environment instead. This was checked against opencode 1.19.5. Each call gets:

  • A fresh, empty temp directory as the working directory and as --dir, with OPENCODE_DISABLE_PROJECT_CONFIG=1: no project opencode.json, .opencode/ or AGENTS.md.
  • XDG_CONFIG_HOME pointing at a second empty temp directory: your global config (~/.config/opencode, with its MCP servers, plugins, agents and AGENTS.md) is not loaded.
  • OPENCODE_TEST_HOME pointing at the temp base, which has no .opencode: opencode always reads <home>/.opencode (its opencode.json, agents, commands and plugins), and takes <home> from this variable before the real home directory.
  • A generated config in OPENCODE_CONFIG_CONTENT whose skill-be agent has every tool off and every permission denied, with the no-tools note as its whole system prompt in place of opencode’s coding prompt. No tool definitions reach the model. Inherited OPENCODE_CONFIG, OPENCODE_CONFIG_DIR and OPENCODE_PERMISSION are removed so they cannot merge back in.
  • --pure and OPENCODE_PURE=1 (no external plugins), OPENCODE_DISABLE_CLAUDE_CODE=1 (no ~/.claude/CLAUDE.md or ~/.claude/skills), OPENCODE_DISABLE_EXTERNAL_SKILLS=1 (no other skill folders) and OPENCODE_DISABLE_AUTOUPDATE=1.
  • OPENCODE_DB=:memory:, so no transcript is written to your opencode.db, and --title skill-be, which skips the extra model call that names a session.

What this isolation cannot do:

  • The ~/.opencode isolation relies on OPENCODE_TEST_HOME, an internal opencode variable meant for opencode’s own tests. A future opencode version may drop it, and your ~/.opencode could then be read again.
  • Moving XDG_CONFIG_HOME hides providers that are defined only in your global opencode.json. A model from such a provider cannot be used through this adapter. Your stored logins are not affected: the data directory holding auth.json does not depend on XDG_CONFIG_HOME or OPENCODE_TEST_HOME.
  • opencode still writes its log file (~/.local/share/opencode/log/opencode.log). It holds run metadata such as the session id, the model and the temp directory, not the prompt or the answer.
  • The system prompt still carries opencode’s environment block (working directory, platform, date), opencode’s built-in plugins still load (--pure only drops external ones), and opencode may still fetch its model catalog from models.dev at startup.

Runs the command line in run, writes the prompt to its stdin and reads the answer from its stdout. The answer is trimmed. A non-zero exit or a timeout is an error.

grader:
adapter: command
id: my-agent
run: my-agent --quiet --model fast
Setting
run Required. The command line
id Required like every entry, but only a label here

run is split into words on whitespace; quote a word that contains spaces with “double” or ‘single’ quotes. It is started without a shell, so pipes, redirects and variables are not interpreted. If you need them, wrap them in a script and run the script. The working directory is the config file’s directory.

Point adapter at a module whose default export is an async function. The path resolves against the config file’s directory, and any other key of the entry reaches the function in settings.

my-adapter.js
export default async function (prompt, { settings, timeoutMs, cwd }) {
// call your model with `prompt` and return its text answer
return `answer from ${settings.id}`;
}
model:
adapter: ./my-adapter.js
id: my-model

An error thrown by the function counts as a failed output.