Security

What lnk measure reads, keeps and sends, and what it never does.

Never content

A measure is a number, never a message, prompt, file, tool argument or answer: each part of what's measured is held to that.

A measure and its unit

A measure is a number of a measure Link names, of a unit named by an ID, a kind, its parent's ID and a name: an agent's or machine's name, or a process's program. An agent's owner and teams are their IDs. There's no field for text. A name or ID is kept only if it's letters, digits and ._:/@-, at most 64 of them.

A process's name

A process is named only by a program on Link's list (lnk and its plugins, bwrap, sandbox-exec, node, bun, deno, python, python3, uv); any other has no name, only its ID. What a system calls a process is what it chose: a script's file name, or anything it set itself, so a name taken from it could carry a conversation's words, or a sandbox's, to wherever measures go.

Names that go with them

An agent's and the machine's names are yours, and go with their measures to your endpoint, as does each agent's owner (link.owner.id): your account (acct-<id>), or <user>@<machine> without a login.

What a harness reports

What a harness reports of itself is read as the same schema, and everything else is dropped as it's read: a measure Link doesn't name, a description, an attribute, text where an ID goes. So no message, prompt, file, tool argument or answer reaches what's kept, shown or sent, even from a harness that wrote one there.

What a harness may say

A harness may report only what it counts itself (a turn's tokens, cost, calls and times); what's measured from outside, and what only its proxy counts (link.requests.*, and the tokens written to the provider's cache), it can't say.

How many values it keeps

Of each measure kept one value at a time (a turn's times), a look takes at most 64 of a harness's, and a minute's summary and each hour of the day keep at most 64 more, a spread of them standing for the rest, so a harness can't fill the disk or the watcher's memory with them.

An agent's tokens

An agent's tokens are counted by its sandbox's proxy, from the usage each model's answer gives as it passes: six counts a proxy (requests, those without usage, and input, output, cached and cache-written tokens), never a word of a prompt or an answer.

What the proxy reads of an answer

It reads at most 4 MiB of a stream's event and one value of each usage key of a whole answer: only the events that name usage, and a whole answer's top-level usage, and keeps their numbers. A count past a billion tokens is taken for what isn't so, and counted as an answer without usage.

Link Harness derives its measures from a turn's events, numbers only. A test sends a turn with a known phrase, through a tool call and its answer, and fails if its measures hold any of it.

The tests that hold it

Another test writes the phrase everywhere a harness could, and fails if anything kept, shown or sent holds it; a third sends the phrase to a model through an agent's proxy and back, and fails if anything counted, kept, shown or sent holds it.

What it reads

What it reads of the machine, its agents, their proxies and their harnesses, each from outside the agent.

Each agent's processes

As this user, from outside: on Linux /proc and its service's control group, on a Mac libproc. Nothing runs inside an agent's sandbox, and no harness is asked anything. Linux kernels without per-thread children files are read by the parent process IDs in /proc/<pid>/stat instead.

The agents and their traffic

The agents here, their processes and where their harnesses report, from the plugin that answers measure.inventory, falling back to the unchanged measure.agents answer for an older or unavailable provider. The inventory names published private traffic snapshots. Measure reads those through the shared typed contract, without commands or writes, checking live PIDs and their start times where available. They hold bytes each way, never where to.

Providers without snapshots

A provider without a published source still answers measure.traffic through its command. Agent and traffic provider discovery share the agent listing, refreshed every minute or when a running agent exits. A sample reads traffic only when a running process needs it, once for all consumers in that sample.

Snapshot ownership

The proxy still writes private snapshots at its existing cadence and removes its own file on exit. It and the sandbox command clean dead or reused PIDs' files. The observer never removes a publisher's state, creates its directory, changes permissions or substitutes traffic telemetry for authorization.

Each agent's model tokens

As its model route counted them, from ~/.lnk/measure/tokens/<agent ID>.json (600, numbers only, never a word of a prompt or an answer). A proxy's file counts for the process of its number only when it names that process's start, where the start can be read (Linux), so a file left by a proxy that crashed, or put there, isn't credited to a process given its number later.

GPUs

Each machine's GPU, where it has one: nvidia-smi (on the PATH or in /usr/bin), /sys/class/drm, each agent process's /proc/<pid>/fdinfo for its descriptors on /dev/dri, and on a Mac ioreg.

A Mac's machine

On a Mac, its CPU from host_statistics, its network and ports from netstat, and which of this user's processes listen from lsof. Each command, a GPU's too, runs at most 5 seconds and only its numbers are read.

Start and stop times

The file of an agent's start and stop times, times.jsonl in its settings, which the harness plugin writes and no harness can: only its starts' and stops' times are taken from it.

A harness's file

The file a harness reports to, in its home, which the harness can write: it's opened without following a link, only if it's a plain file, at most 4 MiB a look, and a line longer than 64 KiB is skipped. A line longer than a look reads is skipped too, to its end however many looks that takes, so it never holds up the lines after it.

What it keeps

What stays on the machine, who reads it, and how it's deleted.

~/.lnk/measure/

Only yours (700, files 600): the watcher's looks, a day of them, and a summary of each minute, 30 days; old files deleted whole. Each agent's processes are shown and sent, not kept, unless your settings keep them (lnk measure settings --processes). lnk uninstall --delete measures deletes the folder.

counted.json

counted.json there keeps what each running proxy had counted at the last look, and which agents their proxy counts, so a restarted watcher counts none of it twice.

Your endpoint's headers

Your endpoint's headers (an API key) are in settings.toml there, which only you read, and never shown: lnk measure export says how many there are. The watcher's log names only the endpoint's host.

Your endpoint's URL

An endpoint's URL can't hold a login (https://user:key@host), a query or a fragment: lnk measure export refuses it, and says to use --header-file for a login, as settings.toml written by hand is refused with one.

What it sends

Nothing, until you name an endpoint (lnk measure export --url <url>). Then every look goes there as OTLP, with your headers, at most 10 seconds a request, over https://, or http:// to this machine alone: a plain http:// endpoint anywhere else is refused, where it's named and where it's read, so your headers never cross the network in the clear. Local Link never receives it.