niceeval/adapter), split into two families by whether the system under test needs an isolated workspace: Sandbox-based (claude-code / codex / bub) run a coding-agent CLI inside a Docker or cloud Sandbox and can install MCP servers, Skills, and Python plugins; non-Sandbox connect non-intrusively to an already-running HTTP service, or save you the event-mapping work when hand-writing an adapter. This page is organized by family and by adapter, and focuses on each adapter’s configuration options — for how to choose one and get the first eval running, see Connect Your Agent.
Sandbox adapters
All three built-in Sandbox agents are constructed withdefineSandboxAgent, authenticate via environment variables (overridable with factory options), and all support installing extensions during the Sandbox’s setup phase. For how to run a built-in Sandbox agent, its directory structure, and writing your own custom Sandbox adapter, see Sandbox Agent; this page only covers what each adapter can install and how its options are written.
On this page, settingsFile / configFile are always resolved relative to the NiceEval project root — the current working directory when you run niceeval, i.e. the directory containing niceeval.config.ts, not the directory holding the eval or experiment file. For example, when the experiment lives at experiments/web/no-search.ts and the config lives at configs/codex/no-web.toml, you still write configFile: "configs/codex/no-web.toml".
claude-code
- Auth:
ANTHROPIC_API_KEY(overridable with the factory optionapiKey), optionalANTHROPIC_BASE_URL(factory optionbaseUrl). - Installing MCP servers: the
mcpServersoption, written duringsetupinto the Sandbox’s user-level~/.claude.json(the top-levelmcpServersfield). The two shapes are distinguished by field: a local stdio process setscommand(optionally withargs/env); a remote Streamable HTTP endpoint setsurl(optionally withheaders, passed verbatim into request headers, commonly used forAuthorization), written as a{ "type": "http", "url": …, "headers": … }entry.urlmust be reachable from inside the Sandbox: if the service runs on your own machine, expose it as a public address first with a tunnel like cloudflared or tailscale. - Installing Skills:
skills: SkillSpec[]— a local Skill ({ kind: "local", path }, a file or directory read from your project root) or a repo Skill ({ kind: "repo", source, ref, skills }, which can pin a commit/tag and enable only part of a multi-Skill repository). Skills land in the project-level.claude/skills/<name>/inside the Sandbox, where the claude CLI discovers them natively (the adapter normalizes the nativeSkilltool call into askill.loadedevent, so it is not also counted as a tool call; assert it witht.loadedSkill(), nott.calledTool("Skill", ...)). - Installing native plugins:
plugins: ClaudeCodePluginSpec[]; each entry declares the marketplace connection (name/source/ optionalref) and the plugin name inside it. That type belongs to claude-code only — it cannot be passed to codex. - Official config file:
settingsFileis a local project path on the machine running NiceEval, not a path inside the Sandbox; it points to a complete Claude Codesettings.json. The path is resolved relative to the project root and must be a plain relative path or use a./prefix —.., absolute paths,~, and symlinks that resolve outside the project root all error. The Adapter reads the file locally, uploads it, and uses it verbatim to replace the Sandbox’s otherwise-empty user-level~/.claude/settings.json; it does not inherit the host machine’s config, and it is never deep-merged or re-serialized.modelandenvbelong to the experiment and the Adapter — if either key appears in the file,setuperrors and names the conflicting key. Keep secrets in environment variables, not in the config file. - Post-install scripts:
postSetup: SandboxHook[]runs your Hook functions in the Sandbox, in array order, after writing settings and installing MCP servers, Skills, and plugins is all done. A typical use is running a plugin’s own setup script (for example, one that needs to register a hook into a global config) — this kind of script can only run once everything it depends on is already installed. Its counterpart,preTeardown: SandboxHook[], runs in reverse order before the agent’s own teardown step, and only fires if execution has already reached the point wherepostSetupran. A Hook that throws counts as an infrastructure error (the Attempt is recorded aserrored), not as the agent failing the task. - Tracing: the claude CLI’s beta native telemetry (
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA); spans carry only structure and timing — details in OTel Integration.
configs/claude-code/no-web.json:
codex
-
Auth:
CODEX_API_KEY(overridable with the factory optionapiKey, notOPENAI_API_KEY), optionalCODEX_BASE_URL(factory optionbaseUrl) for an OpenAI-compatible proxy. -
Installing MCP servers: the
mcpServersoption, appended duringsetupinto the[mcp_servers.<name>]section of~/.codex/config.toml. The two shapes are distinguished by field: a local stdio process setscommand(optionally withargs/env); a remote Streamable HTTP endpoint setsurl(optionally withheaders, written as a[mcp_servers.<name>.http_headers]subtable).urlmust be reachable from inside the Sandbox: if the service runs on your own machine, expose it as a public address first with a tunnel like cloudflared or tailscale. -
Installing Skills:
skills: SkillSpec[], the same type as claude-code. Skills land in.agents/skills/<name>/, and the adapter also writes a discovery instruction into AGENTS.md — codex has no native Skill tool like claude-code, so merely installing the files does not make it read them. Asserting “was it used” can only look at whether it actually ran the shell command that reads that file — there is no tool call to assert directly. -
Installing native plugins:
plugins: CodexPluginSpec[]; each entry declares the marketplace connection (name/source/ optionalref/ optionalsparse) and the plugin name inside it.sparseis a list of paths (e.g.[".agents", "plugins/repo-map"]); each one adds a--sparse <path>tocodex plugin marketplace addso large repos only fetch what the plugin needs, without changing what gets installed. That type belongs to codex only — it cannot be passed to claude-code. -
Official config file:
configFileis a local project path on the machine running NiceEval, not a path inside the Sandbox; it points to a complete Codexconfig.toml. The path is resolved relative to the project root and must be a plain relative path or use a./prefix —.., absolute paths,~, and symlinks that resolve outside the project root all error. The Adapter reads the file locally, uploads it, and uses it verbatim to replace the Sandbox’s otherwise-empty user-level~/.codex/config.toml; it does not inherit the host machine’s config, and it is never concatenated, deep-merged, or parsed and rewritten.model,model_provider,model_providers,model_reasoning_effort,mcp_servers, andotelbelong to the experiment and the Adapter — if any of these keys appear in the file,setuperrors and names the conflicting key. Keep secrets in environment variables, not in the config file. -
Post-install scripts:
postSetup: SandboxHook[]works the same as for claude-code: it runs your Hook functions in the Sandbox, in order, after every install step is done — a good place to run a plugin’s own setup script. Its counterpart,preTeardown: SandboxHook[], runs in reverse order before the agent’s teardown, and only fires if execution has already reached the point wherepostSetupran. A hook that a script registers into codex’s global config does not need interactive trust confirmation — at run time,codex execalready bypasses the hook trust gate, so the hook takes effect directly. -
Tracing: built in, configured through the
[otel.trace_exporter.otlp-http]section ofconfig.toml, protocolhttp/json.
configs/codex/no-web.toml:
bub
- Auth:
BUB_API_KEY+BUB_API_BASE(an OpenAI-compatible proxy), overridable with the factory optionsapiKey/apiBase. - Installing Skills:
skills: SkillSpec[], the same type as the other two adapters. Skills land in.agents/skills/<name>/, with a discovery instruction written into AGENTS.md. - Installing plugins:
pythonPlugins: PythonPluginSpec[]({ package }: a PyPI package, a version specifier, or a git URL), added duringsetuptouv tool install … --with <package>. That type belongs to bub only; the package set is part of the install checkpoint key, so two variants with different plugins never reuse the same install cache. - Prebuilt Bub: NiceEval’s E2B recipe folds Bub, the OTel plugin, and the set of Python plugins into an install fingerprint. The Adapter only reuses an environment whose fingerprint matches exactly; simply having a
bubonPATHis not proof of compatibility. For where to build one, see Sandbox providers · Build on the official baselines to speed things up. - bub has no
mcpServers— MCP belongs to the adapters that support it, and the field simply does not exist on this config. - Post-install scripts:
postSetup: SandboxHook[]works the same as for the other two adapters: it runs your Hook functions in the Sandbox, in order, after every install step is done. Its counterpart,preTeardown: SandboxHook[], runs in reverse order before the agent’s teardown, and only fires if execution has already reached the point wherepostSetupran. - Install method: via
uv tool install(a PyPI package, not an npm package); the first install builds a checkpoint cache to speed up later Sandboxes. - Tracing: built in, injected via environment variables, protocol
http/protobuf.
Comparing the three Sandbox adapters
What got installed is on the record: at the end of
setup the Adapter hands the install manifest to the runner, which stores it as the Attempt Artifact agent-setup.json (read it in the library with attempt.agentSetup(); the manifest never touches the Sandbox disk, so the agent cannot see it). The manifest records only sources, refs, Skill/plugin names, resolved versions, and the official config file’s project-relative path and SHA-256 — never the config’s contents, API keys, or environment variable values. MCP servers are recorded the same way, non-sensitive fields only: for the stdio shape it records name / command / args but not env; for the HTTP shape it records name / url but not headers.
Non-Sandbox adapters
When the system under test does not need an isolated workspace — an already-deployed HTTP service, a home-grown agent loop — use a non-Sandbox adapter to connect non-intrusively; no Docker required.-
uiMessageStreamAgent: built in, connects non-intrusively to an AI SDKuseChatbackend’s HTTP endpoint, zero mapping (HITL included). -
SDK event stream converters (
createClaudeSdkEventStream/createPiAgentEventStream/createCodexThreadEventStream): when hand-writing an adapter that connects to a service running the Claude Agent SDK / pi-agent-core / Codex SDK, the mapping from native frames to standard events is already done officially — you only need to write the transport glue, paired withdriveFrameStreamto drive it frame by frame. -
turnFromAiSdk: a converter from AI SDKgenerateText/streamTextresults to the standard event stream, used when writing your own HTTP web-agent adapter.
How to choose
- The system under test is a coding agent that must edit code and run commands on a real filesystem:
claude-code/codex/bub— see the Sandbox adapters section above for configuration. - The system under test is an AI SDK application (a
useChatbackend):uiMessageStreamAgent, zero mapping and HITL included. - The system under test is some other already-deployed agent system (HTTP / gRPC): go non-Sandbox and hand-write the adapter — the official SDK converters cover most of the mapping work.
Related reading
- Connect Your Agent — the full picture: how to configure an experiment and write an eval.
- Sandbox Agent — how to run the built-in Sandbox agents, and how to write your own.
- Built-in agent capabilities reference — a capability-by-capability inventory of each adapter, plus complete example code for the non-Sandbox adapters.
- OTel Integration — send spans to NiceEval in exchange for the call waterfall in
niceeval view. - defineAgent reference — the full parameters of
defineAgent/defineSandboxAgent.