Skip to content

fix(hermes): isolate projectless host routing#445

Merged
ScriptedAlchemy merged 10 commits into
masterfrom
fix/hermes-home-project-scope
Jul 11, 2026
Merged

fix(hermes): isolate projectless host routing#445
ScriptedAlchemy merged 10 commits into
masterfrom
fix/hermes-home-project-scope

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Jul 11, 2026

Copy link
Copy Markdown
Owner

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 ~/.hermes then 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:

The Hermes host/profile home is user scope, never a TraceDecay project identity. A real registered repository nested beneath that home may still be project scope.

Root cause

Hermes has several independent paths into TraceDecay:

  • registered progressive-discovery tools;
  • the TraceDecay memory provider;
  • the TraceDecay context/LCM engine;
  • post-terminal receipts;
  • per-turn session ingestion;
  • the /tracedecay_status command.

Those paths did not all carry the same authoritative Hermes home or apply the same project validation. In particular:

  1. The low-level generated tool bridge derived a candidate from the session/process cwd and passed it as tracedecay tool --project ....
  2. Omitting --project was not sufficient protection because tracedecay tool then inherited the gateway process cwd.
  3. A path below the Hermes home could resolve upward through the registry to the stale Hermes-home project row.
  4. Custom Hermes profiles were not propagated through every resolution boundary.
  5. Terminal receipt validation was initially synchronous, which would have added foreground post-tool latency.
  6. Read-only cross-project selectors needed a safe projectless registry lookup before dispatch, while mutating selectors still had to remain forbidden.

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:

  • exact Hermes home -> user memory/user LCM;
  • ordinary projectless chat -> user memory/user LCM;
  • registered repo -> that project shard;
  • registered repo nested beneath Hermes home -> that nested project shard;
  • stale/unregistered Hermes plugin/cache/profile descendant -> rejected;
  • explicit new project outside Hermes home -> first-touch remains supported;
  • configured/runtime cwd that is not registered -> does not silently create a project;
  • explicit cross-project reads -> registry-resolved target project;
  • explicit cross-project writes -> rejected.

Generated tool bridge

The generated tools.py now 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 tool from the gateway cwd. The three registry operations use the truly projectless CLI surface instead:

  • tracedecay projects list --json
  • tracedecay projects search ... --json
  • tracedecay projects context ... --json

Their 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_active metadata 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=user or storage_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, and project_selector.project_path through the projectless registry context command. Only the returned registered root is passed to tracedecay 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_context retains both documented modes:

  • with a selector, use the projectless registry CLI;
  • without a selector in an active project, use the normal active-project MCP tool route.

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:

  • no project -> profile-level user memory;
  • active project -> project memory;
  • category=user_pref writes -> user memory;
  • project facts -> active project only.

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's sessions.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

  • Full Hermes integration suite: 65/65 passed.
  • Generated Hermes plugin contract: 39/39 passed.
  • Stock Hermes integration at 732a9ffc572ad2703fbd25cc8a21c9f3f9c10d69: 24/24 passed.
  • Stock plugin manager, progressive tool discovery, context engine, memory provider, project isolation, LCM ingestion, managed skills, hermes plugins list, and tracedecay doctor: passed.
  • Focused agent installer/project-context regression: passed.
  • cargo clippy --all-targets -- -D warnings: passed.
  • Python compilation, 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:

  • exact Hermes home and ordinary projectless chat use user scope without re-indexing ~/.hermes;
  • the TraceDecay repository resolves to its project shard;
  • parallel user/project fact operations do not leak or collide;
  • projectless registry discovery remains available;
  • progressive tool/skill discovery remains active;
  • no synthetic smoke-test facts remain afterward.

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=user or storage_scope=user call 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_search now 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, including project_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/tracedecay package 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.

@changeset-bot

changeset-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest 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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/agents/hermes/templates.rs
@ScriptedAlchemy ScriptedAlchemy merged commit 49bc080 into master Jul 11, 2026
13 of 19 checks passed
@ScriptedAlchemy ScriptedAlchemy deleted the fix/hermes-home-project-scope branch July 11, 2026 15:45
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