OpenHermit identifies users through channel identities and grants permissions through per-agent roles.
| Table | Purpose |
|---|---|
users |
user ID, display name, merge target, timestamps |
user_identities |
maps (channel, channel_user_id) to user_id |
user_agents |
role for a user on an agent |
Roles:
owner: full management and tool accessuser: standard interaction accessguest: limited access for unknown or low-trust identities
A caller identity is:
interface CallerIdentity {
channel: string;
channelUserId: string;
}Examples:
cli+ OS usernameweb+ device fingerprinttelegram+ Telegram user IDdiscord+ Discord user IDslack+ Slack user ID
When a session opens or a message includes a sender, the runner resolves the identity to a user. Whether unknown identities get auto-promoted to a guest depends on the agent's access level. The owner can later link, unlink, merge, or change roles.
Each agent has an access field on its security policy that controls who
can interact with it. Three values:
access |
Auto-create guest from unknown channel sender? | accessToken self-join? | Owner-added members? |
|---|---|---|---|
public (default) |
yes | yes | yes |
protected |
no | yes (must present access_token) |
yes |
private |
no | no | yes (only path) |
public: any incoming message from a previously-unseen(channel, channelUserId)becomes aguestmember on the agent. Suitable for open demos.protected: unknown senders are dropped at the runtime boundary. To join, callers mustPOST /api/agents/:id/memberswith{ accessToken: "..." }matching the policy'saccess_token. Suitable for invite-by-link / shared-secret flows.private: unknown senders are dropped, and accessToken self-join is rejected. Membership is owner/admin-only via the members API. Suitable for personal agents and internal tools.
access_token is set on the agent's security policy alongside access itself.
Three equivalent ways to flip access (or any other field on the security policy):
# CLI: read / write a single field, or pipe a full JSON
hermit config security show --agent one
hermit config security set access private --agent one
hermit config security set access_token "shared-secret" --agent one
echo '{"autonomy_level":"full","access":"private"}' \
| hermit config security write --agent one# HTTP (admin or owner JWT):
curl -H "Authorization: Bearer <token>" \
https://gateway/api/agents/one/security # GET current policy
curl -X PUT -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"autonomy_level":"full","access":"private"}' \
https://gateway/api/agents/one/security # overwriteAdmin UI: the agent's actions menu in the fleet view has a Security entry that opens a JSON editor on the policy.
The runtime reloads its in-memory copy after a successful write — no agent restart needed. Validation rejects unknown values for access.
POST /api/agents/:agentId/members accepts two body shapes:
Auth-mode rules:
admin: either body shape; can grant any role includingowner.- JWT
userwithownerrole on this agent: either body shape; cannot grantowner(use admin). - JWT
userwith no role yet: self-join only (userIdomitted or matches caller). Subject to the access-level rules above.
GET /api/agents/:agentId/members (owner / admin) returns each member with their role, display name, and the list of (channel, channelUserId) identities linked to them — useful for owners auditing who's in.
DELETE /api/agents/:agentId/members/:userId (owner / admin) removes the membership row. The user record and identity links are preserved so re-adding them later is one call.
The first trusted CLI/web identity can bootstrap ownership for an agent. Created agents may also receive an explicit ownerUserId.
Sessions store userIds. Non-owner users can only read/resume sessions where they are participants. Owners can read all sessions for the agent.
Group channel sessions can include multiple users. The runtime resolves per-message sender identity when channel adapters provide sender.
Toolsets are filtered by role and available stores:
| Role | Effective access |
|---|---|
owner |
exec, web, memory, instruction, user, session, session_send, schedules, MCP management |
user |
web, memory, session read/list/summary, permitted normal interaction |
guest |
restricted web/session/schedule read access |
Some tools also require runtime capabilities, such as scheduleStore, userStore, sessionStore, channel outbound adapters, or MCP client manager.
Owner-focused user tools:
user_listuser_identity_linkuser_identity_unlinkuser_role_setuser_merge
Session tools:
session_listsession_readsession_summarysession_send
session_send sends proactive messages through registered channel outbound adapters. The delivery is recorded in the target session as a normal assistant log entry; delivery details (channel, to, messageId, fromSession) are sibling keys under metadata, alongside the marker metadata.source = 'session_send'.
user_merge redirects one user into another by moving identities and marking the old record's merged_into. Resolution follows merge links so old identities continue to map to the canonical user.
- Role is per agent, not per session.
- Fine-grained session capability sets are not implemented; role filtering is the active permission model.