Developing

lnk-link-run is Link Run: an MCP server over stdio that runs an agent's programs, each call in a sandbox of its own. How to use it: Link Run.

Build and test

From tools/, in the public workspace beside the core (LINK_CORE for a core in another place):

cargo build -p link-plugin-link-run
# the sandboxed test skips without bubblewrap, unless LNK_REQUIRE_SANDBOX=1
cargo test -p link-plugin-link-run

How it's put together

serve --files <folder> --programs-path <PATH> --timeout <duration> --output <bytes> --program <program>... -- <sandbox>... is the tool, and Link gives it everything at a start.

The programs of the sandbox file's [commands] this machine has, the spec's [tool.run], and the sandbox, lnk sandbox run with the flags the sandbox file makes. No number or grant is the program's own, and with no sandbox after -- it refuses to start.

A call

A call runs <sandbox> -- <program> <args>, or with --var NAME=VALUE given, <sandbox> -- /usr/bin/env NAME=VALUE... <program> <args>, since the sandbox clears the environment: Link gives an agent with a desktop its DISPLAY and XAUTHORITY so.

Its program and process group

The program is found on --programs-path, in the files folder or a folder of it, in a process group of its own. At its timeout the group is killed, and once the program exits, what it left running is too, a second later.

Its output

Each of its output and its errors keeps its start and its end, half of --output each, and how much there was. The answer's text holds them once; its structured value has the exit, the sizes and the time, not the text again. Calls on one session run at once, each on a thread of its own.

tests/command.rs

It runs the tool without a sandbox, and on Linux under lnk-sandbox run from the core.

Background commands

src/background.rs. With --state (and --way-in, --as <agent id> with --keyring <dir> or Link's keyring, --background, --background-timeout, --background-output, --background-keep, --hops), serve offers start, list, read and stop too.

start

It checks the call as command does, then, under a lock on <state>/start.lock, counts what runs, writes the record (<state>/<id>.json, outside the files folder: no sandbox writes it) and starts lnk-link-run supervise <id> --state <state>. The conversation and hops come from the call's _meta (link.local/thread, link.local/hops): without a conversation, or at --hops, it's refused.

The record's lock

start takes the record's lock (<id>.lock, flock) first and hands it to the supervisor as its input, so no sweep ever sees it free while it starts.

supervise

It holds the lock for its whole life, which the kernel releases however it dies. It runs the sandbox in a process group of its own, with PR_SET_PDEATHSIG on Linux, so the sandbox, which bubblewrap ends with it, dies with the supervisor.

gate

The sandbox starts through lnk-link-run gate, which waits on a pipe until the record names its group, then becomes the sandbox, the same process: a supervisor killed before then leaves nothing running that a sweep can't find.

Its output file

Its output goes to background/<id>.out, opened with openat and never through a link. Past --background-output, the newest half is copied beside and moved in place, and the record counts what was dropped, for the file it names (its inode): a read that finds another file, cut since, loads the record again, so it never reads at the old offsets.

Its end

It stops the group at its timeout or on <id>.stop, records the end, then hands the bridge's way in a message from run:<id>, a bridge's call proved as its agent (--as) with the agent's key from the keyring, read then. The record is marked told before, and unmarked only when the way in can't be reached. SIGTERM, SIGINT and SIGHUP, the agent's service stopping, end it lost, untold.

sweep <state>

Run by the bridge as it starts and by serve at each start, it takes each record's lock. A free one means its supervisor is gone. A running record is then marked lost, and what's left of its group is killed when it's surely its own: the same boot, and its leader the one recorded or gone. Ended records are told, tried again for ten minutes, and those told past their keep are removed with their output.

stop --state <dir> and remove --state <dir>

stop (lnk agent stop) and remove (lnk agent delete, a move) stop what runs, and remove then removes everything.

tests/background.rs

It runs them as real processes: outliving the call and its server, told once through a stand-in way in, a stop leaving nothing, a supervisor killed or sent SIGTERM, the caps, the keep, and on Linux all of it under lnk-sandbox run.