Hermes: the agent with a front door
A terminal-native agent whose chat is a real PTY, and whose channels have a pairing wall: strangers get held at the door until you approve them.
Most agent frameworks treat the outside world as an afterthought. Hermes treats it as the whole point, and then puts a lock on it.
Hermes is one of the frameworks you can deploy on Maritime with one command. Two things make it worth writing about: the chat is a real terminal, and the agent ships with a consent model for who is allowed to talk to it.
The chat is a PTY, not a chat widget
When you open a Hermes agent in the Maritime dashboard, the chat pane is xterm.js speaking over a WebSocket to an actual hermes chat process attached to a pseudo-terminal inside the agent. Raw bytes, ANSI escape codes, cursor addressing. The browser is rendering the same byte stream you would see over SSH.
This sounds like a novelty until you use it. A real PTY means the full terminal contract: interactive prompts work, progress bars work, and what you see in the browser is exactly what the agent's own tooling sees. There is no translation layer that decides which parts of the output you get, because there is no translation layer.
(The engineering footnote: proxying a PTY through a reconnecting WebSocket wrapper means honoring binaryType all the way down. Frames must arrive as ArrayBuffers, not Blobs, or the terminal renders nothing. We learned that one in production.)
The pairing wall
Give an agent an email address and a phone number, and you have created a new problem: anyone on the internet can now put words in front of your agent. For an LLM-driven system, inbound text from strangers is not spam, it is attack surface. Prompt injection walks in through the front door.
Hermes answers with a pairing wall. When an unknown contact messages the agent on a connected channel, the agent does not process the message. It replies with a short "I don't recognize you yet" and holds the conversation. The owner sees the pairing request in the dashboard and approves or ignores it. Approved contacts persist in the agent's pairing store on its volume, so the decision survives sleeps, wakes, and redeploys.
In the dashboard it looks like this: a stranger who messages the agent on any connected channel is held until you approve them, either by the one-time code they were given or by pre-allowing their address outright.
The security property is worth stating precisely: the agent's model never reads a stranger's message. Untrusted input is quarantined before it reaches the context window, which is the only place a defense like this can actually live. Filtering after the model has read the text is advice; filtering before is a boundary.
Channels that survive sleep
Hermes agents on Maritime run under the same sleep/wake lifecycle as everything else. Channel credentials live on the agent's persistent volume, and on our Firecracker hosts the gateway is started automatically on deploy and on every wake. Practically: your agent can be asleep, costing nothing, and still be reachable. The next inbound email wakes it, the pairing wall does its check, and the conversation continues where it left off.
An agent with a real identity
The hermes_identity template goes one step further and provisions the agent an identity of its own: a real email address and, optionally, a phone number. Combined with the pairing wall, that gives you something genuinely useful: an agent that is reachable from the outside world by default and ignores the outside world by default. Capability and consent, shipped together.
maritime create my-hermes --template hermes
# or, with its own email address and phone number:
maritime create my-hermes --template hermes_identityWhy this matters
The industry keeps asking how to make agents more capable. The better question, once an agent has an inbox, is how to make it appropriately deaf. Hermes is the cleanest answer we have shipped: every conversation begins with the owner saying yes.
