Decisions

Why lnk measure works the way it does.

The plugin and its command

Why is the watcher a plugin of its own?

It measures from outside, around the programs it measures as the sandbox is around them, and never part of them: the harness doesn't report its own memory, and an agent on OpenClaw or Hermes is measured as one on Link Harness is. Neither the harness plugin nor a harness owns that, and the machine is measured with no agent at all. It finds what to measure the way any plugin would, from lnk agent list --json and lnk sandbox traffic --json.

Why is the command lnk measure, a group of its own?

The machine is measured as well as its agents, and the watcher, its settings and the export are the measure plugin's, not an agent's. So it's a group of its own, beside lnk agent, and lnk agent list shows the two numbers you'd look for there: memory and the day's spend.

What a measure is

Why OTLP?

Every observability tool reads OpenTelemetry's protocol, so Link's measures go to Grafana, Honeycomb or your own collector as they are, with nothing of Link's to install there. It's also how a harness reports what only it can count.

Why is a measure's rollup in its name?

link.memory.used.sum, link.memory.peak.max, link.time.turn.own: whatever reads a measure knows, from its name alone, how a unit's adds up to its parent's (summed, the largest, or not at all), so a dashboard of agents sums to the machine's without knowing Link.

What is a unit?

The machine, an agent, or one of an agent's processes (its sandbox's bwrap or sandbox-exec is the kind sandbox), each by the ID it has: the machine's, the agent's, and <agent id>/<pid> for a process. An owner and teams join as attributes of their own. An agent from before IDs is <machine id>:<name>. A process is listed under its agent, not its parent process: what an agent costs is the sum of them.

Why is the store on the machine not OTLP?

OTLP's JSON spells each measure's name, unit and kind out at every point, about eight times the disk of the numbers. The watcher keeps each look as one short line of numbers by unit and measure, and speaks OTLP where it crosses programs: from a harness, and to your endpoint. A look keeps the machine and each agent, and each process only when your settings say (processes, off in Link's), since it multiplies the disk by an agent's processes: a trade, so a setting.

How long is it kept?

A trade, disk against history, so it's the watcher's settings, not a constant: Link's (a look every 10 seconds, each kept a day, a minute's summary kept 30 days) are a file the plugin ships, and yours win where you set them. They're the machine's, not an agent's: every agent here shares one watcher.

Where each figure comes from

Why does a harness report through a file?

It runs in its sandbox, which writes only its home and reaches no port of this machine's. So lnk agent start names a file in its home (LNK_MEASURES), the harness appends a line of OTLP a turn, and the watcher reads on from where it left off. The harness moves it aside past 1 MiB, so it never grows.

Why does network come from the proxy?

An agent's traffic leaves its sandbox only through its proxy, which counts every byte each way for the wire's log already. So it's what crossed the proxy, not a guess from the machine's interfaces, which every program shares. An agent with no sandbox has no proxy, and no network measure.

Why do tokens come from the proxy?

Only Link Harness counts its own tokens; OpenClaw, Hermes and DeepSeek Harness don't say theirs. Every agent's model requests already pass through its proxy, which adds the key, and every provider's answer says what it took. So the proxy reads that usage as the answer passes, and every agent's tokens are counted the same way, as the provider counts them, whichever harness it runs. It's read as the answer goes by, never held back: a stream's events one at a time, looked at only when one names usage, and a whole answer scanned for its top-level usage without being kept.

Reading runs inline, as the proxy writes each piece to the program: a stream's event is held only until it ends, at most 4 MiB (one past it isn't read, and its request counts as one without usage), and a whole answer only its top-level usage, one value a key.

Why one source for tokens?

Link Harness reports its tokens too, and its requests pass through its proxy: counting both would count each token twice. An agent whose proxy carries its model takes its tokens from the proxy alone, and from its harness everything else (turns, spend, a turn's times). An agent with no sandbox has no proxy, and its harness's count is the one there is. The proxy's is the one kept where both are, so every agent is counted alike. The watcher remembers which agents their proxy counts, as each last ran, so a turn reported after the agent stopped isn't added on top.

The cost: an agent counted by its proxy loses the tokens of any model it reaches some other way, such as a hosted API through a plain CONNECT with a key of its own, which the proxy can't read.

Why is a stream without usage counted, not changed?

An OpenAI Chat Completions stream gives its usage only when its request asks. The proxy could add the ask, but a request the harness didn't send could change the answer it gets: a last event it doesn't expect. So nothing is added, and an answer that gave no usage counts as a request without it (link.requests.unmetered.sum), so a gap is seen as one rather than as fewer tokens.

Why are a model's answers asked for uncompressed?

A compressed answer can't be read as it passes without unpacking it, which a proxy that only passes it on shouldn't do. So a request for a model's answer asks for it uncompressed (Accept-Encoding: identity), which providers honor and a harness reads either way; a model's answer is text, and small beside what a harness downloads. One that comes compressed anyway counts as a request without usage.

Why are disk read and written the control group's?

On Linux, an agent's service's control group counts every process it ever started, even those that exited between two looks, as a turn's tool server does. A Mac has no control group, so there it's the processes running at the look.

How is an agent's memory counted?

An agent is measured from outside, so every process it runs counts, whichever harness it is. On Linux that is its service's control group, which holds every process it starts, and on a Mac the tree under its processes. Memory in use is PSS on Linux, where pages shared between processes are split between them, so a sum doesn't count a shared page twice. On a Mac it is the footprint, as Activity Monitor counts it.

What a system calls a process is what the process chose: a script's file name, which an agent may take from a conversation, or any name a program sets itself. A name taken from it would carry those words into what's shown, kept and sent, and give a harness with no network a way to write into your endpoint. So a process is named only by one of a short list of programs (Link's own, the sandboxes', and what harnesses run on), and any other only by its ID.

Why is a harness's point capped?

What a harness reports of itself is its own say, so it can claim anything: a turn of 1e308 tokens. Two such points sum to infinity, which JSON can't write, and the look or minute holding it would read back as nothing, every agent's with it. So a point past what a turn could be is dropped, and every sum stops at the largest number there is.