The Bridge

What lnk agent runs beside an agent's harness to carry its connections and serve Link's tools, as the connection contract types them.

Run and file

A start defines the agent's run, file, code and scheduler providers under their existing confinement. Its harness receives namespace MCP servers: task combines runs and schedules, file combines file operations and code lookups, and agent sends messages to teammates. Namespace servers check each provider's current approval policy before forwarding.

A sandbox per call

Each serves outside every sandbox and runs each call as lnk sandbox run with flags made from the agent's sandbox file, a new sandbox per call: Home and the own folder's instructions/, skills/ and chats/ read and its [files] write written (lnk-file-tool apply --agent: a path's first part naming one of those three is in the own folder).

run and file

For run, its network is as [internet] and [tools] say, read = ["~"] as --read ~, and only its [commands]; run's --timeout is the spec's [tool.run] timeout less a tenth, and --output its output. For file, no network and only lnk-file-tool apply, which reads the call on its stdin.

The scheduler's runner

lnk agent bridge runs the agent's scheduler's runner (lnk-link-scheduler run) beside its connections, again each time it stops, and ends it as it stops carrying the agent. Where one bridge carries every agent in a time zone, one runner serves them all (run --agents).

How it ends

It's asked with its stdin closed and a SIGTERM, on which the runner ends in order (its firings ended, its mark on its store let go), killed past 10 seconds, and waited for. A runner ends by itself, too, once the process that started it is gone, and on Linux is sent a SIGTERM then.

The one way the harness keeps a program running, its runners' and memory's model server's alike. A start in a terminal stops it once its harness ends.

With driver = "cron"

Where the spec's [tool.scheduler] driver is cron, lnk agent start has the machine's calendar start it once a minute, lnk-link-scheduler cron install --label <agent id> with the same flags, keeping cron remove's command in ~/.lnk/agents/<agent>/scheduler-cron.json for lnk agent stop.

Where the agent has run, lnk-link-run sweep runs once as it starts: background commands lost while it was down are marked, and each one's end not yet told is sent through the way in.

Proving each firing

The runner and run prove each firing as their own agent alone (--as <agent id>), with its key from the keyring, read at each firing.

Its flags

The scheduler's tool is scheduler-<agent id>, its runner and tool given the agent's spec's [tool.scheduler] as flags (link_scheduler_contract, which any machine running an agent's scheduler gives it the same way): --per-day, --subagents, --hops, --late, --zone, and the runner's --timeout, from firing.

On a team

For an agent on a team, its ID, teams and teammates (--agent, --team <name>=<id>, --teammate <team>/<name>=<agent id>), and the runner each teammate's way in here (--way-in-of <agent id>=<socket>).

With ask

With ask, its task, schedule, send and cancel aren't allowed when it's defined, so each asks first (lnk sandbox asks).

Questions on your chats

The agent's bridge asks you what its sandbox or a webhook asks first, on your direct chats, and your reply with the question's code answers it. The code alone allows it once, and no and the code refuses it.

Where it asks

Where a turn of yours is under way, it asks on that connection's direct chats, even for a turn in a group, or in lnk agent chat itself. With no turn of yours, as for a webhook's question or a schedule's turn, it asks on every connected connection, and once one answers, the others are told how. Each connection's plugin says which chats are yours (lnk-<connection> owner).

Answering

A reply is caught where every message to the agent passes, before the harness, so the agent never sees it. In lnk agent chat, a question shows while its turn runs, and typing its code answers it there; any other line waits for the turn to end. No answer within [connections] ask_wait (ten minutes in Link's spec) is no.

lnk agent bridge

Hidden. Carries the connections of the agent --agent names as ~/.lnk/agents/<agent>/bridge.json says ({"endpoint", "key", "proof", "connections", "from", "recover", "tools", "parts"}, written by lnk agent start), a lnk-<connection> bridge for each, started again when one stops. Read by the agent's bridge service (link.bridge.<agent id>, lnk-bridge-<agent id>.service).

lnk agent bridges <dir>

Hidden. Where machine.toml says shared_bridge = true, carries every agent recorded in <dir>/agents/<agent id>.json as lnk agent bridge would, each from its own bridge.json, each agent carried again on its own when its bridge.json or record changes. With no agent recorded for 20 seconds, it ends.

Each record

~/Link/Agents/.bridge, {"name", "runner", "foreground", "sleep"?}, written by lnk agent start in place of the agent's bridge service, and removed by its stop.

Its runners

It runs one lnk-link-scheduler run --agents <file> --zone <zone> a time zone for the agents' runners (each record's runner, <program> run <flags>), <file> (<dir>/runners/<hash of the zone>.json) listing each agent's flags.

sleep

A record with sleep ({"port", "net", "after_ms", "run", "conversations", "job", "handoff", "shared"?}) is an agent whose [run] idle_after lets it stop. The bridge listens on its endpoint's port, always without handoff (Linux, behind its proxy, which then doesn't serve it), passing each connection to <net>/in.sock (its port as two bytes, big-endian, then the bytes both ways).

handoff

With handoff, its harness listens there itself, so the bridge listens only while it's asleep, letting go of the port at the first connection and passing it on once the harness listens.

Waking it

It starts its job first when it's asleep ({"service": {"label", "unit"}}, link_service_contract::resume: launchd's bootstrap or systemctl --user start; or {"terminal": "<socket>"}, a line wake, answered ok), and the harness its owner's agents share (shared) if none runs. link_connection_contract tries a refused connection to an agent again for 5 seconds, for one handed its port as it wakes.

Putting it to sleep

It stops it the same way (pause, sleep) after after_ms with nothing through or connected, no answer still coming through it, no turn.json in chats and no lock held in run, marking it <dir>/asleep/<agent id>. An answer still coming is read from each connection's HTTP/1.1 framing: an ask unanswered, or an answer's body not all through, or what it can't frame, until the connection ends.

Where it runs

Run by its own service (link.bridges, lnk-bridges.service) where Link's services run, else in a session of its own, which an agent started in the foreground starts again when it's gone.