fix(memory): install profile secret scope in hindsight background threads - #93028
Parker-Fawcett wants to merge 2 commits into
Conversation
…eads
Under gateway multiplexing, get_secret fails closed on unscoped reads
(rather than risk returning another profile's credential). hindsight's
writer, daemon-start and prefetch threads are spawned raw — no contextvars
propagation — so local_embedded could never boot its daemon: _get_client's
get_secret('HINDSIGHT_LLM_API_KEY') raised UnscopedSecretError on every
start and retain.
Capture the profile HERMES_HOME at construction (always scoped) and wrap
each background body in a _profile_scope context manager that re-installs
set_secret_scope(build_profile_secret_scope(home)) plus the home override —
the same contract gateway/run.py applies to its own worker threads.
Per-job wrapping in the writer loop keeps .env edits visible to later
retains; sentinel exit is unaffected.
Closes NousResearch#92608.
a0f2e30 to
20becd4
Compare
Correct fix shape for #92608: capturing profile identity at construction and having every raw thread re-enter it mirrors the gateway's thread-wrapping contract, and the token-based install/reset pair handles nesting safely. Findings:
Minor: reset order in the |
Review findings on NousResearch#93028: 1. Cache the built profile scope at construction instead of re-parsing the profile .env on every writer job / prefetch recall; docstring documents the snapshot lifecycle trade-off. 2. Debug-log a misconstruction signal: multiplex active with no HERMES_HOME override at construction means the captured home is likely the process default. 3. Extract _spawn_embedded_daemon/_daemon_start_body and _prefetch_background from their closures so both wrapper paths are directly testable; add per-path regressions observing current_secret_scope()/get_hermes_home() inside each body. 4. Reuse _bare_provider across all scope tests; switch the multiplex activation to the public set_multiplex_active hook.
|
All four findings addressed in 388686e:
Suite: |
|
Thanks @Parker-Fawcett for the work here. Merged via #101255 (bd81bf0) on current main. Your mechanism — installing the profile secret scope in hindsight background threads — is what landed; it was carried in the combined #101255 diff rather than this PR. You are credited via Co-authored-by on the merged commit and in the PR body. Closing this PR as superseded. |
Fixes #92608
What & why
Under gateway multiplexing,
get_secretfails closed on unscoped reads ratherthan risk returning another profile's credential (
agent/secret_scope.py).Hindsight's background threads — the retain writer loop, the embedded
daemon-start thread, and the prefetch thread — are spawned raw with no
contextvars propagation, so
_get_client()'sget_secret("HINDSIGHT_LLM_API_KEY")raisedUnscopedSecretErrorevery time:the
local_embeddeddaemon could never boot and every retain failed, exactlyas reported. Same root-cause family as #76574 / #86402.
The fix mirrors the established contract in
gateway/run.py(and theweb-server flow runner): capture the profile
HERMES_HOMEonce at providerconstruction — which always runs inside the adapter's per-profile context —
and have each background body re-install both overrides through a
_profile_scope()context manager:Design notes:
an updated
.envmid-session is visible to later jobs, and the sentinelexit path is untouched.
_build_embedded_profile_envread (a third unreportedunscooped site) runs inside the daemon-start body, so it is covered.
but with multiplex off
get_secrettreats it as a.envoverlay overos.environ, preserving legacy behavior.How to test
New tests in
tests/plugins/memory/test_hindsight_provider.py::TestBackgroundSecretScope, all daemon-free:HINDSIGHT_LLM_API_KEYfrom the captured profile.envand the scope is reset after the thread body
get_hermes_home()== the captured home even when theprocess env points elsewhere
_MULTIPLEX_ACTIVE=True, an unscoped read still raisesUnscopedSecretError(guard intact).env+ rawwriter thread → job succeeds inside the scope
Suite: full
tests/plugins/memory/viascripts/run_tests.sh→ 338 passed;the 6
test_hindsight_provider.pyand 1test_openviking_provider.pyfailures are pre-existing on clean
main(verified by stashing this change —identical failure sets). macOS 26, Python 3.11.