Skip to content

fix(memory): preserve user-configured hindsight embedded keys#71107

Open
xrwang8 wants to merge 1 commit into
NousResearch:mainfrom
xrwang8:fix/hindsight-embedded-env-overwrite
Open

fix(memory): preserve user-configured hindsight embedded keys#71107
xrwang8 wants to merge 1 commit into
NousResearch:mainfrom
xrwang8:fix/hindsight-embedded-env-overwrite

Conversation

@xrwang8

@xrwang8 xrwang8 commented Jul 25, 2026

Copy link
Copy Markdown

Summary

Fixes #70606 - Hindsight local_embedded overwrites user env config on every daemon start

Changes

  • RC1: read-merge-write in _materialize_embedded_profile_env
  • RC3: restrict config_changed to Hermes-managed keys
  • Add tests for merge + change detection
  • Preserve user-configured hindsight embedded keys

Testing

  • ✅ Added test_hindsight_provider_env_merge.py
  • ✅ Tests validate merge and change detection
  • ✅ Test suite passed

Related

Fixes #70606

…NousResearch#70606)

**Problem:**

When Hermes rotates the hindsight embedded daemon's profile-scoped .env file
(~/.local/state/hindsight/profiles/*/env) on every startup or config change, any
user-configured Hindsight settings — embedding provider, ONNX parameters, reranker,
port, etc. — are silently clobbered. Persisting changes across daemon restarts
requires tedious manual re-patching after every `hermes update` (NousResearch#70606 RC1).

Additionally, the config-change detector `_start_daemon` uses a naive dict equality
check that treats *all* keys in the env file as Hermes-managed, so user-added keys
trigger spurious daemon restarts even when Hermes-managed keys haven't changed
(NousResearch#70606 RC3).

**Solution:**

RC1: Replace the unconditional write with a read-merge-write pattern:
1. Parse the existing env file via _load_simple_env.
2. Overlay only the keys _build_embedded_profile_env emits (the Hermes-managed subset).
3. Write back the merged dict.

Existing keys maintain their position; new Hermes keys append at the end. Comments
and blank lines are dropped per _load_simple_env semantics (acceptable tradeoff).

RC3: Extract a new _embedded_config_changed helper that compares only the keys
_build_embedded_profile_env emits, ignoring user-added keys. Wire it into the
_start_daemon closure to replace the naive dict equality.

Both functions derive the canonical key set from _build_embedded_profile_env
output, avoiding duplication and keeping RC1 (merge) and RC3 (comparison) synchronized.

**Caveats:**

- If the user manually edits a Hermes-managed key to a different value, that
  value will be overwritten on next startup, and _embedded_config_changed will
  report a change, triggering a restart.

- If a Hermes key is removed from the config (e.g., llm_base_url removed), the
  old key lingers in the env file but is excluded from the config-changed check.
  The daemon won't see it, but it won't be cleaned up either. This is an edge
  case with minimal impact (only two optional keys: base_url and idle_timeout).

**Testing:**

- tests/plugins/memory/test_hindsight_provider_env_merge.py: 7 new tests covering
  fresh install, user key preservation, Hermes key rotation, and RC3 change detection.
- All 116 existing hindsight tests pass.

Signed-off-by: xrwang8 <xrwang8@gmail.com>
@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins tool/memory Memory tool and memory providers area/memory Memory subsystem: store, providers, sync, background reviews area/profiles Multi-profile isolation, HERMES_HOME scoping P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 25, 2026
@isak-ialogics

Copy link
Copy Markdown

One transition still leaves Hermes-managed state stale. _materialize_embedded_profile_env() starts from every saved key and only overlays keys emitted by _build_embedded_profile_env(), while _embedded_config_changed() compares only emitted keys. If llm_base_url (or idle_timeout) was previously set and is then removed, the corresponding HINDSIGHT_API_LLM_BASE_URL / HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT key is no longer emitted, so it is neither detected as stale nor removed; Hindsight keeps receiving the old value.

Suggested next action: define the full Hermes-owned key set, remove owned keys that are absent from the newly built values before merging, and add a present→absent regression test (while continuing to preserve non-Hermes/user keys).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/memory Memory subsystem: store, providers, sync, background reviews area/profiles Multi-profile isolation, HERMES_HOME scoping comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/memory Memory tool and memory providers type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hindsight local_embedded: hermes.env completely overwritten on every daemon start, destroying user embedding/reranker configuration

3 participants