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.

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).

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.

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

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.

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.

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.

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.

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, 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.

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.

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.

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.

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.

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.