fix(hermes): isolate projectless host routing#445
Merged
Conversation
🦋 Changeset detectedLatest commit: b1fa3c8 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb6fbfb114
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is the post-release routing hardening follow-up to #441 and #443. It fixes a live production failure discovered after v0.0.55: when a Hermes session ran from the Hermes profile directory, the generated TraceDecay tool bridge could treat that host directory as an ordinary code project. A preserved legacy registry row for
~/.hermesthen caused project-scoped tools to open that shard and re-index the Hermes installation, including plugin caches and dependency trees.The change establishes one invariant across every Hermes integration surface:
Root cause
Hermes has several independent paths into TraceDecay:
/tracedecay_statuscommand.Those paths did not all carry the same authoritative Hermes home or apply the same project validation. In particular:
tracedecay tool --project ....--projectwas not sufficient protection becausetracedecay toolthen inherited the gateway process cwd.The live symptom was an eight-minute re-index of 3,161 files beneath
~/.hermes, with normal daemon work blocked behind it.Routing model after this change
Project resolution is now tri-state:
registered: the candidate resolves to a registered non-Hermes project root; use the canonical resolved root.unregistered: no registered project exists. Runtime cwd/config fall back to user scope. A trusted explicit Hermes project root may remain first-touch eligible outside the Hermes home.rejected: the candidate is the Hermes home, a nonexistent path beneath it, or a descendant that resolves upward/fails ambiguously beneath it. Never use it as project scope.This distinction preserves both safety and the existing first-touch contract:
Generated tool bridge
The generated
tools.pynow derives its default Hermes home from the installed plugin package, while registration binds the authoritative runtime/custom home when Hermes provides one.Projectless registry discovery no longer uses
tracedecay toolfrom the gateway cwd. The three registry operations use the truly projectless CLI surface instead:tracedecay projects list --jsontracedecay projects search ... --jsontracedecay projects context ... --jsonTheir JSON is normalized back into the existing MCP content envelope. When a safe active project exists, the subprocess cwd remains that resolved project so
is_activemetadata is preserved. Otherwise the subprocess runs from the filesystem root, preventing accidental gateway-cwd discovery.All other registered project tools fail before spawning if no safe project exists. User-memory and user-LCM operations remain available because the bridge explicitly supplies
memory_scope=userorstorage_scope=user; it never relies on process cwd to imply user scope.Read-only selectors and write isolation
Read-only selector calls resolve
project_id,project_path,project_selector.path, andproject_selector.project_paththrough the projectless registry context command. Only the returned registered root is passed totracedecay tool --project.Mutating calls with a cross-project selector are rejected by the plugin before subprocess dispatch. The underlying TraceDecay MCP/CLI cross-project write guard remains the final enforcement layer as well.
tracedecay_project_contextretains both documented modes:Context engine and memory provider
The context engine, memory provider, configured roots, explicit roots, already-bound session roots, runtime cwd, and per-turn tool evidence now share the same Hermes-home-aware resolver.
Trusted explicit project roots remain first-touch eligible only when they are outside the Hermes home. Runtime cwd and configured roots must resolve through the registry; they cannot silently initialize a shard. Existing session roots are revalidated and cannot drift into the host profile.
The memory provider preserves the intended scope split:
category=user_prefwrites -> user memory;The provider setup/warm-up path now applies the same resolver, so setup cannot recreate the stale Hermes-home shard.
Session correlation and terminal receipts
Per-turn project evidence uses the authoritative Hermes home and accepts only a registered non-host project. Projectless turns remain in
user-sessions.db; project turns remain in that project'ssessions.db.Terminal receipt resolution remains asynchronous. The foreground hook performs only bounded path filtering and enqueues an internal candidate. The background worker resolves the candidate, strips internal routing fields, and emits the receipt only for a safe project. Resolver errors, ambiguous descendants, and upward-to-Hermes-home resolutions are dropped fail-closed without delaying or breaking the Hermes response.
The existing queue lock/worker ownership prevents parallel terminal calls from racing worker startup or corrupting receipt routing.
Custom profiles and realpath aliases
Every registered handler is bound to the Hermes home exposed by the registration context. Exact-home checks use
realpath, covering trailing separators and symlink aliases. A custom profile is therefore isolated even if the global/default Hermes home points elsewhere.Registered repositories nested under a Hermes home remain supported. Existing/nonexistent unregistered descendants, plugin directories, caches, and ambiguous resolver failures do not.
Autonomous curation policy
This patch does not turn autonomous TraceDecay curation into a preview-only workflow. Autonomous memory curation already performs a validation pass and then applies accepted, policy-valid mutations. The validation phase is an internal pre-commit check, not a terminal dry-run or approval queue.
Explicit operator preview remains available for destructive maintenance, migrations, retention, repair, and source-edit tooling. Agents may add, update, merge, and remove stale memories through the evidence/trust/audit path; cross-project mutation remains prohibited.
Verification
732a9ffc572ad2703fbd25cc8a21c9f3f9c10d69: 24/24 passed.hermes plugins list, andtracedecay doctor: passed.cargo clippy --all-targets -- -D warnings: passed.cargo fmt --check,git diff --check, and conventional commit validation: passed.Live rollout plan
After merge and release-plz publication, the released asset will be installed, integrations refreshed, the TraceDecay daemon restarted, and the Hermes gateway restarted. Live smoke tests will verify:
~/.hermes;Final projectless transport hardening
A final review found that the generated subprocess transport could re-run cwd discovery after the safe wrapper had already selected user scope. That meant an explicit
memory_scope=userorstorage_scope=usercall could still inherit the Hermes gateway process cwd and receive--project. The transport now treats explicit user scope as authoritative before any cwd resolution: it clears project routing, omits--project, and launches from the filesystem root.Projectless
tracedecay_message_searchnow has a native CLI dispatch path to~/.tracedecay/user/sessions.db; it no longer needs a synthetic project graph just to search untethered Hermes chat. User scope rejects every project selector, includingproject_scope, so reads cannot silently widen back into project shards. Generated-plugin coverage exercises user fact, LCM, and message-search calls while the process is inside a registered repo and proves that all three still omit--project. A Rust regression ingests an untethered Hermes turn into the user session store and retrieves it through the real user-scoped message-search handler.Final focused validation: generated Hermes plugin 39/39; user-scoped LCM/message-search regression 1/1; formatting and diff checks passed.
Profile ownership precedence
The final audit also closed a multi-profile gateway gap in
_resolve_hermes_home(). Explicit/configured homes still win, but otherwise the physical owner of the installed<profile>/plugins/tracedecaypackage now wins before Hermes process-global helpers or inherited environment state. This keeps context engines, memory providers, receipts, managed skills, and config reads on the profile that actually loaded the plugin. The generated harness asserts the registered engine owns that exact profile; the reviewed template snapshot hash was updated.Final local validation after this change: Hermes suite 65/65, generated plugin 39/39, profile-template snapshot 1/1, user LCM/message-search regression 1/1, Clippy with
-D warnings, formatting, Python compilation, conventional commits, and diff checks all passed.