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.
claude-cli
Section titled “claude-cli”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.
pi-cli
Section titled “pi-cli”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_KEYopencode-cli
Section titled “opencode-cli”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_KEYopencode 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, withOPENCODE_DISABLE_PROJECT_CONFIG=1: no projectopencode.json,.opencode/orAGENTS.md. XDG_CONFIG_HOMEpointing at a second empty temp directory: your global config (~/.config/opencode, with its MCP servers, plugins, agents andAGENTS.md) is not loaded.OPENCODE_TEST_HOMEpointing at the temp base, which has no.opencode: opencode always reads<home>/.opencode(itsopencode.json, agents, commands and plugins), and takes<home>from this variable before the real home directory.- A generated config in
OPENCODE_CONFIG_CONTENTwhoseskill-beagent 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. InheritedOPENCODE_CONFIG,OPENCODE_CONFIG_DIRandOPENCODE_PERMISSIONare removed so they cannot merge back in. --pureandOPENCODE_PURE=1(no external plugins),OPENCODE_DISABLE_CLAUDE_CODE=1(no~/.claude/CLAUDE.mdor~/.claude/skills),OPENCODE_DISABLE_EXTERNAL_SKILLS=1(no other skill folders) andOPENCODE_DISABLE_AUTOUPDATE=1.OPENCODE_DB=:memory:, so no transcript is written to youropencode.db, and--title skill-be, which skips the extra model call that names a session.
What this isolation cannot do:
- The
~/.opencodeisolation relies onOPENCODE_TEST_HOME, an internal opencode variable meant for opencode’s own tests. A future opencode version may drop it, and your~/.opencodecould then be read again. - Moving
XDG_CONFIG_HOMEhides providers that are defined only in your globalopencode.json. A model from such a provider cannot be used through this adapter. Your stored logins are not affected: the data directory holdingauth.jsondoes not depend onXDG_CONFIG_HOMEorOPENCODE_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 (
--pureonly drops external ones), and opencode may still fetch its model catalog from models.dev at startup.
command
Section titled “command”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.
Your own adapter
Section titled “Your own adapter”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.
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-modelAn error thrown by the function counts as a failed output.