Context
/mcp authenticates one token for the whole box. It cannot tell whose turn it is serving, so it
cannot scope data per person, and the conversation tools had to be removed because they would
have leaked.
Decision
One Hermes profile per user, each with its own MCP token. For each user the system creates a bot:
hermes profile create bot-<login> --clone # inherits model · provider key · rules
# override three things: its API_SERVER_KEY, its MCP token, its SOUL.md
hermes gateway restart # the multiplexer picks up the new profile
# endpoint becomes /p/bot-<login>/v1 on the existing listener
and records users.bot_profile + users.bot_token_id. A turn is routed to the bot belonging to
the signed-in user, by path on the shared listener.
Why this and not a minted per-turn token
The rejected design kept one shared agent and minted a short-lived token per turn. It needed a
token mint, a turn table, HMAC verification, and a way to get the token into the MCP request —
and that last part does not exist. Hermes resolves an MCP server's url and token once at
connect and caches the connection (the six new tools were invisible until
hermes gateway restart). Between minting and the call, another user's turn can start, and the
failure is silent: right user's blessing, wrong user's data.
A per-user bot never has to answer "whose turn is this?", because the answer travels with the call: one token, one person, one role. It fails closed without trusting the model.
Consequences
- Wanted: per-user context, per-user data scoping, audit with a human actor, and the
chats.*tools stay unnecessary. - Cost: measured at ~55–85 MB per extra profile (two runs, 298 MB baseline), all inside the single existing gateway
process — not a process and a port each. Hermes multiplexes profiles as path namespaces
(
/p/<profile>/v1) with a per-profileAPI_SERVER_KEY(verified: the right key on the right path is 200; a mismatched key is 401 either way). The remaining lifecycle rule stands: deleting a user must delete their bot, because an orphaned profile is a live credential. - Rejected sub-variant: per-user tokens with one shared agent ("B1"). The model, not the server, would choose which token namespace to use, so a crafted prompt selects another person's token. It looks scoped and is not, which is worse than an acknowledged gap.
Status
Designed, not built. Prerequisite: bot_tokens. Recorded in full,
with drawings, at one bot per user.
Rendered from nx-master/decisions/one-bot-per-user.md · OKF bundle · regenerate with npm run render