Harnesses and models
Each seat runs a harness: the agent program that reasons, edits files and calls tools. You choose a harness and a model per harness profile, and a seat uses its profile. Different seats in one organisation can use different harnesses and different model endpoints. They keep the same identity, workspace, memory, permissions and durable messaging whatever harness they run.
Compatibility
A model connection serves one or more APIs. A harness speaks one or more APIs. A profile uses an API both sides support.
| Harness (pinned version) | anthropic_messages | openai_responses | openai_chat | Model settings |
|---|---|---|---|---|
claude-code (Claude Code 2.1.289) | yes | no | no | effort: low, medium, high, xhigh, max |
codex (Codex 0.160.1, app-server) | no | yes | no: Codex rejects wire_api = "chat" | reasoning_effort |
pi (Pi 1.0.4, pi.dev) | yes | yes (preferred) | yes | thinking: off … max; context_window, max_tokens, reasoning |
fake (deterministic, tests) | no model | |||
Each row was verified against the pinned release, with the evidence checked in under harnesses/<name>/CONTRACT.md, and every harness passes the same conformance suite against all its APIs (see Verifying).
Declaring models
A model connection is declared once, with the APIs it serves and optionally its models. A harness profile then selects a connection and a model.
connections = {
anthropic = { adapter = "anthropic", secret_ref = "k8s:anthropic-credentials" } # serves anthropic_messages
openai = { adapter = "openai", secret_ref = "k8s:openai-credentials" } # openai_responses, openai_chat
selfhosted = {
adapter = "model" # any compatible endpoint
endpoint_ref = "https://llm.example.com/v1" # the API base
secret_ref = "k8s:selfhosted-credentials"
model = {
apis = ["openai_chat"] # claimed; checked at readiness
models = [{ id = "qwen3.8-27b" }]
}
}
}
harness_profiles = {
claude = { adapter = "claude-code", image_digest = "…/seat-claudecode@sha256:…",
model = { connection = "anthropic", id = "claude-sonnet-5-5", settings = { effort = "high" } } }
codex = { adapter = "codex", image_digest = "…/seat-codex@sha256:…",
model = { connection = "openai", id = "gpt-5.5", settings = { reasoning_effort = "high" } } }
pi_selfhosted = { adapter = "pi", image_digest = "…/seat-pi@sha256:…",
model = { connection = "selfhosted", id = "qwen3.8-27b" } } # api resolves to openai_chat
}
Compile errors are specific. For example, selecting the selfhosted connection for Codex fails with:
harness_profiles.codex.model: harness "codex" speaks openai_responses but connection "selfhosted" serves openai_chat for model "qwen3.8-27b"; harnesses that would work: pi
The auth field says how the credential is sent: bearer (default), x-api-key (the Anthropic default) or header:<Name>. The configuration reference lists every field.
How model access works
- The harness is configured with a model base URL on
127.0.0.1: the seat runner's local forwarder. Its API key is a placeholder. - The forwarder adds the seat's projected service-account token, read from the token file for every request, plus the lease generation and the current execution, and sends the request to the platform model proxy. A rotated token therefore takes effect immediately inside long-running harness processes (Codex and Pi stay running between turns), without relying on each harness's credential refresh.
- The platform authenticates the seat and checks the request: only
POSTto a model API path, only the profile's API, and only the profile's model ID (a request for another model is refused with 403). Model listing (GET /v1/models) is allowed. - The platform replaces the seat token with the connection's credential and forwards the request to the endpoint. Credentials rotate as described in Rotating credentials.
- Each request is recorded as a
model_requestevent on the seat's execution: connection, API, model, upstream host, status, duration and the token usage the endpoint reported. This is the record of which model and endpoint a seat actually used; the console shows it on the run page.
No model credential enters a sandbox.
Readiness
Readiness checks two things, separately:
- The connection. With
verify = "request"(the default), the platform sends a minimal valid request for each declared model and API, and for each model and API a profile uses: AnthropicPOST /v1/messageswithmax_tokens: 1; ResponsesPOST /v1/responseswithmax_output_tokens: 16; ChatPOST /v1/chat/completionswithmax_completion_tokens: 1(falling back tomax_tokensfor older servers). A successful check is trusted for six hours; a failure names the API and model.verify = "models"lists models instead, andnoneskips the check. - The harness. Each seat's readiness probe is a real turn: the harness is asked to call the platform
selftool. The probe passes only if a model request through the proxy succeeded (model), the harness calledselfthrough its tool bridge (tool), and the turn completed (turn). A connection check alone is never taken as evidence that a harness works.
Per-harness behaviour
claude-code
- One
claude -pprocess per turn, resuming the seat's session (--resume); sessions live on the seat volume. ANTHROPIC_BASE_URLis the forwarder. Every model role (main, background, subagent) is pinned to the profile's model, the only one the proxy accepts.- Tools:
steadmesh-tools mcpthrough--mcp-config --strict-mcp-config; no permission prompts (bypassPermissions). Interruption: SIGINT to the process group.
codex
- One long-running
codex app-serverper seat, driven over JSON-RPC.CODEX_HOMEis on the seat volume, so threads resume natively after a Pod replacement. config.tomldeclares a custom provider at the forwarder withwire_api = "responses", andsteadmesh-toolsas a required MCP server whose tools are approved without prompting. Approval policyneveranddanger-full-access: the Pod is the boundary. Curated plugin sync, analytics and feedback are disabled, so Codex makes no other network calls.- Interruption:
turn/interrupt; if the turn does not wind down within the grace period, the app-server is restarted and the thread resumed.
pi
- One long-running
pi --mode rpcper seat. Its agent directory and sessions are on the seat volume. models.jsondeclares a provider at the forwarder speaking the profile's API (anthropic-messages,openai-responsesoropenai-completions);mcp.jsonaddssteadmesh-toolswithexposure: "direct"(Pi's default would hide the tools). Pi runs offline, without telemetry, and never trusts project.piresources.- Interruption:
abort; the turn ends atagent_settled.
Changing a seat's harness or model
Changing a profile's adapter, model, API or settings changes the seat's configuration revision. The seat finishes its turn, and its Pod is replaced. The seat ID, volume, memory, inbox and handoff are kept. With the same harness, the native session resumes. With a different harness, the seat starts a new session seeded from its portable handoff; the first turn reports that recovery and the console shows it.
Verifying
| Command | What it runs |
|---|---|
make conformance | Fetches the pinned CLIs for your machine (build/harness-bins.sh, digest-checked, no install scripts) and runs each against a scripted model over every API it speaks, through the real forwarder: turn lifecycle, interruption, quiesce, native resume, handoff fallback, a tool call made by the model, the probe turn, the model and destination used, and token rotation during a running session. |
make conformance-images | The same suite inside each seat image, against its Linux binaries. |
make live-harnesses | Each harness on a real endpoint (Anthropic, OpenAI, the self-hosted endpoint) with a real tool call; see tests/live/README.md. |
make live-automations | Each harness with a real model, as a representative asked to set up, move, pause, resume and cancel reminders and recurring checks, against a real platform; it checks the automations left behind. |
make demo | The completion demo (examples/mixed-harness): Claude Code, Codex and Pi seats collaborate on kind, with fakes or the real services. Results: Demo results. |
make e2e | On kind: Codex and Pi seats join a running organisation, pass the probe turn, do delegated work with platform tools, record model_request events, and survive a model key rotation without a restart. |
Adding a harness
A new harness needs no change to the platform core:
- Verify the contract. Pin a release and record, with evidence, how it takes a custom base URL and key, how it reaches stdio MCP tools, how a turn starts, ends and is interrupted, how sessions resume, and what else it reaches over the network. Write
harnesses/<name>/CONTRACT.md. - Write the adapter. A package implementing
harnesses.Adapterthat registers aharnesses.Descriptorininit: name, the APIs it speaks in order of preference, its model settings and capabilities. Point the harness atEnvironment.Model.BaseURL, useEnvironment.Model.APIKeyas its key, and report tool calls astool_requestevents withdata.name. - Register it. Import the package from
harnesses/all. Compile validation and the seat runner pick it up from the registry. - Build an image.
build/seat-<name>.Dockerfile, pinning the version (the pins test checks it matches the adapter) and asserting it at build time. - Pass conformance. Add a
conformance.RunModeltest for each API it speaks, and a fetch rule tobuild/harness-bins.sh.