Decisions
Why lnk is installed, split and updated the way it is.
Why is the command lnk and not link?
The command line is "Link", but /bin/link already exists on macOS and
Linux, and package names are hard to change, so the command, its
plugins (lnk-<name>) and most environment variables (LNK_*) say
lnk.
Why are plugins separate programs and not Cargo features?
A Cargo feature still ships every plugin in the release build. Separate
programs make what you install exactly what runs, and one plugin can't
break another. The cost is a few MB per plugin, and one localhost hop
for lnk model share, where the models plugin's lnk model serve
is reached through a tunnel to its port.
Why do the core and every plugin share one version?
The core and every first-party plugin are built and signed in the same
release, one archive per program (lnk-core-<target>,
lnk-<plugin>-<target>). So lnk plugin add always matches the core,
and lnk upgrade updates them together. A lnk built from source
has no release of its version, so its plugins are built from source
too, as lnk plugin add says.
Plugins from outside Link
How a plugin Link doesn't build is trusted, and why only a harness may be one.
How is a plugin from outside Link trusted?
By its own key, asked about once and pinned after. A harness Link doesn't ship is released and signed by its own repository, as anyone's adapter would be: Link's key signs only what Link's release builds (its plugins, and the OpenClaw, Hermes and DeepSeek Harness adapters), so a fix to an outside adapter ships when its repository releases, without Link vouching for code it didn't build.
Pinned, never listed
lnk keeps no list of trusted outside keys. The first add shows the key and asks, and every upgrade
must be signed by the same key, so a repository taken over later can't
push a release to the machines that added it.
Why may only a harness come from outside Link?
Because the harness contract is the one seam built for it, with a
version both sides check. A plugin from outside must answer it before
it's installed, so lnk plugin add --url <url> can't install something that
takes over a group or a name of Link's.
What Link keeps, and where
Where Link keeps what's yours and what's this machine's, and what an uninstall takes.
Why two folders, not the XDG ones, and why ask where one went?
An owner should be able to put their Link folder in Documents, or on
the Desktop, and have everything still work. So Link keeps what is
theirs, their agents and files, in one folder, and what is this
machine's (its programs, settings, caches, sockets) in ~/.lnk. Every
path Link stores is relative to its folder, so either can move or be
copied.
Not the XDG folders
The XDG folders spread one install over three places and leave the owner's agents among other programs' settings. Only the machine folder's settings name where the Link folder is, and only once it has moved: the places of everything else are one spec shipped with Link.
Ask, never search
Link asks where a moved Link folder went rather than search. A search of the disk is slow, may open folders the owner keeps private, and may find a copy rather than the folder they use. The folder's marker holds an ID that this machine records the first time it sees it, so a moved, renamed or replaced folder is told apart from a fresh start and asked about once.
What does lnk uninstall delete?
The programs, and only the parts you pick. Removing a tool and deleting what it kept are different wishes: an agent's memory, your files and encrypted buckets' passwords can't be made again, so each is its own answer, and none is the default.
Why does each plugin say what it keeps?
So the core knows no plugin's files: it asks each installed plugin
(lnk-<plugin> uninstall --parts --json) what it keeps, by part, and
has it stop its services and delete what was picked (uninstall --delete <part>) before its program goes. A plugin from outside Link
is only told to stop, once you've said yes: its parts are never
offered, so what it keeps in ~/.lnk or ~/Link is rest.
How parts merge
Parts of one name merge (the
cloud adapters each keep some of clouds), and rest is what no
installed plugin claims, which the core deletes itself: a plugin
removed before keeps its files there. lnk plugin remove takes what
needs a plugin with it, the reverse of what lnk plugin add brings.
Why does the relay fill in its version in install.sh?
latest in the releases repo has no version to compare with, so
someone with that repo could point it at an older signed release with
a known hole. The relay, a signed binary, is built at the version it
releases with, so it fills its version into the script it serves, and
the script refuses a latest older than that. The relay can be up a
few minutes before its release is; the script then says to try again.
How does lnk say a newer release is out?
lnk up and lnk agent status, the commands a person runs to see where
things stand, say it once a day, on stderr; lnk tunnel open says it
when the relay tells it. A notice must never cost the command it rides
on: it never waits on the network, never fails, and is quiet with
--json, off a terminal and with LNK_UPDATE_NOTICE=off.
What the notice tells
So it tells
only what the last lnk upgrade --check found, and starts the next in
the background a day after the last, for the next command to tell. The
version comes from the release's signed VERSION, as lnk upgrade
reads it, so nobody but Link's release key can make every lnk say
"upgrade".
Why is lnk --help the groups alone?
Every command and every plugin made it 122 lines, which no one reads
past the first screen. It lists the groups, each in its own help's
words, and the core's commands; lnk help <group> lists a group's
commands, which are its plugin's own help, and lnk plugin list the
plugins and which are installed. A word that is no group gets the one
it most likely meant: a plural or a plugin's name (agents), a
command of one group alone (login), or a group a typo or two away
(agnet).
What a plugin needs
Where what a plugin needs, or goes with, is declared, and when lnk
asks before it installs one.
Where is what a plugin needs declared?
In the core's tables: needs(), what a plugin is of no use without, and
needs_when(), what only one of its features needs (a backup the bucket
plugin, a move to a cloud the box and cloud plugins). One place, and a
test reads every plugin's code for each lnk <group> it runs, failing on
one neither table declares, so no feature runs into a plugin nobody
said it needs. A plugin from outside Link declares nothing yet: only a
harness comes from outside, and it needs only the harness plugin.
Why does a missing plugin install without a terminal?
Because the command typed named the feature: lnk agent backup in a
script is a yes to the bucket plugin it needs. It installs from Link's
signed release, as lnk plugin add would, and says what it installs, on
stderr. Only a plugin's own lnk underneath (it carries LNK_EXE)
installs nothing and names the command instead: a check a plugin makes,
such as whether you have any cloud accounts, never installs one.
Why one question, and sometimes none?
In a terminal, a missing plugin is asked about once, naming everything
it will install with what that needs (Install cloud, accounts and box?), after the command's arguments are read. lnk <group> whose own
plugin is missing asks before: only that plugin can read its arguments.
A command that names the
plugin itself (lnk agent connect slack, lnk agent use --harness hermes, lnk agent tool add web) asks nothing: naming it was the yes.
Why doesn't the bucket plugin need the cloud plugin?
A bucket in a folder uses no cloud. So the cloud plugin is installed when a bucket names a cloud account, the way a box's cloud adapter is, not with every bucket plugin.
Where is what goes with a plugin declared?
Beside what it needs, in the core's goes_with(): a box goes with exit
and sandbox, a tunnel with models, Link harness with link-memory,
link-web and measure. lnk up takes a use's plugins with what goes
with each, and the first command of a plugin whose companions aren't
here offers them once. Link harness's scheduler, run and file aren't
companions but needs: it does without none of them.
How is a refusal remembered?
Where it was made: the tools an agent is offered, in that agent's
settings; anything else, on this machine, in ~/.lnk/offered.toml.
Neither is asked again; only the command that adds it (lnk agent tool add, lnk plugin add) does. A line said without a terminal isn't a
refusal: a later start in a terminal still asks. Memory's embedding
model is offered once, when memory is picked, and memory says it
searches by words only without it.
Why is a harness's runtime asked about every time?
It's a large download (DeepSeek Harness's is about 800 MB), so even
lnk agent use --harness deepseek, which names it, asks first, saying what it
downloads; without a terminal it downloads only with --yes.