Security

What lnk-hermes installs and runs, beyond what Link's sandbox guarantees every harness (Link's Agents Security page).

Installing it

What the adapter runs to install Hermes, and what checks it.

Its installer, pinned

It runs Hermes's official installer, what hermes-agent.nousresearch.com/install.sh serves, pinned: the script comes from Hermes's repository at a release's commit, and runs only if its SHA-256 is the one in this adapter's source.

A git checkout

It installs that release with --commit, a git checkout, so the commit's hash is the code's.

In the home and the sandbox

It runs over HTTPS, in the home and the sandbox: it can download anything, but write only the home. Its code goes in its home (--dir) even for root, whom its installer would otherwise give /usr/local.

Its own Python and Node

It brings its own Python, through uv, and Node, with no setup wizard and no service of its own. Its installer runs uv's installer from astral.sh unchecked.

Its Python packages

They come from its uv.lock, pinned by hash, unless that install fails and it resolves them afresh.

Packages on first use

Hermes installs some packages on first use: its Anthropic SDK and each connection's library. This adapter installs those its settings need as it's configured, in the same sandbox as the install, so the running harness never needs a package registry for them. Hermes pins their versions, not their hashes.

Its updates

A new Hermes release comes with a release of this adapter, never fetched on its own, since the adapter pins its commit.

Running it

What it may do while it runs, and where its secrets are.

It asks for any program

--command '*', granted only once you allow it: it runs the Python it installed in its home, which it can write, so no list of programs would hold it.

Its local API

Hermes's local API, on 127.0.0.1, takes Link's key for each start, which only Link's bridge holds. It holds no connection's token: Link's bridge carries them.

Its keys and addresses

Keys go in ~/.hermes/.env in its home (600), never on a command line. The model's address goes there too (LNK_HERMES_MODEL_URL, which holds its proxy's secret for a model on this machine), named in its config by variable (${LNK_HERMES_MODEL_URL}, which Hermes reads), and its tools' addresses into config.yaml itself (600): none on the command line of hermes config set.

Its API's key in lnk secret list

lnk secret list lists its API's key, the one Link hands it at each start, in each agent's home.

Not listed: Hermes's own logins

~/.hermes/auth.json, which hermes login writes when you run it in the agent's home yourself. Link never runs it nor relies on it: Link hands Hermes its model's key, or a placeholder for its proxy, in .env. One you made stays in the home, and moves with the agent (lnk agent move).

Its terminal's home

Its terminal's tools get a home of their own (terminal.home_mode profile) rather than reaching for your ~/.ssh and ~/.gitconfig.

Its state at a switch

lnk agent use asks Hermes's hub for its state over ASP: its sessions, files, skills and memory, read and written by its own SessionDB and files in its home. What comes in is data, never instructions, and confers no Link authority.

Its ports

Its endpoint, 8642 for main (another agent's is in its own block): with a local model's, the only localhost ports the sandbox lets it connect to.

Outside its home

Unsandboxed, Hermes writes ~/.hermes, ~/.local/bin/hermes and its own service (the LaunchAgent ai.hermes.gateway, or the systemd unit hermes-gateway.service).

Finding and removing them

lnk agent harness show --harness hermes looks for these; lnk agent harness delete --harness hermes deletes them with its home. Its own service would run outside the sandbox, so lnk agent start stops it and moves it aside.

What it leaves

The Python it installs through uv (~/.local/share/uv) is shared with other programs, so Link leaves it.

Portable session archive

.hermes/link-imports.json holds exact imported ACP updates and their native projection, mode 600. A native continuation exports the original updates plus later work. Divergent history preserves both branches. Historical tool calls become records without effects; thinking and unsupported metadata remain archival and are named as omissions from live context.