Skip to main content
Any container image that speaks the documented ACP stdio contract can register as a HarnessRelease, pass conformance, and run Sessions — no Checkfu code review, no privileged path. This guide teaches that contract by walking a complete, runnable implementation: examples/custom-acp/server.mjs in the Checkfu repository, an ACP v1 server in about three hundred lines of plain Node with no dependencies at all. Two companion pages carry the normative truth this guide deliberately does not restate. Harness wire extensions is the contract page — its capability matrix and slot inventory are machine-generated from the same code the platform runs, so they cannot drift. The acp-v1 extension schema and golden wire vectors are the machine-readable wire artifacts. When this guide and those artifacts ever disagree, the artifacts win. Every server.mjs block below is excerpted from that file, not retyped, and the repository’s documentation checks fail if one drifts from the source. The CLI invocations later on are ordinary examples, not quotes — check them against checkfu harness --help if a flag ever looks wrong.

The wire: JSON-RPC over stdio

A harness is a process. The Runner launches it inside the sandbox, writes newline-delimited JSON-RPC 2.0 to its stdin, and reads the same framing from its stdout. Stderr is yours for logging. The entire transport layer of the reference server is one line:
path=examples/custom-acp/server.mjs
and one read loop, with a drain rule worth copying: when stdin closes, finish the requests already in flight, flush stdout, and exit zero. The platform reads settlement from the durable event log, not from your process staying alive.
path=examples/custom-acp/server.mjs

initialize: declare only what you serve

The first request is initialize. The response names your protocol version and capabilities:
path=examples/custom-acp/server.mjs
mcpCapabilities: { http: true } is load-bearing: it tells the platform this harness can accept HTTP MCP servers in session/new, which is how the platform tool plane arrives (below). Richer harnesses additionally declare Checkfu’s namespaced extensions — native steering, cooperative interrupt, observed subagents — in the initialize response _meta; the exact admission rules are in the declaration handshake. Declare honestly, and only what you serve. A declaration is never evidence: conformance actively exercises every declared surface, and only server-issued, signed conformance evidence realizes a capability. Declaring an axis you do not serve is worse than not declaring it — the platform will drive the declared surface and the failure modes are honest ones, up to failing the Run.

Session and turn lifecycle

session/new opens a session and hands over the MCP server list; the reference server keeps it and answers with a session id:
path=examples/custom-acp/server.mjs
session/prompt is a turn. While it runs, the harness streams session/update notifications — agent message chunks, tool calls, tool results — and settles the turn by answering the request with a stop reason:
path=examples/custom-acp/server.mjs
The conformance grammar holds every harness to the same shape — exactly one ready handshake before any output, exactly one terminal observation per turn, unique correlated tool-use ids. The full rule table is in the conformance grammar.

The model plane arrives in the environment

A harness never holds provider credentials. The sandbox environment carries CHECKFU_MODEL_BASE_URL and CHECKFU_MODEL_TOKEN — the platform-neutral names, and the ones to prefer in new code — pointing at the governed model gateway. The same values are also exported under the vendor-shaped aliases OPENAI_BASE_URL/OPENAI_API_KEY and ANTHROPIC_BASE_URL/ANTHROPIC_API_KEY, so a harness built against an existing SDK works unmodified; note that ANTHROPIC_BASE_URL is the origin without the /v1 suffix its OpenAI-shaped sibling carries. Routing, budget, and usage accounting stay with the platform, and the model name you send is rewritten to the admitted route. The reference server reads the OpenAI alias:
path=examples/custom-acp/server.mjs
Bake no credentials into the image, and fail closed when the model gateway refuses a model call. The model gateway forwards only the request paths of the wire dialects your image declares — the reference server also probes a non-model path and expects rejection. Which dialects reach which providers is generated data in harness and model-provider reachability.

The platform tool plane: MCP handoff

Granted platform tools arrive as ordinary HTTP MCP servers in session/new — the handoff your initialize response opted into. The reference server looks up the control server by name when its turn needs a governed capability:
path=examples/custom-acp/server.mjs
and then speaks stock MCP to it — initialize, tools/list, tools/call:
path=examples/custom-acp/server.mjs
While a tool runs, emit tool_call and tool_call_update session updates so the activity is observed rather than narrated — the platform’s own served-call record, not your narration, is the authority on whether tool delivery happened.

The image contract

Your harness runs under a container posture the sandbox imposes, not one your image chooses: the process runs as user 65532:65532, the root filesystem is read-only, and exactly /workspace, /tmp, and /home/checkfu are writable. Build for it — a harness that expects to write elsewhere, or to be root, fails at runtime rather than at registration. Be precise about what enforces this. The sandbox provider applies the posture at materialization; the values above are the same ones the catalog’s image contract records for first-party entries, and a custom image registered through harness add oci has no catalog entry at all. Conformance does not check container posture — its case set covers the ACP handshake, tool correlation, and the declared control axes, and contains no uid, read-only-root, or writable-path case. So the reference server does what any harness author should do while developing: it proves each property from inside and reports it as ordinary evidence, which is a self-report, not platform attestation.
path=examples/custom-acp/server.mjs
Non-secret configuration reaches the harness as visible JSON in CHECKFU_HARNESS_CONFIGURATION, validated against the configuration schema registered with the release. Credentials never belong there — provider access rides the model plane above, and tool authority rides the MCP handoff.

Register, verify, run

Registration is the same public arc for every custom harness: build and push an immutable image, adopt the digest as a HarnessProfile, request server-signed conformance, then create and publish an Agent on the verified profile.
Only the digest-pinned name@sha256:... form registers; a mutable tag fails before anything persists, and a new image is always a new release. The walked five-step transcript — including the Agent creation, PermissionAssignments, and Session chat — is the Custom ACP harness example.

Upgrade and roll back one logical harness

Keep the HarnessProfile name stable after initial adoption. Test and publish a new immutable digest, then create the release, advance the profile with optimistic concurrency, and verify the exact resulting version in one CLI workflow:
--expected-version is checked before the CLI creates a release, then the API guards the patch with the same current version. The command prints a recovery command as soon as an OCI release exists, so a failed or interrupted profile patch never strands an undiscoverable artifact. After a successful patch it prints the exact rollback command. Rollback is an ordinary forward version:
The earlier HarnessRelease and every HarnessProfileRevision remain immutable. An in-flight Run keeps its resolved pins; later admission resolves the new profile version. Moving any release, driver, or configuration member invalidates the old conformance tier, which is why --verify targets the returned profile version directly instead of resolving the logical name a second time. Non-secret configuration is retained unless --configuration is supplied, and the CLI validates the resulting configuration against the target release before patching. For an OCI-to-OCI update, omitted process, platform, configuration-schema, model-wire, and MCP options inherit from the current immutable release. This keeps an image-only rebuild exact without silently dropping a declared capability; pass an option only when the new binary intentionally changes that part of the release contract.

Or hand us the repository instead

If you would rather not run a container build at all, POST /v1/organizations/{organization_id}/harness-builds takes a GitHub repository URL, an optional ref, and an explicit platform build pool or Workspace/RunnerPool target. Checkfu builds the image and mints the Organization-owned, digest-pinned release for you. Ownership never implies execution authority. The ref is resolved to an exact commit at submission and only that commit is stored, so the immutability rule above is unchanged rather than relaxed: no branch or tag is ever stored on the result, and re-registering a repository is always a new build. Submission is accepted with 202; poll GET /v1/organizations/{organization_id}/harness-builds/{id} for settlement. The CLI drives that same public resource. It can stop at the ordinary release-profile handoff, or compose adoption and signed verification without introducing a privileged build-only profile path:
Without --adopt, success prints checkfu harness add release --name ... --release hrel_... --driver checkfu:acp-v1@1. With --adopt, the CLI submits that same public profile-creation payload; --verify additionally waits for signed conformance and prints status. Building an image grants no conformance or admission privilege. For an existing profile, leave --adopt off and pass the resulting release to checkfu harness update <profile> release --release <hrel_...> instead. OCI releases select the canonical protocol driver automatically; a non-OCI release requires the explicit --driver value. The update path then follows the same fresh-verification rule above.
This path is alpha and has real edges worth knowing before you depend on it. A build settles only where a build-capable Runner is available to your Workspace — with none, the build stays pending rather than failing, and nothing is minted. A private repository needs the Checkfu GitHub App installed and bound to that exact Workspace and repository; without it, submission fails without distinguishing private from missing. And a build whose image digest was already registered reuses that existing release, which carries no build provenance — a success guarantees you a release, not a stamped one.

Where to go next

  • To understand the boundary, stay with examples/custom-acp — minimal on purpose, every line of the contract visible.
  • To adapt an SDK-authored agent, use the OpenAI Agents SDK over ACP example — it pins the official SDK, routes its fixed model through Checkfu’s gateway, disables the SDK’s separate trace exporter, and declares no MCP handoff.
  • To build a real harness inside an authorized Checkfu source checkout, start from apps/harness — pi’s agent loop behind the same ACP server contract, with durable turn journals, checkpoints, completion evidence, and the extension declarations this page only gestures at. Its README carries the same export → modify → checkfu harness add ocicheckfu harness verify arc. The package and standalone export are private qualification artifacts today; no public repository or npm package is claimed.
  • For the contract itself, Harness wire extensions and the schema and vector artifacts remain the normative source.