Interface, one contract for every harness

The commands a harness adapter, lnk-<name>, answers, and the typed conversations it speaks.

Harness adapters (lnk-<name>)

What lnk-link-harness answers, and every harness adapter from outside Link. The contract's source is harness.rs, and the authenticated storage and context protocol is in Portable context.

Versions

The contract has a version (link_harness_contract::harness::VERSION, now 2), which a change to it bumps.

Over ACP

Every harness is reached over the Agent Client Protocol (ACP), an externally maintained open standard for client-agent communication. Sessions, their lifecycle, cancellation and capability discovery follow ACP. Link's Agent State Protocol extends it for portable state, separately from the installation and launch commands documented here.

Where each command runs

install, configure, command and health run inside the sandbox, unless it's off; traces, written, forget, connections, state, internet-hosts, needs, endpoint and contract run outside it, and only print.

Their environment

The four inside the sandbox run with HOME set to the harness's home, TMPDIR its tmp/, LNK_FILES the files folder, the agent's Home, and LNK_AGENT_FOLDER the agent's own folder (Agent/, its collections kept at file). LNK_AGENT is the agent's name, LNK_AGENT_ID its ID and LNK_OWNER its owner (acct-<id>, or <user>@<machine> before you sign in).

The agent's ports

LNK_PORT is the agent's base port, not set for main, which takes the adapter's default. Its ports go from there, within as many as its contract's ports and one more, or 200 when it doesn't say, and Launch.ports outside that are refused.

Its own measures

LNK_MEASURES is a file in its home where the harness may append its own measures, a line of OTLP JSON at a time, holding the file's lock (flock, LOCK_EX) as it writes (link_measure_contract::client::append): what it counts itself, a turn's tokens, cost, calls and times. The rest is dropped as lnk measure reads it, and the file emptied, holding the lock, once every line is read.

Asked once

contract, connections, internet-hosts, endpoint (for each base port) and needs (for each input) are asked once a process while the adapter's program stays the same file, and again once it's replaced or rewritten.

Set it up and run it

The commands lnk-agent asks to install the harness, configure it, run it and check it answers, each assumed as it says when an adapter doesn't answer it.

lnk-<name> contract

What the adapter speaks and its harness takes, asked once a process: the contract's version and each of its parts, on The Contract.

lnk-<name> install

Installs the harness with its own installer, or does nothing, and asks nothing. It says so first when it installs, and shows progress.

lnk-<name> configure

Writes the harness's own config from the agent's settings: Settings. lnk-<name> needs, beside it, says what the harness needs beyond Link's defaults (Settings).

lnk-<name> command

Prints Launch as JSON, how to run it in the foreground: {"argv", "env", "write", "read", "ports", "serve"?, "services", "wait_secs"?}. lnk-agent refuses one that would widen the sandbox.

serve

Of ports, those Link reaches from outside the sandbox (its gateway or endpoint, its health check), the only ones served on this machine's loopback behind its proxy. Left out, all of ports.

wait_secs

How long the harness may take to answer health after it starts, at most 600. Left out, 60, and 180 for a version 0 adapter.

lnk-<name> health

Exits 0 when the running harness answers, reaching it on its ports on this machine's loopback, the only network it has in the sandbox. Read by lnk agent start, and by lnk agent status when nothing on its saved ports answers a request with success, a 2xx. What does is the harness, up, its proxy closing without an answer; any other answer, such as a 503, is the health check's to judge.

lnk-<name> internet-hosts

A JSON list of the hosts its install and configure reach, as lnk agent allow --internet-host takes them (["registry.npmjs.org", "*.githubusercontent.com"]). With the sandbox and [internet], every step runs behind a proxy of its own, direct, and those two reach only these hosts; an adapter without the command lets them reach any host on the internet.

What the other steps reach

link reaches what the agent may while it runs (lnk agent allow --internet-host, --tcp; "*": any host), and health only the harness's ports on this machine's loopback.

What it says of its harness

The commands that say what the harness speaks, where it's reached, what it keeps and where it writes, each read by the lnk agent command named.

lnk-<name> connections

A JSON list of the connections its harness speaks, by their plugins' names (["telegram", "slack", "discord", "signal"]); an adapter without it speaks Telegram. Read by lnk agent connect, which refuses one the harness lacks, lnk agent use and lnk agent status.

lnk-<name> endpoint

For a harness that speaks no connection itself, where Link posts its messages: The Endpoint.

Conversations

lnk-<name> instructions [--json] prints the system prompt its harness sends its model now, lnk-<name> conversation <thread> a conversation as a person reads it, and lnk-<name> conversations move <way> moves the agent's conversations to another way of keeping them: Conversations.

lnk-<name> state

Prints link_harness_contract::harness::State as JSON: {"include", "exclude"}, paths ~/... in its home that hold the agent, and those under them that are its install (programs, runtimes, logs, caches, .env). Read by lnk agent state-send.

For a linked connection (describe's linked: Signal), whose account the harness links itself: links it in the home, its QR code on the terminal, once configured. It exits 0 at once when it's linked (--again: links it anew), and 3 when it isn't and there's no terminal. Read by lnk agent connect (--again), lnk agent start and use, before starting the harness.

lnk-<name> traces

A JSON list of each place the harness writes when it isn't in its home, and what kind of thing is there: {"kind": "home", "path": "~/.<name>"}, the path ~/... or absolute, {uid} for the user's id. Read by lnk agent harness show, which labels each by its kind, and lnk agent harness delete --harness <harness>.

Its kinds

A kind is home, config, secrets (a .env-style file of keys and tokens), login (one of its own that can't move), service, program, package (what its installer put beside it), cache, log, lock, browser, agent or profile; any other entry is refused.

An install's agents

An agent path is one of several agents sharing the home: {agent} is its name, {-agent} a dash and its name, and for the install's own agent main and nothing (~/.openclaw/workspace{-agent}). A profile path is another agent's whole home (~/.hermes/profiles/{agent}). lnk agent migrate makes each an agent of Link's.

lnk-<name> written <home>

A JSON object naming the version that wrote the install whose home is <home>, and why Link can't run that state when it can't: {"version": "2026.9.6", "refused": null}. Read by lnk agent migrate, which refuses a state with refused set before copying anything.

lnk-<name> forget <home>

Takes the secrets on its stdin, a JSON list of their values, out of the files of its own in the home <home> keeps them in, other than its secrets traces, which Link edits itself. An adapter whose contract says forget answers it. lnk agent migrate runs it on its copy of an install for each secret it takes, so the copy keeps none of them before the agent's first start.

lnk-<name> uninstall --parts --json

As every plugin's (Plugin Protocol), run outside any home: the credentials its harness keeps in each agent's home (link_harness_contract::harness::homes: ~/Link/Agents/<agent>/Harnesses/<name>), such as a token or a linked device it made itself, as part agents's credentials, with no paths: the folders are the harness plugin's. Read by lnk uninstall --parts (lnk secret list).

Without it

An adapter without uninstall holds nothing lnk secret list shows, so the list names no credential in its agents' homes.

Any lnk-<name> answering the commands above is a harness: lnk agent use <name> runs it like Link Harness. A harness Link doesn't write lives in its own repository, and releases and signs itself.

Releasing

A GitHub release holds lnk-<name>-<target>.tar.gz for each target it runs on (aarch64-apple-darwin, x86_64-apple-darwin, x86_64-unknown-linux-musl, aarch64-unknown-linux-musl), each with lnk-<name> alone. lnk on a machine whose target it lacks refuses it, and so does a box of that processor. lnk-<name> --version prints lnk-<name> <version>.

Its signature

The release holds a SHA256SUMS of its archives, signed with the repository's own Ed25519 key (ssh-keygen -Y sign -n link-release -f <key> SHA256SUMS, giving SHA256SUMS.sig), and the key's public half as release-key.pub.

Installing

lnk plugin add --url https://github.com/<owner>/<repo> takes the latest release, shows the key and asks, then pins both (Plugins). It asks the program only --link-describe, installing any plugin that describes itself by its name but a standalone program.

Updates, lists and boxes

lnk upgrade takes each one's latest release, signed by the pinned key, when it's newer. lnk agent use lists it with where it came from (lnk plugin list --json), and lnk agent move adds it on the box from the same repository, with the same key.

Without a release

A lnk-<name> you put on the PATH yourself works too, by name, unsigned, and yours to update and put on a box.

The smallest one

The smallest adapter answers contract, command (its Launch), health, needs and endpoint, and serves ACP at that address: Link carries Telegram for it, and the sandbox, the model's key on the wire, moving and switching work as for any harness.

Docs

  • The Contract: What a harness adapter says of itself when asked lnk-<name> contract: the version of this contract it speaks, and each part of the contract its harness takes
  • Settings: What a harness adapter is told of the agent it runs, and what it says its harness needs: lnk-<name> configure writes the harness's own config from the settings, and lnk-<name> needs reads them to say what the harness needs beyond…
  • The Endpoint: How Link carries the messages of a harness that speaks no connection itself: its adapter names its endpoint, where it speaks ACP, the Agent Client Protocol, and each message from its connections is a prompt there
  • Conversations: The commands that show what a harness tells its model and the conversations it keeps, and the typed Link conversations that a harness may answer as well
  • Portable Context: An adapter can capture readable conversation records and read shared memory through Link's authenticated storage bridge, whichever carries its connections
  • Agent Client Protocol: The Agent Client Protocol (ACP) is an externally maintained open standard for connecting clients to agents, letting a client prompt an agent and receive its responses and tool activity through a common interface