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-agent.
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 through tools
It grows mostly through tools: almost every new capability is a tool, not code in Link Harness. Its runtime adds only what a tool can't be: an event that starts a turn, an answer no request waits for, a turn that waits on an event, and what a message does mid-turn. Everything else is a tool in Link's tools.
Declared, not discovered
What an agent can do is declared, not discovered. Its spec and its sandbox file decide it when the agent is configured, and the prompt and the tools then stay the same from turn to turn, so the provider's cache holds.
What's in it
Schedules, subagents, notes the agent writes, skills, web and compaction are in Link Harness because without them it can't stand in for OpenClaw or Hermes, and those are what people use agents for.
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 the network as it
is (--network-mode host) 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.
An adapter like anyone's
OpenClaw, Hermes and DeepSeek Harness are each 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 --harness 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.
Where they're named
They're named only in the core's plugin lists (PLUGINS, needs) and
the harness plugin's KNOWN, besides what it keeps for agents, 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-agent
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-agent 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_harness_contract::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 delete --harness <harness> 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.
.lnk/configured
OpenClaw's and Hermes's adapters keep a hash in the home,
.lnk/configured. It covers the settings they were given (OpenClaw's,
those its configure reads: not the spec, what Link knows of the model,
or the environment, so a teammate added doesn't configure it again), 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's configured again
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 an agent is named, owned, given its ports and its folder, and which model it starts with.
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. The first lnk agent start makes main: most people have one agent and never name it. No
agent or box is named here, home or all, Link's own words where a
command takes a name, so lnk agent move --here always means this
machine; a computer named one of them before keeps its name.
What is named after the agent?
What people see by its name: its folder, its settings file, its log and its rules files.
Keyed by its ID
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
connections (link.bridge.<id>, lnk-bridge-<id>.service, unless one
bridge carries every agent's), its memory's and its scheduler's tools
(memory-<id>, scheduler-<id>) and its proxy's sockets
(~/.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.
Where the ID is read
The ID for these names is read from the agent's settings file alone, as it is: a service's name is asked for at any moment, and loading an agent's settings moves old sections and writes the file. An agent from before IDs is keyed by its name.
Where does the truth about an agent live?
On the machine running it, and nowhere else. Every Link is equal, so a list of agents is asked for and never kept, and losing one machine loses only what ran there. A computer that moved an agent to a box keeps only a note, so the machine holding the state is the only one that makes its ID, and migrating both ends would never make two agents with one bot.
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. The machine's part is its name in
machine.toml, made once from its host name and kept, so renaming the
host changes no agent's owner.
Signed in
Once signed in (lnk auth login), it's the account's owner ID,
acct-<id>: at an agent's next start the account replaces the stand-in,
and the agent keeps all it has. It's the start, not lnk auth login,
since signing in is the accounts plugin's, which writes no agent's
settings.
One owner per agent
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.
Where it may move
An agent nobody signed in for moves to any machine, signed in or not, and on one signed in it becomes that account's at its next start there; 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's ports
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.
A free block
A block is free of the other agents of the user running Link, and of
every port a program on the machine holds when it's given; a start makes
it free of everything else on the machine: each user's agents allocate
from 20000, so where another program already listens on one of its
ports, another user's agent say, the start moves the agent, main too,
before it starts anything, and says so.
Where it moves
It moves to a free block picked at random below 32768, where the system gives programs ports for their outgoing connections at any moment, so two users' agents moving at once rarely land on the same block.
A program on its ports
A program on the ports would otherwise be taken for part of the agent, or be handed its messages; one that takes a port in the moment before the harness listens leaves the agent unanswered, never answered by that program, since a bridge sends nothing to an endpoint that can't prove it holds the agent's key.
How are agents kept apart?
The sandbox hides ~/Link/Agents from every harness except its own
agent's folders, and ~/Link/History, what agents that left kept, even
with --read '~', since agents run as one user. Their ports stay
reachable wherever localhost is; each harness's own token guards them.
Its folder
Why an agent has two folders and how they're named.
Why is an agent's folder two, Home and Agent?
What's yours and what's the agent's are kept apart: Home/ holds your
documents and its work, Agent/ its brain, each collection a folder
(instructions/, skills/, memory/, chats/, schedule/).
A Home you pick (--files, a repository) no longer gets the agent's
conversations and instructions written into it, Agent/ stays put
wherever Home is, and each collection kept at file is one folder, so
nothing has two ways in. Agent/ is open to you: you edit its
instructions/ and skills/ as plain files.
Seen as one
The agent sees both as one, as before: its tools and its sandbox file's
[files] name instructions/, skills/ and chats/ beside
Home's own folders, and a move carries them so (files/...). One view
kept every grant written before meaning what it meant, every move
between Links reading the same stream, and every prompt saying the same
paths. . is all of Home, no longer the agent's instructions: a
repository it works in is Home, and its own folder is named beside it
where it may write it.
How does a restore know a backup carries the agent's own folder?
Its buckets say so: each backup labels them lnk-layout=2 through your
cloud accounts (lnk bucket label), and a restore reads the label
before what the backup folder holds.
Why not the backup folder
The backup folder sits in Home, which the agent writes, so nothing in it
can say how it's laid out: a version written into the harness's archive
could be removed as easily as Agent.tar itself, and so would say no
more than whether Agent.tar is there.
Why the label
The label is set with your accounts, which the agent never holds, and the key a box gets for an agent's bucket reaches its objects alone, never its labels.
Why are the agent's folders' names lowercase?
What the agent reads and writes (instructions/, skills/,
chats/, 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 folders themselves keep their names, Home
and Agent, since you browse them. chats/ 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 Home read only and write only some of its
folders only if the harness writes nowhere else there. Link harness
writes only chats/ of the agent's own folder, and says so in
its contract (folders); a harness from outside Link works in the whole
of Home, so its sandbox writes all of it and never reaches the agent's
own folder, and a start says your [files] holds only for Link's tools
there.
How does a skill reach an agent?
lnk agent skill add copies its folder into the agent's skills/, so a
skill is a file of the agent's like any other: it moves with the agent,
a backup keeps it, and you can edit or delete it there. It's named as
its SKILL.md says, as the Agent Skills standard does, and checked
before anything is copied: a folder that isn't a skill is refused,
saying why.
From a URL
A URL is a git repository, cloned once with your own git into a private folder of the system's, never the agent's, since skills are shared as repositories, GitHub's page of a folder naming one in it.
How it's copied
A copy leaves out links and anything that isn't a file or folder, takes at most 1,000 files, 64 MB and 1,000 folders at most 32 deep, and is put in place by a rename, so a harness never lists a skill half copied.
Never through a link
The harness writes skills/ while skill add runs outside its sandbox,
so every file and folder is read and written through its folder's handle
without following a link: one swapped in, in the skill or where it's
copied to, is refused, and a repository's folder that's a committed link
can't lead out of the clone. A harness that doesn't take Link's
instructions doesn't read skills/, and skill add says so.
In the prompt
Skills are listed in the prompt whether or not memory is on, so the
prompt stays the same across conversations, a narrowed subagent's
included. Memory is opt-in, so skill add says when it's off: the one
place it's seen.
Which model does an agent use with no --model?
Link's default for the provider, named, in every harness:
claude-haiku-5-5 on Anthropic, gpt-6-luna on OpenAI and
anthropic/claude-haiku-5.5 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.
One price
The three are one price, $0.10 a million tokens in and $0.50 out, so
switching provider never multiplies the bill without your saying so. A
mid-size model such as Claude Sonnet costs twenty times that, and you're
paying, so the default is the cheap one and --model names a larger
one. OpenRouter's own openrouter/auto isn't the default: it sends each
prompt to a model of its choosing and bills that model's price, which is
often a large one.
A hosted key
A start doesn't check a hosted key: that takes a call to the provider,
and start and status look only at this machine. They say the agent
is started and running, which is its harness answering; a key the
provider refuses shows at the first message, as "Your agent couldn't
answer" on your phone and the provider's error in lnk agent logs.
Commands
Why the agent is a flag, and a harness's commands a word of their own.
Why is the agent --agent, even for move?
A positional argument names what the verb acts on (the
grammar).
move acts on a destination, as in lnk agent move --here --agent work.
The agent is selected with --agent, which can be omitted when there is
only one. create, remove and restore take an agent as their
object.
Why are a harness's commands lnk agent harness?
A slot holds Link's words or names you chose, never either (the
grammar).
lnk agent <harness> allow put a harness's name where Link's commands go, so no
harness could be named start or use, and a mistyped command read as
a harness.
The agent's harness
lnk agent harness allow --network-mode host acts on the agent's
harness, the one you most often mean, and --harness hermes names
another, as --agent names another agent. remove takes the harness as
its object, since it ends it.
Messages
Which agent a message is for, how many agents share a machine, what a message mid-answer does, and how others' messages, subagents and teams reach an agent.
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.
Answered as that agent
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, unless the machine shares one
(below).
Link-Agent-Id
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. It's a header
of its own, not the ID in Link-Agent, which keeps the name every
adapter already reads and which an ID could be taken for. 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.
Many agents on one machine
The trades a person makes to pack a machine with agents: one harness, one bridge, and an agent that sleeps.
Why is one harness for an owner's agents off by default?
Because it trades a sandbox for code. An idle agent's harness is six processes, its sandbox and the harness in it, of the eight it holds; one harness for all of an owner's agents takes that down to the agent's own proxy.
What it trades
One harness for an owner's agents is one process in one sandbox holding every one of those agents' folders, and which agent a request touches is kept apart by Link Harness and its tests, where a sandbox each kept them apart by the kernel. Each agent's proxy, key and grants stay its own either way.
Whose trade
Giving up a sandbox each is a trade a person makes for their machine, a
box packed with agents, say, so it's machine.toml's shared_harness,
which no agent's sandbox reaches, and never across two owners. An agent
that reaches anything but through its proxy keeps a sandbox of its own,
since one network for all couldn't keep its endpoints its own.
Why does no process serve two owners' agents?
Because one owner's agents together are one person's, and two owners' aren't. Memory's service and the harness an owner's agents share each hold every one of those agents' folders, so each is one owner's: nothing asks for one across owners, and offering it would be a second trade on top of the first.
Why is one bridge for every agent off by default?
Because it trades a fault kept to one agent for fewer processes. Each
agent's bridge is a service of its own, with a scheduler runner beside
it: two processes an agent, idle or not. One bridge for every agent on
the machine, machine.toml's shared_bridge, carries them all from one
process, with one runner a time zone, since a runner's times are its
process's zone.
What it doesn't change
Nothing is less isolated than before: every agent's bridge already runs as you, outside every sandbox, and reads every agent's settings, and each agent still has its own way in, key and connections' bridges.
Whose switch
With one bridge for every agent, a fault in it stops every agent's connections until it's back, where a bridge each stopped one. That's a trade a person makes for their machine, so it's a switch they set, which no agent's sandbox reaches. An idle agent then holds no process of the bridge's, which is what lets one with no turn running hold none at all.
Why one scheduler runner a time zone?
Because a runner's times are its process's time zone, which is set once as it starts, so one process can't hold two. The bridge every agent shares runs one for each zone its agents are in, and hands it the agents as a file it reads again when it changes. An agent taken off it stops only once its firings have ended, so a change of agents never restarts the runner, and no job is fired twice.
Why does the bridge listen on an idle agent's port?
Because something must hear a message to wake an agent that holds nothing. Connections and schedules reach an agent at its endpoint's port, so where one bridge carries every agent, the bridge listens there. On Linux behind its proxy it does so always, in the proxy's place, passing each connection into the sandbox through the proxy's folder, and on every byte it knows the agent is busy, whatever its harness.
Where the harness listens itself
Where the harness listens on the loopback itself, as on a Mac, the bridge can't hold the port while the agent runs, so it holds it only while the agent sleeps and hands it over at the first connection, Link's connections trying a refused connection again for the moment between. Awake, the agent is busy while anything is connected to its port. Only bytes pass either way, and the endpoint proves itself with the agent's key as before.
When it sleeps
It stops once nothing has come for [run] idle_after, no answer is
still coming through its door, no turn runs and no background command
holds its lock.
What counts as an answer
The door reads how each HTTP message on a connection is framed (its
head, then a body by length, in chunks or until the connection ends),
never what it says, so an answer that pauses longer than idle_after (a
long tool call, a harness closing its tools) is never cut; what it can't
frame (a tunnel, an upgrade to another protocol) counts as an answer
coming until the connection ends. A connection its client keeps open,
idle, between answers isn't. No ceiling cuts one: an answer that never
ends, like a turn that never ends, keeps the agent awake.
How long it waits
How long an idle agent waits before it sleeps is an opinion, memory against a start from cold, so a key of the spec, and Link's is five minutes: a person's back-and-forth never waits on a start, and a start after that is too fast to notice.
How fast does an idle agent answer?
Fast enough that a person doesn't notice: its first byte reaches the model within about 200 ms of its message reaching the machine. That's the start of its sandbox and harness, or, where its owner's agents share a harness, of its proxy alone, which is why a sleeping agent stays one the shared harness serves. With Link Memory, an agent woken when every one of its owner's agents has been asleep starts memory's service too, which ends 5 seconds after its last session, and starts again in about a tenth of a second, never waiting on a model server the machine doesn't run.
What ends a process no agent owns?
Its last agent leaving. Memory's service ends 5 seconds after its last session, and the harness an owner's agents share and the bridge every agent shares 20 seconds after their last agent, so an agent started again meanwhile isn't left with none. Each is a service of its own where Link's services run, so stopping the agent that started it doesn't stop it, and in a session of its own elsewhere, which an agent in a terminal starts again when it's gone.
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 a conversation
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), judged to the millisecond, 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.
Where the lapse is read
The store reads the lapse from the agent's settings, not from the lease, because a crashed process can't say how long its lease lasts.
A crashed turn
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.
Its 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.
Where that answer goes
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.
One taker
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 steered message
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
([connection.<name>] steered; Link's: no mark). Link's spec marks no
steered message: the answer on the first message already shows it was
taken in, and a reaction is your choice.
Steered as the turn ends
A message steered in as the turn ends is answered by the turn, which takes the lease again after letting it go when the message came after its last look, else by the message's own request. So none is left unanswered.
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.
One tool and one runner
Since all three are a message from someone else, 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 connections, so the
runner runs from it, through a driver that tests swap for one in
process.
Kept as theirs
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
An answer nobody waits for goes only to the owner's own chat (an allowed user's on Telegram and Signal, a direct message on Slack and Discord), or to a group an allowed user talked to the agent in, never another chat the harness names, since a harness could otherwise make the bot message anyone.
Its groups
The groups are kept by the bridge, in the connection's folder, which the harness never reaches, and an agent whose spec never answers in groups sends to none. Signal holds one connection, the bridge's, so a message sent there goes through the running bridge.
Why doesn't the scheduler ask before it starts a subagent?
Because every subagent would otherwise wait on you, and the caps already
bound them. Whether task, schedule and cancel ask first is an
opinion, so a key of the agent's spec ([tool.scheduler] ask), and
Link's is not to ask. With it on, the three aren't allowed when the tool
is defined, so each asks as any tool's call you haven't allowed does.
Can an agent turn its scheduler off?
Yes, in its spec ([tool.scheduler] enabled = false): at the next start
the scheduler isn't defined, nothing runs its jobs, and a harness that
reads who a message is from has none. Without the key, every such
harness has one, since a subagent and a schedule are what people ask an
agent for. There's no command for it: the key is the one way, as for
every other opinion in the spec.
What wakes the scheduler?
The agent's bridge, by Link's spec, since it already runs for the
connections and restarts with the machine: the runner sleeps beside it
until a job is due. A machine where a resident process costs too much
can say [tool.scheduler] driver = "cron" instead: the machine's
calendar starts the runner once a minute, and nothing of it runs
between.
A key, not a guess
It's a key, as every opinion is, rather than something Link guesses from
the machine. A machine that runs an agent only while it answers says
address: the scheduler tells it when the jobs are next due, and it
wakes the agent then, so even a calendar's minute isn't spent on an
agent with nothing due.
The bridge still runs
Whichever wakes the scheduler, the bridge still runs, since a firing reaches the agent through its way in. Where one bridge carries every agent, one runner fires every agent's jobs in a time zone, each through that agent's way in, and an agent's started again only stops once its firings have ended, so none is fired twice.
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.
Never widened
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.
Additive
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.
Where Link Harness keeps 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.
Teams
What a team is, how its agents message each other, and who answers you in one.
What is a team?
One owner's agents working together, each with a purpose: a file of its
own (~/.lnk/teams/<team>.toml) listing its members by ID, each with a
name in the team and a purpose, a folder they share, and the opinions
they share, a spec beneath each member's own.
Where it's kept
It's kept on the machine like an agent's settings, outside every
sandbox, and only lnk agent team writes it, so no member can put
itself on a team or another on its. The commands are lnk agent team,
not a group of their own: a team is agents' settings, which the harness
plugin keeps, and a plugin of its own would read another's files. A team
isn't shared agents, where strangers talk to yours: every member is one
owner's.
Why does a member have a purpose, not a role?
Because what a member is for is a line in words, for people and its
teammates' prompts, and grants nothing. In access control a role is a
policy, what a holder may do, so the word is kept for that. A team's
file with a member's role is refused, naming purpose, the key it
takes.
How do a team's agents message each other?
Through Link Scheduler, as subagents and schedules do: send, to a
teammate by name or to the whole team. Its runner hands the message to
the teammate's bridge, in that agent's conversation with the sender (or
the team's), marked as from agent:<id>, never from you, and the
teammate's answer comes back to the conversation it was sent from, as a
message from it.
Its answer
The answer comes back as a job of its own, so failing to deliver it never sends the message twice.
Who may send to whom
Who may send to whom is the teams' files': the scheduler is told an agent's teammates at its start, and each bridge reads the files again as a message comes, taking one from another agent only when the two share a team. No message goes through a harness or the relay, and no process waits on another.
What stops two teammates answering each other for ever?
The scheduler's caps, as for subagents: each message carries its hop
count, a message and its answer each one more, and send refuses past
[tool.scheduler] hops (Link's 8), saying so to the model; the cap a
day (per_day, Link's 100) bounds how many messages an agent sends in
all. A team has no cap of its own: its file can set the scheduler's keys
for its members, as any opinion.
When does a change to a team take effect?
At each member's next start, which lnk agent team makes for the
members that run. What a team changes (an agent's spec, its tools, the
folders its sandbox reaches, whom its scheduler may send to) is fixed
for a start, so the prompt and the tools never change inside a turn, and
the provider's cache holds between them. The one thing read again each
time is who may message whom, by the bridge, so a member taken off a
team is refused at once.
Who answers you in a team?
Each member, on its own connections: a team has no connection of its
own. You give a task to the member you text, it passes parts to the
others with send, and their answers come back to it, so the one you
asked answers you.
What does a mention in a group get?
An answer, there, on every harness and connection: the owner mentioning
their agent in a group, connection 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 (<connection>:<group id>), so
switching harness doesn't change it.
Connections
How a connection is set up, shared, carried for a harness that speaks none, and how an agent's webhooks take their posts.
Connecting a connection
Where a connection's setup lives, which a harness speaks, and who a connection answers.
Where does a connection's setup live?
In a plugin per connection (lnk-telegram, lnk-slack, lnk-discord,
lnk-signal), installed the first time you connect it, rather than in
the harness plugin. You install only the connections 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 connection: its settings, stopping the agent while it pairs,
disconnect, switching and moving.
Which connections does a harness speak?
Its adapter says, with lnk-<name> connections, so connect refuses one
it lacks and use says which stop answering. A connection 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 connections Link carries.
Why does the harness plugin know no connection by name?
So a connection 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.
One map
An agent keeps one map of connections, 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 connection's name. The typed sections of Link's
own connections stay in link_harness_contract::harness, the contract
the adapters read, and a connection from outside Link sits beside them.
One map for every connection
Settings and moves hold connections in one map, bots, and lnk agent list --json prints it as bots. A Telegram token keeps its Keychain
name, bot-token, so no Mac asks for it again.
Who does a connection 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 (Signal), as its owner, and no one else as its owner: a chat you share it with is answered as its members. 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.
Shared chats
Why the owner shares a chat, and who in it may do what.
Why share a chat rather than a workspace?
The owner shares individual Slack connections, Telegram groups and Discord connections. A member's direct message stays private to the owner, and bots are refused. Each agent is the cordon: everyone in a shared chat can use all its tools, files and memory. Separate files belong to separate agents. The owner pays for every turn, with no cap per chat.
Why keep the audience in access?
[access] message lists chat principals. link-principal owns their
one grammar, also used by Link-From, without depending on another Link
crate. A connection names a person as person:<connection>:<id>, including
the workspace when its user IDs are workspace-scoped. The harness keeps
the person's identity and name beside their message, never treating them
as the owner. Sharing requires a harness that supports these senders.
Who may approve a shared agent's actions?
The owner alone shares, unshares and approves sandbox requests. An admitted member can use the agent's tools. A stranger receives only the tools the spec gives strangers, on a harness that can narrow a conversation's tools. The agent's own schedules, subagents and background commands act for its owner.
Why does sharing stop the agent?
The connection plugin takes a paired user's mention with a one-time
code, within ten minutes, while the bridge is stopped. It never writes
the spec. The command atomically edits only [access] message,
preserving comments, then restarts the agent. Sharing is done on its
machine before a move to a cloud. The three connections use the same
contract and ship together.
Link's own connection
Why Link has a connection of its own, and how its answers and Ctrl-C work.
Why does Link have a connection of its own?
So you can talk to your agent, and a script can check what it says, with
no bot, phone or third party: every other connection is someone else's
app, made for each agent, and some can't be scripted at all. link has
no account and no pairing; its owner is whoever reaches the agent's
endpoint the way its bridges do, which is its user on its machine. Its
threads are link:<name>, link:main unless named.
Why does lnk agent chat send send as a bridge does?
So a harness speaks it with nothing new: a message is POSTed to its
endpoint with its thread, key and proof, exactly as a connection's
bridge sends one (link_connection_contract::ask_for), and the
conformance kit's endpoint checks cover it. A harness that speaks its
own connections has no endpoint to send to, so lnk agent chat send refuses
it, saying so, rather than reach it another way. An agent on a box is
reached through lnk box run, its own lnk agent chat send there: nothing
new on the box.
Where does an answer nobody waited for go?
To the next chat on its thread. One to a message from others in a link
thread (a reminder) is held by the agent's bridge, in the connection's
folder, as a connection's plugin would send it to its chat. One the
harness finished after its caller went (a crash, or Ctrl-C) stays with
the harness, which keeps it in two phases: the bridge leaves a link
thread's there, and the chat on that thread asks for it, so one ask
hands it over and nothing is held twice.
What does Ctrl-C stop?
The chat's wait. A harness runs a turn to its end whether its caller
waits or not, and keeps the answer, so Ctrl-C loses nothing: that answer
comes at the next message. A message sent while that turn still runs
does what the agent's spec's [turns] busy says, as on any connection:
by default it joins the turn, and the chat waits for that turn's answer,
which answers both. Which kept answer that is, the harness doesn't say,
so the chat notes what's kept in the thread before it sends and takes
the first answer kept after: one turn runs in a conversation at a time,
so that's the turn it joined.
Carrying a connection
How Link carries the connections of a harness that speaks none, and where that runs.
How does Link carry connections for a harness that speaks none?
The connection plugins already make the bot and pair its owner; for a
harness that speaks ACP at its endpoint instead of speaking connections
(lnk-<name> endpoint), the plugin carries the messages too:
lnk-<connection> bridge sends each message from the owner there and the
answer back, streamed while it's written.
ACP
Any harness that answers one local endpoint gets every connection Link carries, and a new connection reaches every such harness at once. ACP, the Agent Client Protocol, is the shape, as every harness is driven the same way so they differ as little as possible, a harness on stdio through the hub; 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 the session's ID, <connection>:<chat id>: each thread is an ACP
session of its own.
Where does the carrying run?
In each connection plugin, outside the harness's sandbox, beside the
agent while it runs: a background service of its own (lnk agent bridge, which runs lnk-<connection> bridge per connection), stopped with
the agent, or a thread of lnk agent start --foreground; or, where the
machine says so, one bridge for every agent (lnk agent bridges).
What the bridge holds
The harness never holds the connection's token, and the bridge holds no state but what the connection 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.
Why a proof
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.
Additive
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 connections does Link carry?
Telegram, Slack, Discord and Signal, where people already chat: all four for any harness, so any harness can answer anywhere Link connects. How a long answer is split, and showing the agent typing, are each connection 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.
- Signal caps how often a message can be edited, so it shows typing and sends the whole answer, split past 2,000 characters.
Where does a linked connection's device live?
In a folder of the agent's for that connection,
~/.lnk/agents/<agent>/connections/<connection>, which the connection's
plugin links into (lnk-<connection> 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.
When it moves
A move carries what the plugin names as the link (lnk-<connection> 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 the connection itself keeps the link in its own state, which
moves as it always has.
Webhooks
Where an agent's webhook listens, whose its post is, and how long a post's id and a rolled secret are kept.
Where does an agent's webhook listen?
On 127.0.0.1 alone, in the agent's bridge, which already runs outside
its sandbox beside it and holds its endpoint's key. Anything public in
front of it is a tunnel you open (lnk tunnel open --public), so the
address a stranger can reach is always one you chose, and add never
opens one.
Its port
The port is chosen once and kept with the agent's settings, so a tunnel
to it keeps working after every restart. The listener names no
connection: each hook says which plugin checks its posts (sift), as on
a machine that packs agents, where the same plugin answers the same
sift.
Who hands it over
The bridge, not the plugin, hands a post to the agent, through the way in every message from others takes: who it's from is the webhook's own, so a plugin can't send a post as the owner's, and its tools are a stranger's.
Why is a webhook's post a stranger's message?
Anyone who holds the secret can post, and a service that signs (GitHub)
passes on what other people wrote. So the post reaches the agent from
webhook:<name>, quoted, never as its owner, with only the tools its
spec gives strangers, none unless it says, and only a harness that can
say who a message is from, and keep a conversation's tools, gets a
webhook. No answer goes back to the poster: 202 says it was taken, and
nothing the agent says leaks to whoever sent it.
How long is a post's id kept, and a rolled secret?
A post's id is kept 30 days, the longest any sender Link knows keeps its
own (GitHub's, whose posts sign no time), so one keep covers every
scheme. It's kept only once the agent's harness has taken the post, when
it answers the way in, before its turn ends: a sender gives up long
before a turn might, and waiting would hold the post's place for the
whole turn. One the harness refused, or that couldn't reach it (its
harness down), is answered 503 and let go, so the sender's redelivery
is taken rather than refused as a repeat.
A rolled secret
A rolled secret's previous one is taken for 24 hours, its expiry kept
beside it, or until roll --done: Link can't see when the sender
switched without carrying which secret matched through sift, and a day
is enough to paste the new one in.
Sandbox
How a harness is sandboxed and kept to its proxy, how its bridges know who calls, and whose its grants are.
The sandbox
How the sandbox runs on each OS, where a harness's temporary files go, and how to run something as your agent would.
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.
Its grants
Everything else is a grant, a section of a sandbox's
spec:
the internet through its proxy and, on a Mac, the developer tools are on
by default; your home, your browser, this machine's other ports, your
network and the network as it is 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 grants.
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 a harness is kept to its proxy, what gives it this machine's network back, and how its install reaches the internet.
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 [internet] and the sandbox is on, not only on a
box or with an exit. The network as it is, with no proxy, 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?
[network] mode = "host" (lnk agent allow --network-mode host), 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 that [lan]
through the proxy doesn't reach needs it. An exit ignores it: its proxy
is its only way out.
Does run go out through the agent's route?
Yes: what lnk agent show --permissions lists is what every path of the agent
runs with, so a program run starts leaves as the harness does. With a
route (an exit), each call's proxy goes out through it, and [network] mode = "host" and [lan] reach nothing past it, as for the harness.
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, as [internet] hosts, 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 connections', which a switch of
harness doesn't change. Link's file has "*", 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.
Which hosts
The installer and configure, which fetch code, reach only the hosts
the adapter lists (lnk-<harness> internet-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 connection'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.
When status runs it
status runs it only when nothing on the harness's ports answers a
plain request with success (a 2xx): a port its proxy holds closes
without an answer, so one that answers with success is the harness, up,
known without a sandbox, and status costs a running agent about what
status --json does. Any other answer (a 503 from a harness without
its key) is the health check's to judge.
Why do the scheduler and run take the conversation from the harness?
Because nothing else knows it. Which conversation a call is made in, and
its hops, come in the call's _meta (link.local/thread,
link.local/hops), which the harness writes. The sandbox's tool proxy
forwards a call as it comes, and its secret is one per sandbox, not per
conversation, so it has nothing to check them against.
What that trusts
Link Harness writes _meta itself and runs nothing the model writes,
and run's commands reach no tool. A harness that runs what the model
writes (a shell) could name another conversation, or reset its hops,
bounded then by the caps a day alone. A source the proxy could trust, a
token per message the bridge makes and the proxy checks, would change
the bridge, every adapter and the harness contract; the trust is stated
instead (Security).
Where is a hosted model's key?
Added by the proxy, outside the sandbox (lnk agent secret wire). The
harness speaks plain HTTP to the proxy's local address and the proxy
speaks TLS to the provider.
Why not a CA of Link's
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.
Whose harness holds it
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. A
harness from outside Link holds its key, since no test of Link's runs
it. lnk agent secret sets it either way for an agent.
Bridges
How a bridge knows which agent calls, which key proves it, and why every bridge and store driver runs as it does.
How does a bridge know which agent is calling?
By a proof made with the agent's own key, never the operating system's word. Every way out of a sandbox but the proxy is a bridge (a connection's, the way in, the store bridge), and each call to one is the same three things: the agent's identity (its ID and owner, as Link already defines them), the action, and an HMAC of a challenge the bridge sends on that connection and that identity, keyed with the agent's key.
One type
One type in link-bridge for every bridge, so none tells who's calling
its own way.
Why not the system's word
A Unix socket's permissions or its peer's process say which user or program connected, not which agent, and differ from one system to the next; a harness serving several agents, or one bridge serving many, is one process to the system. A key proves only its own agent, wherever it runs, so a harness, however it's steered, acts on a bridge only as itself, and a signature gives the key away to no one. It's the endpoint's proof turned the other way, under a label of its own, so an endpoint, which signs any challenge, is never asked to make one.
Which key proves an agent's calls?
The one made at each start, which the harness already holds as its endpoint's key: bound in the machine's keyring while the agent runs, and unbound at its stop. A key that leaks then dies with the start.
Where an agent runs only while it answers
Where a machine runs an agent only while it answers, the key is made each time something there is bound to run the agent instead: that receives the agent's identity and its key as it's bound, from the machine's own store of secrets, never from the agent's state, and discards them when it stops, blank again for the next agent, which gets a key of its own. What runs an agent there is then only ever one agent, and only while bound.
Why does every bridge run on one server?
Because a bridge is a door out of the sandbox, and what keeps one standing (a deadline before the proof, caps, a busy answer, an error shape) is the same whatever it carries.
Written once
Each bridge once wrote its own socket loop, and each got some of it
wrong in its own way: a deadline on each read but not on the whole
proof, a cap counted before the proof, one call at a time, five shapes
of error. One server, in link-bridge, does it once, and one test
checks every bridge against it, running; a bridge says only what it does
with a call and how many it holds. It's a library, not a plugin: no one
uses it on their own.
Why five seconds to prove, and unproven connections apart by user?
A caller with the key proves at once: the challenge is a line, the proof a line. Five seconds over the whole exchange is far more than that takes, and short enough that a caller dripping a byte at a time holds nothing for long.
Places by user
Connections not yet proved hold places of their own, so they never take one a proved call needs. Before its proof, the one thing a bridge knows of a caller is the user at the other end, as the system names it: a machine that packs many owners' agents runs each as a user of its own. So each user gets a few places, and its newest takes the place of its own oldest: a user flooding a bridge closes only its own connections, never another user's proof, and room is kept for every user such a machine runs.
Why does send.sock check the keyring too?
A connection's bridge is handed its agent's key as it starts, and could check only that. But the keyring is where a key stops proving anything: unbound at the agent's stop, replaced at its next start. Checking both means a bridge that outlives its agent's stop takes nothing more proved with the old key, as every other bridge already did.
Why does a store driver run outside the sandbox?
Because whatever a driver needs to reach its store, a credential or a way to a database, would otherwise be in the harness's sandbox, where an agent with a shell could read it and do anything the credential lets it. Behind the store bridge, the harness asks for the store's operations alone (append if the number is free, read, the lease), as itself, and only for its own conversations; the bridge runs the driver outside, and the harness neither knows nor cares which it is.
Another driver's conversation
A conversation kept by another driver than the spec's now is read as before, the driver run where its caller runs, until it's moved.
Why does the store bridge keep a driver running?
Because a process and a fresh connection to the store for every call are most of a short turn's store calls: a turn makes a dozen or so, and each paid a process's start and a connection's handshake on top of its one request, about twice the time at 30 ms to a database.
serve
A driver whose contract says serve answers call after call on one
process and the connection it holds, so a call costs its one request. It
stays one call at a time, in order, stopped past 30 seconds as before,
and a driver that doesn't say serve is run once a call as it always
was, so no driver had to change.
Why not a library
The alternative, a library each driver links into the bridge, would put every driver's code and credentials in one process and tie drivers to Rust.
Grants
Whose each grant is, why the agent's file is a sandbox spec, and who may turn the sandbox off.
Whose are the grants?
Two layers, each in its own file. What you grant an agent is the agent's
(~/Link/Agents/<agent>/Config/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.
The harness's
What a harness needs beyond that is the harness's
(~/.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.
Only what it asked for
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; reset 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 is the agent's file a sandbox spec?
So there is one way to say what a sandbox may do. The agent's file, a
harness's grants and its needs are a sandbox
spec's
sections, and lnk agent allow and deny take the flags lnk sandbox run takes, so what you learn for one you know for the others.
In the agent's terms
The file is in the agent's own terms: [files] names the folders it
sees (work, instructions, . for all of Home), and a section a
harness's sandbox can't enforce is refused, naming the line, never a
grant that silently doesn't hold. Its [files] and [commands] hold
where Link enforces them: [files] in a harness that keeps to the
layout and in Link's tools, [commands] in each of run's sandboxes.
What does a member reach of its team's folder?
Only what its own sandbox file grants it, [teams.<team>]: the folders
of the shared folder it writes, and those it only reads, or all of it.
Two members' sets may overlap or not, so a checker can read the sources
no one else does.
Hidden but for what's granted
Every sandbox hides ~/Link/Teams, and each team's folder kept
elsewhere, but for what's granted inside, as it hides other agents'
folders, whichever harness runs the member and whatever --read '~'
allows.
The agent's grant
The grant is the agent's, not the team's, so it follows the agent like
its other grants; being on a team is still what lets it reach that
team's folder. lnk agent team add grants the whole folder unless told
otherwise. lnk agent harness check reads each team folder the member
is granted (it must) and every other team's (it must not).
Why is no sandbox the agent's setting, not a harness's grant?
The agent owns one sandbox setting. lnk agent start --no-sandbox
sets it to false, --sandbox turns it back on, and the setting persists
across starts and appears in status. A harness reports whether it can
run confined through its needs answer. A start that requires an
explicit choice refuses until the person supplies --no-sandbox. The
sandbox is enabled by default.
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, with --no-sandbox, the only way. --yes answers 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.
Never an agent
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.
unsandboxed = false
A deployment that must never run an agent unsandboxed (a cloud starting
harnesses for others) sets unsandboxed = false in
~/.lnk/machine.toml: then every way there is refused, and a harness
that can't run in a sandbox doesn't run.
A service already running
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 and a box
The file may hold the switch alone, written before lnk first runs: its
ID and name are added. It's read only when you (or root) alone could
have written it. A box needs no switch: root's /etc/lnk/kind.toml says
it's a box, and a box never runs an agent with no sandbox, since what a
person's machine.toml says loosens nothing root decides.
Why does removing a harness keep its grants for another agent?
lnk agent harness delete --harness <harness> 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 a sandbox file that names permissions?
It's read once, through one table, and written back as a spec's sections, the same table for an agent's file, a harness's grants and a move's manifest.
Never widened
What a section says as broadly is kept, and what it can't say as
narrowly is left out and said once, never widened: a localhost without
lan reached every port behind its proxy, so it's left out, and each
port it needs is named with --port; lan without network opened
nothing, and is left out too. One thing is gained, the one exception:
ports named without network, which only macOS reached, reach on Linux
from then on, relayed into its sandbox as on macOS, and that's said as
it's read.
What a harness asks for
Whether a harness says what it needs and risks, when Link asks, and what the ask covers.
Does [commands] bind the harness's own programs?
Yes, as it binds run: what lnk agent show --permissions lists is what every
path of the agent runs with, so a harness names the programs it runs,
past the program Link starts and its #! interpreter. One that runs
what it installed in its home asks for any program in its needs.
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 connection),
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.
A spec
A need is a sandbox's spec, as lnk sandbox run --spec takes it, with
no name, version or relative path, so it's shown and taken back as the
flags lnk agent allow takes.
Version 2
The contract's version 2 made needs specs: an adapter of version 1,
whose needs names permissions, is refused, saying it's older than this
Link and to update it, rather than read as something it didn't say. An
adapter that doesn't answer it (an error exit, or no answer) needs
nothing more, since an outside adapter releases on its own schedule.
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.
Kept with its answers
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 reset 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.
At every switch
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 spec names, such as a port of its own that asks for
no credential, which every user of a Mac reaches on its loopback.
Named by the adapter
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 spec, then hands the adapter those allowed (allowed) as it's
configured.
One it can run without
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 is asked as a spec 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 grants 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.
One fact
No sandbox, [network] mode = "host", 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 --network-mode host it asks for itself, which
needs hears before it's granted.
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 the internet runs with it unless you deny it.
Does the ask cover hosts?
No: it covers what a harness needs beyond Link's defaults, the grants
that widen the sandbox past its proxy, and risks of its own. The agent's
hosts stay as they are, any until you list them, and the hosts its
installer reaches stay its adapter's internet-hosts.
Why are only this machine's permissions listed?
--developer-tools opens Xcode's tools, which only a Mac has, and the
network and a port open different things under Seatbelt and bubblewrap.
A list that names another OS's grant, or describes one as it works
there, makes a person reason about a machine they aren't on. So lnk agent show --permissions, help, and what a grant says show this OS's own, each
as it works here. Another OS's table ([commands.macos]) is still read
and kept, never refused: an agent's grants move with it between a Mac
and a box, and a script works on both.
Actions
Why an agent's commands, tools, routes and CLI come from one registry of namespaced actions, and how the registry is shaped.
Why is there one kind of action?
An action is a typed request from a caller, answered outside the
caller's walls. A part that relays a protocol it does not type, such as
MCP or inference, is the same action with carries in place of
answer, and one of the two is required. A scheduled run, a webhook and
a connection's message are callers, not actions: the caller is Trigger,
which acts under the grant it was set up with, since it acts later with
no owner there.
What it costs
One registry, one way of proving a caller and one table for the proxy
replace a separate kind with its own plumbing for each. The server
branches once, between a typed answer and relayed bytes. Every place
typing cannot hold is one search for carries.
Why are the names two parts?
A name is <namespace>:<action> as a command, <namespace>__<action>
as a tool and /_link/<namespace>/<action> as a route, so one action is
found by the same words everywhere. Another harness's familiar spellings
are aliases that resolve to an action and add no handler or permission.
Why these namespaces?
Each holds one subject and is named in the singular. The ways a message
goes into a conversation, such as goal, are chat's
rather than namespaces of their own. memory stays apart from chat
because it outlives conversations and is a store of its own, and
browser and desktop are separate from web so that two-part names
can hold each.
Moving an agent
Where an agent runs, the stream it moves in, what happens where it lands, and how it's backed up.
Where does an agent run?
In one place at a time, and a move carries its state, not its install.
Installed fresh
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.
The stream
What carries an agent between machines, how its two ends stay compatible, and what happens to a message or a move cut short.
What carries an agent between machines?
One machine's lnk agent state-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.
Why does a move use each place's storage contract?
A conversation kept in SQLite and one kept as files are the same collection. Every end reads and writes them through the same collection transfer, so a move keeps events, narrowed tools, notes and scheduled jobs across places. Home stays a stream and an engine carries its index in its own format.
How do settings reach a box?
In the move's stream, over SSH from your computer, never through the relay, as the rest of the agent goes.
Why is the complete switch sealed, when SSH carries it?
SSH protects the stream on each leg, but your computer is in the middle and passes it whole: a plaintext recording would expose linked-device credentials and harness state as well as manifest secrets.
Its key pair
The receiving end makes a key pair for the switch alone. HPKE seals a fresh streaming AEAD key to it; bounded records authenticate order and a required final record proves completion. A machine drops its receiver after the move.
A hosting service
A hosting service keeps an independent receiver for restart, deletes it after commit when possible, and refuses it after 24 hours. The preliminary copy retains SSH or TLS protection. It stops passive capture; whoever holds your computer holds its SSH keys to both machines too, which a seal never could stop.
How do the two ends of a move stay compatible?
The stream has a format number, and a manifest named for it
(lnk-move-4.json), so a receive of another format finds none of its
own and says which it found, 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.
Why does a move go in two phases?
So the agent answers throughout, wherever it goes. Its files and its memory's index, the bulk of it, go first while it runs, and only what changed since goes once it's paused, which takes seconds however long its history. One protocol for every destination is less code than a stop-and-copy for boxes beside it, and a move to a box stops being minutes of silence. Each file carries its SHA-256, so what the other end holds already isn't sent again: a copy tried again, or a move back to a machine the agent left, sends only what's new.
What happens to a message sent during the switch?
It waits, never lost, on a connection Link carries for the agent. Those connections stop reading while it's paused, so their platforms keep what comes, and it's answered where the agent went. A harness that reads a connection itself isn't paused by Link: what comes there is its own, as always.
On a cloud that hosts it
A cloud that hosts agents holds what comes while the agent is paused
there and hands it over once it answers where it went, where it's kept
with the agent's settings until it's given: by lnk agent deliver, or
by its bridge as it reaches its harness, so an agent moved with
--no-start gets them when it starts.
Until it's given each
Until where it went has them, the computer running the move keeps them
too (~/.lnk/held/). One it's never given there, from a sender its
connection isn't paired with or on a connection Link doesn't carry for
it, is shown whole to whoever moved it rather than kept forever. The
switch takes seconds, and an answer from the agent beats a notice that
it's moving.
What if a move is stopped midway?
From the pause until the agent answers where it went, Ctrl-C, a closed
terminal or a stop doesn't end lnk midway: what it runs then ends, the
question it asks is answered no, and the move fails as any failure does.
A move that ends with the agent not answering where it went fails too,
and its copy there is removed.
Where it answers
A move stopped midway, or one whose agent doesn't answer where it went,
leaves the agent answering where it was: resumed on a machine, the move
ended (agent-abort) on a cloud that hosts it, so a move tried again
isn't blocked. What its harness needs where it's going is asked while
it's paused, since it's the copy there that's asked, which exists only
after the switch.
Finished later
A move that can't be ended at once is kept in its journal on the
computer running it, one entry a move, finished by the next lnk agent move or lnk agent start of the agent there. A copy stopped on a box
that doesn't answer stays an entry when the agent moves again, so it's
still removed once the box answers. An end on a cloud that hosts agents
counts as over only when its agent-abort says so: one that fails
keeps the entry, rather than lose track of a copy there.
How does a move reach a cloud with no box?
Through the cloud's adapter, which may host agents by answering the
agent-* commands, and owns its transport: its service's API, signed in
as the account. lnk only pipes the move's streams to and from it, as
it pipes a box's over SSH. The core then speaks no cloud's API, as for
boxes, and the public code names no service.
Where it lands
When a move is done, what it replaces, and whose word it takes for what the agent may do.
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 (the provider's variable, or asked for). 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 connection's
plugin runs outside any sandbox.
The agent's grants
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 '~', --network-mode host, --lan-host, --port, --browser-host,
its endpoints (--tcp), its tools that run outside (--tool) and a
program of its [commands] named by a path or * (--command), which
the receive names, as the lnk agent allow flags that give them here. A
program named alone is found on this machine's own PATH, so it stays.
The harness's grants
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.
The rest of the stream
What of a connection'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 a move starts a box, and how an agent's traffic leaves one.
Why does moving to a cloud start a box without asking?
lnk agent move --cloud 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 --cloud 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 --cloud 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 keeps only a note that its agent is on a box, 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 here?
Through the exit plugin's exit: ssh -R, a SOCKS proxy on the box
(exit).
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 exit, not the agent. The exit is useful with no agent and on any
machine you reach, so lnk agent exit --here 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 a backup keeps the harness's state, what a backup and a restore replace, and what removing an agent keeps.
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 removing 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: removing 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 remove deletes them.
Its files
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
removes the agent and keeps its files unless --files says otherwise.
Decisions of other parts
Each part keeps the decisions about its own code on its own page:
- Portable context, in the harness contract's
- Link Memory, what it writes, indexes and searches
- Link's tools, where they live and a team's tool
- Web, Run, File and Code
- Telegram, Slack, Discord and Signal, each connection's bot or app