Decisions

Why lnk-hermes runs Hermes Agent the way it does. Link's own decisions about harnesses are on its Agents Decisions page.

When does Hermes get its packages?

As it's set up. Hermes installs its Anthropic SDK and each channel's library from PyPI the first time it needs them. Left to that, the running harness would need a package registry, which a list of hosts limited to its model and channels refuses. So its adapter's configure, in the same sandbox as the install, installs those its settings need, with Hermes's own installer for them. Its code goes in its home with --dir even for root, whom its installer would otherwise give /usr/local, outside the sandbox and shared by every agent.

How does Hermes use a local model?

As a custom provider at the runtime's /v1 address, for any local runtime lnk model list finds, such as Ollama, LM Studio and llama.cpp.

How does Hermes reach Signal?

Through a relay of this adapter's, lnk-hermes with-signal, on the port Hermes takes for signal-cli's HTTP daemon. That daemon asks for no credential, and on a Mac, or on Linux with lan, its port is on the machine's loopback, open to every user and program there. Hermes speaks only HTTP to it, and can't reach a socket, but it sends the user and password in SIGNAL_HTTP_URL as Basic auth, so the relay asks for a secret there and answers the daemon's three calls (a check, JSON-RPC calls, a stream of events) from signal-cli running behind it over its stdin and stdout, as Link's own Signal bridge runs it. The relay starts the gateway only once it holds the port, so nothing else can be there for Hermes to take for signal-cli. It runs everywhere, not only on a Mac, so one path is tested.

The relay checks the secret once a request's head is in, before its body, and keeps only 16 connections without it at once, each for 10 seconds at most, the oldest closed when another comes: on a Mac its port is open to the whole machine, and a stranger should cost it nothing but those places, nor keep Hermes out just by holding them. A call made while signal-cli starts waits up to 10 seconds for it, rather than fail, and a message that comes as Hermes reconnects its stream of events waits for the new stream.

Why is Hermes's WhatsApp bridge still on a port?

Because Hermes runs it and reaches it itself, at http://127.0.0.1:<port>/, with no setting for a secret or a socket, and the bridge listens there with no credential. A relay in front of it can't help while the bridge's own port is open, and making either side do otherwise means changing Hermes's code. So on a Mac, and on Linux without its proxy, it stays open to the machine's other users and programs; on Linux behind its proxy it stays in the sandbox's network.

Why is Hermes with WhatsApp asked about on a Mac, and without its proxy?

Because there its bridge is open to every user and program of the machine, and that's the user's call, not the adapter's. Link tells the adapter whether it runs behind its proxy (proxied in its settings) rather than which permissions it has: what decides where a port is is whether the harness has a network of its own, and lan, no sandbox and anything later that takes it out of its proxy all come to that one fact. A Link from before that field doesn't send it, so the adapter takes its absence as no proxy, and asks. lnk-hermes needs names it as a risk of its own, whatsapp-bridge, asked before Hermes runs as any harness's need is, and Hermes doesn't run with WhatsApp until it's allowed. Patching Hermes at install to send a token would close it, but would break at each Hermes release: a bridge token belongs in Hermes itself.

Where does Hermes deliver its scheduled jobs?

To the owner's direct message on each channel, set as Hermes's home channel at every start (<CHANNEL>_HOME_CHANNEL in its .env, which wins over its config.yaml). Otherwise Hermes asks for one in the chat and suggests /hermes sethome, which Slack refuses: Link's app has no slash commands. The direct message is the only place a Link channel talks to its owner, so Link already knows the answer. Telegram, Slack, WhatsApp and Signal take the owner's id or number, which Hermes turns into the direct message. Discord needs the direct message's channel, so lnk-discord connect keeps the one the pairing code arrived in (dm_channel).