Agent interface for tools and transfers

What other programs can rely on from lnk agent: its commands' output, the commands a move runs, and what other plugins ask of it.

lnk agent

The commands other programs read, each for its JSON or its exit status.

lnk agent list --json

{"agents": [...], "unreached": [{"id", "name", "error", "hosted"?}], "answered": [{"name", "hosted"}]}: every agent, each as status --json prints one: this machine's and each running box's (lnk box run <box> -- agent list --json --here).

unreached and answered

unreached is the boxes it couldn't ask (id their machine ID), and each cloud account that hosts agents but didn't answer, or answered without its agents (hosted, id null). answered is each cloud account that hosts agents and answered with its agents, any of them there or none. An account answered doesn't name says nothing of the agents there.

An agent on a box

An agent that moved to a box is listed as the box says, or, while the box doesn't answer, as the note here says (running null, location the box).

--here

--here asks only this machine, and lists no notes. It adds hosted, [{"id", "name", "account", "home"}], each agent this machine moved to a cloud account that hosts agents (account), and home, the folder here its Home is pushed from and pulled to.

processes

Each agent here also has processes, {"pids", "group", "groups"}: the main processes it runs as now (its service's and its connections' service's, or its start in a terminal; [] when it's stopped) and, on Linux, its service's own control group (/sys/fs/cgroup/.../lnk-agent-<agent id>.service, else null), and each of its services' own (its connections' service's too, lnk-bridge-<agent id>.service; never a bridge it shares with other agents).

measures, times and teams

measures is the file its harness appends its own measures to (LNK_MEASURES); times the file the harness plugin appends each of its starts' and stops' times to (~/.lnk/agents/<name>/times.jsonl, link_measure_contract::client::TIMES: a line of OTLP JSON each, link.time.start_cold.own, link.time.start_warm.own or link.time.stop.own); and teams the IDs of the teams whose files list it ([] in none). All are null for an agent that moved away.

Who reads it

lnk agent list on another machine (--here); lnk agent connect <connection> (a bot or app another agent runs: bots, or from an older lnk, the connection's own field); this plugin's answers below (--here, and hosted: the Homes lnk bucket push and pull reach through the account; whether a cloud account that hosts an agent answered without it, so a secret's copy there went with it); and lnk remote (your agents, to post them to your account; --here between looks at the boxes).

lnk agent status --json

{"id", "name", "machine", "harness", "model", "running", "folder", "files", "port", "bot", "slack", "discord", "signal", "connections", "bots", "exit", "location", "processes", "measures", "times", "teams", "wire", "spec", "beyond_defaults"}, for the agent --agent names (else the only one). It exits nonzero only when the settings can't be read. Read by lnk agent move (whether the agent answers where it went), and this plugin's answer to secret.agent.

Current wire state

Status reads the current stop and pause flags without a sandbox child command. Unreadable state stays unavailable through the owner's read-only API. Observation changes no flag and supplies no authorization to open a wire.

spec and beyond_defaults

spec is what it may do on this machine while its harness runs it, the agent's own grants and what the harness needs joined, as a sandbox's spec ({"internet": {"hosts": ["*"]}, "network": {"mode": "host"}}). beyond_defaults is, of that, what Link's defaults don't give, as the flags lnk agent deny or lnk agent harness deny takes it back with (["--read '~'", "--tcp github.com:22", "--tool gh"]). Both are null for an agent that moved away.

The agent and its machine

Its ID and name; this machine's {"id", "name"} (machine.toml; null if it can't be read or written); its base port (null: the adapter's own); the active harness; the model it answers with by name (its provider's when it has no name; null before its first start); whether the agent runs (in the background or in a terminal); and its files folder, or null when that setting is one lnk agent start wouldn't accept as it is.

Its bots

Its bot {"id", "username"} (never the token; null without Telegram), its Slack app's bot {"id", "name", "team"} (its member id, never a token; null without Slack), and its Discord bot {"id", "name"} (its user id, never the token; null without Discord).

connections and bots

connections is the connections connected (["discord", "slack", "telegram"], whether or not the harness speaks them), and bots each one's bot ({"<connection>": {"id", "name", "team", "title"}}, never a token). bot, slack, discord and signal are the same for Link's own connections, as they were before bots: Signal's {"id"} the agent's number.

exit, location and wire

Its exit is "here" or null, and location null. wire is why its harness's wire is closed (every wire stopped, or its own paused, and why), else null; only status gives it, not list.

An agent that moved away

For an agent that moved away, what its note says: its ID (null until the box says it), name, the machine it went to, location that machine's name, and null for the rest.

lnk agent chat send [--chat <name>] --message <text> --json

{"thread", "answer", "held"} (link_plugin_agent::chat::Chatted): the message sent to the agent --agent names in thread link:<name> (link:main unless named; a name is 1 to 64 letters, digits, -, _ and ., or a copy's, main~hermes), as a connection's bridge sends one to its harness's endpoint, and its answer. With no --message, the message is stdin. Read by scripts and the command line's QA.

--command

-c <command> sends one agent command and exits, as -m sends one message. model:show and /model:show both send /model:show. It conflicts with --message, and follows the same local or remote chat path.

held

held is the answers in that thread nobody waited for, handed over first, oldest first, each one the bridge held cut at 64 KB with a line saying lnk agent chat show has it whole. The C1 controls and the bidi and zero-width marks in it are written as \u escapes.

Its exit status

0 only with an answer: nonzero when the agent is stopped, paused, fails or answers nothing, or its harness has no endpoint; 130 when Ctrl-C stopped waiting.

An agent elsewhere

For an agent on a box, it runs there (lnk box run <box> -- agent chat ...), the message on its stdin. For an agent a cloud account hosts, it goes through that account's adapter (agent-chat), and held is always empty.

lnk agent -a <agent> start --yes --files <dir>

Starts the agent with <dir> as its Home, its files folder, in its own sandbox file, allowing what its harness asks beyond Link's defaults; exit status 0 once it runs.

Run as a person would

lnk remote runs start, stop, connect, disconnect, backup, restore, remove, move and use as a person would, each read only for its exit status and last line (remote).

Moving an agent

What moving an agent between machines uses (lnk agent move, restore); most are hidden from --help. The stream it moves in is the Move Stream, and an account that hosts agents answers the commands of Accounts That Host Agents.

lnk agent state-send

lnk agent state-send [--phase copy|switch] [--move <id>] [--held <file>|-] [--seal-to <key>] writes the move's stream of that phase on stdout. Read by lnk agent move.

Phase copy

The agent may run: the manifest with the agent and its harness alone, then files/... and memory/<engine>/index.sqlite (memory's index, as SQLite's online backup of it, never the live file).

What files/ holds

The agent's folder as it sees it: Home's files, and beside them its instructions/, skills/ and chats/, from its own folder, Agent/. A receiver puts those three in its own folder, the rest in Home.

Phase switch

The default, refused while the agent runs: files/... and the index again, but what --held (a file, or - for stdin) holds the same, then the rest of the manifest, harness/... (the state lnk-<name> state names) and connections/<connection>/..., links as links.

The rest of the manifest

"model", "fallback"?, "connected", "telegram"?, "bot"?, "slack"?, "slack_bot"?, "discord"?, "discord_bot"?, "signal"?, "sandbox"?, "spec"?, "exit", "state", "connections", "bucket". fallback is its fallback model (lnk agent start --fallback), its key with it, when it has one; sandbox what you granted the agent (its sandbox file, a sandbox's spec, when it has one); spec its own spec's text; and bucket what lnk bucket export says, or null.

connected

connected is the agent's connections as its settings keep them ({"<connection>": {"settings", "bot", "about"}}), and the per-connection fields the same for Link's own connections. A manifest without connected is read from them.

--seal-to

A switch requires --seal-to, the key receive printed: the complete tar payload is a link_secret_value::stream envelope with HPKE context {"owner":"move","kind":"stream","version":1}, 64 KiB AEAD records and an authenticated final record.

lnk agent receive

lnk agent receive [--no-start] [--phase copy|switch] reads one JSON line, then send's stream on stdin, into the agent's staging, ~/Link/Agents/.moving/<agent id> (0700). Read by lnk agent move.

Its key

A switch first prints on stdout one line, lnk-seal-to <key>, a key made for it alone, whose private half it drops once it has opened what's sealed to it.

Its line

{"sealed"?, "upstream"?, "sandbox"?, "grants"?, "yes"?}. sealed is its {"key"?, "bucket_keys"?} sealed to that key as {"owner": "move", "kind": "header", "version": 1}: a model key, and, for a box, lnk bucket keys' keys. upstream is where its traffic goes there, and yes says what its harness asks for beyond the defaults is allowed.

sandbox and grants

When the agent leaves the machine running the move, what it may do there: sandbox the agent's sandbox file as that machine has it, kept whole, and grants its harness's file (~/.lnk/harnesses/<harness>.toml), added to the harness's here. Both are a sandbox's spec, and an older manifest's, with permissions, is read through the same table as an older sandbox file.

Without sandbox

The agent keeps its file here, else takes the stream's without what a stream never grants, each left out and said as lnk agent allow flags: --read '~', --network-mode host, --lan-host, --port, --tcp, --tool, --browser-host, --command of a path or *, and its teams' folders.

A copy and a switch

A copy prints what staging holds then, its have. A switch takes out what it says was removed and puts the agent in place here, as the agent the stream names: its fallback the one that came (but a model on the machine it came from that this one can't reach, left out and said), its index in its memory engine's folder and its buckets with it (lnk bucket adopt).

Starting it

A switch starts the agent; move always passes --no-start, then asks lnk agent needs and starts it with lnk agent start.

What it refuses

A switch without the complete encrypted stream envelope, sealed to another key, tampered with, truncated or missing its authenticated final record is refused before successful application. It refuses a stream whose first entry isn't lnk-move-4.json, a switch of another move than its copy, an agent here of that name with another ID, and one of another name with its ID.

A switch that fails

A switch refused, or cut short, removes the staging: nothing of the agent stays.

lnk agent receive --have --agent-id <agent id>

What agent <id>'s staging here holds, its have. Read by lnk agent move, before the copy.

Its staging

A new staging starts from the files the agent left on this machine, when it left in the last 30 days and they're kept (hard links, or copies across file systems). Staging no switch came for in 7 days is removed.

lnk agent pause

Hidden. Whatever carries the agent's connections stops reading them (~/.lnk/agents/<agent>/paused, which it says it saw with paused.quiet beside it), and its way in takes nothing; a turn running (a live lease in the conversation's store) finishes, for 60 s at most; then the agent stops. Nothing for an agent that isn't running. Read by lnk agent move, before the switch.

lnk agent resume

Hidden. The pause is over, and the agent starts again; a start ends a pause too. Read by lnk agent move, after a switch that failed.

lnk agent deliver

Hidden. Reads JSON lines on stdin, each a message a cloud that hosts agents held while the agent was paused there (link_host_contract::Held, {"connection", "chat", "from", "text"}), and gives each to the agent as its connection's bridge would. Read by lnk agent move, after agent-left.

How each is given

Each is asked of its harness as a message of its owner's in that chat, the answer sent there (lnk-<connection> send). Only for a connection Link carries for it, from a sender in the connection's allowed_users; any other isn't given, and the command fails, saying which.

lnk agent needs

Hidden. {"harness", "needs": [[need, why]], "running": [agent]}: for the agent --agent names, what its harness needs beyond Link's defaults (lnk-<harness> needs) and hasn't been answered on this machine, each a spec's flags (--network-mode host) or a risk's name, and the agents here running that harness.

--allow <need>

Repeated: grants those to the harness first, each one it asks for now, as answered; one it doesn't ask for refuses them all. Read by lnk agent move where the agent came, before it starts there: asked in the move's terminal, granted with --allow.

lnk agent left (--on <machine> | --cloud <cloud>) [--machine-id <id>]

On the machine an agent left, once it answers on <machine> (with --cloud, the cloud account of that name, which hosts it): deletes its settings, harnesses, service and any staging of it, keeps its files 30 days (or machine.toml's keep_moved), writes its note. Read by lnk agent move.

Answers and routes

What other plugins ask of lnk agent, how an agent's traffic leaves its machine, and what a move reads of a box and of lnk bucket.

lnk agent answer <question>

Hidden. The questions of the plugins below this one it answers, as its manifest's answers names them, each asked as JSON on stdin and answered as JSON on stdout, in the asker's types.

secret.copies

For lnk secret delete: each copy of a secret a move gave, reached. On a box, it's removed there (lnk box run <box> -- secret delete <name> --here --agent <id>), or gone with a box that's no longer yours; on a cloud account, gone once the account answers without the agent, else held with it.

secret.agent

For lnk secret add --agent <name>: where the agent of a name is, here by its ID, unstarted, or on another machine.

bucket.folders

For lnk bucket: this machine's agents' files folders, each one's Home on a cloud account that hosts it, and each hosted team's folder (~/Link/Teams/<team>/Shared).

bucket.home

For lnk bucket push and pull: a hosted agent's Home (or a hosted team's folder) pushed from or pulled to its folder here, through the cloud account that hosts it (agent-take and agent-give with phase files). Only what the other end doesn't hold the same goes, and what went and how many were the same already is the answer.

measure.agents

For lnk measure: the agents running here, their processes, folders and where their harnesses report. Its existing shape remains compatible with older readers.

measure.inventory

The agents plus traffic entries naming each provider and its private snapshot directory, in the measurement contract's types. Measure prefers this answer, falling back to measure.agents for an older or unavailable provider. The sandbox source uses existing per-PID files, without taking over cleanup.

lnk agent route (--socks <address> | --direct | --none | --here) [--default]

Hidden. On the machine the agent runs on: its proxy's upstream, or none for no route of its own (direct, unless the harness has [network] mode = "host"); restarts it if running. Read by lnk agent exit (through lnk box run; --here, else the port for a box that refuses it), lnk agent move and lnk agent create --on readying a box (route --direct --default).

Its upstreams

direct, socks5://127.0.0.1:<p>, socks5://<socket>, http://127.0.0.1:<p> (an HTTP proxy handed each name, which it resolves), or the exit from here (--here): its socket in this machine's home folder.

--default

This machine's default (agent-defaults.toml): every agent made here from then on starts with it, and one here with no route yet takes it.

A box's version

lnk agent move reads a box's lnk --version through lnk box run, its first line lnk <version>: a box older than this computer is upgraded with lnk upgrade <version>.

Buckets

A move reads the agent's buckets through lnk bucket's hidden commands, export, adopt, delete, keys, revoke, restore, label and find (Interface).

What lnk agent runs beside an agent's harness to carry its connections and serve Link's tools is on The Bridge.