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.
lnk-<name> link <connection> [--again]
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.
Adapters from outside Link
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> configurewrites the harness's own config from the settings, andlnk-<name> needsreads 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