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.
Why is a process named only by a program Link knows?
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.
