Get Started

How Link's code is laid out, what may depend on what, and where new code goes. Why it's built this way: Philosophy and Decisions.

The core and its plugins

The core architectural decision: lnk is a small core, and everything Link does is a plugin, the way git runs git-<name>. It is also the first thing a user meets: install.sh installs the core alone, and lnk up asks which plugins to add (docs/en/plugins).

  • A plugin is its own program, lnk-<name>, next to lnk; lnk <group> ... runs it. What a user installs is exactly what runs, and one plugin can't break another.
  • The core only installs, runs, updates and removes plugins. New behavior goes in a plugin, never the core.
  • Plugins use each other only by running lnk <command> and reading a documented output (docs/en/dev/contracts.md), never by linking each other's code or reading each other's files.
  • Each of Link's plugins ships as its own signed archive, at the core's version, and so do Link Harness, Link's tools (Link Memory) and the OpenClaw, Hermes and DeepSeek Harness adapters, built from their own repositories; a harness from outside Link ships from its own repository, signed by its own key.
  • Code goes in the plugin whose purpose it serves, and a plugin's purpose is its name. Before adding code to a plugin, ask: would I still use this code without installing the plugin it serves? Does this plugin have to know another's files, API or quirks? If either is yes, it goes in the plugin it serves, or in a plugin of its own when several need it and none owns it (the login is accounts, not the tunnel's, because boxes need it too). The same holds for link-plugin and the core. The test worked through: "Where code goes: the seam test" in docs/en/dev/plugins.md.

Layout

docs/ (the docs of the core and its plugins, in English in docs/en/; the website, link-web beside this folder, renders them) and src/ (a Cargo workspace, Cargo.toml at the root), organized by where the code runs:

FolderRuns onShips as
src/server/The public internetlink-relay, plus deploy scripts in src/server/deploy
src/local/The user's machinelnk (cli, the core) and its plugins (plugins/<name>)
src/shared/Both sidesThe protocol library, and release-signers: the keys that may sign a release
src/demos/A developer's machineDemo binaries, never shipped
src/tests/CINothing (e2e: end to end, architecture, licenses, docs; conformance: the kit every harness runs; fixtures/mini-harness: the harness the core's tests run on)

Dependency rules

src/tests/e2e/tests/architecture.rs enforces these:

  • link-protocol and link-plugin depend on no other link-* crate.
  • link-relay and link-agent depend only on link-protocol. The server and the local machine never depend on each other.
  • Plugins (link-plugin-*) depend only on link-plugin and link-agent. They never depend on each other: one uses another by running lnk <command> (link_plugin::lnk()) and reading a documented output, such as lnk model serve --json.
  • link-cli (the core) depends only on link-plugin; it runs plugins as programs and links none of them.
  • Demos depend on no link-* crate.
  • The mini harness and the conformance kit depend only on link-plugin.

A new crate goes in members of the root Cargo.toml, and in its [workspace.dependencies] when another crate depends on it, and in allowed() in architecture.rs if it is a new kind.

Where does new code go?

You are adding…Put it in
A message or field on the agent↔relay wiresrc/shared/protocol (see "Changing the protocol")
Public edge behavior: routing, auth, limits, domainssrc/server/relay
Hosting the relay: TLS, Caddy, systemd, updates, backupssrc/server/deploy. Scripts stay idempotent, pass shellcheck, and those run on a Mac work with bash 3.2
Connection behavior: reconnects, multiplexing, several services per connectionsrc/local/agent
Your login to the relay (lnk auth): logging in, its token, who you aresrc/local/plugins/accounts. Other plugins ask lnk auth ... --json for it, never its file
Tunnel behavior on the user's sidesrc/local/plugins/tunnel
Finding, serving or managing modelssrc/local/plugins/models
Installing, configuring, running or switching harnessessrc/local/plugins/harness
Where an agent's traffic leaves from (lnk agent exit), and the filtering proxysrc/local/plugins/harness (which upstream: settings.upstream, moving.rs), the sandbox plugin (proxy.rs: a sandbox in its own network, Link's proxy the only way out) and the vpn plugin (src/local/plugins/vpn: lnk vpn exit <machine> home, this computer as a machine's exit, held by the agents using it; for a box through lnk box forward, SSH home). The exit is always the user's own computer
Moving an agent between machines (lnk agent move)src/local/plugins/harness (moving.rs, transfer.rs), plus each harness adapter's state. One machine's lnk agent send piped into the other's receive, over SSH (lnk box run); never the relay
Support for one channel (Telegram, Slack, Discord, ...), where you talk to your agentA new plugin src/local/plugins/<channel> answering connect, describe and rule in link_plugin::channel (making the bot and pairing its owner, saying what the harness keeps of it), plus PLUGINS (core) and each adapter that speaks it; the harness plugin names no channel (see "Adding a channel" in docs/en/dev/plugins.md)
Link Harness: its turns, its conversations, its window, calling toolsLink Harness's repository (link-harness), not this one, with its pages (link-harness/docs/en/harness). The core's tests run on the mini harness (src/tests/fixtures/mini-harness), and every harness passes the conformance kit (src/tests/conformance)
What the adapter contract asks of every harnesssrc/local/plugin (link_plugin::harness), answered first by the mini harness and checked by the conformance kit
A tool for agents (an MCP server any harness calls), or Link Memory: what it indexes, its search, its MCP toolsLink's tools repository (link-tools), not this one, with its pages (link-tools/docs/en/memory). lnk-link-memory serve <folder> is an MCP server over stdio; it reads only its folder, and never follows a link out of it
Support for one memory engineA plugin of its own (Link's in link-tools) answering install and command in link_plugin::memory, plus MEMORIES (harness settings), PLUGINS and needs() (core) (see "Adding a memory engine" in docs/en/dev/plugins.md)
Support for one harness (OpenClaw, Hermes, ...)Its own repository, not this one: an lnk-<name> answering the adapter commands in link_plugin::harness (with contract), released and signed with its own key, added with lnk plugin add <url> (see "Adding a harness" in docs/en/dev/plugins.md). It runs sandboxed with HOME = the harness's home folder: write everything under link_plugin::home_dir(). What's specific to it is its adapter's answer through the contract. OpenClaw, Hermes and DeepSeek Harness ship with Link: they're named in PLUGINS (core) and KNOWN (harness settings), and nowhere else under src/ (architecture.rs)
What a sandbox can and can't reach: the Seatbelt and bubblewrap rules, permissions, the probes (lnk sandbox check), lnk sandbox runsrc/local/plugins/sandbox; the policy and permissions a caller passes are link_plugin::sandbox. What one harness may do (its folders, ports, which permissions it has) stays in src/local/plugins/harness. Anything else that should run confined builds a Policy and calls link_plugin::sandbox::wrap
Measuring agents and this machine (lnk measure): what's read from outside, the watcher, what it keeps, the exportsrc/local/plugins/measure. The measures and how they cross programs, OTLP, are link_plugin::measure; what a harness counts itself it appends to LNK_MEASURES (Link Harness's repository for its own)
Files to the user's clouds (lnk bucket)src/local/plugins/bucket. Cloud keys stay on the machine; files never pass through the relay
Connecting cloud accounts (lnk cloud), and machines in them (lnk box)src/local/plugins/cloud and src/local/plugins/box. Neither knows which cloud: that's the adapter. A box is a machine running lnk; what runs on it belongs to the plugin that runs it locally
Support for one cloud (AWS, Google Cloud, ...)A new plugin src/local/plugins/<name> answering the adapter commands in link_plugin::cloud, plus KNOWN there, PLUGINS and needs() (core) (see "Adding a cloud" in docs/en/dev/plugins.md)
A new part of Link (a new lnk group)A new plugin src/local/plugins/<name> (see "Adding a plugin" in docs/en/dev/plugins.md)
A new kind of thing to expose (whisper, ...)The owning plugin serves it on 127.0.0.1 via serve <target> --json, like lnk model serve; the tunnel runs that for lnk tunnel open <target>
A new lnk commandThe plugin owning its group plus its line in COMMANDS in src/local/cli/src/main.rs (a new group also in GROUPS)
What lnk uninstall stops and deletes of a pluginThat plugin, answering uninstall (link_plugin::uninstall); the core only asks
Something every plugin needssrc/local/plugin, only if small and truly shared
A test spanning server + local + demosrc/tests/e2e/tests/
Websitelink-web, beside this folder, not this one. It talks to the relay only over HTTP

Prefer a new plugin over growing the core or another plugin. Keep crates small and names literal. Adding a plugin: "Adding a plugin" in docs/en/dev/plugins.md.

Changing the protocol

Relays and installed CLIs upgrade at different times, so old peers must keep working. Prefer additive #[serde(default)] fields (no version bump). Anything breaking bumps link_protocol::VERSION, and the relay keeps serving agents down to MIN_AGENT_VERSION. New behavior is negotiated in Hello/Welcome, like flow_control. Details and tests: docs/en/dev/protocol.md.

Principles

  • Every engine is swappable behind a documented contract. A harness is any lnk-<name> answering the adapter commands, so closed code stays in its own repository, and Link never names it.
  • Link owns what stays the same when the harness changes: settings, the files folder, the channels, switching, and which machine runs it. A harness that speaks a channel keeps speaking it; for one that speaks none, Link carries the messages.
  • Work runs where it belongs. A box routes, a model cloud thinks, a bucket remembers, a scheduler wakes. Nothing that belongs elsewhere runs on the box.
  • Link's harness runs no code. Tools are MCP servers outside it, sandboxed, in any language, and shared by every harness. Keys never reach it: the proxy adds them.
  • Turns hold no state between them. Any process on any machine can take the next turn.
  • Conversations are append-only. An event log, never rewritten, and the prompt sent the same way so the provider's cache holds. One layout on disk and in a bucket, in plain files a person can read, behind a storage seam. Tokens are always recorded, and cost only from a real rate.
  • Build the seams, not the implementations. An interface with one implementation; the others aren't built until something needs them.
  • Written for the densest deployment, run at the safest. Code is written as if many agents share one process (the agent always an argument, every path from one function, a test that one agent can't reach another's), and runs one process per agent.