Managed harness runtimes
Managed mode selects a native harness executable, records its resolved identity, and keeps reusable binaries in an external cache. It does not provide provider credentials or create an operating-system sandbox.
Runtime and version selection
Native agent selections accept runtime="system" or runtime="managed". System is the default and uses the executable discovered by that adapter. A system installation does not guarantee an exact version. Managed accepts an explicit semantic version or latest:
| SDK selection | CLI selection | Meaning |
|---|---|---|
{"harness": "codex", "models": [model]} | --harness codex=MODEL | System executable. |
{"harness": "codex", "models": [model], "runtime": "managed", "version": "0.155.1"} | --runtime managed --harness [email protected]=MODEL | Resolve or reuse that pin. |
{"harness": "codex", "models": [model], "runtime": "managed", "version": "latest"} | --runtime managed --harness codex=MODEL | Resolve the current release for this invocation. |
An SDK version requires runtime="managed"; AgentSpec validation raises ValueError("harness version requires managed runtime") otherwise. A CLI KIND@VERSION=MODEL likewise requires --runtime managed; the CLI rejects that combination during option validation. With managed mode and no explicit version, the SDK and CLI resolve latest. Do not supply latest where the test asserts an immutable pin. The accepted explicit version is MAJOR.MINOR.PATCH with an optional lowercase prerelease suffix. Build metadata and a leading v are not accepted by the selector parser.
Supported native adapter names are codex, pi, claude_code, and opencode. Selector aliases accepted by runtime acquisition include claude_code for Claude Code and open-code for OpenCode. ACP is not a managed native runtime; CLI validation rejects --runtime managed with an ACP selection. A custom ACP agent owns its launch executable and cache.
latest uses release metadata. The CLI creates an invocation-scoped pin shared by workers in that m3 test invocation, including xdist workers. A later CLI invocation resolves it again. A concrete version can use a valid matching cache entry without a metadata request. Cache hits are keyed by harness, resolved version, target, and archive digest.
Release metadata and target rules
The built-in recipes use these upstream release sources and tag conventions. The selected release still has to publish one asset matching the computed target.
| Harness | Metadata source | Versioned GitHub tag |
|---|---|---|
| Codex | api.github.com/repos/openai/codex/releases/{latest or tag} | rust-v<VERSION> |
| Pi | api.github.com/repos/earendil-works/pi/releases/{latest or tag} | v<VERSION> |
| OpenCode | api.github.com/repos/anomalyco/opencode/releases/{latest or tag} | v<VERSION> |
| Claude Code | downloads.claude.ai/claude-code-releases/latest or /{VERSION}/manifest.json | Vendor manifest version; not a Git tag lookup |
Target strings are computed by detect_target(kind, env=...). amd64 and x86_64 normalize to x64; aarch64 normalizes to arm64. The resolver reads target overrides from the selector mapping's env field, not from a new CLI option:
| Override | Effect |
|---|---|
M3_RUNTIME_ARCH | Replaces platform.machine() before architecture normalization. |
M3_ROSETTA=1 | On arm64 macOS, labels the target architecture x64-rosetta. |
M3_RUNTIME_LIBC | For OpenCode on Linux, selects the libc label; otherwise libc is detected and falls back to glibc. |
M3_RUNTIME_AVX2=1 | For OpenCode on Linux, selects the avx2 variant instead of baseline. |
Examples of target labels include darwin-arm64-64, linux-x64-64, windows-arm64-64, linux-x64-glibc-baseline, and linux-x64-musl-avx2. These labels describe M3's lookup input; they do not promise that a vendor publishes an asset for it.
Acquisition and recorded identity
The resolver reads the vendor release manifest, selects one target asset, and requires an independent SHA-256 value from GitHub release metadata. Claude Code uses the checksum in its platform manifest. A caller-supplied runtime selector must provide its own sha256; a hash computed only after downloading is not independent verification. M3 stages the archive, compares its SHA-256, rejects unsafe archive paths and entry types, extracts within size and member limits, checks the expected executable with --version, writes a receipt, and then creates a session lease. It does not infer a provider identity from an executable download.
The stable agent identity separates the requested selector from resolved metadata. snapshot.agent.harness records kind, runtime, and requested_selector; after resolution it can also record resolved_version, target, digest, verification_method, and immutable_release. Model identity separately records the requested model ID, observed provider, and observed model when the adapter reports them. An unresolved startup retains the requested selector without fabricating resolved fields.
Platform support
The resolver computes OS and CPU targets, then applies vendor filename or manifest-key matching rules. The rules below do not claim that matching assets exist in any particular vendor release:
| Harness | Target details |
|---|---|
| Codex | codex-{x86_64|aarch64}-apple-darwin on macOS, -unknown-linux-musl preferred then -unknown-linux-gnu on Linux, and -pc-windows-msvc.exe.zip on Windows. |
| Pi | pi-{darwin|linux|windows}-{x64|arm64} with .tar.gz, .tgz, or .zip suffix. |
| Claude Code | Looks up a platform key derived from the target in the vendor manifest and uses that entry's checksum. M3 does not hard-code a fixed platform allowlist. |
| OpenCode | macOS/Windows patterns include OS and x64 or arm64. Linux patterns additionally encode libc and baseline/AVX2; arm64 has a separate musl suffix. |
M3 fails if the target's manifest key or matching asset is missing or ambiguous. Cache roots inside the project or PATH are rejected; so are blocked system-directory roots (including Windows system directories).
Cache location and precedence
Managed sessions download missing runtimes and reuse valid cached assets automatically. The default cache persists between runs; per-execution state is created separately.
Set an external path with M3_HARNESS_CACHE_DIR, MCPTestKit(harness_cache_dir=...), AgentSession(..., harness_cache_dir=...), or the CLI test option --harness-cache-dir. Precedence is AgentSession argument, kit argument, CLI-provided environment, M3_HARNESS_CACHE_DIR, then the OS default. The standalone cache commands accept --cache-dir (alias --harness-cache-dir) directly, which takes precedence over the environment:
| OS | Default cache root |
|---|---|
| Windows | %LOCALAPPDATA%/m3/harnesses (or the standard Local AppData path) |
| macOS | ~/Library/Caches/m3/harnesses |
| Linux and other supported Unix targets | $XDG_CACHE_HOME/m3/harnesses, falling back to ~/.cache/m3/harnesses |
m3 runtime cache list and prune also accept --project-root. Without an explicit path or M3_HARNESS_CACHE_DIR, M3 uses the same OS default. Cache roots must be outside the project and PATH, and outside blocked system directories. Symlinked cache paths are rejected. In CI, retain the selected cache directory between jobs to avoid downloading the same pinned releases again.
The receipt's provenance object stores kind, version, target, query-stripped url, sha256, source, executable, asset_name, verification_method, and immutable_release. m3 runtime cache list --cache-dir PATH reports ready entries by kind, version, target, digest, and status. A malformed receipt or changed executable is reported as corrupt and is not accepted as a cache hit. Progress events report resolution, download, verification, extraction, readiness, and cache hits; download URLs omit query values. The cache stores immutable executable assets and receipts. Adapter configuration, credentials, MCP server state, and harness home/config files are per execution and are not shared as cache assets.
Runtime acquisitions and sessions share cache entries. M3 serializes entry installation and pruning with a lock. An active session holds a lease, shown as in_use by CLI listing. Sessions release their leases when closed.
Inspect and prune cached runtimes
To inspect downloaded versions or troubleshoot a cache entry, run:
m3 runtime cache listThe JSON output includes each entry's harness, version, target, digest, and status. To inspect a non-default cache, pass --cache-dir PATH or set M3_HARNESS_CACHE_DIR.
Cleanup is optional. To reclaim disk space after trying several versions, close sessions and kits, then run:
m3 runtime cache prunePruning removes entries without active leases, including corrupt entries. Later tests reacquire any runtimes they need. Keep the cache between comparison runs to retain the download savings. Use the same --cache-dir PATH override if your tests use a custom cache.
Pruning preserves leased entries. The CLI waits briefly for leases to clear, then exits with an operational error if entries remain in use. It does not stop running sessions.
Authentication and failure behavior
Downloading a binary and authenticating it to a model provider are separate operations. A warm runtime cache can avoid a vendor download while the test still needs provider network access. A provider login does not avoid an uncached runtime download.
| Adapter | Auth source used at launch | Runtime isolation detail |
|---|---|---|
| Codex | When credential_references is empty, M3 copies CODEX_HOME/auth.json (default ~/.codex/auth.json) to its private child home if present. Explicit references suppress that copy and map child variables from the process environment. | Uses a per-execution CODEX_HOME; the copied auth file is mode 0600, is not followed if it is a symlink, and is removed with the execution workspace. A managed executable does not supply the login. |
| Pi | Pi resolves its own provider credential from child environment/provider configuration. M3 can map credential_references from named environment variables. | Launches with a temporary isolated home; host Pi settings are not copied. |
| Claude Code | Claude resolves auth from supported environment or its configured login mode. M3 can map credential_references; host auth files are not copied into the isolated child home. | Receives a per-execution home, MCP config, and selected child environment. |
| OpenCode | OpenCode uses its configured provider environment. M3 can map credential_references into named child variables; host OpenCode settings are not copied. | Starts an isolated server process and home/config tree for the execution. |
See the configuration reference for supported mappings, provider variable examples, and login prerequisites. Credential specifics do not alter runtime download verification.
RuntimeManager.acquire() raises the public RuntimeValidationError (a RuntimeError and ValueError) for selector/acquisition failures. Common message strings identify these conditions:
| Condition | Runtime validation message |
|---|---|
| Unsupported kind | unsupported runtime kind |
| Bad semantic version | invalid runtime version |
| Missing URL and metadata URL | selector must provide a download URL or manifest_url |
| Missing independent checksum | runtime metadata lacks an independent sha256 |
| Digest mismatch | runtime sha256 mismatch |
| No/ambiguous GitHub asset for target | no ... release asset matches target ... / ambiguous ... release asset matches target ... |
| Cache symlink or tampered receipt/tree | runtime cache path contains a symlink / runtime cache entry failed verification |
| Expected executable absent | runtime archive contains no expected executable |
| Version smoke test failure | runtime executable failed --version smoke check / runtime executable reported an unexpected version |
| Entry lock timeout | runtime cache entry is busy |
| Latest manifest lock timeout | runtime manifest resolution lock timed out |
In AgentSession, managed runtime resolution is the first startup action, before server startup, probes, or provider launch. Acquisition exceptions are recorded as a failed startup and surfaced to the caller as TransportError("agent session startup failed"); the detailed vendor error is not propagated as a stable session API. A missing native executable or provider credential is a harness startup/readiness failure, not a download failure. Invalid CLI selection and an exhausted cache-prune wait use operational exit code 2.
Managed binaries are reusable. Per-execution home/configuration and progress files are temporary; the active lease is released when the agent session closes. Cancellation during acquisition leaves the worker responsible for staging cleanup; if acquisition completes after its caller is cancelled, M3 releases the resulting lease. Pruning skips an entry with an active lease. A runtime cache controls where M3 stores vendor executables. It does not restrict the harness process to a filesystem, network, or CPU sandbox.