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).
