hermes - 💡(How to fix) Fix [Feature]: Dashboard authenticated user identity should propagate to the agent session

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Fix Action

Fix / Workaround

  • Plugin-based monkey-patching of _ws_auth_ok and _resolve_chat_argv: works locally but is fragile across upgrades. The gaps are small enough that a native fix would be more maintainable.
  • Adding Platform.DASHBOARD with per-user chat_id isolation (like Telegram): overkill for the immediate need. The existing user_id plumbing in AIAgent + Honcho is sufficient for identification and memory scoping without architectural changes. This could be a natural Phase 2 if there's demand for full session isolation per dashboard user.
  • Custom middleware injecting headers: the WS upgrade path doesn't go through standard HTTP middleware, making this unreliable.

Code Example

Authentik/NousSession(user_id="<sub>", ...)    ← base.py:10, Session dataclass
POST /api/auth/ws-ticket → mint_ticket(user_id=sess.user_id)  ← routes.py:449
ticket = "abc123"  ← ws_tickets.py:44, info = {"user_id": "<sub>", "provider": "..."}
BrowserWS /api/pty?ticket=abc123

---

consume_ticket(ticket)  # returns {"user_id": "<sub>", ...} but the return value is ignored
return True

---

env = os.environ.copy()
# ... sets HERMES_TUI_DISABLE_MOUSE, HERMES_TUI_RESUME, HERMES_TUI_SIDECAR_URL ...
# No HERMES_DASHBOARD_USER_ID or similar

---

return AIAgent(
    ...
    platform="tui",
    session_id=session_id or key,
    # user_id is not passed — always None
    ...
)
RAW_BUFFERClick to expand / collapse

Problem or Use Case

The dashboard's OAuth authentication (gated mode, Phases 1–7) successfully verifies who the user is — the DashboardAuthProvider returns a Session with user_id, email, display_name, etc. The SPA's AuthWidget even shows Logged in as <user_id> via nous.

But that identity is discarded at the WebSocket boundary. Once the user opens the Chat tab, the agent has no idea who it's talking to. This makes the dashboard the only Hermes channel that authenticates without identifying:

ChannelAuthenticationUser identity in agent session
TelegramBot API tokenuser_id = Telegram chat ID
DiscordBot token + guilduser_id = Discord snowflake
Dashboard (gated)OAuth (Nous / Authentik / etc.)user_id = None

Concrete consequences:

  • Memory providers can't scope by user. Honcho uses runtime_user_peer_name=kwargs.get("user_id") to isolate per-user context (plugins/memory/honcho/__init__.py:362). On the dashboard, every logged-in user shares the same anonymous peer — memory from one user leaks to another.
  • No audit trail. Even after the _ensure_db_session fix above, the dashboard path never passes user_id to AIAgent, so the DB never records who used the session.
  • No personalization. Hooks like pre_llm_call receive sender_id=agent._user_id (agent/conversation_loop.py:710), which is empty for dashboard sessions.
  • Multi-user deployments are blind. Self-hosted instances (corporate OIDC, self-hosted Authentik, etc.) where multiple people log in to the same dashboard all appear as the same anonymous user.

Future potential: Once the agent knows who it's talking to on the dashboard, the same user_id plumbing that already works for Telegram/Discord (per-user memory scoping, hook context, session tracking) becomes available. This also lays the groundwork for programmatic role/group definitions in the dashboard context — a natural Phase 2 after identity propagation is in place.

Proposed Solution

Rather than proposing a specific implementation, I'll trace the data flow to show where the identity exists today and where it's lost, then illustrate one possible path so the gap is clear.

Where the identity exists today

The OAuth flow already carries user_id all the way to the WS ticket:

Authentik/Nous → Session(user_id="<sub>", ...)    ← base.py:10, Session dataclass
POST /api/auth/ws-ticket → mint_ticket(user_id=sess.user_id)  ← routes.py:449
ticket = "abc123"  ← ws_tickets.py:44, info = {"user_id": "<sub>", "provider": "..."}
Browser → WS /api/pty?ticket=abc123

Where it's lost

Gap 1 — _ws_auth_ok discards the ticket identity (web_server.py:3464):

consume_ticket(ticket)  # returns {"user_id": "<sub>", ...} but the return value is ignored
return True

consume_ticket() already returns the user_id and provider from the ticket store — the function's own docstring says so ("Stash returns the info dict to the caller on consume so the WS handler can carry the identity forward"). The caller just doesn't use it.

Gap 2 — PTY child env has no user identity (web_server.py:3508):

env = os.environ.copy()
# ... sets HERMES_TUI_DISABLE_MOUSE, HERMES_TUI_RESUME, HERMES_TUI_SIDECAR_URL ...
# No HERMES_DASHBOARD_USER_ID or similar

The PTY bridge spawns hermes --tui as a child process. Environment variables are the standard data channel between the dashboard web server and the PTY child (sidecar URL, resume ID, etc. already use this path). No user identity is passed.

Gap 3 — TUI gateway doesn't pass user_id to AIAgent (tui_gateway/server.py:2043):

return AIAgent(
    ...
    platform="tui",
    session_id=session_id or key,
    # user_id is not passed — always None
    ...
)

The gateway path (gateway/run.py:12056) correctly passes user_id=source.user_id. The TUI path doesn't — even though AIAgent.__init__ already accepts it and the downstream infrastructure is ready.

What already works (would activate automatically once user_id reaches AIAgent)

Once AIAgent(user_id="<sub>") is called, the existing infrastructure handles everything:

  • agent._user_id is set (agent/agent_init.py:265)
  • Subagents inherit it (agent/agent_init.py:1119–1128)
  • pre_llm_call hook receives sender_id=agent._user_id (agent/conversation_loop.py:710)
  • Honcho scopes memory via runtime_user_peer_name=kwargs.get("user_id") (plugins/memory/honcho/__init__.py:362)

One possible path (for illustration)

This is not a PR proposal — just showing the gaps are small and the fix is backward-compatible:

  1. _ws_auth_ok could return Optional[dict] instead of bool. A dict is truthy, so existing callers (if not _ws_auth_ok(ws)) keep working. The dict carries the ticket info.

  2. _resolve_chat_argv could accept the ticket info and inject HERMES_DASHBOARD_USER_ID into the PTY child env — following the same pattern as HERMES_TUI_SIDECAR_URL and HERMES_TUI_RESUME.

  3. tui_gateway/server.py could read HERMES_DASHBOARD_USER_ID and pass user_id= to the AIAgent constructor.

All three changes are backward-compatible. When the dashboard runs in loopback mode (no OAuth), no ticket is consumed, no env var is set, user_id remains None — zero behavior change.

Alternatives Considered

  • Plugin-based monkey-patching of _ws_auth_ok and _resolve_chat_argv: works locally but is fragile across upgrades. The gaps are small enough that a native fix would be more maintainable.
  • Adding Platform.DASHBOARD with per-user chat_id isolation (like Telegram): overkill for the immediate need. The existing user_id plumbing in AIAgent + Honcho is sufficient for identification and memory scoping without architectural changes. This could be a natural Phase 2 if there's demand for full session isolation per dashboard user.
  • Custom middleware injecting headers: the WS upgrade path doesn't go through standard HTTP middleware, making this unreliable.

Feature Type

Gateway / messaging improvement

Scope

Medium (few files, < 300 lines)

This issue was drafted by an AI assistant (GLaDOS, Aperture Science) under human supervision and reviewed by the reporter before submission.


Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix [Feature]: Dashboard authenticated user identity should propagate to the agent session