Decisions
Why Link Harness works the way it does. Link's own decisions about harnesses are on its Agents Decisions page.
Design
What Link Harness is called, how it's judged, and the seams it's built on, which let any process on any machine take an agent's next turn.
What is Link Harness called?
Link Harness, the plain word, no brand. You type lnk agent use --harness link;
its program is lnk-link-harness and its plugin link-harness.
KNOWN (harness settings) maps the name you type to the plugin, and an
adapter from outside Link stays lnk-<name>, found by its own name.
How is Link Harness judged?
By what each agent costs, idle and per message, as Link measures it
(lnk measure). So it gets lighter one bottleneck at a time, the
largest first.
How are Link Harness's seams built?
For the densest deployment, run at the safest: the agent is always an argument of a turn, and each request names it; settings are read at each turn; every path comes from the agent through one function; and one turn runs at a time per conversation, a lock in the process. Each has one implementation now, one agent per process.
Does Link Harness need the harness hub?
No. Link Harness runs on Link's own services alone: the model proxy, the sandbox, the shared bridge that carries connections, the store and the tools. The hub is only for an agent that runs on, or moves between, another harness, so an agent that is Link's code from end to end pays nothing for it. Link Harness still answers ASP's state methods, so another harness can take an agent from it and give it back.
What does Link Harness keep in memory?
Nothing that matters when its process dies. It's a small router: each turn reads what it needs through its store, and writes there. The events are the truth, each number written once. Everything else (where the window starts, the copy for people, its settings as last read, each tool's list of calls) is derived, and made again by a process without it. So any process on any machine can take the next turn.
Agents
How one process of Link Harness answers for many agents, or starts ahead for one it doesn't know yet.
Can one Link Harness serve several agents?
Yes, of one owner, where the machine says so (serve --agents <file>,
the contract's shared). Each agent is answered on its own port, as
itself, from its own home, files and tools, the ones it would have had
alone; a request naming another agent needs that agent's key.
What it costs
Since it keeps nothing that matters in memory, one process for many
costs only what each turn reads, and a restart (an agent joining or
leaving) is finished like any other. Stopping for one, it takes no new
connection, and answers on each it has until its client closes it: a
bridge opens one for each message, so a message asked just before the
stop is answered, never cut off. Link runs it so only where
machine.toml says shared_harness, since the agents' folders then
share a sandbox.
An agent asleep
An agent stopped for being idle stays one it serves, so its sleeping and waking restart nothing, and with every agent asleep, it ends.
Why can Link Harness start blank?
So a machine that runs an agent only while it answers needn't count a
start in the time to answer. It keeps units started ahead, each holding
nothing of anyone's: no settings, no key, no port (serve --blank <control>, the contract's blank).
Bound once
Bound to an agent by one line on its control socket, a unit answers as that agent at once, with a key made for that binding alone, the settings it was bound with kept in memory, and its conversations through the store bridge it was given. It's bound once and never reused: there's no unbinding, so nothing of one agent's can be left for the next, and the process ends when its agent sleeps.
What it keeps and reads
What goes through its store, where its prompt's sections come from, and what becomes of an answer nobody took.
What becomes of an answer nobody was there to take?
It's kept for the bridge, as a crashed turn's answer is. A turn runs to its end even when the caller goes, since its calls were made and its answer is in the conversation either way. A caller goes when Link's bridge stops mid-answer, and where one bridge carries every agent, one stop cuts every agent's turn at once.
Handed over once
So an answer whose caller was gone before it was told it is handed over
at _link/recover with the crashed turns', and the bridge, asking
every half minute while the harness runs, sends it to the chat once.
The message is never asked again, so no call is made twice.
What goes through Link Harness's store?
Everything it reads and writes: its settings (config.json, written by
configure through the store too), its instructions, its conversations
and where each one's window starts. One seam per resource, keyed by
agent: the store has one implementation, the agent's folders on disk,
which keeps each conversation's events one of three ways
(below), and the code is
the same on every machine.
The workspace and memory
The workspace, the folder an agent's tools work in, is the agent's files folder, named by the same one function. Link Memory reads a log's copy on disk itself, and makes its own of a conversation kept any other way, reading its events as Link harness does: the compact frames, or the driver.
Where does Link Harness take its instructions from?
From sections, each with an owner and a most, composed in the order of
how often each changes, least first: Link's base, yours for the model
it runs on, what its tools say of themselves, the environment (its
files folder and connections, at its start), the repository's, its
skills, who it is and who you are, whatever the task
(instructions/persona.md and owner.md), your standing instructions
(instructions/instruction.md, read at each turn, a file like any
other), its notes, and now.
Why that order
A provider caches a prompt up to its first change, so what changes least goes first, and the prompt stays the same each turn: the date, the time, the connection and who's speaking change every message, so they head each message instead, kept with it so it reads the same at each later turn.
The time and who's speaking
The time is the machine's local time, where a personal agent's owner is; the scheduler's zone is its own key. Who's speaking is shown only for the owner, since a message from others already says who, quoted.
Each one's source and size
Each one's source and size are opinions, the spec's [instructions];
every section can be replaced, the base included. Everything, a file of
yours too, is read through the store, as conversations are, never
through a link.
The repository's
The repository's are AGENTS.md, the standard most coding agents read,
else CLAUDE.md for a repository written for Claude Code: one per
folder, so a CLAUDE.md that only points at AGENTS.md isn't read
twice.
Where are a model's own instructions?
In the agent's files, instructions/models/<maker>/<model>/instruction.md,
beside the standing instructions, never in its spec. Instructions are
long and formatted, so they're markdown files you edit, and a spec only
names a file or link or none, never holds the words.
How a model is named
A model is named as OpenRouter names it, its maker then its name
(anthropic/claude-opus-5-5, openai/gpt-6-luna), since it names
every hosted model and models through it already are; one on a runtime
here has no maker Link can know, so it goes under the runtime's name
(ollama/qwen3:8b).
Two names for one model
The same model reached two ways can have two names (Anthropic's
claude-opus-5-5 and OpenRouter's anthropic/claude-opus-5.5): each
is its provider's, never guessed between.
Why it comes second
The section comes second, after the base: it changes only when the model does, and a model changed is a new cache anyway. A fallback model is sent its own file in the first's place, the rest of the prompt the same.
Where does the tools section come from?
From the tools: MCP's initialize answer has a server's own
instructions, how its tools work together, which Link's tools write
(the scheduler's subagents and teammates, memory's notes) and any MCP
server may. The text comes from the tools you added, as their
descriptions already did.
How it's listed
Link Harness keeps them with each tool's listing, at most 2 KB each, and lists them under each tool's name, sorted, for the tools the turn offers: a conversation narrowed to no tool of one has none of its words. Kept beside the listing, a tool that fails to start in a new process is still described as it was, so the prompt, and the cache, hold.
Where do Link Harness's notes come from?
From its memory: remember, a tool of Link Memory's, writes
memory/notes.md, a line a note, and the composer reads the file into
the seventh section at each turn. A tool rather than code in the
harness, as every tool is, so any harness with Link Memory takes notes
the same way.
Late and small
The section comes late, so a new note changes the prompt from there on
and the provider's cache holds up to it. The notes are held small, 4 KB
by Link's spec, since they're in every prompt: past it, remember
refuses and says to change or forget a note first, as Hermes Agent
holds its memory file to a limit for the same reason.
An empty file
The section is the file under "# Your notes", and none when the file is empty, so an empty file costs no tokens.
How does Link Harness use a skill?
The prompt lists each skill's name and description. The model reads its
instructions through Link Memory's read when needed, following the Agent
Skills format. Skill metadata is read through the store at each turn and
sorted by name, keeping the prompt stable. Instructions are cached until
the skill file changes. A skill missing its matching name or description
is omitted, and prompt limits remove whole skills rather than fragments.
Conversations
How a conversation is kept, how Link Harness tells which way, and why it stays in files by default.
How does Link Harness keep a conversation?
A conversation is an append-only event log, selected by the agent spec's
[storage] conversations: log, compact or a store driver. A log is
events.jsonl, with one JSON event per line. A lock serializes writers;
an existing sequence number is refused, and an incomplete final line is
removed before appending.
What its events retain
Events retain text, time, model, provider, response ID, tokens, latency and provider-reported cost. Thinking accompanies model calls but is not shown. A Markdown copy supports people and memory (what it holds). Storage is keyed by agent and conversation so backups, moves and harness switches read the same record.
Why is how a conversation is kept the spec's to say?
Because people want different things of it. One opens the folder and
reads it; another wants it as small as it can be, and never reads it by
hand; a third keeps it in a database, off the machine. So it's an
opinion, a key of the spec ([storage] conversations), read by Link
harness from its settings as every opinion is; a harness from outside
Link keeps its own however it does, so the key is Link harness's, and
the spec says so. The rest of what Link harness keeps would be more
keys under [storage].
How does Link Harness know how a conversation is kept?
From its folder: an empty store.<driver> says a driver keeps it,
events.zst that it's compact, and anything else is the log, a file per
event of before it included. The spec says only how a new one starts,
so changing it moves nothing: each conversation is read where it is.
Moving the old ones
lnk agent chat convert, by hand and with the agent stopped,
moves the old ones: each is written the new way in a folder beside it,
put in its place in one rename, what else it held (its tools, an answer
still to send) moved along, and the old way's rows forgotten. Never on
its own: a key changed by mistake shouldn't rewrite every conversation.
What is a compact conversation?
The same events as the log, a line of JSON each, in zstd frames
appended to events.zst, so a write never rewrites what's there; the
events since the last frame are lines of events.jsonl, and every 32
of them (or 1 MB) become the next frame, compressed hard (level 19),
since a frame is written once per many events.
Its lock and its frames
The tail's lock is the conversation's: a reader holds it shared, so it never sees a frame written and its events still in the tail. A frame is whole on its own, so one cut short by a writer that stopped isn't read, and the next fold cuts it off; its events are still in the tail, emptied only once the frame is on the disk, and an event found in both is read and framed once.
Its first frame
The first frame is one zstd skips, naming the thread, which its
folder's name doesn't say whole (telegram-42 for telegram:42).
What compression costs
Compression reduces disk use at the cost of work during writes and reads. A compact conversation has no frame index, so each turn reads the whole conversation, including when it only needs the latest window checkpoint. Use a store driver when efficient access to a long history matters.
Why no copy for people beside a compact conversation?
A copy grows as the conversation does, and as plain text, so it would
take most of what compact saves. lnk agent chat show <thread>
prints it from the events instead, and Link Memory makes its own.
What is a store driver?
A plugin, lnk-<name>, that keeps conversations for Link harness: it
writes an event only if its number is free and the next, reads from a
number, and keeps the lease that says a turn runs. That's all a
conversation needs, so any process on any machine sharing the driver's
store can take an agent's next turn.
SQLite
Link's first is SQLite (lnk-sqlite), local: every conversation of the
agent in one file, chats/events.sqlite, so it moves with the
agent; each write is a transaction that takes the file's lock first.
Google Cloud Storage
Off the machine, Google Cloud Storage's (lnk-gcs): an object per
event, written only if it isn't there (ifGenerationMatch=0), and a
lease replaced only if it's still the one read, which is all an
append-if-free and a lease need, with no database to run. A write sent
again after its answer was lost, that finds its own bytes there, wrote
them.
Behind the store bridge
The driver runs outside Link harness's sandbox, behind the agent's store bridge, which Link runs beside the agent: Link harness asks it for each call, proved with the agent's key, so whatever the driver needs to reach its store never enters the sandbox, and Link harness neither knows nor cares which driver it is.
Another driver, and what the folder keeps
A conversation kept by another driver than the spec's runs that driver in Link harness's sandbox, as memory does. A call is stopped past 30 seconds. A conversation's folder keeps only its marker, and what isn't an event: the tools it was narrowed to, a turn asked to end, an answer that finished a crashed turn.
Why do Link Harness's conversations stay in files by default?
An agent runs in one place at a time, and a move carries its conversations with its files folder, so on your own machines nothing needs a store two machines share. A bucket syncs files, which made a file per event look right; a store off the machine is a database driver instead, which takes appends. Backups already put the files in your buckets.
Why not a bucket of files
Keeping them in the bucket instead, for your Mac and a box to answer in turn, would take each cloud's own API (rclone can't refuse a write whose name is taken, so it can't keep one turn at a time), a bucket tool outside the sandbox, and a copy here for memory.
The copy, groups and forks
What the copy for people holds and why it only grows, how a group is a conversation, and why a fork copies events.
What does a conversation's copy hold?
What was said, up to the agent's last answer: the owner's messages and the agent's answers, never a tool's call or its answer, and never the owner's message while it's being answered. Memory searches the copy, so a search would otherwise find the question it's searching for, and the next search what the last one returned, an echo that grows with each call. The events keep everything, calls and answers included.
Why is the copy added to rather than made again?
The copy holds what was said up to the agent's last answer, so it only grows, and only when the agent answers. Each answer adds what was said since the last one to its end, from the messages the turn already holds, rather than reading every message and writing the whole copy again. When the copy doesn't end with the last answer (it was cut short, or written elsewhere), it's made again from every message.
How does Link Harness answer in a group?
Each group is a conversation of its own, chats/<connection>-<group id>. Link's bridge takes your mention of the agent out of the message
before it goes on, and a mention with nothing else is ignored. Each
message is sent to Link Harness alone, and Link Harness adds the
conversation before it.
Why do forks copy events rather than visible messages?
A visible transcript omits tool calls and results, provider thinking and signatures, summaries and window marks. Rebuilding a conversation from that transcript changes the model's prompt and loses its cacheable prefix. A fork copies the complete immutable prefix before the edited owner message, preserving timestamps and the source's tool restrictions. Only the new suffix changes. This keeps each provider's normal cache behavior without changing its cache keys or retention settings.
The window
How much of a conversation the model sees, how a long one keeps going, and where a model's window comes from.
How much of a conversation does the model see?
A window: the conversation sent the same way each turn, so the provider's cache holds, as much of it as the model takes. Past that, the oldest turns leave half the window at a time, at points fixed by the conversation itself, so the prompt's start stays the same for many turns between drops. What leaves is summarized, and the window starts with the summary.
How does a long conversation keep going?
By compacting it. Once the conversation fills the share of its room
the spec says ([window] compact_at, three quarters in Link's), the
window moves on at its next drop point, as it always does, and what
leaves is summarized.
The answer first
The turn answers first, with the window it last sent: the room
compact_at leaves holds what came since, so the provider's cache
holds and the answer comes as soon as any. The answer goes to the chat,
and then, still under the turn's lease, the model is asked for the
summary as a continuation of that same prompt: the window, its answer,
and the request after them, which quotes the start of the first message
that stays. So the provider's cache holds for all of it, and the
summary costs little more than what it writes.
The summary
The summary is an event of the conversation, written once and never changed, and the window starts with it until the next drop point, when the next summary covers it and what left after it. Nothing is lost that it didn't choose to leave out, and the prompt's start changes only at a drop point. The summary never reaches the chat or the copy for people, which hold the messages it stands for.
A window past its room
A window that has outgrown even that room (a long round of calls) is summarized first, from what leaves alone, at most the walk's budget of it, newest first, so the request never outgrows the window; then it's answered.
The words asking for it
The words asking for a summary are Link Harness's, not a key: they're the mechanism's, as a tool's description is, and what to keep is the model's.
Saving notes first
Where the agent can write notes (memory__remember), the model saves
what matters to them first ([window] save_notes), in the same
continuation: a summary is for this conversation, and notes are for
every conversation after. So it saves only from a conversation its
owner spoke in: notes are about its owner, and one of others' messages
alone (a teammate's, a schedule's) could otherwise write them.
While it saves
While it saves, it may call nothing else, and with nothing to save, nothing at all; its tools are still offered, as in every round, so the cache holds for them too. The calls it made are kept with the summary.
A note changes the prompt
A note it saves changes the instructions from there on: a turn that summarized before answering composes them again first, and every turn composes them at its start, so the prompt after a save misses the cache from the notes on, as after any note.
When it compacts
When and whether it compacts are the spec's, since each summary is a
call that costs: compact_at = "never" lets the oldest turns leave
unsummarized. A summary that fails, or is cut short, leaves them
unsummarized too, and the next turn tries again.
A message while it summarizes
A message that comes while it summarizes, or until its turn has let its lease go, waits for a turn of its own, since the answer it would have joined is given; and the turn that gave its answer answers nothing after it, its caller gone. A message written by another process sharing the store can't see that, joins as it would any running turn, and is answered in a turn after, in the conversation but not in either reply.
A crash after answering
The answer is kept and sent before the summary is asked, so a turn that fails or crashes after answering loses only the summary: the window's mark stays where it was until a summary is written, so the next turn answers with the same window and summarizes after it. A note it saved before the crash may be saved twice.
Where does a model's window come from?
From what's known of the model, which is data, not code: Link's file
of models' facts, refreshed at every release, under yours
(~/.lnk/model-facts.toml), which wins field by field, so a
model newer than your Link works without a release (lnk model show).
In what order
Link Harness takes the window from your file first, then from what a
runtime on the machine says it runs the model with (it knows better
than any file), then Link's, and gives a model nobody described the
spec's small window ([window] unknown, 32,768 tokens) rather than a
guess that overflows.
Ollama's window
Ollama runs a model with a smaller window than it was trained for unless told, so a trained window Ollama reports counts only up to that small one, unless you say otherwise.
How much of it to send
How much of it to send is the spec's. Link's [window] max is held to
the model's window; one you or a team set is taken as set, whatever
Link knows of the model: the window you set always wins, and the only
limit is the provider refusing a prompt as too long, which the harness
reports as that, saying the window it sent and where to change it.
The refusal
No provider has a common code for the refusal, so the harness tells it by the message: Anthropic's "prompt is too long", OpenAI's and OpenRouter's "context length", a runtime's "context window".
The conversation's share
The conversation's share is the window less the answer's ([model] answer, else [window] answer's share, at most the model's longest),
the instructions and tools, and the margin ([window] margin).
Counting tokens
Tokens are counted roughly before a turn, by characters (four ASCII characters to a token, one for any other, so Chinese or an emoji isn't undercounted), since each provider's tokenizer is its own; the margin covers the difference. After a turn, the provider's own counts are what's kept, measured and priced. The window's drop points stay on the rough count, which never changes for a message once written, so the provider's cache holds.
How does a turn find the window without reading every message?
Each answer records the window's checkpoint: the starting message, its time and the tokens before it. A turn reads the latest checkpoint from the last 32 messages and resumes counting from there, or from an earlier answer or summary when needed. The checkpoint stays with the events that determine it.
Reading the log
Log reads search backwards by sequence number and load only the required lines. A summary counts as one message, while the complete conversation remains available for reconstruction.
Tools
How Link Harness reaches its tools, keeps their calls, and holds their sessions between turns.
How does Link Harness get tools?
As an MCP client of the servers Link's tool bridge already runs outside
the sandbox ($LNK_TOOL_<NAME>, memory at $LNK_MEMORY, the scheduler
at $LNK_SCHEDULER, and run, file and code at $LNK_RUN,
$LNK_FILE and $LNK_CODE), never as code of its own. A tool written
once works for every harness, the calls you allow and the ones that ask
are the same for each, and Link Harness still runs no code.
Their names
Memory is the first. Each tool is offered to the model as
<tool>__<call>, so two tools can have calls of the same name, and
memory's are memory__search, memory__read, memory__list and
memory__links. run__command runs a program, and file__read,
file__write, file__replace and file__find work on files, each
call in a sandbox of its own. The tools are listed the same way each
turn, so the provider's cache holds.
A command in the background
run__start, run__list, run__read and run__stop run a command in
the background, which outlives the turn and the tool's session that
started it. Link Harness keeps nothing of it: the command's end comes
back as a message from run:<id>, and Link Harness says who's
speaking, as for a schedule.
A team's tools
A team's tools come the same way: its memory over the team's folder is
$LNK_TOOL_<TEAM>_MEMORY, offered as <team>_memory__search and the
rest, one server for every member.
A subagent's tools
A subagent's task can come with a shorter list of tools (Link-Tools),
which Link Harness keeps in tools.json beside the conversation's
events, or with the conversation in its store when its machine keeps
conversations off the machine (tools_narrow, narrowed there in one
step): never on a disk that a sleep empties, where losing it would give
a woken subagent every tool. A later list narrows it again and never
widens it, and a call to a tool the subagent wasn't offered is answered
as one there isn't.
How are a tool's calls kept?
As events of the conversation, like any message. The model's answer that makes calls holds them, and each tool's answer is an event of its own after it, written as it happens. A turn cut short leaves the conversation as far as it got. A call left without an answer is answered in the next prompt as unanswered, so the prompt stays valid. With the tools gone, the calls and answers go to the model as text.
Does a turn hold anything of its tools?
Only the sessions of memory, run, file and code, and of the
task and file namespaces over them, kept between turns: code's
holds its language servers, which index a project once.
A turn opens a session with a tool only when the model calls it, so a
message that calls no tool starts none. Every other tool's session ends
with its turn.
Memory's session
Memory's is kept for the turns after, and ended once none has used it for 10 minutes, or an hour after it started, so what it started without (its model, not up yet) is tried again: ending it with each turn started its process again with every message that called it (its sandbox, its model server and its index), and its embedding ran only inside turns.
run's and file's
run's and file's are kept as long, so a turn that runs a command or
reads a file doesn't start a server and list its tools first. So are
the task and file namespaces, which reach them, and the scheduler,
through Link's namespace server: ended with each turn, every warm turn
started each one again, its server's and the namespace's sandboxes
with it (a confined warm turn took about twice as long). Kept, they
hold their processes until idle: about a dozen, under 20 MB between
them, when confined.
One process for every conversation
A kept session is one process for all of an agent's conversations at
once, and a tool can keep what a conversation did in it, such as a
browser's pages or a shell's folder: memory, run, file, the
scheduler and the namespace server are the agent's own in every
conversation and keep nothing of one in their session (each call says
its conversation, and the namespace server passes that on), but
another tool's state from one person's turn mustn't reach another's.
The tool and agent namespaces still end with each turn.
Derived from the tools
Each tool's list of calls is kept in the process between turns, and asked again whenever a session starts. Both are derived from the tools: a process that hasn't listed them yet lists all of them at once when its first turn starts, and starts its own sessions. The conversation's events hold everything else the next turn needs, so any process on any machine can take it.
Starting a tool
A tool has 30 seconds to start and list its calls. One that takes longer is given up on and its connection closed, so the tool bridge that starts it can end the start rather than hold a session for nobody; a session it started is ended too. A tool that fails to start after it was listed is still offered, and a call to it is answered as unavailable.
Their order
A server's tools are offered in order of name, whatever order it lists them in. So the tools, and the prompt they start, stay the same, and the provider's cache holds.
tool-lists.json
The lists are kept beside its settings too (tool-lists.json in its
home), so a new process does the same with a tool that fails to list
its calls when it starts, rather than leaving it out: a restart with
one tool down doesn't change the prompt, then change it back.
What the file holds
The file is derived: it's read once a process, written only when a list changes, holds only the agent's tools now (a tool taken away, or moved, isn't offered from it), and a process without it lists the tools as before. It holds each tool's name, a hash of its address (which carries the proxy's secret), and its calls, and each agent a process serves has its own. A tool that was never listed and doesn't answer is left out of that turn only.
How many calls does a turn make?
At most eight rounds. The model is then asked once more and told to call nothing, so a model stuck in a loop still answers. Each answer is kept up to 32 KiB, so one answer can't fill the window. The tools one round calls run at once. A message that steers a running turn is answered even past the last round of calls.