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).
The bridge and Link's tools
What lnk agent runs beside an agent's harness to carry its
connections and serve Link's tools is on The Bridge.