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_messagesopenai_responsesopenai_chatModel settings
claude-code (Claude Code 2.1.289)yesnonoeffort: low, medium, high, xhigh, max
codex (Codex 0.160.1, app-server)noyesno: Codex rejects wire_api = "chat"reasoning_effort
pi (Pi 1.0.4, pi.dev)yesyes (preferred)yesthinking: 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

  1. 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.
  2. 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.
  3. The platform authenticates the seat and checks the request: only POST to 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.
  4. 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.
  5. Each request is recorded as a model_request event 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:

  1. 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: Anthropic POST /v1/messages with max_tokens: 1; Responses POST /v1/responses with max_output_tokens: 16; Chat POST /v1/chat/completions with max_completion_tokens: 1 (falling back to max_tokens for older servers). A successful check is trusted for six hours; a failure names the API and model. verify = "models" lists models instead, and none skips the check.
  2. The harness. Each seat's readiness probe is a real turn: the harness is asked to call the platform self tool. The probe passes only if a model request through the proxy succeeded (model), the harness called self through 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

codex

pi

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

CommandWhat it runs
make conformanceFetches 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-imagesThe same suite inside each seat image, against its Linux binaries.
make live-harnessesEach harness on a real endpoint (Anthropic, OpenAI, the self-hosted endpoint) with a real tool call; see tests/live/README.md.
make live-automationsEach 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 demoThe completion demo (examples/mixed-harness): Claude Code, Codex and Pi seats collaborate on kind, with fakes or the real services. Results: Demo results.
make e2eOn 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:

  1. 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.
  2. Write the adapter. A package implementing harnesses.Adapter that registers a harnesses.Descriptor in init: name, the APIs it speaks in order of preference, its model settings and capabilities. Point the harness at Environment.Model.BaseURL, use Environment.Model.APIKey as its key, and report tool calls as tool_request events with data.name.
  3. Register it. Import the package from harnesses/all. Compile validation and the seat runner pick it up from the registry.
  4. Build an image. build/seat-<name>.Dockerfile, pinning the version (the pins test checks it matches the adapter) and asserting it at build time.
  5. Pass conformance. Add a conformance.RunModel test for each API it speaks, and a fetch rule to build/harness-bins.sh.