Skip to content

feat(memhub): let users pick a Hermes profile directory as the source root - #150

Open
luorihu wants to merge 2 commits into
Tencent-Hunyuan:mainfrom
luorihu:fix/hermes-profile-picker
Open

luorihu wants to merge 2 commits into
Tencent-Hunyuan:mainfrom
luorihu:fix/hermes-profile-picker

Conversation

@luorihu

@luorihu luorihu commented Oct 22, 2026 •

Copy link
Copy Markdown

Problem

Hermes is multi-profile, but the hermes source has exactly one root and it is not pickable.

  • Default profile: ~/.hermes/state.db
  • Other profiles: ~/.hermes/profiles/<name>/state.db

The source candidate for hermes is ~/.hermes (registry.py, _configured_root(default_home=".hermes")), and the adapter walks that root recursively for state.db / sessions/**/*.jsonl. So authorizing the source imports every profile into one memory pool — there is no supported way to import one profile without the others.

The only escape today is setting HERMES_HOME for the whole runtime, which is an environment override, requires --confirm-overrides, and replaces the picker entirely. For a user who runs two profiles (e.g. one per person sharing a Mac), that is an all-or-nothing switch with no UI path.

Fix

Add hermes to PICKER_SOURCE_IDS so a profile directory can be selected through the existing picker flow (user_selected root), exactly like WorkBuddy/Cursor/Claude Code/Codex. No new code path: SourceCandidateStore._root() already validates a selected directory, and connections.py already resolves USER_SELECTED candidates with a revision check.

Result: the grant is scoped to ~/.hermes/profiles/<name>, the adapter only sees that profile's state.db, and the default profile stays out of the pool.

Tests

  • test_preview_is_metadata_only_and_cancel_changes_nothing — parametrized with hermes (metadata-only preview still holds).
  • test_hermes_profile_directory_can_be_selected — selects ~/.hermes/profiles/<name>, asserts the resolved candidate is USER_SELECTED at that path, revision 1.
tests/test_source_candidates.py ................ 33 passed
tests/test_source_candidate_api.py + connections + intents + registry ... 50 passed

(The repo-wide audit script audit_core_test_design.py fails on this machine because system python3 is 3.9 and test_memory_qa_permissions.py uses type alias syntax — unrelated to this change.)

Docs

memhub/docs/harness-support/hermes.md gains a 多 Profile 隔离 section describing both options (picker vs HERMES_HOME) and noting they are mutually exclusive once the env override is set.

Second commit: the host install follows HERMES_HOME too

Importing the right profile is only half the story. The recall host layout for Hermes is hardcoded to ~/.hermes in packages/runtime-control/src/recall-hosts.ts, so the one-click install (native provider + MCP block + recall skill) always lands in the default profile. A user who moved the source with HERMES_HOME then gets:

  • imports from profile A (state.db of the selected root), and
  • the recall injection in profile B (the default one).

That is both a wrong-profile write and a UI lie: the Applications page reports "connection pending" for the profile that actually holds the data, and clicking "Connect" installs into the other profile.

Fix: resolve the Hermes host directory from HERMES_HOME when it is set, falling back to ~/.hermes — the same treatment DSH_HOME already gets a few lines above.

Test: recall-hosts.test.ts asserts presencePath and layout.configPath move with HERMES_HOME.

Notes

  • No schema or migration changes; picker state is an existing table.
  • Backwards compatible: users without a selected candidate keep the default ~/.hermes root.
  • Happy to add a CHANGELOG entry or adjust the doc wording if you have a house style for it.

… root

Hermes keeps every profile in its own state.db: the default profile at
`~/.hermes/state.db`, all others at `~/.hermes/profiles/<name>/state.db`.
The Hermes source candidate defaults to `~/.hermes`, which the adapter walks
recursively, so authorizing the source imports every profile into one pool.
There was no supported way to import a single profile without also importing
the others (the only escape was setting HERMES_HOME for the whole runtime).

Add `hermes` to the source picker allowlist so a profile directory can be
selected like any other picker source; the resulting grant is a
`user_selected` root and the adapter only walks that profile. Document both
options (picker and HERMES_HOME) in the Hermes harness doc.

Tests: extended the metadata-only preview test to cover hermes, plus a new
test that selects `~/.hermes/profiles/<name>` and asserts the resolved
candidate is the selected root. 33/33 in test_source_candidates.py, 50/50 in
the candidate/connection/registry suites.
The recall host layout for Hermes is hardcoded to `~\u002f.hermes`, so the
one-click install writes the native provider, the MCP block and the recall
skill into the default profile — even when the user pointed the source at
another profile through HERMES_HOME. Imports then read one profile while the
recall injection lands in another, and the UI keeps reporting
"connection pending" for the profile that actually has the data.

Resolve the directory from HERMES_HOME when set (same treatment DSH_HOME
already gets), falling back to `~\u002f.hermes`.

Test: recall-hosts.test.ts asserts presencePath and layout.configPath move
with HERMES_HOME.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant