emma-hermes/docs/profile-isolation/implementation-status.md

5.9 KiB

Profile Isolation Implementation Status

Purpose

This file is the changelog-style bridge between the design docs in this folder and the code that already landed. Use it when you need to answer three questions quickly:

  1. What isolation work is already implemented?
  2. Which code paths enforce it?
  3. Which follow-up gaps still need explicit review?

Completed phases

Phase 1 — Prompt and profile layering

Implemented:

  • layered prompt assembly for system / principal / session ordering
  • shared knowledge prompt block with separate budget and doc pointers
  • principal-aware profile resolution helpers

Primary files:

  • agent/prompt_builder.py
  • agent/system_prompt.py
  • agent/shared_knowledge.py
  • agent/agent_init.py
  • gateway/principal_profiles.py
  • hermes_cli/config.py

Phase 2 — Session, dashboard, and memory ownership gates

Implemented:

  • principal-aware session visibility in dashboard routes
  • owner-gated session detail / export / descendant access
  • principal-scoped remote durable memory writes
  • principal-aware session_search profile access filtering

Primary files:

  • hermes_cli/web_server.py
  • tools/memory_tool.py
  • tools/session_search_tool.py
  • gateway/platforms/api_server.py

Phase 3 — Verified-identity routing and managed-download ownership

Implemented:

  • verified identity carried into API-server session binding
  • principal-aware managed-download owner metadata
  • owner-bound managed downloads rejected outside the matching request scope

Primary files:

  • gateway/platforms/api_server.py
  • hermes_cli/managed_downloads.py
  • hermes_cli/web_server.py

Phase 4 — API response-chain and session CRUD isolation

Implemented:

  • api_server session CRUD / message / fork / chat endpoints now resolve the verified caller first and then operate inside the matching principal profile scope
  • response snapshots used by previous_response_id, conversation-name chaining, and GET/DELETE /v1/responses/{id} now live in a per-profile response_store.db
  • streaming and non-streaming responses share the same principal-scoped response-store behavior
  • /v1/runs now resolves previous_response_id from the active principal profile instead of a process-global store
  • a lazy compatibility proxy preserves adapter._response_store call sites in tests without opening SQLite on every adapter construction

Primary files:

  • gateway/platforms/api_server.py

Implemented:

  • managed downloads now support explicit link_mode selection
  • private mode keeps owner_metadata bound to the link
  • public mode strips owner_metadata while preserving the existing expiry policy
  • api_server request bodies now accept download_link_mode and return machine-readable hermes.download_link_mode* metadata so callers can prompt for private vs public link behavior
  • /v1/responses non-streaming payloads now keep response.output[*] aligned with the rewritten managed-download URL instead of exposing a host-local artifact path in the assistant message item
  • /v1/responses streaming terminal events now carry the same managed-download rewrite and hermes.download_link_mode metadata as the non-streaming path
  • live streaming delta paths now hold back split local file-path prefixes and rewrite them before delivery instead of briefly exposing /Users/... or C:\... during SSE streaming
  • the OpenWebUI runs proxy now accepts download_link_mode, forwards it upstream, and rewrites local artifact paths using owner-aware private/public managed-download selection instead of always minting public links

Primary files:

  • hermes_cli/managed_downloads.py
  • tests/hermes_cli/test_managed_downloads.py
  • gateway/platforms/api_server.py
  • tests/gateway/test_api_server.py
  • tests/gateway/test_session_api.py
  • /Users/hermes/.hermes/scripts/openwebui_runs_proxy.py
  • tests/gateway/test_openwebui_runs_proxy_handoff.py

Verified tests already exercised for this workstream

  • tests/agent/test_system_prompt_principal_precedence.py
  • tests/agent/test_system_prompt_shared_knowledge.py
  • tests/gateway/test_principal_profile_routing_phase1.py
  • tests/gateway/test_principal_profile_runtime_phase2.py
  • tests/gateway/test_api_server_session_ownership.py
  • tests/hermes_cli/test_web_server_session_access.py
  • tests/gateway/test_api_server.py -k 'ResponsesEndpoint or ResponsesStreaming or Truncation or ConversationParameter'
  • tests/gateway/test_multiplex_api_server_routing.py
  • tests/hermes_cli/test_managed_downloads.py

Remaining follow-up review items

These are not known regressions, but they remain explicit review targets whenever this isolation layer changes again:

  • artifact-rewrite call sites outside api_server that may still default to public links without a caller-selected mode
  • gateway UX hooks that should ask the user whether a link should stay owner-bound or become public
  • any future response-chain storage outside response_store.db
  • non-API file-delivery paths that may need the same principal-aware mode selection contract

Operational finding from live runtime inspection

  • the current macOS LaunchAgent ~/Library/LaunchAgents/ai.hermes.gateway.plist was found running gateway run --replace
  • launchctl print gui/$(id -u)/ai.hermes.gateway showed repeated restarts (runs=41, forks=2400, last terminating signal Killed: 9) while the service was still healthy
  • for a long-lived supervised LaunchAgent, --replace should be removed and reserved for explicit operator takeover flows instead of steady-state service startup
  • see restart-recovery-runbook.md for the safe reload / health-check sequence before applying that launchd change

Current invariant

If a request is authenticated as principal A, then:

  • it must read and write only principal A's session store and response store,
  • it must not resolve principal B's response chain,
  • and any owner-bound download minted from that request must remain inaccessible to principal B.