Settings

What a harness adapter is told of the agent it runs, and what it says its harness needs: lnk-<name> configure writes the harness's own config from the settings, and lnk-<name> needs reads them to say what the harness needs beyond Link's defaults. lnk-agent runs both.

lnk-<name> configure

Reads link_harness_contract::harness::Settings as JSON on stdin ({"files", "agent"?, "model": {"provider", "api_key", "name"?, "base_url"?}, "<connection>"?: {...}, "endpoint_key"?, "store"?, "storage"?, "memory"?, "tools"?, "allowed"?, "unsandboxed"?, "proxied"?, "spec"?, "defaults"?, "model_facts"?, "environment"?, "fallback"?, "fallback_facts"?, "identity"?, "remembered"?}) and writes the harness's own config, leaving out a connection it doesn't speak. It prints nothing unless it fails.

Each connection's section

Each connected connection's section is under its name, as its plugin's connect made it, typed in link_harness_contract::harness. Link's own are "telegram": {"bot_token", "allowed_users", "api_root"?}, "slack": {"bot_token", "app_token", "allowed_users", "dm_channel"?}, "discord": {"bot_token", "allowed_users", "dm_channel"?} and "signal": {"account", "allowed_users", "program"?}, program the signal-cli lnk-signal cli install put in the home.

A connection from outside Link is in Settings::connections, and no connection is named as one of the settings' own keys, such as files, model, endpoint_key, store, storage, memory, tools or spec.

Keys and bridges

What the adapter is handed to reach the harness and the agent's things: its endpoint's key and the storage bridges.

endpoint_key

For an adapter with an endpoint: what its endpoint takes (Authorization: Bearer <key>), refusing anything else, and a connection Link carries for it is left out of the settings. It's also the agent's key for this start, which proves the agent's calls to its bridges (Bridges).

store

For an agent whose spec keeps its conversations with a driver: {"driver", "socket"}, where the agent's store bridge listens (link_store_contract::store::BridgeAt). A harness that keeps conversations calls that driver through it (link_store_contract::store::Bridged) and never runs it itself.

storage

Where the agent's machine resolved each of its collections is kept (link_store_contract::storage::StorageAt, {"places": {"<collection>": {"place", "overridden"?}}, "socket"?}, Agent Spec). Left out, each is in the files folder.

A collection kept elsewhere

A collection at a place other than file is the driver of that place's, reached only through the storage bridge at socket: its files with the file_* calls, its conversations with the store's calls, and the tools each was narrowed to with tools_read and tools_narrow. It's never on this machine's disk, and nothing is written there to say where it is.

What the harness holds in memory

Nothing of a conversation kept elsewhere is written to the machine's disk: a turn asked to stop, or an answer of a crashed turn not yet sent, is held in the harness's memory.

A collection at file

One at file is in the agent's own folder (agent, Agent/), kept as lnk-file-store keeps it, which the harness reaches itself, as its sandbox lets it.

lnk agent start hands it on every machine to a harness that keeps Link's collections (its contract says chats), each place resolved from the agent's [storage] and the machine's machine.toml. It installs the needed drivers and serves the bridge, each call answered by the driver of the place its collection is kept at (link_store_contract::store::serve_storage, Request::collection).

telegram.api_root

With keys on the wire: where the harness must reach Telegram's Bot API, its proxy, which adds the real token to bot_token's placeholder (lnk-telegram rule --json).

Tools and grants

The agent's tools, and what the harness may do beyond Link's defaults.

memory

The address of the agent's memory, an MCP server over HTTP through the harness's proxy (lnk agent memory), there only while the agent has one and its harness runs behind its proxy.

tools

Each tool that runs outside the harness that it may call (lnk agent allow --tool), by name, at its address on its proxy, an MCP server over streamable HTTP. The adapter adds each, and memory as memory, to its harness's MCP servers (Settings::mcp_servers), and removes those it added before that are gone (link_harness_contract::harness::tools_gone).

allowed

The risks its needs named that the user allowed, by name. The adapter leaves out what any other would open, or fails, saying how to allow it, when the harness can't run without it.

unsandboxed

true when the harness runs with no sandbox at all, its ports on the machine's loopback.

proxied

true when it runs behind its proxy: sandboxed, with [internet] and not [network] mode = "host", or routed through an exit. On Linux its ports other than those its proxy serves (Launch.serve) then stay in the sandbox's own network; left out, they're on the machine's loopback, as they always are on macOS, and an adapter names a risk such a port opens.

Opinions and models

The agent's spec, and what Link knows of its model and its fallback.

spec

The agent's opinions, its spec as it runs it (Link's, its teams' and its own), which a harness takes in place of any of its own. model.name is already its spec's default, and one its allow lists.

defaults

The keys of spec that are Link's, set by no team and not by the user (["window.max", ...]): a harness holds a default of Link's to what the model takes, and takes a key the user set as set.

model_facts

What Link and the user know of model.name (link_model_contract::models::Known, {"name", "window"?, "answer"?, "thinking"?, "price"?: {"input", "output", "cached"?}, "yours"?}), left out for a model neither knows (lnk model show).

What each fact is

window and answer are the model's whole window and longest answer in tokens, thinking how it's asked to think (adaptive, budget, effort or no), price dollars a million tokens, and yours which of these the user's ~/.lnk/model-facts.toml set.

fallback

A second model (lnk agent start --fallback), in model's shape and reached as it is, with keys on the wire at the proxy with a placeholder key. A harness tries it for a turn model can't answer: its runtime off, the turn's prompt past its window, or its provider busy or failing (429, or a 5xx) before any of its answer. A harness that can't fall back ignores it.

fallback_facts

What Link and the user know of the fallback model, in model_facts's shape, left out for a model neither knows.

Who it is and where it runs

Who the agent is, and what it runs with on this machine.

identity and remembered

Who the agent is and what it remembers, for a harness whose contract says identity (Who the agent is).

environment

What the agent runs with here, as Link resolved it (link_harness_contract::harness::Environment, {"connections"?, "folders": {"write", "read", "teams"?}, "commands"?, "teams"?}), for a harness to tell its model. connections names the connections it's reached on, by their plugins' names.

Its folders

folders is its sandbox file's [files]: the folders it may write and those it may only read, by name (link_harness_contract::harness::layout: Home's work/ and memory/, and the own folder's instructions/, skills/ and chats/; . all of Home), and of each of its teams' shared folders (folders.teams, {"<team>": {"write"?, "read"?}}, folders' names in it or . for all of it).

Its programs

commands names the programs its run may start here, those of its sandbox file's [commands] this machine has, by name (["sh", "python3"], or ["*"] for any). Left out, it has no run.

Its teams

teams is the teams it's on, [{"name", "id", "member", "purpose"?, "teammates"?: [{"name", "id", "purpose"?}], "shared"?}]: its own name in each, the others by name and agent ID, and the shared folder where it's granted some of it. A harness whose sandbox Link makes is given its teams' folders as granted, whatever harness it is.

lnk-<name> needs

Reads link_harness_contract::harness::Settings on stdin as configure does, but with no key or token (the model's api_key and each connection's secrets empty), and prints link_harness_contract::harness::Needs as JSON: {"needs"?: [{"spec", "why"}], "unsandboxed"?: {"why"}, "risks"?: [{"name", "why", "without"?}]}. Read by lnk-agent.

What it says it needs

What its harness needs beyond Link's defaults with those settings, each with why in its own words, one line of at most 200 characters. The settings carry unsandboxed, true when the user runs it with no sandbox, and proxied, with the grants it has so far, so a harness that asks for [network] mode = "host" itself counts it.

Each need is a spec

Each of needs is a sandbox's spec's sections (Sandbox Spec), such as {"spec": {"network": {"mode": "host"}}, "why": "Slack's websocket ignores Link's proxy (HTTPS_PROXY)"}.

The programs it runs

Its harness runs no program but the one its launch names (and that one's #! interpreter) and those [commands] allows: a harness that starts another (node, git, a program it installed) names it, {"spec": {"commands": {"allow": ["node"]}}, ...}, or any, ["*"]. Its commands that read the agent's files (instructions, chats) run only its own program and the store drivers it's handed.

What a need may not say

Each need is a spec with no name, version, relative path or limit of calls (per_minute, the agent's own), opening something on some OS, and taken as it is on this OS: one that opens nothing here is passed over. It's shown and taken back as the flags lnk agent allow takes (--network-mode host), and a key Link doesn't know is refused, the harness not started.

unsandboxed

unsandboxed is only for a harness that can't run in any sandbox: it's never asked or granted, and a start is refused unless the agent runs with no sandbox (lnk agent start --no-sandbox).

risks

risks names what running it risks that no spec does (a port of its own that asks for no credential), each by a name of its own: lowercase letters, digits and dashes, at most 32, and none that unsandboxed or a name of a sandbox setting (network, lan, ...).

A risk it runs without

One with without, a line saying what it does without it, runs either way, and only a yes in a terminal allows it. One without without is asked as a need is. configure gets those allowed in allowed.

How each is asked

lnk agent use, a start and lnk agent move ask the user for each not answered yet, and grant what they allow to the harness (~/.lnk/harnesses/<name>.toml), held only while it runs, remembering the answer. What the agent has of its own (lnk agent allow) is had, and not asked.

An adapter without it

An adapter without needs needs nothing more: one that exits with an error (its answer to a command it doesn't know) or prints nothing. An answer that isn't Needs is refused.