Decisions
Why the repository's tree, its layers, its files' sizes and its crates' names are what they are.
The tree
Why each folder is where it is, and what the folders' names say of who owns the code in them.
Why does a folder say what a component provides?
So you find a component without reading a Cargo.toml, and know who
owns it. Link's own capabilities are in plugins/, integrations with
third-party products in integrations/, shared libraries in
packages/ and the framework in core/. A component goes where its
primary responsibility puts it, never where its users are: a domain,
then the plugin, then its contract and implementations. Intuitive
placement wins over a balanced tree; a category exists only once
something needs it, never for symmetry. When a component does two
things, it splits into two components rather than moving whole.
Why integrations/, not vendor/ or adapters/?
vendor/ is where cargo vendor writes copied third-party source.
adapters/ would fit, since each integration connects to Link through
a contract, but Link's own implementations (file-store, webhook,
Link Harness) implement contracts too, so the name wouldn't say who
owns what. "Adapter" already names the harness adapters.
Why do tests/ and web/ stay at the root?
tests/ holds the suites that cross subjects, and web/ is the
npm/Turbo workspace holding every JavaScript app and package. Neither
belongs to one component.
Why does docs/ hold only what spans the repository?
A page about one plugin or package lives with it, in its docs/en/.
A page about the whole (credentials, the specs overview, architecture,
developing, contributing, the code of conduct) is in docs/. Every
README links its folder's pages, so a reader finds a package's docs
from its folder.
Why don't website URLs change when a folder moves?
A page's URL comes from its path under docs/en/, and a move keeps
that path. Links from outside keep working however the tree changes.
Why is sqlite in plugins/?
It's Link's own store, not an integration with a product: it sits
beside file-store in plugins/agents/stores/. file-store is the
default place.
Why are the binding and the bridge packages?
The bridge protocol, the binding's keyring and its revoker are used by
the route, secret, store and memory contracts and the sandbox client,
across the agents and security domains, so they are shared
libraries in packages/. A package imports only packages, so the
owner check sits in the bridge beside Identity, the type it checks,
and the agent's identity contract re-exports it.
Layers
Why the group folders are ordered as they are, and how a part learns what a part above it has without naming it.
Why is a layer a group folder, not a crate?
A crate's dependencies are its Cargo.toml's, and Cargo already
refuses a cycle between crates. What Cargo can't see is a part filed
in the wrong place: an agent's names in a primitive, a box's setup in
the cloud provider. A group folder (packages/, core/, tests/,
each plugins/<domain>/ and integrations/<domain>/) is what can be
held back, tested or understood alone, so it is the unit the layers
order. Inside a group, the crates' manifests decide.
Why does a layer.toml list every layer, and no exceptions?
Each layer lists every group folder it is built on, none implied
through another, so the file alone says what the layer may use. It
states the target, never today's crossings: a list of exceptions would
make a crossing permanent. A part that would cross moves to where it
belongs, and a depends_on is never added for it.
Why are packages below core, and networking below security?
Both are choices, not facts of the code. core/plugin is built on
spec, version, fetch, component and secret-value, and
nothing in packages/ needs the framework. Security's secrets and
sandbox use networking's relay client and exit socket, while
networking needed security in two places only (the exit's address
rules and a kept secret's type), the fewer to move. Your account on a
relay (lnk auth) is built on the relay client alone, so it is
networking's too.
Why are agents the top layer?
Agents are an application of Link: they run on boxes and in sandboxes, as any program does. Nothing below knows of agents: no agent contracts in the cloud provider, no agent stores under buckets.
Why does the cloud provider know nothing of Link's?
It is the shared interface to AWS and Google Cloud: machines, images, buckets and the calls each cloud answers. Boxes, buckets and agents are built on it, so it names none of them. An agent's commands are its own contract's, asked through the provider's generic call.
How does a plugin learn what a plugin above it has?
It asks a question (link_plugin::answers), and each installed plugin
that answers it does. The asker's layer defines the question and both
its types; the one above names in its manifest the command that
answers. So the asker names no plugin above it, the types are one
source both sides share, and an answer is always current: whether an
agent runs, which accounts answered. A file the plugin above writes
for the one below would go stale, and couldn't carry an action, such as
removing a secret's copy on a box.
Files
How a file that has grown too large is moved and split, and why it may only shrink while it waits.
Why do moves and splits happen apart?
Moving is mechanical and checked by the build; splitting changes code
and can change behavior. Every move comes first, with
scripts/move.py, so each split happens once, at the component's
final home.
How does a large file split?
Where a responsibility ends: its items move unchanged into child
modules, visible to the files that call them, and a library's public
module keeps its paths, its children private and re-exported. Each
test moves with what it tests; an integration test becomes
tests/<name>/main.rs with modules beside it, keeping its target
name. A trait impl too large for one file keeps its methods in the
parent, each calling a same-named method in a child that holds the
body. The secret audit's per-file counts, and any list or test that
names a file by path, follow the code.
Why may a file over its limit only shrink?
The no-growth rule keeps a large file from getting larger while it
waits to be split, and large-files.toml names each one with its
lines, lowered as it shrinks. No Rust file is over 1,000 lines.
Names
How a crate is named, so its name says what it is and who owns it.
Why is a plugin's crate link-plugin-<id>?
The layout check, the component metadata and the release read a
plugin's id from its crate's name, and its one binary is lnk-<id>.
How is every other crate named?
link-<subject>[-<part>]: the subject is the plugin, package or
service that owns it. A plugin's own contract is <subject>-contract,
a part of it <subject>-<part> (link-sandbox-route,
link-relay-limits). Tests and fixtures end in -fixture, -check or
-e2e. One name never means two things, so the cloud providers'
contract is link-cloud-contract.