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.

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.

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.