Decisions
Why agents work the way they do.
Harnesses
Link Harness's own are on its Decisions page.
Which harness is the default?
Link Harness: you install only what you need, and OpenClaw is a large
install someone else wrote, most of the minutes before a first answer.
Link Harness installs nothing from a third party. OpenClaw, Hermes and
DeepSeek Harness stay a lnk agent use away, and a box installs Link
Harness too. An agent whose settings name no harness but that has a
model keeps OpenClaw, the default it started with, and start saves
the harness, so a new default never switches an agent. Changing it is one line in
lnk-harness.
Why does Link have a harness of its own?
So the smallest engine does the whole job: it answers you on your phone and remembers what you said. It is one more harness beside OpenClaw and Hermes, never part of the core, in a repository of its own. Every harness, Link's and those from outside, is proved against the conformance kit in the core's tests, which the core's own small fixture harness passes too. It's a router in Rust: it runs no code, holds no keys, and keeps nothing between turns, so an idle agent costs nothing and a restart loses nothing. Its tools run outside it.
It grows mostly through tools: almost every new capability is a tool, not code in Link Harness. Few things are added to its core; skills, as instructions it's given, may be one.
Why are OpenClaw and Hermes adapters in repositories of their own?
Because Link works on Link Harness, and supports any other harness the
way it supports any model: through a contract. On one machine OpenClaw
takes about 90 seconds to answer after a start, needs lan for its
Slack and Discord sockets and, with OpenAI, runs a program no sandbox
can run; Hermes answers fast but takes about ten minutes to install.
Link Harness answers faster than either with nothing beyond Link's
defaults. So each of them, and DeepSeek Harness, is an adapter in its
own repository, riding the same adapter contract as Link Harness, the
way anyone's adapter would, and its issues go there. Link's release
builds and signs them (below).
Why does Link's release ship OpenClaw, Hermes and DeepSeek Harness?
So that one install gives you all four harnesses: lnk agent use hermes works with nothing to add or trust first. Link's release builds
the three adapters from their repositories, at the core's version, and
signs them with Link's key, as it does its own plugins; lnk upgrade
updates them with Link. They're named only in the core's plugin lists
(PLUGINS, needs) and the harness plugin's KNOWN, besides what it
keeps for agents and adapters from before the contract had a version,
so what's specific to each is still its adapter's answer through the
contract. A harness
Link doesn't ship comes from its own repository.
How do the core and an outside adapter keep working together?
Through a versioned contract. They release apart, so a newer
lnk-harness must run an older adapter and the other way round. The
adapter says which version it speaks (lnk-<name> contract), and each
change is additive, as in the relay's protocol: a new command with what
lnk-harness assumes when an adapter doesn't answer it, or a new field
with a default. What one harness needs, such as how long it takes to
answer after a start or whether it takes the proxy's address, is the
adapter's answer, never a name in the core.
How does a change to the contract land?
In link_plugin::harness first, as an addition an older side can
follow. The mini harness answers it, the conformance kit checks it, and
then the harnesses take it up, each passing the kit. Whatever crosses
repositories is in the contract, never in one harness's code.
Which release of a harness runs?
The one its adapter pins. It's installed by the harness's own installer, fetched from its repository at that release's commit and run only if its SHA-256 matches, the way the bucket plugin pins rclone. The installer is the harness's to trust, but the one that runs is always the one Link checked. Moving to a new release is a Link release.
Where is a harness installed?
In a home of its own, ~/Link/Agents/<agent>/Harnesses/<name>, which is
its HOME. Its installer, its configuration and the harness itself run
there, inside the sandbox, so nothing spreads across the machine. Left
alone on a Mac, a harness writes ~/.openclaw, ~/.local/bin and a
LaunchAgent of its own. lnk agent <harness> show lists the home and any traces
outside it, and lnk agent <harness> remove deletes both. Shared files live in
the agent's Home.
Does every start configure the harness again?
Only when something changed. Each harness CLI run is a Node or Python
start, and a full configure is ten or more of them. So OpenClaw's and
Hermes's adapters keep a hash in the home, .lnk-configured. It covers
the settings they were given, the adapter's and the harness's releases,
the adapter program itself (a rebuilt one keeps its version), the
agent's ports, and the config and .env they wrote. When it all
matches, configure does nothing. A harness that edits its own config,
or a home that moved without its .env, is configured again, so Link's
settings still win at the next start. OpenClaw's doctor --fix runs
once per install of a release, when it succeeds.
How does a harness find its tools?
In its own MCP settings: each tool the agent may call, and its
memory, is handed to the adapter by name and address on the harness's
proxy, and the adapter adds it the way that harness takes an
MCP server over HTTP. OpenClaw's mcp.servers and Hermes's
mcp_servers then list it with the servers you added, and a harness's
own tools call it as any other. An adapter removes only the servers it
added, recorded in its home, so one you added under another name stays.
Link Harness reads the same addresses from its environment.
Agents
How are agents and machines named?
Each has a name you type and a random ID, kept in machine.toml and the
agent's settings. An agent's ID is 12 random characters that never
change. Names are what people say; IDs keep two machines' or two users'
main apart, with no registry to hand out numbers.
What is named after the agent?
What people see by its name: its folder, its settings file, its log
and its rules files. What runs is keyed by its ID, which no other
owner's agent shares, so two owners' main never meet even on one
machine: its service (link.agent.<id>, lnk-agent-<id>.service),
the service carrying its channels (link.bridge.<id>,
lnk-bridge-<id>.service), its memory's and its scheduler's tools
(memory-<id>, scheduler-<id>) and its proxy's sockets
(~/.config/lnk/agents/.net/<id>). Nothing of one agent is shared
with another. A harness can't
claim any of Link's services as its own: every name starting link. or
lnk- is refused.
Why does the first lnk agent start make main?
Most people have one agent and never name it.
Who owns an agent?
Two kinds of owner, each whole, so everything works without signing
in. Someone who never signed in owns their agents as the user running
Link on the machine, <user>@<machine>, from the machine's own
properties, with nothing to set up. Once signed in with GitHub (lnk auth login), it's the account, github-<user id>: at an agent's next
start the account replaces the stand-in, and the agent keeps all it
has. Most harnesses assume one person; Link keys each agent by its ID
and its owner's, so two people's agents on one machine, or one process,
never meet. The owner is in the agent's settings, never taken from a
request, and moves with it. An agent nobody signed in for moves to any
machine, signed in or not; an account's moves only to a machine signed
in as that account, so an account's agents never land where anyone
else, or nobody in particular, is signed in. One owner per agent;
people who share agents are an owner of their own.
How does each agent get its ports?
Through LNK_PORT on every adapter command. Every agent but main gets
a block of ports from 20000, as many as its harness says it takes (its
contract's ports) and one more, for its proxy on a Mac: two for Link
Harness. An adapter that doesn't say gets 200, as every agent did
before. Switching harness grows or shrinks the block at its next start,
at the same first port when no other agent's block is in the way.
main keeps the adapter's defaults, OpenClaw's 18789 and Hermes's
8642. This is the same for every adapter, leaves nothing to remember
between commands, and lets a lone agent keep its harness's usual ports.
An adapter that doesn't take LNK_PORT runs only main: its ports
must be in the agent's block.
How are agents kept apart?
The sandbox hides ~/Link/Agents from every harness except its own
agent's folders, even with read-home, since agents run as one user.
Their ports stay reachable wherever localhost is; each harness's own
token guards them.
Why are the files folder's names lowercase?
What the agent reads and writes (instructions/, skills/,
conversations/, work/) is named for it, in lowercase, and side by
side, never one inside another: a sandbox has no read-only folder inside
a writable one, so instructions/ can be locked ([files] read) while
work/ stays writable. The folder itself keeps its name, Home, since
you browse it. conversations/ is never writable to a tool: its
harness alone writes it, so nothing rewrites what was said.
Why does [files] hold only for some harnesses?
A harness's sandbox can make the files folder read only and write only
some of its folders only if the harness writes nowhere else there. Link
harness writes only conversations/, and says so in its contract
(folders); a harness from outside Link works in the whole folder,
so its sandbox writes all of it, and a start says your [files] holds
only for Link's tools there.
Which model does an agent use with no --model?
Link's default for the provider, named, in every harness:
claude-sonnet-5-5 on Anthropic, gpt-6-luna on OpenAI and
openrouter/auto on OpenRouter. Each harness's own default differs
(OpenClaw's for OpenAI costs many times Link Harness's), and would
leave status saying only "the harness's default". A named model says what it
costs, and stays the same when you switch harness.
Messages
Which agent is a request for?
The one its Link-Agent: <agent> header names, sent by every bridge
on each message and its proof. A header, like the thread, since chat
completions has no field for it. An adapter whose contract says
agent answers a request as the agent it names, with that agent's key
and settings, and one naming none as its own agent's. It refuses one
naming an agent it doesn't run before the model sees it, so one process
could answer for many agents. Link Harness and the mini harness read
it. An adapter that doesn't answers every request as its own agent's,
so the contract stays additive. Each agent runs a process of its own.
Beside it, Link-Agent-Id: <id>: every owner has a main, so a
process answering for more than one owner's agents needs the ID. Link
Harness keys everything of an agent's (its folders, its settings as
last read, its tools' listings and sessions) by its ID and owner, never
its name, and a test runs one process for two owners' mains.
What does a message sent mid-answer do?
What the agent's spec says ([turns] busy), since whether a second
message should change the answer, wait, or stop it is an opinion; Link's
is to steer, as OpenClaw does. Whatever it does, the message is kept at
once, so nothing said is lost to a turn that crashes. One turn runs
per conversation, whichever process runs it: a lease in the
conversation's folder (turn.json), renewed as often as the spec says
([turns] renew; Link's: every 10 seconds), says who. A lease not
renewed for as long as the spec says ([turns] lapse; Link's: a
minute) belongs to a process that died: how soon a crash is noticed
against how long a slow machine may go without renewing is a trade, so
both are keys. The next to take the conversation finishes its turn
first: its tool calls that hadn't answered are answered as not made
again, and the model decides whether to call again, so nothing runs
twice unseen. A turn that had answered every message before it crashed
only has its lease let go: asking again would send a second answer. An
answer finished that way at the harness's start was waited for by
nobody, so the agent's bridge, outside the sandbox, asks the harness
for it (/_link/recover) as it starts and when the harness answers
again after it couldn't be reached (it stopped, so it may have
crashed), not on a timer that reads every conversation. It sends it
where that turn's answer was to go: the bridge keeps, from each
message from others until its answer went, where the answer goes
(nowhere, the chat, or a subagent's parent, up the chain); an owner's
goes to the chat. Asked in two steps, the harness keeps it until the
bridge says it was sent, so one lost on the way is handed over again
rather than dropped. Taking a lapsed lease is one taker's at a time
(turn.take), so two processes never both finish a turn.
Every event is written once, conditionally on its number, so a turn
and a message arriving race safely: the one that loses reads again.
A message that steered a running turn is answered with a header and
no body (Link-Steered, status 202): its answer comes on the request
that started the turn. The bridge marks the message instead, as the
spec says ([channel.<name>] steered; Link's: no mark).
How does a message from someone else reach an agent?
A subagent, a schedule, and a message from another agent look like
three features, but they're one: a message for the agent from someone
who isn't its owner, now or later. The hard part isn't writing down
when; it's something holding the message until it's due and waking the
agent then. So one tool and one runner do all of it, Link Scheduler, a
program of its own in Link's tools, and the core only gains a header
(Link-From, with Link-Hops) and the bridge's way in. The bridge
already holds the agent's endpoint and key, restarts with the machine
and delivers answers to channels, so the runner runs from it, through
a driver that tests swap for one in process. A message from someone
else is kept as theirs, the model is told who's speaking, and it never
counts as the owner's. That every message carries a hop count and every
agent a cap is decided; the numbers are the agent's spec's. An answer
nobody waits for goes only to the owner's own chat, never a group's.
How does a subagent get fewer tools?
A subagent is a new conversation of the same agent, so by itself it
has every tool its parent has. Narrowing it is a list per conversation,
in the harness contract (tools): the subagent's task comes with
Link-Tools, the names of the tools it may use, and the harness keeps
that list with the conversation, offering it only those from then on.
A later list is intersected with it, so nothing widens a conversation
again. The harness tells its tools which a conversation has (_meta
link.local/tools), so the scheduler gives a subagent its parent's
tools or fewer, a narrowed parent's subagent included, and never more.
It's additive: a harness that doesn't say tools gives a subagent all
of them, as before, and a list is refused for it rather than ignored,
since a subagent with tools it was meant not to have is worse than one
not started. The mini harness answers it and the conformance kit
checks it. Link Harness keeps the list beside the conversation's
events, not in them: it's a grant, like the agent's settings, and
finding it in the events would read every event of every conversation
at each turn.
What does a mention in a group get?
An answer, there, on every harness and channel: the owner mentioning
their agent in a group, channel or server gets it answering, as in a
direct message, since silence looks broken. Only the owner's mention
is answered; anyone else's, and a message that doesn't mention it, get
nothing. Link sets it in each harness's settings (OpenClaw's and
Hermes's group policies), and Link Harness's bridges carry the
mention, each group a conversation of its own
(<channel>:<group id>), so switching harness doesn't change it.
Channels
Connecting a channel
Where does a channel's setup live?
In a plugin per channel (lnk-telegram, lnk-slack, lnk-discord,
lnk-whatsapp, lnk-signal),
installed the first time you connect it, rather than in the harness
plugin. You install only the channels you use, with what each needs,
such as Discord's Gateway client, and adding one doesn't grow the harness
plugin. The harness plugin keeps what's the same for every channel: its
settings, stopping the agent while it pairs, disconnect, switching and
moving.
Which channels does a harness speak?
Its adapter says, with lnk-<name> channels, so connect refuses one it
lacks and use says which stop answering. A channel stays in the
settings when the harness changes, so switching back brings it back. An
adapter that doesn't answer it speaks Telegram. An adapter with an endpoint also speaks the
channels Link carries.
Why does the harness plugin know no channel by name?
So a channel is a plugin like a harness: lnk-matrix on the PATH
plugs in, and adding one of Link's own touches only its plugin and the
core's list. An agent keeps one map of channels, each as its plugin's
connect made it and describe said of it (its title, which fields
are secret, whether it's linked as a device, its hosts), and hands each
section to the adapter under the channel's name. The typed sections of
Link's own channels stay in link_plugin::harness, the contract the
adapters read, and a channel from outside Link sits beside them. Settings
and moves with a field per channel move into the map once, and a move
still writes them for an older receive, and lnk agent list --json still prints bot, slack, discord, whatsapp
and signal for their readers, beside the new bots. A Telegram
token keeps its Keychain name, bot-token, so no Mac asks for it again.
Who does a channel answer?
Whoever sends the one-time code printed in your terminal, in a private chat or direct message (Telegram, Slack, Discord), or the number you type (WhatsApp, Signal), and no one else. Knowing the bot, or being in the workspace, isn't enough. Slack's allowlist is by member id: the harness's own for one that speaks Slack, the bridge's for one Link carries.
Why one Telegram bot per agent?
Telegram lets one program read a bot's messages, so two agents on one
bot would take turns missing them. connect refuses a bot another agent
here has, or one Telegram says another program reads.
Why one Slack app per agent, in Socket Mode?
Slack shares a Socket Mode app's messages among whatever connects, so
two agents on one app would each miss some. Each app is made from Link's
manifest, and connect refuses an app another agent here, or running on
a box, has. Socket Mode, because a public address for each agent would
mean a tunnel just for Slack.
Why one Discord bot per agent?
Discord sends a bot's messages to every connection it has, so two agents
on one bot would both answer. connect refuses a bot another agent
here, or running on a box, has.
How does Link see a Discord code arrive?
On Discord's Gateway, for as long as it waits, with only the direct messages intent: a bot can't list its direct messages over HTTP. It connects before showing the code, so the code can't arrive unseen.
How is WhatsApp connected?
Linked as a device, as WhatsApp Web does. Not through Meta's business
API: no business account and no public address, at a small risk to the
number, which Link says before linking. A harness that speaks WhatsApp
(OpenClaw and Hermes, through Baileys) links it itself (lnk-<harness> link), since what links it is its own state. For any other,
lnk-whatsapp link does (which client).
Your number is typed at the terminal, so no code is needed.
How is Signal connected?
Linked as a device through signal-cli, which both harnesses speak to.
Link installs it as a pinned, checked release into the harness's home:
the native build on Linux x86-64, else the Java build with a pinned Java
runtime. That's one command instead of asking you to install it and
Java, and nothing outside the home for the sandbox to open. signal-cli
is GPL-3.0, so Link downloads it from its releases and runs it as its
own program, never built into Link. It reads Java's proxy settings, not
HTTPS_PROXY, so Link's wrapper passes the proxy on. The Signal plugin
installs and links it (lnk-signal cli install, cli link), for the
adapters as for its own bridge, so nothing about signal-cli is in the
library every plugin links. For a harness speaking Signal, the harness
plugin runs both in that harness's sandbox, reaching its adapter's
hosts, rather than outside it: the program lands in a home the harness
can write, so running it outside the sandbox would run what the harness
wrote. The adapter is handed the program's path in Signal's settings.
Link's own Signal bridge speaks to signal-cli over JSON-RPC on its
stdin and stdout, not its HTTP daemon, which would listen on the
loopback, where any user of the machine could send as the agent.
Carrying a channel
How does Link carry channels for a harness that speaks none?
The channel plugins already make the bot and pair its owner; for a
harness that answers a chat completions endpoint instead of speaking
channels (lnk-<name> endpoint), the plugin carries the messages too:
lnk-<channel> bridge sends each message from the owner there and the
answer back, streamed while it's written. Any harness that answers one
local endpoint gets every channel Link carries, and a new channel
reaches every such harness at once. Chat completions is the shape,
since most harnesses and every model runtime answer it; only the new
message is sent, never the thread's history, since remembering is the
harness's.
Where does the thread go in the request?
In a header, Link-Thread: <channel>:<chat id>: chat completions has no field for it,
and a model server ignores a header it doesn't know where some refuse a
field they don't.
Where does the carrying run?
In each channel plugin, outside the harness's sandbox, beside the agent
while it runs: a background service of its own (lnk agent bridge,
which runs lnk-<channel> bridge per channel), stopped with the agent,
or a thread of lnk agent start --foreground. The harness never holds
the channel's token, and the bridge holds no state but what the
channel needs, so a harness answering elsewhere sees no difference.
The harness's endpoint takes a key Link makes at each start, since
other users of the machine can reach its loopback.
How does a bridge know the harness answers on its port?
The endpoint proves it: each message goes on a connection of its own,
which first answers a random challenge with an HMAC of it made with the
endpoint's key, and only then gets the key and the message. A start
checks the agent's ports are free, but another program can take one in
the moment after, or while the harness's service restarts, and a bridge
would hand it the key. The other way, the endpoint on a unix socket in
a 700 folder, keeps everyone else off it, but a harness's endpoint is
an address on the loopback in the contract, which an adapter released
earlier can't change, and an adapter can't always make its harness
listen elsewhere. A challenge needs only a route and a function in the
adapter, so it's in the contract as proof, a field with no new
version, which an adapter says it answers: older ones keep working,
unchecked. The proof
is on the same connection as the message, so the port can't change
hands between them, and it's a signature of the challenge, never the
key, so asking for one gives nothing away.
Which channels does Link carry?
Telegram, Slack, Discord, WhatsApp and Signal, where people already chat: all five for any harness, so any harness can answer anywhere Link connects. How a long answer is split, and showing the agent typing, are each channel plugin's, as their limits differ:
- Telegram's answer is edited in place about once a second and split past 4,096 characters.
- Slack's is the same, split past the 4,000 characters Slack recommends; Slack shows no bot typing, so 👀 goes on the owner's message while it's answered.
- Discord's is the same, split past 2,000 characters.
- WhatsApp and Signal cap how often a message can be edited, so they show typing and send the whole answer: Signal's split past 2,000 characters, WhatsApp's past 65,536.
Where does a linked channel's device live?
In a folder of the agent's for that channel,
~/.config/lnk/agents/<agent>/channels/<channel>, which the channel's
plugin links into (lnk-<channel> link) and carries from. A linked
device's keys read and send as the account, like a token, so they stay
out of the harness's home, as tokens stay out of its settings. A move
carries what the plugin names as the link (lnk-<channel> state),
never a program it installed, which differs between a Mac and Linux:
the machine the agent left deletes its copy, so one device holds the
account. It's decided when the agent is sent, from its harness: one
that speaks WhatsApp itself keeps the link in its own state, which
moves as it always has.
Which WhatsApp client carries WhatsApp?
whatsapp-rust, a Rust port of the WhatsApp Web protocol, inside
lnk-whatsapp: nothing more to install, where Baileys, the one the
harnesses use, needs Node. It runs as a process of its own speaking
JSON lines (lnk-whatsapp engine), so the carrying is tested against a
fake of it.
How does the Slack bridge hear messages?
In Socket Mode, with the app-level token lnk agent connect slack
already asks for: the plugin opens the connection, so the user needs no
public address and Link runs no web server for Slack. Each event is
acknowledged at once, before the answer is written, so Slack doesn't
send it again; one it sends again anyway is carried once. A thread is
slack:<the direct message's channel id>, the conversation the answer
goes back to. Discord's Gateway is the same kind of connection.
Sandbox
The sandbox
How is a harness sandboxed?
On by default, and managed by Link: Seatbelt on macOS, bubblewrap on
Linux. The harness reaches its home, its agent's Home, its own
localhost ports and a local model's, and system libraries. Everything
else is a named permission: network and developer-tools are on by
default; open, read-home, localhost and lan are off. lnk agent allow and deny change them for the agent, a harness's needs
add to them while it runs, and lnk agent <harness> check tests them. The sandbox is the sandbox plugin's, so every harness runs in
the same one, with its own folders and permissions.
How does Link handle Ubuntu's AppArmor rule for user namespaces?
With a profile for bubblewrap alone, added with your consent, rather
than turning the rule off with sysctl, which would open user
namespaces to every program. Never a silent fallback to no sandbox.
Where do a harness's temporary files go on a Mac?
In its home's tmp, its TMPDIR, with a mktemp of Link's first on
its PATH. macOS's mktemp without a template uses the per-user temp
folder whatever TMPDIR says, and the sandbox refuses that folder,
which every app shares: installers calling mktemp -d (uv's, in
Hermes's) fail. Link's makes the file in TMPDIR and hands a call
with a template to /usr/bin/mktemp. Opening the shared folder, even
only its tmp.* entries, would show the harness other programs'
temporary files. A program calling the C library directly, as xcrun
does, still meets the refusal.
Is there a package cache for installs?
No. A cache the sandbox can write to is one harness's install poisoning another's.
How do you run something as your agent would?
lnk agent <harness> run -- <command>, under the harness's own policy, beside
the running agent. Without it, debugging an agent is guessing from its
log what the sandbox or its proxy did to a call. It builds the same policy the harness gets rather than entering the
running one, so it works whether or not the agent runs, and it gets its
own rules file and proxy so it can't disturb the agent's.
The proxy
How is the harness kept to its proxy?
In a network namespace of its own (bubblewrap's --unshare-net),
crossed only by Link's relays over unix sockets, rather than firewall
rules: no root, no rules that go stale when the service restarts, and
anything ignoring the proxy has no network at all. On macOS, with no
namespaces, Seatbelt allows connecting only to the proxy's loopback
port. The proxy is the sandbox plugin's.
Is an agent behind its proxy on your own computer too?
Yes, whenever it has network and the sandbox is on, not only on a box
or with an exit. network alone opens every localhost port and X11's
sockets on Linux, and localhost through ::ffff:127.0.0.1 and the Mac's
own address on macOS, which the proxy closes.
What gives an agent this machine's network back?
The lan permission, off by default: the network as it is, with the
gaps it brings, which lnk agent <harness> check shows. The proxy refuses this machine and its networks by
design, so a NAS or a model on another computer needs it. An exit
ignores it: its proxy is its only way out.
Which ports does a harness reach through its proxy?
HTTPS's and HTTP's, and endpoints you name
(lnk agent allow tcp github.com:22). A raw protocol reaches only what's
named, and a name reaches only its port, so nothing on the internet is
reachable on a port no one chose. With a list of hosts, both apply.
Where are an agent's hosts kept?
In its sandbox file, next to its permissions, and passed to its proxy as its list, whichever harness runs it: the hosts are what you trust the agent to reach, its model's and its channels', which a switch of harness doesn't change. With no list, any host, so a new agent works from its first start.
How does a harness's install reach the internet?
Through a proxy of its own, direct from this machine, for each setup
step (the installer, configure, link): no step gets the network as
it is, so none reaches this machine or its network. The health check
gets no proxy and no network at all: only the harness's own ports on
this machine's loopback.
The installer and configure, which fetch code, reach only the hosts
the adapter lists (lnk-<harness> hosts): what a harness installs is
the adapter's to know, where the running harness's hosts are yours.
link reaches any host, since a channel's servers are its own. The
exit isn't used: an install needs nothing from home.
Why does the health check reach only the harness's ports?
All it asks is whether the harness answers on them, and lnk agent status asks it of every running agent. Behind a proxy it started a
proxy too, holding the agent's keys where the agent has them, for a
request that never leaves the machine. On the loopback alone it needs
none of that, and reaches less: no internet, no other port of this
machine, never a local model's.
Where is a hosted model's key?
Added by the proxy, outside the sandbox (lnk agent keys wire). The
harness speaks plain HTTP to the proxy's local address and the proxy
speaks TLS to the provider. The alternative, a certificate authority of
Link's trusted inside the sandbox to read its TLS, would be a CA to
protect, and this way only providers the proxy knows get a key. It's on
by default for a harness a test shows reaching its model through the
proxy, since a harness that ignores the address would fail with the
placeholder: Link Harness, OpenClaw, Hermes and DeepSeek Harness,
which each run in the
sandbox against a stand-in provider in their own repository's tests
(tests/on_the_wire.rs). A harness from outside Link holds
its key, since no test of Link's runs it. lnk agent keys sets it either way for an agent.
Grants
Whose are the grants?
Two layers, each in its own file. What you grant an agent is the
agent's (~/.config/lnk/agents/<agent>/sandbox.toml, lnk agent allow): it follows the agent to any harness, so a switch never asks
for it again, and two agents on one harness can differ. What a harness
needs beyond that is the harness's (~/.config/lnk/harnesses/<harness>.toml):
what it is decides it (OpenClaw's sockets ignore the proxy whichever
agent runs it), so it's asked when an agent switches to it, held only
while it runs, never given to another harness, and the same for every
agent here that runs it. A harness's layer holds only what it asked
for: lnk agent <harness> allow and deny change a need it declares,
and refuse anything else, which is lnk agent allow's to grant the
agent; repermit forgets the answers, so its next start asks again.
Allow and deny mirror each other, so taking a need back is never a
one-way door. A harness that needs nothing runs with the agent's
alone.
Why isn't the agent's file a sandbox spec?
It holds the grants a harness's sandbox enforces, by the names lnk agent allow takes, not a sandbox spec's sections.
A harness's sandbox has permissions no section names
(developer-tools, lan as the network as it is, every localhost
port), and a section it doesn't enforce ([commands], other
[files]) would be a grant that silently doesn't hold. So the file
takes what holds, and refuses the rest, naming the line; --sandbox
needs no --sandbox-tool, since it has no flags of a spec's.
Why is no sandbox a grant of the harness?
Because a harness that can't run in one can't, whichever agent runs it:
it's asked for and kept like any other grant, and every start warns.
lnk agent start --no-sandbox stays for one start, the agent's own,
and a machine that forbids no sandbox refuses both.
Who can turn the sandbox off?
You, or your script, from outside any sandbox, by name; never an agent.
Running without one is reasonable on a machine you know, or in a test,
so a script may: --allow-unsandboxed answers a harness's need for no
sandbox at all, and nothing else, as lnk agent <harness> allow unsandboxed and lnk agent start --no-sandbox do. --yes answers the
rest of what a harness asks and never that, so a script that starts or
restores an agent doesn't turn its sandbox off by running a harness
that asks: no sandbox reads and writes all you can.
An agent can't turn its own sandbox off, or another's: what decides it
(its settings, where --no-sandbox is kept, its spec, its sandbox
file, its harness's grants, the machine's settings and the services'
units) is all in ~/.config, which no sandbox reaches, and a test
tries every way at each from inside one. A deployment that must never
run an agent unsandboxed (a cloud starting harnesses for others) sets
unsandboxed = false in ~/.config/lnk/machine.toml: then every way
there is refused, a grant made before included, and a harness that
can't run in a sandbox doesn't run. A service already running a harness
unsandboxed reads it too: it runs the harness through lnk agent unsandboxed, which checks the file at every restart and boot, and
removes the service rather than run it, since systemd and launchd
restart a service without asking lnk. The file may hold the switch
alone, written before lnk first runs: its ID and name are added.
Why does removing a harness keep its grants for another agent?
lnk agent <harness> remove removes one agent's home of it. Its grants
go with it, so adding it again asks again, unless another agent here
runs it: deleting them would change what that agent runs with at its
next start.
What happens to grants kept per agent?
The first run that finds grants in an agent's settings moves each agent's into its harness's file, and where two agents granted one harness different things, keeps all of them, saying once per harness what it has now. Keeping all of them changes nothing that ran; keeping fewer would stop an agent that worked.
What a harness asks for
Does a harness say what it needs?
Yes, lnk-<harness> needs, with why, in the harness's words; Link
doesn't change a harness to fit its sandbox. It reads the settings,
since what it needs can depend on them (a model provider, a channel),
without keys or tokens, since it runs outside the sandbox. An unknown
name is refused, so a typo never runs a harness with less than it
needs, or more than it said. It's a new command with no new version
of the contract: an adapter that doesn't answer it (an error exit, or
no answer) needs nothing more, whichever version it speaks, since an
outside adapter releases on its own schedule and may be built against
a newer link-plugin without the command.
When does Link ask?
Before the harness runs: use, its first start, a start after its
needs grew, and a move, in the terminal running it, before the agent
starts where it goes: the machine it goes to answers what it needs
there, and a refusal removes the copy put there, so the agent starts
where it was and never runs twice. The answer is kept with
the needs it answered, so it asks only for what's new, and never again
for what you allowed or took back: a deny is an answer too, until lnk agent <harness> repermit forgets them. What the agent has of its own is
never asked. Without a terminal, it refuses and names --yes, rather than run a harness
without what it needs, or with what nobody allowed. The question names
the agents here that run the harness, since the answer holds for each.
Why does every switch that widens what an agent may do ask?
Because a harness's needs are its own, so switching an agent to
another harness can give it more than it had, and the sandbox is only
worth what the user knows of it. lnk agent use lists each need the
new harness holds beyond the old one's and the agent's own, with what
it opens, beside what the new harness asks for now, and asks once, in
the warning color; no keeps the agent where it was. It asks at every such switch, not once
per harness: the agent and the harness it leaves are what change, and
a grant already answered for a harness is still new to an agent coming
from one without it. Less is said in a line and asks nothing.
How does a harness say what running it risks?
By a risk of its own in needs: a name, and why. Some of what a harness
does opens a door no permission names, such as a port of its own that
asks for no credential, which every user of a Mac reaches on its
loopback. Link can't know each harness's ports, and the fix is the
harness's to make, so the adapter names the risk and Link asks for it
as it asks for a permission, then hands the adapter those allowed
(allowed) as it's configured. A risk the harness can run without
(OpenClaw's managed browser) says what it does without it, and only a
yes in a terminal turns it on: off is safe, so --yes and no terminal
leave it off rather than fail. One it can't run without (Hermes's
WhatsApp bridge) is asked as a permission is, and taken back, the
adapter refuses to start. Its name is refused where allow or deny
takes the name for something else, and the field is new without a new version:
an older Link refuses an answer with it rather than running without
asking.
How does an adapter know where its ports are?
Link tells it whether its harness runs behind its proxy (proxied),
not which permissions it has. On Linux behind its proxy the harness has
a network of its own, and its ports other than those the proxy serves
stay there; without it they're on the machine's loopback. No sandbox,
lan, and anything later that takes a harness off its proxy all come
to that one fact, so an adapter that names a risk by it needs no change
when a new way off the proxy comes. The adapter adds what it knows:
that macOS has no network of the sandbox's own, and a lan it asks for
itself, which needs hears before it's granted. The field is new
without a new version, and a Link from before it never sends it, so an
adapter takes its absence as no proxy and asks: a question too many,
never a port opened unasked.
Can a harness ask for less than the defaults?
No: a harness says only what it needs beyond them. A harness with no
use for network runs with it unless you deny it.
Does the ask cover hosts?
No: it covers permissions and no sandbox, the grants that widen the
sandbox past its proxy. The agent's hosts stay as they are, any until
you list them, and the hosts its installer reaches stay its adapter's
hosts.
Why are only this machine's permissions listed?
developer-tools opens Xcode's tools, which only a Mac has, and
network and localhost open different things under Seatbelt and
bubblewrap. A list that names another OS's permission, or describes one
as it works there, makes a person reason about a machine they aren't
on. So lnk agent permissions, help, and what a grant says show this
OS's own, each as it works here. Another OS's permission is still read
and taken, never refused: an agent's grants move with it between a Mac
and a box, and a script works on both.
Moving an agent
Where does an agent run?
In one place at a time, and a move carries its state, not its install.
A harness's home on a Mac holds macOS programs that don't run on Linux,
so the machine it moves to installs the harness fresh; each adapter's
state says which paths are state. The harness's own Telegram bot has
one poller, so it follows the agent.
What carries an agent between machines?
One machine's lnk agent send piped into the other's receive, through
your computer, rather than pushed to buckets and pulled on the other
side. A box then needs no cloud keys nor the agent bucket's password to
receive, nothing sits in a bucket after, and a move is as fast as the two
SSH connections. The cost is that your computer is in the middle, which
it is anyway, since you run the move there. Backups stay on buckets.
How do settings reach a box?
In the move's stream, over SSH from your computer, never through the relay.
How do the two ends of a move stay compatible?
The stream has a format number, and a manifest an older receive can't
find (lnk-move-2.json), so an old box fails instead of putting the
agent in place of its own. The formats must match, not the versions,
since a dev build has no release. move upgrades a box older than
itself.
When is a move done?
When the agent answers where it went, running, with its ID (its status --json there). Link can't see a Telegram reply, so that's the proof it
has; the files left behind stay 30 days in case it's wrong. How long is
an opinion, disk against safety: keep_moved in the machine's
machine.toml ("90d", or "never" to delete them yourself) changes
it for that machine.
What happens to the copy an agent leaves?
Its settings, state and keys are deleted once it answers elsewhere, and
its files are kept a while. A second copy would be a second truth, and a
second poller of its bot. A note says where it went, and start refuses
its ID.
What does a move replace?
The state wholly, the files folder additively. The harness's state is its own, so the stale copy where it arrives is replaced; the files folder is shared with you, so a move overwrites files but never deletes them.
Which model does an agent use after a move?
Each machine keeps a local model it can reach. A box without a GPU
can't run the Mac's, so it needs a key once (--key). Back on your
computer, your own model is used again if it answers.
Whose word does a move take for what the agent may do?
The machine running lnk agent move's, never the stream's. The stream
is written by the machine the agent leaves, and when that is a box, the
box would choose what the agent may do on your computer: its whole home,
its loopback, its network past the proxy, a program its channel's plugin
runs outside any sandbox. So the computer running the move sends the
agent's grants in the first line when the agent leaves it; an agent
coming back keeps the grants it has there; and one arriving any other
way gets the stream's, but for read-home, localhost, lan and
open, its endpoints (tcp) and its tools that run outside (tools),
which the receive names so lnk agent allow can give them. A
harness's grants never come from a stream: what the machine running
the move asked about, and was allowed, is granted where it lands
(lnk agent needs --allow), for what the harness asks for there; of
the harness's grants it sends, only what the harness asks for there
(its needs on that machine) is kept, since a grant there holds for
every agent that runs the harness, and the file here may hold more than
this agent's harness needs anywhere else. What of a
channel's folder comes is what the receiving machine's own plugin
names, and a model on the loopback comes only from the computer running
the move. The rest of the stream (the harness's state, the files) goes
where only the sandbox reaches.
Are hard links in a harness's folders carried?
Yes. Memory, moves and backups keep to their folders by path: they never follow a symbolic link, but a hard link is a regular file, read and carried like any other. Package stores such as pnpm's and uv's make hard links in ordinary folders, so refusing files with more than one link would break harnesses that use them. A harness can link only a file its sandbox shows it, and on Linux the sandbox's bind mounts stop a link across them.
Boxes
Why does moving to a cloud start a box without asking?
lnk agent move aws says where the agent should run, and a box is how
it runs there: asking again would only stop the move halfway. The move
says it's starting one, billed by the cloud, before it does. With one
box there that this computer made, that's the box; with several, move can't know which, so it
lists them and asks for a name.
Why does a move with --image always start a new box?
An image is where a new box's disk starts, so lnk agent move aws --image web-ready asks for a box made from it, even where you have a
box already: one of those wasn't made from it, or was and has changed
since. To move onto a box you have, name the box. The image is the
setup a box needs before an agent comes, done once and saved, so the
agent lands on a box ready for its work.
Why does a cloud's name take only a box this computer made?
A box found in a cloud is yours by its lnk-owner tag, which anyone
who can tag machines in a shared account can set. The agent carries
its keys, tokens and a new bucket key, so lnk agent move aws takes
its one box unasked only when this computer made it, which lnk box list --json tells by its created time. A box found by its tags is
asked about on a terminal, and without one the move stops and names
it: typing a box's name is the choice the tag alone can't make. Having
reached a box once doesn't count, since lnk agent list reaches every
box it lists.
Who makes the ID of an agent that runs on a box?
The machine holding its state, never the computer that moved it. A computer whose settings, from before agents had IDs, say its agent is on a box keeps only a note of it, and learns its ID from the box, so one agent never gets two.
Why does a box's lnk agent list show only its own agents?
Giving boxes keys to find each other would let a box broken into reach your account and your other boxes. Your computer shows them all.
How does an agent on a box exit from home?
Through the vpn plugin's exit: ssh -R, a SOCKS proxy on the box
(vpn). With your computer off the proxy isn't there,
so it fails closed: no home, no internet, never the box's own address.
Who keeps the exit?
lnk vpn, not the agent. The exit is useful with no agent and on any
machine you reach, so lnk agent exit home holds it for the agent, by
its ID, and gives it back when the agent leaves or its exit is off.
Agents on one box share its exit, and a move gives the old box's back.
Backups
Where does a backup keep the harness's state?
In one archive in the files folder, .lnk-backup/<harness>.tar, pushed
like any file, rather than in a third bucket per cloud. lnk bucket
knows two folders and nothing about harnesses, and the state is the
harness's own files, which it can read anyway.
What does a backup replace in the clouds?
The archive and every file that changed since the last backup, since the machine running the agent holds its latest copy. A conversation grows every turn, so keeping the old copy would leave it out of every backup after the first. Nothing is deleted from the clouds: a file gone here stays there.
What does going back to a backup replace?
The harness's state wholly, and nothing else. What it learned since is what went wrong, so it's gone. The files folder is yours and keeps its files, and the keys and settings are Link's and never in a backup. The state is unpacked aside first, so a broken archive leaves it as it was.
What does restoring a lost agent bring back?
The agent's ID, files and its harness's state, not its keys. The model key and bot token are asked again, so no key sits in a bucket, even encrypted.
What does deleting an agent keep?
lnk agent delete takes everything Link keeps for the agent on this
machine, as a move away does, but leaves no note of where it went. Its
buckets stay: deleting an agent is often a mistake found a day later,
and lnk agent restore brings it back from them, as for a machine
that's gone; lnk bucket delete deletes them. Its files are the
person's, so they go only when the person says so: kept, they're put
aside where a moved agent's are, for as long, and a files folder they
chose stays where it is. Without a terminal, --yes deletes the agent
and keeps its files unless --files says otherwise.
Memory
Link Memory
What is Link Memory called?
Link Memory, the plain word, no brand. You type lnk agent memory use link; its program is lnk-link-memory and its plugin link-memory,
mapped the way Link Harness's is (MEMORIES in the harness plugin).
How does your agent reach its memory?
As a tool outside its sandbox, through the bridge every tool uses (lnk sandbox tool add), at $LNK_MEMORY: one way in for every harness, the
calls logged and gated by the proxy. The tool is the agent's own,
memory-<agent id>, defined again at a start when its settings, the
engine or Link changed since, or the tool is gone, so it follows the
agent to another machine and a new release of the engine. The harness
gets one variable, whatever the agent's name.
Does Link Memory write?
No. It searches, reads and lists, and never changes the folder: what an agent keeps is in its files, which it writes as it writes any file. Memory an agent writes to on purpose would be a design of its own.
What does Link Memory add to a folder?
Only what's true of any folder of text: search by words and meaning, filters, what changed since, links, a size limit. It assumes no kind of note (people, projects, facts), runs no model over the notes, and imports nothing. An engine built around one way of working plugs in by the engine contract instead.
Which folder does memory read?
The agent's own files folder, where its conversations already are. One folder shared by several agents would be a new decision about the files layout, and agents are kept apart everywhere else.
Where are conversations in memory's folder?
In conversations/, where Link Harness keeps them, a folder per
conversation, its conversation.md the one indexed, never the event
files. Memory follows the harness, and an end-to-end test holds the two
together. A search says which results are conversations, so a document
isn't lost among the chats about it.
What does a memory engine's contract fix?
Only how the engine starts: install, and command <folder> <state>
printing the command that serves MCP over stdio and which calls only
read (link_plugin::memory). The bridge passes each server's tools
through, so no engine's names are translated, and a harness reads each
tool's description. Engines compete on what they serve, not on a shape
Link picked.
Where is memory's index kept?
In the agent's folder, Memory/<engine>, beside its files and
harnesses: no harness reaches it, and it goes with the agent's folder
(lnk uninstall with agents). Link's settings folder was the other
place, but every sandbox hides it, the engine's included.
Memory's sandbox
Why does the memory engine run in a sandbox too?
It runs outside the agent's sandbox, as every tool does, but it needs nothing a tool usually runs outside for: no logins, no desktop. So it gets the least: it reads the files folder and its program, and writes its state folder. Link Memory already reads nothing else; the sandbox makes that true of any engine.
Why is memory's model served outside its sandbox?
Link Memory searches by meaning with a model lnk model serve serves,
and running that from inside would need lnk, the models plugin and
what they run, so its sandbox read the whole folder its program is
in. Instead Link starts the model server for it outside, with each
session of its tool, and gives it the address, as a harness is given
its tools'. Its sandbox reads its own program and nothing beside it.
Why does Link Memory's sandbox have the network?
To reach its embedding model on this machine: on Linux, without
network a sandbox reaches none of this machine's ports. The other
way, the proxy's filtered path to a model, would give the engine a
proxy of its own for one port. It still reads only its folder, and
writes only its state.
Search
How does memory search by meaning?
With an embedding model on your machine, served by lnk model serve,
so memory stays keyless and free. A hosted model through the proxy
would be one more source, but memory is kept keyless. Each document is
split at its headings, then at blank lines, into chunks of up to about
1,500 characters, each embedded with its document's title in front.
How does memory rank what it finds?
By its words (SQLite's bm25, the title counting four times the body)
and by its meaning, the two lists merged by their ranks. Within each
list, documents within a tenth of the best score around them match
about as well, and the newest of them comes first: a newer note beats
an older one saying nearly the same, never one that matches clearly
better. A title from a file's name is embedded as its own chunk, so it
counts by meaning as it does by words. A copy (all the same words, or,
from twenty words, nine in ten of them) comes once, the others named in
its copies, the shorter path kept. A test of sixteen questions over a
fixed folder (tests/quality.rs) holds how many find their document
first, so a change can't make search worse unnoticed.
How do words and meaning merge?
Reciprocal rank fusion: each ranks its best 50 documents, and a document
scores the sum of 1 / (60 + its rank) in each. There are no scores to
calibrate between the two, and a new model needs no retuning. There is
one search, no mode to pick: matched says why each hit came back.
Where are the vectors kept and compared?
In the same SQLite index, a blob of numbers per chunk with the model's name, compared in Rust: 100,000 chunks of 1,024 numbers is well under a second, with no new dependency. Comparing is one function, so another store can replace it.
A server keeps the vectors in memory between searches. The index counts every change to its chunks, by triggers in the file, so a change by the embedding thread or another server over the same file counts too, and the vectors are read again whenever the count moved: a changed or deleted chunk's old vector is never used.
Why does embedding run in the background?
So the first index doesn't hold up a search: what's embedded is searched by meaning, the rest by words, and the reply says how far it got. It needs the index in a file, which the embedding thread opens too, and which keeps each vector from one session to the next, so only what changed is embedded again.
Which embedding model does memory ask for?
qwen3-embedding, Apache-2.0 and many languages, over the smaller
nomic-embed-text. A model under terms that aren't permissive isn't
asked for.
The agent's spec can name another, or none to search by words only.
A spec that names something that isn't a model's name is refused, so a
typo never runs another model than asked.
How does an agent catch up on what changed?
list {since}, with since on search too: what was changed or first
seen after a time, each new or changed, and what was removed. The
index notes when it first saw each document, so a file copied in with
an old date is new all the same, and keeps the paths and titles of what
was deleted, the latest 10,000. What was in the folder when memory first
read it counts as there before, not new, so turning memory on doesn't
make everything news. since takes a date, since a model writes one
more reliably than a count of seconds, or the seconds list gives. It
holds for any folder of text, as everything Link Memory adds does.
How much does a memory call give?
At most about 6,000 tokens, four characters each, unless the call asks
for another size: under the 32 KiB Link Harness keeps of a tool's
answer, so an answer arrives whole rather than cut mid-way. search
gives whole results and read whole lines, at least one, and each says
where it stopped (next), so the next call carries on from there
rather than starting again. Only a single line longer than the size is
cut, and the read says so (cut). list and links keep the same
size, leaving out whole entries that don't fit, and a title is at most
200 characters, so no one document fills an answer.
How does memory follow links?
links {path} gives what a document links to and what links to it,
from markdown links and wiki links, the two ways notes link in a folder
of text. The index keeps each link as written, resolved against the
folder, and matches it to documents when asked, so a link to a note
made later starts working without reindexing. A wiki link names a
document by its path or its file's name; several of that name, the
shortest path, as notes apps do. Nothing is followed on disk: a link
only names an indexed document, and one to the web or out of the folder
is no link. It is a fourth read-only call, so it never asks you first. A
document keeps its first 1,000 links, and none longer than 512 bytes.
