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

# Connect a local agent

> Offer "connect your local agent" in your app: mint a pairing code, run one command on the user's machine, and drive the Session — online or offline.

Your app's users can bring an agent they run themselves — on a laptop, an
always-on Mac mini, or a VM in their own cloud. **Location is not a kind.**
Every one of those is the same `connected` posture with the same pairing flow;
they differ only in how often the machine is expected to be online. The choice
your UI offers stays exactly two-valued — **managed by Checkfu** or
**connected (yours)** — never a menu of hosting geographies.

The copyable client's protected pairing-recovery store currently supports
macOS and Linux. Windows is not supported: the client returns a typed failure
before filesystem access because POSIX mode bits cannot prove a Windows ACL,
owner SID, or reparse-point boundary. Windows support requires those native
checks and a real Windows proof lane; a simulated platform test is not that
evidence.

The runtime dials out to Checkfu, so there is no inbound port, tunnel, or
public URL to configure, and your app never sees the runtime's credential: the
one-shot pairing code is the only secret that crosses your product, and the
user's machine redeems it directly against the platform.

`CHECKFU_API_KEY` and `CHECKFU_WORKSPACE_ID` come from
[Get access](/reference/access#the-three-variables-ready). The examples use the
[TypeScript SDK](/reference/typescript-sdk).

## 1. Mint a pairing code in your app

A pairing binds an end-user Principal to an AgentDefinition and produces a
single-use code. The code is returned exactly once, by this authenticated
create; the public pairing read exposes state, never the secret.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const { pairing, code } = await checkfu.connectedRuntimes.pairings.create({
  agent_id: agentId,
  principal_id: endUserPrincipalId,
  name: "laptop-agent",
})
```

Show the user the `code` and the copyable client command. The
`ConnectLocalAgent` component in [`@checkfu/ui`](/guides/embed-a-session-view)
renders this state (code display, presence badge, offboard control) from
injected data, holding no credential and making no network call itself.

## 2. Run the client on the user's machine

Any supported macOS or Linux machine, same command:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu harness connect --code -- <your-acp-agent>
```

The CLI prompts for the code on stdin — it never rides argv, a URL, or shell
history — then redeems it, keeps a durable mode-0600 `redeem_id` so a crashed
redemption recovers instead of double-pairing, announces outbound presence
under a fresh generation, and supervises the local agent behind the
[ACP boundary](/concepts/harnesses-and-models#managed-openclaw-and-an-existing-gateway-are-different-modes).
Redemption is exactly-once: an idempotent retry recovers the same runtime, a
second redeemer conflicts.

The `<your-acp-agent>` argument is any command speaking ACP on stdio — the
same contract every harness in the catalog uses.

## 3. Watch the pairing complete

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const state = await checkfu.connectedRuntimes.pairings.retrieve(pairing.id)
// state.state: "pending" → "consumed"; state.runtime_id once redeemed

const presences = await checkfu.connectedRuntimes.presences.list()
const live = presences.data.find((p) => p.runtime === state.runtime_id)
// live?.status: "starting" | "ready" | "draining"; absent = offline
```

Presence is disposable liveness only — never execution or capability
evidence. Render an absent presence as *offline*, not as an error.

## 4. Drive the Session — online or offline

Address a turn to the runtime by naming it in the `user.message`. Everything
else is the ordinary public Session surface:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
await checkfu.sessions.events.send(session.id, {
  type: "user.message",
  payload: {
    content: "summarize my inbox",
    connected_runtime: state.runtime_id,
    authored_by: endUserPrincipalId,
    caused_by: { kind: "api" },
  },
})
```

Because the Session log is the durable work record, an offline runtime loses
nothing: the Run is created and durably parked, the Session honestly reads
`provisioning` (with `run.created` in the log and no `run.started`), and the
turn executes when the runtime next announces. Tell the user exactly that —
"queued; it runs when your machine reconnects" — rather than surfacing a
failure. Under standard retention an Automation targeting an offline runtime
queues the same way; under ZDR it fails closed instead of retaining execution
content.

## 5. Offboard

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
await checkfu.connectedRuntimes.revoke(runtimeId, { expected_version })
```

Revocation is platform truth: the connector token stops authenticating
immediately.

Removing the owning Principal also ends its runtimes' authority, but **lazily**
— the runtime is revoked when it next presents its token, not at the moment of
deletion. The credential is dead either way (the next claim, announce, or
ToolInvocation is refused), but a runtime listing can still show `state: "active"`
in the window before that next call. If you need the row to read revoked at a
definite moment, revoke it explicitly rather than relying on the cascade.

## What connected execution is — and is not

A connected runtime shares the durable Session log, governance, and audit
path, but makes no Runner, sandbox, conformance, or model-control claim; its
narration is attested against its registered key and honestly classed as
narration, never as platform-witnessed evidence. See
[what a connected runtime realizes](/concepts/harnesses-and-models#what-a-connected-runtime-realizes)
for the capability ceiling, and note that a ConnectedRuntime is not a
customer-VPC Runner — that is a separate `customer_runner` topology.

Connecting an *existing third-party gateway* (for example an operator-owned
OpenClaw Gateway) is the sibling story — a control-plane federation rather
than an end-user pairing. It rides the same connected-ACP boundary; see
[managed OpenClaw versus an existing Gateway](/concepts/harnesses-and-models#managed-openclaw-and-an-existing-gateway-are-different-modes).

## A worked example

`examples/personal-assistant` wires this whole journey into a chat assistant:
`/connect local-agent` mints the pairing and relays the code,
`/local <message>` drives the paired runtime and relays the reply when it is
online — or answers honestly that the turn queued durably when it is not —
and `/disconnect local-agent` revokes it.
