> ## Documentation Index
> Fetch the complete documentation index at: https://docs.checkfu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bring a custom harness

> Connect an existing ACP agent with a recipe, or implement the ACP stdio contract using the CLI scaffold.

A custom harness lets you run your own agent implementation through Checkfu's Agent, Environment, and Session model. Package it as an immutable image, register it, and verify its supported capabilities before starting a Session. If your agent already speaks ACP, use the [repository recipe path](#or-hand-us-the-repository-instead) to install and launch it through the shared adapter. If you need to implement ACP, start with the CLI scaffold below.

This guide covers the ACP and one-shot image contracts. The native Cloudflare
Agents integration is a separate execution path: its internal package exports
do not provide a customer BYOH scaffold or make an ACP image a native Durable
Object harness. Native package tests also do not qualify your image or its
tools, model formats, continuation, or cancellation. Use the exact release's
admitted capabilities and deployment status for either path.

[Harness wire extensions](/concepts/harness-extensions) describes the contract and its generated capability matrix and slot inventory. The [`acp-v1` extension schema](/harness-acp-v1-extension.schema.json) and [golden wire vectors](/harness-acp-v1-extension-vectors.json) provide machine-readable wire contracts. Use the artifacts matching your release. They describe protocol support; the exact release's signed conformance report and the deployment's [capability status](/getting-started/status) determine which capabilities you can use.

The `server.mjs` excerpts explain the protocol scaffold; connect it to your own agent's tools and execution loop. Use the scaffold supplied by the authorized alpha CLI to start your own implementation; no Checkfu source checkout is required. See [CLI access](/getting-started/cli-access) for artifact access and authentication.

<Warning>
  The Checkfu CLI and TypeScript SDK are private-alpha artifacts with no self-service public installation channel.
  Part of this flow is behind a rollout fence; a deployment that has not enabled it refuses the request rather than running it.
  Confirm [capability status](/getting-started/status) before making availability part of your application's contract.
</Warning>

## Start from a scaffold

Use this path when your company owns the harness implementation. To use an
official harness, select its named profile instead; you do not need to author
a custom recipe. A catalog entry marked `image_required` still needs a
qualified image and is not a ready-to-run default. See [capability
status](/getting-started/status) for availability.

`checkfu harness init acp` writes a minimal ACP starter and image packaging:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu harness init acp --directory ./my-harness
```

The generated starter calls Checkfu’s model plane and implements the ACP floor.
It declares `loadSession: false` and does not declare or connect MCP tools.
The protocol examples below come from that maintained CLI template. Implement
the tool connection in your agent before declaring MCP delivery or the
corresponding capability.

Connect the starter to your domain implementation and declare only the capabilities it supports. Pass `oneshot` for the one-shot image contract. An existing ACP executable can use a recipe that installs and launches it; it does not need a TypeScript rewrite or Vercel dependency in its own implementation.

## 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 starter uses the official ACP SDK for framing and request dispatch rather
than maintaining a separate JSON-RPC server. Its input drain keeps the SDK
connection open until an accepted turn has settled:

```js path=apps/cli/templates/acp/server.mjs theme={"theme":{"light":"github-light","dark":"github-dark"}}
const input = new PassThrough()
process.stdin.pipe(input, { end: false })
process.stdin.on("end", () => {
	void (async () => {
		await new Promise((resolve) => setImmediate(resolve))
		while (activeTurn !== null) await activeTurn.done
		input.end()
	})()
})
```

Protocol settlement and stream closure do not prove provider termination,
rollback, a durable checkpoint, or physical descendant drain. Checkfu reads
accepted settlement from the Session event log.

## `initialize`: declare only what you serve

The first request is `initialize`. The response names your protocol version and capabilities:

```js path=apps/cli/templates/acp/server.mjs theme={"theme":{"light":"github-light","dark":"github-dark"}}
const app = agent({ name: "checkfu-custom-acp" })
	.onRequest(methods.agent.initialize, () => ({
		protocolVersion: PROTOCOL_VERSION,
		agentCapabilities: { loadSession: false },
		authMethods: [],
		agentInfo: { name: "checkfu-custom-acp", version: "1" },
	}))
```

This starter does not declare HTTP MCP support. A custom agent that actually
connects the supplied HTTP MCP servers may declare `mcpCapabilities: { http:
true }` and the matching recipe delivery. Richer harnesses may implement
Checkfu's namespaced extensions; see [the declaration
handshake](/concepts/harness-extensions#the-declaration-handshake). Qualify each
implemented capability on the exact release before relying on it.

Declare only what your implementation serves. A declaration is not proof: only server-issued, signed conformance evidence realizes a capability. Inspect the exact report's checks and admitted capability matrix before configuring a Session. A check marked `unsupported`, `skipped` or failed does not realize that capability; some surfaces have no executable qualification case yet. Registration or a passing base protocol check does not make those surfaces available. A Session requesting an unproven capability is refused rather than given a substitute.

## Session and turn lifecycle

`session/new` opens one process-local session. The starter refuses MCP servers
and additional directories rather than pretending to connect them:

```js path=apps/cli/templates/acp/server.mjs theme={"theme":{"light":"github-light","dark":"github-dark"}}
	.onRequest(methods.agent.session.new, (context) => {
		if (sessionCreated)
			throw new RequestError(-32001, "this container admits exactly one ACP session")
		if (
			context.params.mcpServers.length > 0 ||
			(context.params.additionalDirectories?.length ?? 0) > 0
		) {
			throw new RequestError(-32602, "the starter does not admit local MCP servers or directories")
		}
		sessionCreated = true
		return { sessionId }
	})
```

`session/prompt` streams the model answer through `session/update` and returns
its native stop reason:

```js path=apps/cli/templates/acp/server.mjs theme={"theme":{"light":"github-light","dark":"github-dark"}}
			await context.client.notify(methods.client.session.update, {
				sessionId,
				update: {
					sessionUpdate: "agent_message_chunk",
					messageId: `message-${context.requestId}`,
					content: { type: "text", text },
				},
			})
			if (turn.cancelled) return { stopReason: "cancelled" }
			return { stopReason: "end_turn" }
```

The starter's cancellation handler aborts its original fetch and joins the turn.
That local behavior still needs qualification with your actual model route and
agent implementation.

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](/concepts/harness-extensions#the-conformance-grammar).

The portable text prompt includes a JSON envelope with
`conversation.event_cursor`, `conversation.history`, and the current `input`.
The starter forwards that text to the model; it does not maintain its own
conversation store. On an ordinary subsequent Run, Checkfu rebuilds the envelope
from retained Session context. Reloading the console reads that retained history;
it does not restore an agent process. A supported native checkpoint is a separate
recovery path and must avoid replaying history already present in restored state.

### Qualify the complete agent journey

Before adopting a release, exercise your actual agent implementation through the
Session path you intend to support:

* Complete useful work with a real native tool or granted MCP tool, and inspect
  the recorded tool result as well as the final response.
* Start a subsequent Run in the same Session and check the context your agent
  actually retains. A successful first turn does not prove continuation. The
  scaffold above declares `loadSession: false`; connect and qualify your own
  state handling before advertising restoration.
* Cancel while work is in flight. A cooperative ACP implementation handles
  `session/cancel`, stops its pending work, and settles the prompt. Check both
  the terminal outcome and any tool side effects. A cancellation acknowledgement
  does not prove that effects were undone or an upstream provider request
  terminated; a notification alone does not prove that execution stopped.
* Exercise a known provider refusal, then submit a valid fresh prompt through
  your release's supported retry path. Check the failed Run's recorded outcome,
  the next Run's useful result, and the absence of credentials in errors or
  diagnostics. Pinned BYOK refusal must not switch to paid managed tokens. A
  successful retry does not establish native checkpoint restoration or reverse
  effects from the failed attempt. If a request, tool effect, cancellation, or
  custody callback has an uncertain outcome, retain its original operation and
  use only the release's supported reconciliation path. Do not create a new
  operation or reopen the native owner to repeat an effect whose outcome is
  unknown; a joined or closed local process does not establish rollback.
* Exercise recovery only for the mechanisms your release supports. An externally
  custodied native checkpoint must be exported and imported through the declared
  Checkfu extensions. Native restore requires nonempty, bounded native state.
  The wire value `same_run_resume` also covers completed conversation state
  carried into a later Run of the same frozen Session; its name does not require
  the same Checkfu Run ID. Pending approvals require their exact admitted
  continuation, rather than an unrelated new input. Keep that state outside
  agent-readable files, and do not put provider credentials in it.

A native conversation identifier does not archive the agent's private files.
If recovery depends on a local transcript, automatic Memory, or other files,
qualify how those files reach a fresh sandbox before advertising portable
recovery. Check both the original conversation identity and the required file
contents; a successful same-sandbox resume does not prove fresh-sandbox recovery.
Native automatic Memory is separate from Checkfu's governed Memory resources.

A later Run receives fresh MCP credentials. If your upstream adapter fingerprints
the complete MCP configuration in its saved state, that state may reject the new
credentials. Preserve that compatibility check and rebuild from retained Session
context until your adapter supports and qualifies credential rebinding. Set
`portable_native_checkpoint: false` on a recipe whose state cannot cross this
boundary. BYOH recipes omit external checkpoint custody by default. Set
`portable_native_checkpoint: true` only after qualifying native state across
fresh processes, replacement sandbox filesystems, and MCP credentials. If the
upstream state refers to private files, retain those files through a supported
filesystem recovery path or carry and restore their required contents in the
checkpoint. A restart against the old filesystem does not prove recovery after
sandbox replacement. Same-Run restoration with its original admitted
configuration is a separate qualification.

Select recovery support from the exact admitted release and its qualified
capabilities. A protocol integration or an upstream conversation identifier does
not establish checkpoint support. Checkfu's current Pi coding and generic ACP
guest adapters refuse restored transport state; Cursor's protocol helper does
not establish native restoration. Their source integration is not a portable
recovery offering. For a custom harness that keeps history in sandbox-local
files, qualify a supported file-restoration path before advertising recovery in
a replacement sandbox. A retained filename alone does not restore its contents.

The shared adapter refuses checkpoint export, checkpoint import, and native
approval or question parking when `initialize` does not declare
`checkfu.capabilities.native_checkpoint: "external_custody"`. It returns ACP
invalid params (`-32602`) with `data.reason: "native_checkpoint_unsupported"`.
That refusal does not establish a checkpoint or a restored Session. Explicit
recipe opt-in supplies the declaration; conformance must still qualify the
release before Checkfu admits the capability.

Use the [capability matrix](/concepts/harness-extensions) to record unsupported
continuation, interruption or checkpoint behavior explicitly. Source support,
passing local protocol cases, a qualified packaged image and hosted availability
are separate results. Keep the tested release digest and profile version with
your receipts; rebuilding or changing the profile requires qualifying the
changed inputs.

## The governed model plane arrives in the environment

This scaffold uses Checkfu's governed model plane. On this path, the harness receives a scoped gateway token; Checkfu keeps the provider credential outside the workload. Routing, budget enforcement, and platform-observed model usage apply to calls through that gateway.

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` for the formats your image declares (both pairs when it declares none), 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. When every route the Session admitted serves one format your image declared, the environment also carries `CHECKFU_MODEL_API` — `openai-completions`, `openai-responses`, or `anthropic-messages` — and only that format's alias pair, so an image that speaks several formats picks the one the gateway will forward. Official and customer harnesses selecting this model plane receive the same projection. The model name you send is rewritten to the admitted route. The protocol scaffold reads the OpenAI alias:

```js path=apps/cli/templates/acp/server.mjs theme={"theme":{"light":"github-light","dark":"github-dark"}}
	const response = await fetch(`${baseUrl.replace(/\/$/u, "")}/responses`, {
		method: "POST",
		headers: {
			accept: "application/json",
			authorization: `Bearer ${token}`,
			"content-type": "application/json",
		},
		body: JSON.stringify({ model: "checkfu-admitted-model", input }),
		signal,
	})
	if (!response.ok) {
		await response.body?.cancel()
		throw new Error(`model plane returned HTTP ${response.status}`)
	}
```

The maintained starter uses OpenAI Responses and passes its original turn's
AbortSignal to the fetch. Declare that dialect and select a compatible admitted
route; it does not translate other formats automatically. Bake no credentials
into the image and fail closed when the gateway refuses. Provider reachability
is listed in [harness and model-provider
reachability](/concepts/harness-extensions#which-harnesses-can-reach-which-model-providers).

A `harness_owned` model plane uses the harness's own credentials and routing. Checkfu has no model wire or token-usage authority on that path, so Session text alone cannot establish platform-observed model usage. An `opaque` declaration records unknown model control. Declare only the model control and usage authority your integration can demonstrate; admission rejects requested guarantees that the selected path cannot provide.

## The platform tool plane: MCP handoff

For an ACP release that implements `session_http` delivery, granted platform
tools arrive as HTTP MCP servers in `session/new`. Connect those original
servers using a maintained MCP client and preserve their current exact-call
approval and credential boundaries. The generated starter does not implement
this connection and refuses a nonempty server list. Use `--mcp-delivery none`
until your implementation and exact release have qualified the handoff.

Emit native `tool_call` and `tool_call_update` observations for actual tool
work. Narrative output is not a tool-delivery receipt; Checkfu's served-call
record remains authoritative. Declaring MCP support creates no tool grant.

## 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. The maintained starter does not test or attest that posture. Verify the
selected Environment and immutable image through the actual owner-supported
checks before claiming isolation. Measurements your harness reports from inside
its workload remain self-reports, not platform attestation.

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.

A generated Vercel recipe image forwards this variable only when its
`forward_env` explicitly includes `CHECKFU_HARNESS_CONFIGURATION`. Use that
opt-in for an image whose native process understands the JSON, and register the
closed configuration schema with the release so profile validation happens
before the image starts. An arbitrary customer Dockerfile already receives its
declared environment; this recipe `forward_env` switch is not a requirement
for that path. The configuration is not a provider route or credential
authority. Official DeepSeek managed rows are not BYOH recipe
examples; a BYOH image must document its own accepted schema and native
selection.

## Register, verify, run

Registration is the same advanced lane arc for every custom harness: build and push an immutable image, register the digest, request server-signed conformance, then create or update an Agent so Checkfu records the next immutable AgentVersion. Low-level registration records do not replace the AgentVersion or Environment admitted by a Session.

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu harness test oci \
  --name my-harness \
  --image ghcr.io/acme/my-harness@sha256:<64-hex-digest> \
  --entrypoint /usr/local/bin/node \
  --arg /opt/checkfu/custom-acp.mjs \
  --protocol acp-v1 \
  --working-directory /workspace

checkfu harness add oci \
  --name my-harness \
  --image ghcr.io/acme/my-harness@sha256:<64-hex-digest> \
  --entrypoint /usr/local/bin/node \
  --arg /opt/checkfu/custom-acp.mjs \
  --working-directory /workspace \
  --model-wire openai_responses \
  --mcp-delivery none \
  --platform linux/amd64

checkfu harness verify my-harness --sandbox standard --model primary --wait
```

This example calls the Responses API, so `--model-wire openai_responses`
records its supported dialect on the release. Declare the dialects your own
implementation actually uses; the model route must serve one of them.

These registration commands use `--mcp-delivery none` for the unmodified
`checkfu harness init acp` starter shown above. The starter refuses MCP servers.
Use `--mcp-delivery session_http` only after your implementation connects the
HTTP MCP servers supplied in `session/new`. That flag records the delivery
contract on the release; the `initialize` capability alone does not set it.

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 [Custom ACP harness example](/examples/custom-acp) describes how the resulting profile fits the Agent, Environment, and Session model.

### Upgrade and roll back one logical harness

Keep the logical harness lane 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:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu harness update my-harness oci \
  --image ghcr.io/acme/my-harness@sha256:<new-64-hex-digest> \
  --working-directory /workspace \
  --platform linux/amd64 \
  --expected-version 1 \
  --verify \
  --sandbox standard \
  --model primary
```

`--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:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu harness update my-harness release \
  --release hrel_<previous-release-id> \
  --driver checkfu:acp-v1@1 \
  --expected-version 2 \
  --verify \
  --sandbox standard \
  --model primary
```

The earlier HarnessRelease and every HarnessProfileVersion 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.

Two repository shapes are recognized:

1. **`harness-recipe.json` at the repository root** — the recipe-in-repo path.
   Commit the recipe plus the context its install command needs. Checkfu
   prepares a Vercel-shaped adapter image from that recipe. You do not
   need a Dockerfile for this path. The selected Runner must have a
   recipe base configured by its operator. Adopt the result as `acp-v1` with entrypoint
   `/opt/checkfu/bin/checkfu-vercel-harness` and `model_wire_dialects`
   from the recipe's route. The private
   [`andyrewlee/checkfu-customer-harness-fixture`](https://github.com/andyrewlee/checkfu-customer-harness-fixture)
   repository is an additional example for developers who already have access.
   The recipe below works with your own published ACP package.
2. **A Dockerfile and no recipe file** — Checkfu builds that Dockerfile
   as a stranger image. Direct-OCI registration (`checkfu harness add
   oci`) remains the path for images you already built yourself.

For an existing ACP agent published to npm, commit this as `harness-recipe.json`.
Replace the package name, exact version and executable with your agent's values;
the package must be accessible to the build Runner. This example assumes the
agent reads `OPENAI_BASE_URL` and `OPENAI_API_KEY` and uses OpenAI Chat Completions.
Choose the route and environment mapping that your agent actually supports.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "schema_version": 1,
  "harness_id": "my-domain-agent",
  "source": {
    "type": "npm-simple",
    "package_name": "@acme/my-domain-agent",
    "package_version": "1.0.0"
  },
  "executable": "my-domain-agent",
  "args": [],
  "env": {},
  "forward_env": [],
  "model_route": "openai_chat",
  "provider_env": {
    "OPENAI_BASE_URL": { "source": "gateway-base-url", "ensure_suffix": "/v1" },
    "OPENAI_API_KEY": { "source": "gateway-api-key" }
  },
  "builtin_tools": []
}
```

The package supplies the native agent loop and ACP server. The empty
`builtin_tools` list declares no native tools; add only tools your agent exposes.
Declare `session_http` MCP delivery only when the agent implements that transport.

For a native ACP server that enforces an allow-list supplied in `session/new`
metadata, the recipe can declare that mapping:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "builtin_tool_filtering_mapping": {
    "type": "session-meta",
    "key": "company.allowed_tools"
  }
}
```

This is a recipe fragment, not a complete recipe. Checkfu projects the admitted
selection to the listed tools' `native_name` values (or `tool_name` when absent),
then Vercel delivers the array under that exact `_meta` key. The native server
must enforce it before executing any built-in. An empty array disables every
built-in; an absent selection preserves native defaults. `read`, `glob`, and
`grep` are separate grants. Merely hiding a tool from the model is insufficient.
Unknown names and ambiguous native aliases are refused. Vercel continues to own
bootstrap, transport, Session restoration, turns, and cancellation; the mapping
does not introduce another agent loop.

Recipes do not inherit Vercel's generic approval capability by default. Declare
`"builtin_tool_approvals": "acp-permission-request"` only if the native server
actually requests ACP permission before the governed operation and obeys the
decision. Without that declaration, approval-requiring modes fail before native
startup. The declaration alone grants no approval authority: signed conformance
must exercise the request and continuation on the exact release and profile.
A Session using this capability must enable native continuation capsules in its
capture policy; Workspaces with zero-data-retention (ZDR) mode cannot use native
approvals. The noncatalog Docker proof runs both decisions through the
public API (`owned/harness-sandbox/public-customer-native-action-approval`).

The separate `launch-file-arg` / `dsh-overlay` mapping is not a generic file
writer. It selects the reviewed DeepSeek plugin adapter and requires pinned
`@deepseek-ai/dsh@0.1.5-rc.1`, `dsh --profile acp`, and the complete matching
native tool inventory and built-in aliases. A recipe cannot relabel DeepSeek's
shell as a read tool. Other harnesses use their own enforceable mapping;
declaring DeepSeek's mapping cannot grant filtering support to another engine.

This DeepSeek integration also accepts the maintained adapter's model configuration.
Declare these fields in the release's configuration schema and set them on the
HarnessProfile so the native selector and context/output limits match the model
you route through Checkfu:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "native_model": "my-deepseek-model",
  "model_catalog": [
    { "id": "my-deepseek-model", "context_window": 98304, "max_tokens": 12345 }
  ]
}
```

Use your model's actual limits. The selected ID must occur in the catalog and
its output limit must be below its context window. Keep `DSH_HOME` in retained
Harness state, such as `/home/checkfu/.checkfu-harness/deepseek`, so the native
journal is available to later Runs. This does not establish portable native
checkpoint restoration.

The following example uses an ACP recipe that consumes HTTP MCP servers and
OpenAI-compatible chat completions. Run it from your own checkout of the harness
repository, not from a Checkfu source checkout. Commit `harness-recipe.json` at
the repository root and push that commit to GitHub first. With `--recipe`, the
CLI reads the recipe from the local commit that `--ref` names and derives the
entrypoint and wire dialect from it. `--ref` may be a full commit SHA, a tag, or
a branch (`tags/v1.0.0`, `heads/main`, or a bare name that is not both). Checkfu
resolves the ref once to an exact commit and the build records only that commit,
so a tag rebuilds a historical release after the branch has moved on. If your
checkout and Checkfu resolve the ref to different commits, the CLI refuses the
result, and the Runner refuses a build whose entrypoint or wire dialects differ
from the recipe at the frozen commit. A bare SHA is accepted only while it is
the tip of a branch in that repository, so name the tag for an older commit, and
a commit that exists only in a fork is refused. The command can stop at the ordinary
release-profile handoff, or compose adoption and signed verification without
introducing a privileged build-only profile path. Set
`CHECKFU_RUNNER_POOL_ID` to the `rpool_...` ID of a build-capable RunnerPool
in your Workspace first:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
# In your own checkout, after pushing the commit that contains harness-recipe.json.
COMMIT_SHA="$(git rev-parse HEAD)"

checkfu harness build github \
  --repository https://github.com/acme/my-harness \
  --ref "$COMMIT_SHA" \
  --runner-pool "$CHECKFU_RUNNER_POOL_ID" \
  --recipe \
  --mcp-delivery session_http \
  --platform linux/amd64 \
  --profile-name my-harness \
  --adopt \
  --verify \
  --sandbox standard \
  --model primary
```

Add `--dry-run` to print the exact build request without submitting it. If the
waiting process exits, resume without resubmitting the build:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu harness build status hbld_<build-id>
```

A Dockerfile-only repository omits `--recipe` and supplies its own entrypoint
and model wire dialect.

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.

### Prepare an Environment from the release

Create your Agent and its dependencies through the ordinary resource
operations. No installer prepares them for you. Put the harness reference,
model, and instructions in an Agent project and run `checkfu agent apply`, or
call Agent create directly. Then create the Environment that uses the built
release's exact image repository and digest, and grant the Agent
`use_environment` through a PermissionAssignment. The
[quickstart](/getting-started/quickstart) shows that Environment grant.

The Agent also needs a `use_harness` PermissionAssignment for the adopted
Workspace HarnessProfile. Set the subject to `kind: "agent_definition"` with
`agent` set to the Agent ID, and the resource to `kind: "harness"` with `id`
set to the HarnessProfile ID. Adopting the profile or creating the Agent does
not grant this permission automatically. Use
[Create a PermissionAssignment](/api-reference/permissionassignments/create-a-permissionassignment).

An inline BYOK model configuration pins `model_credential_id` and does not need
an additional `use_model` grant. If you use a reusable ModelRoutingProfile,
select it with an Agent `use_model` PermissionAssignment whose resource kind is
`model-binding` and whose resource ID is the profile ID. See
[Create a PermissionAssignment](/api-reference/permissionassignments/create-a-permissionassignment).

Keep the harness reference consistent with the adopted profile and the Agent
manifest. These are nonsecret configuration files; no Checkfu checkout or manual
trust evidence is needed.

For this preparation sequence, build with `--adopt` first, then create the
Agent and Environment and verify against that Environment. The combined build
command above assumes its named sandbox is already prepared.

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu agent plan ./customer-agent
checkfu agent apply ./customer-agent --idempotency-key customer-invoice-agent-1 --json

checkfu harness verify my-harness \
  --sandbox customer-invoice-prepared --model primary --wait
```

Save the Agent ID that `agent apply` prints and pass it as `--agent-id` on later
applies. The derived sandbox digest can differ from its harness base-image
digest. Creating these resources does not establish exact-image execution
readiness. That remains the separate check below.

Official catalog identity is recorded when a release is adopted. A later catalog
refresh does not invalidate an already-admitted Run's pinned release. Native
checkpoint and approval authority come from its verified capability admission,
with the same provider, custody, and policy checks for official and customer
harnesses. A catalog label alone grants neither capability.

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.

<Warning>
  This path is `alpha`. A build stays pending until a build-capable Runner is available to your Workspace. Recipe builds additionally require an operator-configured recipe base; a Runner without one rejects that build. A private repository needs the Checkfu GitHub App installed and bound to that Workspace and repository. Successful builds record their source commit and provenance. If an existing image digest belongs to an incompatible release contract or different build provenance, the build fails with `image_rejected`; it does not replace or silently reuse that release.
</Warning>

## Check execution readiness

A completed build creates an immutable release. Adoption selects a profile,
and verification exercises its declared capabilities. Before an ordinary
Session, the execution deployment must also serve that exact image on a
qualified runtime and provide placement capacity. A passing conformance
report or ready Runner alone does not supply that execution authority.

After attaching the profile to an Agent, inspect public support diagnostics:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu doctor --agent AGENT_ID --environment ENVIRONMENT_ID
```

With these flags, doctor exits nonzero when the Workspace's broader public
Session support is incomplete. That result does not check exact admission for
your selected Agent and Environment: a qualified basic custom-harness path may
be usable while advanced capabilities such as steering remain unavailable.
Doctor exit zero is not a prerequisite for attempting that qualified subset,
and a successful basic Session does not prove those advanced capabilities.

Ask your alpha operator about the missing public support prerequisites. Session
creation separately checks the exact image, qualification and authority; missing
or expired execution qualification is still a refusal. If admission reports
`harness_artifact_mismatch`, ask the operator to publish and qualify an execution
offering and matching placement for the exact release image. Supply your release
ID, profile ID/version, and image digest. These prerequisites apply to new and
upgraded releases; do not change the digest to an unrelated image to bypass
the refusal.

A successful preflight still leaves exact admission to Session creation. Keep
your existing profile until the new release is verified and its execution
path is available. Existing Sessions and in-flight Runs retain their frozen
choices; later Session admissions can select the updated profile.

## Where to go next

* **To understand the boundary**, use the maintained CLI ACP template — the protocol floor is intentionally minimal, and its omitted capabilities are explicit.
* **To adapt an SDK-authored agent**, use the [OpenAI Agents SDK over ACP example](/examples/openai-agents-acp) — 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.
* **For the contract itself**, [Harness wire extensions](/concepts/harness-extensions) and the schema and vector artifacts remain the normative source.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.