Repository navigation
Fixes from Hermes-side PRs worth carrying into hindsight-integrations/hermes #4662
Description
Hi @nicoloboschi — follow-up to #4640. With the bundled provider removed from hermes-agent (NousResearch/hermes-agent#119888), we closed every open hermes-agent PR that patched plugins/memory/hindsight/ and redirected the authors here. Before closing, each PR was checked against hindsight-integrations/hermes at the catalog pin dc75038866a3966b5bf5c82ff57c7a758bb4070a (v0.10.1 era). This issue lists the ones whose fix still applies at that pin — the buggy code is present unchanged in your tree — so nothing is lost in the move. Feature proposals were not included (their authors were pointed here to open them directly).
Format: hermes-agent PR · author · one-line symptom · where in your tree (file:line at dc750388). Grouped by hotspot; several are duplicates of each other, which is itself useful signal about which bugs users hit most. Credit belongs to the linked authors.
Total: 117 closed PRs. Two hotspots account for a large share: embedded.py::_build_embedded_profile_env / _materialize_embedded_profile_env (profile-env rewrite drops or truncates HINDSIGHT_API_* keys → daemon restarts with the wrong embeddings/LLM config) and __init__.py::_tool_retain (the manual hindsight_retain tool ignores retain_async and blocks the turn).
__init__.py::_fetch_hindsight_api_version (2 PRs)
- fix(hindsight): set User-Agent on version probe NousResearch/hermes-agent#96256 · gazzumatteo · /version probe uses urllib default Python-urllib UA; WAF/CDN-fronted Hindsight returns 403, probe yields None and is cached as legacy API, silently disabling update_mode=append ·
__init__.py:149 - fix(hindsight): bound version probe response reads NousResearch/hermes-agent#54784 · ooiuuii · _fetch_hindsight_api_version() does an unbounded resp.read() on the /version probe before json.loads ·
__init__.py:152
__init__.py::_check_api_supports_update_mode_append (2 PRs)
- fix(hindsight): respect bank document storage policy NousResearch/hermes-agent#84819 · cmoiccool · Append capability is decided by API version only; on a bank whose effective store_document_text is false, retains after the first for a session are rejected (no source text to append to), silently dropping turns ·
__init__.py:160 - fix(memory): gate update_mode='append' on features.store_document_text NousResearch/hermes-agent#90470 · Robs87 · _check_api_supports_update_mode_append gates on /version alone; a server >=0.5.0 reporting features.store_document_text=false still gets update_mode='append' retains, which it rejects ·
__init__.py:174
__init__.py::_get_loop (1 PR)
- fix(hindsight): tear down shared event loop at exit to free connectors NousResearch/hermes-agent#34423 · Bartok9 · Shared module-global hindsight event loop runs on a daemon thread and is never stopped/closed at interpreter exit, so aiohttp ClientSession/TCPConnector are GC-reclaimed -> 'Unclosed client session' / 'Unclosed connector' warnings at exit (#34415) ·
__init__.py:218
__init__.py::_load_config (1 PR)
- fix(memory/hindsight): make HINDSIGHT_* env vars overlay config.json (#51166) NousResearch/hermes-agent#51375 · mkslzk · _load_config() returns the first readable config.json outright, so HINDSIGHT_BANK_ID (and HINDSIGHT_MODE/API_KEY/BUDGET...) from a profile .env are silently ignored whenever a config.json exists; retains/recalls go to bank 'hermes' (issue #51166) ·
__init__.py:301
__init__.py::_mint_document_id (1 PR)
- fix(hindsight): forward session title into retain metadata (#86824) NousResearch/hermes-agent#86849 · smfworks · Provider ignores the session_title kwarg core passes to initialize(), so retain metadata never carries the conversation title (issue #86824 still open) ·
__init__.py:341
__init__.py::is_available (2 PRs)
- fix(memory): probe daemon health as a fallback for local_embedded status NousResearch/hermes-agent#70123 · jaisinha77777 ·
is_available()for local modes is import-only (_ensure_local_runtime()), so a healthy local_embedded daemon started outside Hermes's venv is reported 'not available' byhermes memory status(issue #70089) ·__init__.py:434 - fix(hindsight): log why HindsightMemoryProvider fails to register instead of failing silently NousResearch/hermes-agent#35332 · Interstellar-code · is_available()'s outer
except Exception: return False(init.py:446) still swallows _load_config()/secret-lookup errors with no log line; the local-runtime import failure itself is now surfaced via unavailable_reason() -> agent_init warning ·__init__.py:446(landed in hermes-agent34c727c5c2abefore the move; residual part still open at the pin)
__init__.py::unavailable_reason (1 PR)
- fix(hindsight): surface actionable hint for cloud mode with missing credentials NousResearch/hermes-agent#86078 · woshicby · In cloud mode with HINDSIGHT_API_URL/HINDSIGHT_API_KEY missing, unavailable_reason() returns '' so the agent_init warning gives no hint which variables to set ·
__init__.py:449
__init__.py::_new_embedded_client (1 PR)
- fix(hindsight): re-read on-disk config when recreating the embedded client NousResearch/hermes-agent#110804 · nikkoxgonzales · local_embedded daemon respawn (idle shutdown / stale-connection retry) rebuilds HindsightEmbedded from the initialize()-time self._config snapshot, silently reverting later config.json edits (e.g. llm_model bump) in ~/.hindsight/profiles/
.env ·
__init__.py:698
__init__.py::_new_cloud_client (1 PR)
- fix(memory): bypass proxies for local Hindsight NousResearch/hermes-agent#40021 · yinkev · With HTTP_PROXY/HTTPS_PROXY/ALL_PROXY set, loopback Hindsight URLs (local_external / localhost:8888) are routed through the proxy because the generated client uses trust_env=True and no NO_PROXY bypass is applied; the urllib /version probe is also proxy-aware ·
__init__.py:732
__init__.py::_get_client (3 PRs)
- fix(memory): recreate Hindsight client when shared event loop is replaced NousResearch/hermes-agent#17373 · HuangYuChuh · _get_loop() silently replaces a dead shared loop but _get_client() only rebuilds when _client is None, so a cached aiohttp session bound to the dead loop raises 'Timeout context manager should be used inside a task' (#17226) ·
__init__.py:734 - fix(hindsight): guard client lifecycle with a leaf lock NousResearch/hermes-agent#117236 · yingliang-zhang · _get_client() is unlocked check-then-act; concurrent first access or the stale-daemon retry (self._client = None then rebuild) constructs duplicate clients whose aiohttp sessions are never closed ('Unclosed client session', #11923) ·
__init__.py:734 - fix(hindsight): guard _get_client() against writer/prefetch TOCTOU NousResearch/hermes-agent#42328 · nftpoetrist · _get_client() lazy-inits _client behind a bare
is Nonecheck; writer thread (_writer_loop->_run_hindsight_operation) and prefetch thread (queue_prefetch._run->_do_recall) can both build a client on first turn, leaking one aiohttp session / spawning a second embedded daemon ·__init__.py:736
__init__.py::_run_hindsight_operation (2 PRs)
- fix(hindsight): bound local embedded startup hangs NousResearch/hermes-agent#37190 · LeonSGP43 · local_embedded: _run_hindsight_operation evaluates operation(self._get_client()) synchronously, so HindsightEmbedded's proxied _ensure_started()/daemon startup runs outside the _run_sync timeout and a hung daemon hangs the tool call indefinitely ·
__init__.py:748 - fix(hindsight): close aiohttp client on retry to prevent connection leak NousResearch/hermes-agent#45693 · liuhao1024 · _run_hindsight_operation() retry path sets self._client = None without closing the stale embedded client, leaking its aiohttp session ·
__init__.py:754
__init__.py::_ensure_writer (1 PR)
- fix(hindsight): clear the shutdown latch on session switch so long-lived processes keep retaining NousResearch/hermes-agent#69001 · benegessarit · If a provider instance is reused after shutdown(), _shutting_down stays set forever (only _ensure_writer clears it, unreachable once sync_turn/queue_prefetch gate on it) so all later retains are silently dropped — NOTE: hermes-agent sweeper review found no lifecycle path on main where the same provider survives shutdown() (new provider per AIAgent), so verify the premise before filing upstream ·
__init__.py:765
__init__.py::_writer_loop (1 PR)
- fix(hindsight): queue failed retain writes NousResearch/hermes-agent#54989 · hallveticapro · Failed automatic retains are only logged and dropped by _writer_loop (init.py:792) and failed hindsight_retain tool calls return an error with no retry; string recall_tags (e.g. 'a,b') is passed to the client un-normalized (init.py:1062) ·
__init__.py:792
__init__.py::_resolve_retain_target (2 PRs)
- fix(memory/hindsight): probe the local embedded daemon's real port for append-dedup NousResearch/hermes-agent#75606 · TristanB1994 · local_embedded: /version probe falls back to static api_url (localhost:8888) when the embedded client is not yet created, so first retains fail the append-capability check and use legacy per-process document_id instead of update_mode='append' ·
__init__.py:909 - fix(hindsight): stop the session-switch flush from blocking on daemon readiness NousResearch/hermes-agent#112322 · hartmans · _resolve_retain_target reads HindsightEmbedded.url on the caller thread (sync_turn / on_session_switch inside compression commit), which calls _ensure_started() and can block 180s then raise, skipping session rotation ·
__init__.py:909
__init__.py::_apply_connection_settings (5 PRs)
- fix(hindsight): validate and normalize api_url, fail fast on bad config NousResearch/hermes-agent#78968 · Realmagnum · api_url is used verbatim with no scheme validation; a schemeless value like host:8888 makes every hindsight_* tool fail at call time with a cryptic 'Failed to search memory: host:8888/v1/...' error ·
__init__.py:999 - fix(memory): respect HINDSIGHT_BANK_ID when config exists NousResearch/hermes-agent#51173 · westkite1201 · When ~/.hermes/hindsight/config.json exists but omits bank_id, the documented HINDSIGHT_BANK_ID override (README.md:183) is ignored: _apply_connection_settings falls back straight to banks.bankId/'hermes' and only the no-config-file env branch (init.py:317) reads HINDSIGHT_BANK_ID ·
__init__.py:1006 - fix(memory): sync Hindsight bank missions NousResearch/hermes-agent#42320 · Qwinty · bank_mission / bank_retain_mission are read from config but never pushed to the live bank (no get_bank_config/update_bank_config call), so Hermes config and the bank's reflect/retain mission silently diverge (#18774) ·
__init__.py:1019 - hindsight: send configured bank missions via acreate_bank and close stale client before retry replacement NousResearch/hermes-agent#44264 · drphil2691 · bank_mission / bank_retain_mission are read from config (init.py:1019-1020) but never sent to the server (no acreate_bank call anywhere); and the local_embedded retry path drops self._client=None without aclose(), leaking aiohttp sessions (init.py:754-755) ·
__init__.py:1019 - feat(hindsight): retain parity, idempotent async retains, apply bank missions (client 0.10.0) NousResearch/hermes-agent#110846 · nicoloboschi · bank_mission / bank_retain_mission are read from config (init.py:1019-1020) but never sent to the bank (no aupdate_bank_config call anywhere), so a configured mission is silently ignored; the retain-parity fields, operation_id idempotency and failed-append replay are features not yet at the pin (_MIN_CLIENT_VERSION still 0.6.1 at settings.py:17) ·
__init__.py:1019
__init__.py::_apply_retain_policy (1 PR)
- fix(hindsight): read retain_every_n_turns from banks.hermes config NousResearch/hermes-agent#18938 · xuli500177 · retain_every_n_turns set under banks.hermes in ~/.hermes/hindsight/config.json is ignored (always 1); only budget falls back to banks.hermes ·
__init__.py:1048
__init__.py::_apply_recall_settings (3 PRs)
- fix(hindsight): normalize recall tag configuration NousResearch/hermes-agent#75850 · jackroofan · recall_tags configured as a CSV/JSON-list string is passed to arecall(tags=...) unnormalized (not split/stripped/deduped), unlike retain_tags ·
__init__.py:1062 - fix(hindsight): normalize recall_tags to a list before recall NousResearch/hermes-agent#106156 · excessbaggage · recall_tags configured as the documented comma-separated string is passed raw to RecallRequest.tags (list[str]) and the API rejects recall with HTTP 422 ·
__init__.py:1062 - fix: harden Hindsight memory config on Windows NousResearch/hermes-agent#52168 · sgoettl17 · User-edited recall_max_tokens/recall_max_input_chars/retain_every_n_turns are cast with bare int() (crash on bad value), recall_tags string is not normalized/recall_tags_match not validated, and legacy/embedded ~/.hindsight paths use Path.home() (ignores HOME on Windows) ·
__init__.py:1066
__init__.py::_daemon_start_worker (8 PRs)
- fix(gateway,memory): stabilize local proxy sessions and Windows Hindsight startup NousResearch/hermes-agent#68577 · ethereumturkey · Windows local_embedded: uv venv Scripts/pythonw.exe launcher respawns console python.exe, so the embedded daemon opens a visible blank terminal tab (root cause in hindsight_embed DaemonEmbedManager._find_api_command; integration starts it unpatched) ·
__init__.py:1114 - fix(qqbot, hindsight): is_reconnect param, model passthrough, config comparison, eager recall NousResearch/hermes-agent#58205 · hejp001 · Embedded-daemon restart check compares the FULL profile .env dict (
_load_simple_env(...) != _build_embedded_profile_env(...)); if hindsight_embed rewrites the .env with only HINDSIGHT_API_* keys the comparison never settles and the daemon restarts every startup ·__init__.py:1128 - fix(plugins): scope hindsight config-drift check to plugin-managed keys (#82943) NousResearch/hermes-agent#83076 · Chinmayrawat15 · _start_daemon compares the whole saved profile .env against _build_embedded_profile_env(); hindsight_embed's create_profile writes HINDSIGHT_API_PORT (a key the plugin never emits), so 'config changed' fires on every start and stops a healthy daemon ·
__init__.py:1128 - fix(hindsight): compare only plugin-managed keys before restarting embedded daemon NousResearch/hermes-agent#104324 · weiyuntong · full-dict compare of profile .env vs _build_embedded_profile_env() is never equal once the daemon writes HINDSIGHT_API_PORT back, so every provider init rewrites the env and stops a healthy embedded daemon (restart storm, ASGI 500s on in-flight retain/recall) ·
__init__.py:1128 - fix(memory/hindsight): stop the per-session embedded daemon restart NousResearch/hermes-agent#107324 · Volevanius · Whole-mapping equality between on-disk profile env and the built env is permanently unequal (hindsight-embed writes HINDSIGHT_API_PORT into the same file), so 'Config changed' fires and a healthy local_embedded daemon is SIGTERMed on every session start ·
__init__.py:1128 - fix(plugins): don't stop externally-managed hindsight daemons on config change NousResearch/hermes-agent#83003 · Anshtyagi1729 · On a config change the plugin calls client._manager.stop(profile) on any healthy daemon found on the port with no ownership check, SIGTERMing daemons an external supervisor (systemd) started ·
__init__.py:1133 - fix(hindsight): repair pg0 OpenSSL path for nonstandard Homebrew NousResearch/hermes-agent#35656 · LeonSGP43 · local_embedded startup on macOS with a non-/opt/homebrew Homebrew prefix fails because pg0 binaries reference /opt/homebrew/opt/openssl@3/lib; _start_daemon calls client._ensure_started() with no OpenSSL discovery/repair path (issue #35195, still open) ·
__init__.py:1141 - fix(hindsight): explain embedded memory startup failures after reboot NousResearch/hermes-agent#110845 · fangliquanflq · Embedded daemon startup failure (e.g. pg0 'Instance already running' stale PID) is written only to hindsight-embed.log; nothing is surfaced to the user ·
__init__.py:1144
__init__.py::system_prompt_block (1 PR)
- fix(memory): gate auto-injection prompt on auto_recall in Hindsight system_prompt_block NousResearch/hermes-agent#52156 · liuhao1024 · system_prompt_block() advertises 'Relevant memories are automatically injected into context' even when auto_recall=false, while _recall_disabled()/queue_prefetch skip auto-recall, so the model is told memories arrive that never do (#52153) ·
__init__.py:1146
__init__.py::_reflect (2 PRs)
- fix(hindsight): pass configured max_tokens to reflect requests NousResearch/hermes-agent#62205 · jackroofan · recall_max_tokens is forwarded to client.arecall (init.py:1173) but the areflect call used by reflect-prefetch and the hindsight_reflect tool omits max_tokens, so reflect responses ignore the configured limit and fall back to the server/client default ·
__init__.py:1184 - fix(hindsight): forward the configured tag filter to areflect NousResearch/hermes-agent#115629 · JoaoMarcos44 · _reflect() calls client.areflect(bank_id, query, budget) with no tags/tags_match, so hindsight_reflect and recall_prefetch_method=reflect scan the whole bank even when a tag filter is configured (#115499) ·
__init__.py:1184
__init__.py::_do_recall (5 PRs)
- [HOLD] fix(hindsight): gate automatic memory on user-turn provenance NousResearch/hermes-agent#117513 · Xipong · Automatic recall/prefetch/retain run on host-generated runtime turns (process_complete, async_delegation_complete, internal_notification, cron/subagent, bot-authored), so notifications recall other notifications and get retained as user speech ·
__init__.py:1188 - fix: keep Hindsight memory usable when local reflect LLM fails NousResearch/hermes-agent#37199 · shoveller · When recall_prefetch_method=reflect (or the hindsight_reflect tool) fails because the synthesis LLM is unavailable/slow, the prefetch returns empty and the tool returns an error instead of falling back to raw recall results; no separate reflect timeout ·
__init__.py:1194 - feat(memory): add WARNING-level logging for Hindsight sync/prefetch failures NousResearch/hermes-agent#44128 · liuhao1024 · Hindsight recall/prefetch failures are logged only at DEBUG (
_do_recallexcept -> logger.debug), so at default INFO level 'memory doesn't work' leaves no log trail ·__init__.py:1204 - Warn when automatic Hindsight recall is unavailable NousResearch/hermes-agent#96671 · luken · Automatic recall failures degrade to empty context with DEBUG-only logging, so a recall outage is invisible to the user ·
__init__.py:1204 - fix(hindsight): surface recall failures at default verbosity NousResearch/hermes-agent#103859 · javthak · Automatic recall/reflect failures are logged at DEBUG only and return '' — at default verbosity a broken backend is indistinguishable from an empty bank, so sessions silently run without memory ·
__init__.py:1204
__init__.py::prefetch (1 PR)
- feat(hindsight): make prefetch join timeout configurable NousResearch/hermes-agent#42232 · UniGood · prefetch()/on_session_switch() hardcode join(timeout=3.0) while typical recall takes 1.5-4.4s so prefetch often returns empty; and a prefetch worker that outlives the session-switch join can write the old session's recall into the new session's cache (no generation guard) ·
__init__.py:1235
__init__.py::queue_prefetch (3 PRs)
- fix(hindsight): gate prefetch spawns on the session that owns the slot NousResearch/hermes-agent#117244 · yingliang-zhang · queue_prefetch ignores session_id and publishes unfenced: a prefetch worker outliving on_session_switch's 3s join (or one queued before an inline compression switch) writes the old session's recall into the new session's slot; _get_client/_run_hindsight_operation rebuild the client without a lock so concurrent callers can orphan aiohttp sessions ·
__init__.py:1247 - fix(memory): harden Hindsight auto-retention NousResearch/hermes-agent#70019 · lukas-ebner · A timed-out old-session prefetch can still write its result into _prefetch_result after on_session_switch cleared it (stale recall leaks into the next session); retained turns are not scrubbed of injected before aretain_batch ·
__init__.py:1257 - fix(hindsight): fence prefetch publication to the owning session NousResearch/hermes-agent#64745 · yingliang-zhang · queue_prefetch worker publishes its recall into _prefetch_result unconditionally; on_session_switch only joins 3s then clears, so a worker that outlives the join (drain up to 10s + recall) injects the OLD session's memories into the NEW session's first turn; rapid turns also stack concurrent workers with last-finisher-wins ·
__init__.py:1260
__init__.py::_build_turn_messages (3 PRs)
- fix(hindsight): sanitize base64 data URLs before retain NousResearch/hermes-agent#21752 · bwjoke · Inline data:*;base64 payloads in retained turn text or hindsight_retain content/context are sent unredacted to Hindsight extraction, causing multi-MB retain jobs and runaway LLM spend (#21751) ·
__init__.py:1267 - fix(memory): omit inline data URLs from Hindsight retain NousResearch/hermes-agent#18093 · ThomasMarcelis · _build_turn_messages serializes user/assistant content verbatim, so inline base64 data: URLs and multimodal image parts from gateway turns are shipped to Hindsight in every automatic retain (cost + data exposure) ·
__init__.py:1270 - fix(memory): omit inline media from Hindsight turns NousResearch/hermes-agent#40027 · yinkev · Plain-string inline data:*;base64,... payloads in a user/assistant turn are retained verbatim into Hindsight documents (no redaction in _build_turn_messages) ·
__init__.py:1270
__init__.py::_make_turn_retain_job (3 PRs)
- fix(hindsight): journal retains to disk so a crash cannot drop a turn NousResearch/hermes-agent#116492 · bdrogja · Retains live only in the in-memory writer queue (_retain_queue / _make_turn_retain_job closure); SIGKILL / os._exit between enqueue and server ack silently drops the turn (#88944) ·
__init__.py:1327 - fix(hindsight): flatten retained conversation messages NousResearch/hermes-agent#94968 · zhuxiucai · Retain payload is a nested
[[user, assistant], ...]array (each turn json.dumps'd then joined), so Hindsight's conversation chunker falls back to plain-text chunking and append-mode merges yield concatenated JSON arrays instead of one flat message list ·__init__.py:1332 - fix(hindsight): bind retain work to enqueue-time session identity NousResearch/hermes-agent#64499 · yingliang-zhang · _make_turn_retain_job snapshots content/metadata/lineage/bank_id at enqueue but the writer _job still calls self._build_retain_kwargs() at run time, which re-reads mutable _retain_tags/_observation_scopes (init.py:1310-1312); a session switch between enqueue and drain stamps an OLD-session retain with NEW-session tags/scopes, contradicting its session:OLD lineage tag ·
__init__.py:1339
__init__.py::sync_turn (5 PRs)
- fix: flatten sync_turn content to enable turn-aware chunking for hindsight plugin NousResearch/hermes-agent#51270 · qxxaa · sync_turn() stores each turn as a JSON array and the retain job wraps the joined turns in another array, so retained content is a nested [[{user},{asst}],...] list that fails Hindsight's all(isinstance(turn, dict)) check and falls back to blind text chunking ·
__init__.py:1377 - fix(hindsight): flush buffered turns on session end NousResearch/hermes-agent#55046 · kumaxs · Provider does not override on_session_end(); turns buffered below retain_every_n_turns are silently dropped on gateway idle eviction/shutdown (core calls on_session_end then shutdown(), which only drains already-enqueued jobs; no on_session_switch flush on that path) ·
__init__.py:1377 - fix(hindsight): flush buffered turns on session end NousResearch/hermes-agent#36219 · aggarwaldev · With retain_every_n_turns > 1, turns buffered below the modulo boundary are silently dropped when the session ends (CLI exit, /reset, gateway expiry); provider implements on_session_switch flush but no on_session_end ·
__init__.py:1381 - fix(hindsight): retain only pending Hermes turns NousResearch/hermes-agent#38676 · de1tydev · Residual only: sync_turn advances _last_retained_turn_count right after enqueuing the append job, before the writer knows aretain_batch succeeded, so a failed append retain permanently drops that batch of turns (the PR's restore-pending-on-failure half) ·
__init__.py:1417(landed in hermes-agent09d66037f8fbefore the move; residual part still open at the pin) - fix(hindsight): acknowledge append retains after success NousResearch/hermes-agent#64599 · Tosko4 · Append-mode retain advances _last_retained_turn_count at enqueue time; if the background aretain_batch fails the writer only logs and drops the job, so those turns are silently lost ·
__init__.py:1417
__init__.py::_enqueue_retain (1 PR)
- fix(hindsight): keep retains working and profile-isolated on background threads NousResearch/hermes-agent#105898 · Kirelund · Retain writer thread is spawned via spawn_context_thread (inherits the FIRST retaining profile's secret scope) and _enqueue_retain puts jobs bare, so later profiles' retains on the shared writer run under the first profile's credentials; the daemon-start UnscopedSecretError half is already handled upstream (embedded.py:187-191) ·
__init__.py:1424
__init__.py::_tool_retain (10 PRs)
- fix(hindsight): make hindsight_retain tool fire-and-forget NousResearch/hermes-agent#22836 · bitfieldz · hindsight_retain tool calls _retain_batch(item, bank_id=...) without retain_async, so configured retain_async is ignored and the tool blocks on server-side extraction (can hit the 120s _DEFAULT_TIMEOUT -> 'Failed to store memory: ') ·
__init__.py:1437 - fix(memory/hindsight): honor configured retain_async on manual hindsight_retain NousResearch/hermes-agent#34529 · spiky02plateau · manual hindsight_retain tool calls
_retain_batch(item, bank_id=...)without retain_async, so the configured retain_async (default True) is ignored and the retain runs server-synchronously, blocking the turn ·__init__.py:1437 - fix(hindsight): make tool-call retain async to match auto-retain behavior NousResearch/hermes-agent#44414 · mgarmash ·
hindsight_retaintool call dispatches_retain_batch(item, bank_id=...)withoutretain_async, so the server processes synchronously and the agent blocks (~30s) even though auto-retain honoursself._retain_async·__init__.py:1437 - fix(hindsight): pass retain_async=True to aretain_batch in tool handler NousResearch/hermes-agent#60648 · yingliang-zhang · _tool_retain() calls _retain_batch(item, bank_id=...) without retain_async, so the configured retain_async setting is dropped (None is filtered out) and the server default applies, unlike sync_turn which forwards it ·
__init__.py:1437 - fix(hindsight): pass retain_async from retain tool NousResearch/hermes-agent#61512 · yu-xin-c · hindsight_retain tool path calls _retain_batch(item, bank_id=...) without the configured retain_async and runs inline through _run_sync, so tool retains block until HTTP ack (and trip the timeout under server backlog) unlike sync_turn's queued path ·
__init__.py:1437 - fix(hindsight): queue async retain tool calls NousResearch/hermes-agent#61513 · zzz163519 · hindsight_retain tool path never passes the configured retain_async to aretain_batch and always runs inline, so the tool call blocks the agent loop on the retain request even with retain_async=true (issue #61442) ·
__init__.py:1437 - fix(hindsight): queue tool retain with retain_async like sync_turn NousResearch/hermes-agent#70714 · Per0-1 · hindsight_retain tool calls aretain_batch inline and never passes the configured retain_async (unlike sync_turn), so a long synchronous extraction can exceed the client timeout and surface an empty TimeoutError ·
__init__.py:1437 - fix(memory/hindsight): tool retain honors configured retain_async NousResearch/hermes-agent#114542 · cagerber · _tool_retain calls _retain_batch(item, bank_id=...) without retain_async, so explicit hindsight_retain tool calls fall to the client default (sync) and block on server-side LLM extraction until the daemon LLM timeout, surfacing as an empty 'Failed to store memory:' ·
__init__.py:1437 - fix(memory): deliberate hindsight_retain saves coalesce into the session document NousResearch/hermes-agent#116093 · jervaise · _tool_retain (hindsight_retain tool) sends no document_id/update_mode, so every deliberate save mints a fresh server-side UUID document instead of appending to the session document like sync_turn does ·
__init__.py:1437 - fix(hindsight): make hindsight_retain acknowledgement reflect sync vs async response NousResearch/hermes-agent#64371 · xor-xe · _tool_retain discards the RetainResponse and hard-codes 'Memory stored successfully.'; once the tool path submits async (see #61442/#61513) that ack would misreport queue acceptance as completed storage (issue #64326) ·
__init__.py:1439
__init__.py::handle_tool_call (1 PR)
- fix(hindsight): diagnose empty TimeoutError message from tool handlers NousResearch/hermes-agent#103156 · JackReis · hindsight_retain/recall/reflect timeouts surface as a blank 'Failed to store memory: ' because TimeoutError stringifies to '' in handle_tool_call's generic except ·
__init__.py:1472
__init__.py::on_session_switch (2 PRs)
- fix(memory): respect auto_retain in on_session_switch flush path (#54183) NousResearch/hermes-agent#54192 · liuhao1024 · on_session_switch flushes buffered _session_turns to Hindsight on 'if self._session_turns:' alone, ignoring auto_retain=false, while sync_turn gates on _auto_retain (init.py:1366) - buffered turns get archived on /new, /reset or compression even with automatic retention disabled ·
__init__.py:1503 - fix(hindsight): flush only the un-retained delta on session switch NousResearch/hermes-agent#41911 · Vesna-9 · In append mode on_session_switch re-sends the full _session_turns buffer (list(self._session_turns)) although sync_turn already appended those turns and advanced _last_retained_turn_count, duplicating the whole session in the document on every /resume, /branch, /new, /reset or compression ·
__init__.py:1506(landed in hermes-agent6e1de4850e6before the move; residual part still open at the pin)
__init__.py::shutdown (6 PRs)
- fix(hindsight): implement on_session_end to flush buffered turns at session exit NousResearch/hermes-agent#28845 · fayenix · With retain_every_n_turns > 1, turns buffered in _session_turns since the last boundary are dropped on clean shutdown: shutdown() only drains the writer queue and there is no on_session_end() (only on_session_switch flushes) ·
__init__.py:1558 - fix(memory/hindsight): flush buffered turns on session exit NousResearch/hermes-agent#55936 · bernardokyotoku · With retain_every_n_turns > 1, turns buffered in _session_turns since the last boundary are silently dropped on exit: the provider has no on_session_end() override and shutdown() only drains already-queued retains ·
__init__.py:1558 - fix(hindsight): guard prefetch results by query and flush buffered turns on shutdown NousResearch/hermes-agent#77454 · zayn-0101 ·
shutdown()sets the shutting-down flag without flushing_session_turns, so withretain_every_n_turns > 1a session ending below the cadence loses its buffered turns;prefetch()also still consumes_prefetch_resultwithout binding it to the query that produced it ·__init__.py:1558 - fix(memory): flush Hindsight's buffered turns when a session ends NousResearch/hermes-agent#89801 · AP3X-Dev · With retain_every_n_turns > 1, turns buffered in _session_turns are dropped when a session ends without a session_id rotation or on shutdown(): no on_session_end() and shutdown() sets _shutting_down without flushing the buffer ·
__init__.py:1558 - fix(memory): harden Hindsight lifecycle durability NousResearch/hermes-agent#94252 · rutgerblom · shutdown() drops the partial retain batch (retain_every_n_turns>1) and pending retain ops are tracked against a single bank id, not (bank_id, op_id); also no isfinite guard on port_health_grace_timeout ·
__init__.py:1558 - fix(hindsight): flush final partial retain after memory sync drain NousResearch/hermes-agent#109147 · stepanov1975 · shutdown() sets _shutting_down and sends the writer sentinel without enqueuing the buffered partial batch, so with retain_every_n_turns>1 the last <N turns are lost at process exit (#36216) ·
__init__.py:1558
__init__.py::register (1 PR)
- fix(hindsight): cache provider instance to preserve turn counter across agent re-initialization (#35763) NousResearch/hermes-agent#35775 · liuhao1024 · register() builds a fresh HindsightMemoryProvider on every plugin (re)load, so _turn_counter resets and retain_every_n_turns>1 never fires when the agent is re-initialized in-process (#35763) ·
__init__.py:1590
config_schema.py::CONFIG_SCHEMA (3 PRs)
- fix(hindsight): add local_embedded mode and full local-mode fields to config schema NousResearch/hermes-agent#84586 · blut-agent · Desktop Memory panel for a local_embedded Hindsight config shows Mode=Cloud, the cloud API URL default and API key 'not set', because the declared schema only knows cloud/local_external and has no llm_* fields (#84572) ·
config_schema.py:17 - fix(desktop): show Hindsight local embedded mode NousResearch/hermes-agent#57126 · dschnurbusch · declared config schema lacks
local_embeddedin mode options, so Desktop/web coerce an existing local_embedded config to cloud ·config_schema.py:23 - fix(hindsight): add local_embedded to mode options in declared config schema NousResearch/hermes-agent#83644 · hharkins · Declared config schema mode select lists only cloud/local_external, so a configured mode: local_embedded is coerced to the default 'cloud' in the desktop Memory settings payload (shows Cloud + missing API key badge) ·
config_schema.py:23
embedded.py::_check_local_runtime (2 PRs)
- fix(memory/hindsight): handle lazy import and mock exceptions correctly NousResearch/hermes-agent#79622 · vivagas2007-glitch · _check_local_runtime unconditionally imports sentence_transformers, so local_embedded with remote embeddings (gemini/openai/tei) + non-local reranker is reported unhealthy on hosts without the torch/sentence-transformers stack; also _run_hindsight_operation drops the stale embedded client (self._client = None) without closing it and serializes nothing across concurrent providers ·
embedded.py:70 - fix(hindsight): surface the exception chain when the local runtime is unavailable NousResearch/hermes-agent#61611 · falkoro ·
_check_local_runtime()returnsstr(exc), dropping the cause/context chain, so a wrapped ImportError (e.g. sentence-transformers wrapper hiding a huggingface-hub version conflict) reports the misleading outer message only ·embedded.py:74
embedded.py::_ensure_local_runtime (1 PR)
- fix(hindsight): preserve the official embedded lifecycle with MCP 2 NousResearch/hermes-agent#98569 · neilinger · local_embedded installs
hindsight-allinto the Hermes venv (embedded.py:133 self-install, setup.py:118 setup), whose server stack resolves MCP 1.x against Hermes' mcp==2.0.0 (#95855); verify whether hindsight-all 0.10.x still pins mcp<2 before filing ·embedded.py:133
embedded.py::_build_embedded_profile_env (17 PRs)
- fix(hindsight): propagate local CPU env on macOS NousResearch/hermes-agent#8648 · sgaofen · local_embedded daemon on macOS/Apple Silicon hangs or times out at startup because HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU / HINDSIGHT_API_RERANKER_LOCAL_FORCE_CPU are never written to the profile env or the daemon start environment (#7135, #8972 still open) ·
embedded.py:211 - feat(hindsight): pass embeddings/reranker config to local_embedded daemon NousResearch/hermes-agent#18521 · laoli-no1 · local_embedded profile .env is rebuilt from an LLM-only env map, so a daemon restart drops HINDSIGHT_API_EMBEDDINGS_LOCAL_/HINDSIGHT_API_RERANKER_LOCAL_ settings the user added ·
embedded.py:211 - fix(hindsight): propagate embedding/reranker config to embedded daemon NousResearch/hermes-agent#28451 · Mxin-9527 · build_embedded_profile_env emits only LLM/log/idle-timeout keys, so HINDSIGHT_API_EMBEDDINGS/HINDSIGHT_API_RERANKER_ from env/config never reach the embedded daemon, which silently falls back to local sentence-transformers ·
embedded.py:211 - fix: propagate Hindsight embedding env vars NousResearch/hermes-agent#31511 · radigant · HINDSIGHT_API_EMBEDDINGS_PROVIDER/_OPENAI_MODEL/_OPENAI_BASE_URL/_OPENAI_API_KEY are dropped from the embedded daemon profile env, so the daemon falls back to local SentenceTransformer and can hit embedding-dimension mismatches ·
embedded.py:211 - fix(hindsight-plugin): propagate local LLM concurrency setting NousResearch/hermes-agent#31681 · GregChinn · HINDSIGHT_API_LLM_MAX_CONCURRENT (from ~/.hermes/.env or config) is not written into the embedded daemon's per-profile env and therefore does not trigger the profile-env change/restart detection; a running daemon keeps the default 32 and can starve a shared local LLM endpoint ·
embedded.py:211 - fix: forward Hindsight embedded env and bound client setup NousResearch/hermes-agent#35052 · s09x · Embedded profile env drops extra HINDSIGHT_API_/HINDSIGHT_EMBED_ config keys (and llm_reasoning_effort); tool-path retain ignores configured retain_async ·
embedded.py:211 - fix(hindsight): force CPU embeddings on macOS ARM64 to prevent MPS memory leak NousResearch/hermes-agent#37340 · E-R-Butch · On macOS Apple Silicon the embedded daemon's local embeddings/reranker use PyTorch MPS, which pre-allocates 4GB+ IOAccelerator memory and can hang startup on 8GB machines (#7135/#8972); nothing sets HINDSIGHT_API_EMBEDDINGS_LOCAL_FORCE_CPU/RERANKER_LOCAL_FORCE_CPU ·
embedded.py:211 - fix(hindsight): persist embed_model config across daemon restarts NousResearch/hermes-agent#48717 · ycj · _build_embedded_profile_env never writes HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL, so a restarted embedded daemon falls back to the 384-dim default and crashes with an embedding-dimension mismatch against the existing DB ·
embedded.py:211 - fix(hindsight): preserve embedded config and shared HF compatibility NousResearch/hermes-agent#63936 · lipton-agent-hermes · Embedded profile .env is rebuilt from only LLM/idle settings (drops configured HINDSIGHT_API_/HINDSIGHT_EMBED_ values) and compared with strict equality (restart churn); prefetch join timeout is hardcoded to 3.0s instead of honoring prefetch_join_timeout ·
embedded.py:211 - fix(hindsight): persist HINDSIGHT_EMBED_API_DATABASE_URL into embedded profile env NousResearch/hermes-agent#68479 · ygd58 · _build_embedded_profile_env omits HINDSIGHT_EMBED_API_DATABASE_URL, so an embedded daemon restart reconnects to the default per-profile pg0 DB even when a database URL override is configured (#21809) ·
embedded.py:211 - fix(memory/hindsight): preserve extended HINDSIGHT_API_* daemon config through env materialization NousResearch/hermes-agent#81844 · KIAgent01 · build_embedded_profile_env emits only the base LLM keys, so extended HINDSIGHT_API* (embeddings/reranker/consolidation/failover/DEFAULT_HEADERS) settings are dropped on rewrite, and any extra key in the daemon env file makes the saved!=expected comparison permanently true -> rewrite + daemon restart every session start ·
embedded.py:211 - fix(hindsight): keep custom embeddings in the embedded profile env NousResearch/hermes-agent#102908 · fs-lx · local_embedded: _build_embedded_profile_env forwards only LLM keys, so HindsightEmbedded.ensure_running/_register_profile re-renders the profile .env with embeddings/reranker keys commented out; next cold start falls back to bge-small (384-d) and fails with 'Cannot change embedding dimension' ·
embedded.py:211 - fix(memory): carry unmanaged keys forward in embedded profile env NousResearch/hermes-agent#112580 · pnpwin · _build_embedded_profile_env rebuilds ~/.hindsight/profiles/.env from a fixed key set and _materialize_embedded_profile_env overwrites it, dropping operator-owned daemon keys (embeddings/reranker/failover/PORT) ·
embedded.py:211 - fix(hindsight): preserve default LLM headers NousResearch/hermes-agent#112655 · fangliquanflq · _build_embedded_profile_env omits HINDSIGHT_API_LLM_DEFAULT_HEADERS, so relay-required LLM headers are dropped on every daemon env rewrite and extraction fails (#112645) ·
embedded.py:211 - fix: preserve embedded Hindsight database URL NousResearch/hermes-agent#39414 · psanger · _build_embedded_profile_env() never writes HINDSIGHT_EMBED_API_DATABASE_URL, so a configured explicit pg0 database_url is dropped when the profile env is rewritten and the embedded daemon drifts back to the default pg0 database ·
embedded.py:215 - fix(hindsight): allow local LLM without API key (#70814) NousResearch/hermes-agent#70886 · webtecnica · Embedded daemon env gets HINDSIGHT_API_LLM_API_KEY=""; for openai_compatible (normalized to 'openai' by _daemon_llm_provider) with no key, hindsight-api still raises 'LLM API key is required' (memory_engine.py:2257). Plain ollama/lmstudio/etc. are now tolerated by hindsight-api's requires_api_key() ·
embedded.py:217 - fix(memory): preserve Hindsight embedded LLM headers NousResearch/hermes-agent#112648 · KoNit-K · _build_embedded_profile_env never emits HINDSIGHT_API_LLM_DEFAULT_HEADERS, and the profile env is rewritten on every daemon start, so relay session headers (e.g. x-opencode-session) are dropped and extraction requests get rejected (#112645) ·
embedded.py:230
embedded.py::_secure_write_profile_env (1 PR)
- fix(hindsight): preserve profile env, export offline mode, and eliminate restart storms NousResearch/hermes-agent#118858 · E-R-Butch · _secure_write_profile_env O_TRUNC-overwrites ~/.hindsight/profiles/
.env (drops user keys like HF_HUB_OFFLINE), and the full-dict on-disk-vs-built env compare restarts a healthy daemon every start when upstream appended extra keys (e.g. HINDSIGHT_API_PORT) ·
embedded.py:244
embedded.py::_materialize_embedded_profile_env (5 PRs)
- fix(hindsight): preserve embedded profile env NousResearch/hermes-agent#26890 · yaronelh · User-managed keys in ~/.hindsight/profiles/.env (e.g. HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY) are erased because the file is rewritten wholesale from only Hermes-managed values whenever it differs from the expected set ·
embedded.py:262 - hindsight: preserve HINDSIGHT_API_EMBEDDINGS_* vars on env file rewrite NousResearch/hermes-agent#41365 · qjsby1989 · materialize_embedded_profile_env() rewrites hermes.env wholesale, so hand-added HINDSIGHT_API_EMBEDDINGS* vars are clobbered (and the drift check at init.py:1128 sees a mismatch on every start, rewriting + restarting the daemon) ·
embedded.py:262 - fix(hindsight): preserve HINDSIGHT_API_PORT during profile env materialization NousResearch/hermes-agent#48191 · buwenzheng · _materialize_embedded_profile_env rewrites the profile .env from _build_embedded_profile_env (LLM keys + idle timeout only), so a user-set HINDSIGHT_API_PORT is dropped on every restart and the daemon rebinds to the hash-derived port ·
embedded.py:262 - fix(memory): isolate providers and preserve Hindsight config NousResearch/hermes-agent#68376 · jasonlizs12-ui · materialize_embedded_profile_env rewrites the profile .env from scratch (O_TRUNC) and the change check compares the whole dict, so hand-added HINDSIGHT* keys are erased and cause a rewrite+daemon restart on every start; blank llm_provider/llm_model can overwrite a working profile ·
embedded.py:262 - fix(memory): preserve user-configured hindsight embedded keys NousResearch/hermes-agent#71107 · xrwang8 · local_embedded rewrites the whole profile .env from Hermes-managed keys only, destroying user-added HINDSIGHT_* keys (embedding/reranker/port), and any user key makes the full-map compare report 'config changed' and restart the daemon every start ·
embedded.py:262
settings.py::_OBSERVATION_SCOPE_KEYWORDS (module constants) (3 PRs)
- fix(memory): bump hindsight-client pin to >=0.7.2,<0.8 for Hindsight server v0.7.x compatibility NousResearch/hermes-agent#41848 · fengtianyu88 · client floor still 0.6.1 (plugin.yaml
hindsight-client>=0.6.1, settings._MIN_CLIENT_VERSION='0.6.1'): an already-installed 0.6.x client passes the version gate but 404s on POST /v1/retain against 0.7+ servers ·settings.py:17 - fix(hindsight): preserve shared observation scopes NousResearch/hermes-agent#72565 · wayne1992127 ·
_OBSERVATION_SCOPE_KEYWORDSlacks 'shared', soobservation_scopes: sharednormalizes to None and is never forwarded to the Hindsight API ·settings.py:44 - fix(hindsight): the one scope that dedupes across sessions was unreachable NousResearch/hermes-agent#114716 · i-Hun · observation_scopes='shared' (and the explicit [[]] spelling) silently normalizes to None -> 'combined', so per-session provenance tags make every session its own consolidation scope and standing facts duplicate once per session ·
settings.py:44
settings.py::_normalize_observation_scopes (1 PR)
- fix(hindsight): preserve explicit [[]] shared observation scope (#74933) NousResearch/hermes-agent#75092 · jeff-mettel · _normalize_observation_scopes drops the explicit empty inner list, so observation_scopes=[[]] (Hindsight's 'shared' scope) silently normalizes to None and reverts to the 'combined' default, fragmenting observations by session tag ·
settings.py:109
setup.py::run_setup (2 PRs)
- fix(memory): use slim Hindsight stack on Intel macOS instead of bare hindsight-all NousResearch/hermes-agent#81530 · Enough1122 · local_embedded setup/heal unconditionally installs
hindsight-all; on Intel macOS (x86_64) the resolver backtracks to ancient releases and the daemon fails with 'Unknown embeddings provider: onnx' (#81421) ·setup.py:118 - fix(memory): extend embedded Hindsight install timeout NousResearch/hermes-agent#85409 · neilinger ·
hermes memory setupinstalls hindsight-all for local_embedded with install_specs(..., timeout=120); a clean venv rebuild of the heavyweight embedded stack routinely exceeds 120s and fails ·setup.py:119
Also seen in hermes-agent PRs that stay open for an unrelated core-side change
These PRs remain open on hermes-agent because they also carry a generic core fix; their Hindsight half is dead there but the same bug exists at the pin:
- fix(shutdown): harden 44 cleanup blocks against second-Ctrl-C KeyboardInterrupt crash NousResearch/hermes-agent#22876 · Ramadas108 · second Ctrl-C during shutdown() escapes the unguarded writer.join()/prefetch join as KeyboardInterrupt and skips client close ·
__init__.py:1566 - fix: clean up evicted memory providers (salvage of #63703 + review fixes) NousResearch/hermes-agent#64284 · jaisinha77777 · HindsightMemoryProvider.shutdown() never atexit.unregister()s _atexit_shutdown, so every torn-down instance stays reachable from atexit's registry until process exit ·
__init__.py:1530 - fix(memory): scope hindsight status env-var check to active mode NousResearch/hermes-agent#68073 · patrykkopycinski ·
hermes memory statusfor hindsight in local_embedded lists cloud-only HINDSIGHT_API_KEY (twice) as missing and prints an empty/stale config block because the provider has no get_status_config() override ·__init__.py:478 - fix: local_embedded runtime probe detects fastmcp/mcp 2.0 conflict (#95855) NousResearch/hermes-agent#95873 · kokhlo · _check_local_runtime still does importlib.import_module('hindsight') first, so if hindsight-all's fastmcp dep conflicts with Hermes' mcp==2.0.0 pin (pyproject.toml:220/294/305 on main) the in-venv probe fails with a bare 'No module named'/ImportError and local_embedded reports unavailable with a generic hint ·
embedded.py:70
Notes
- Line numbers are from a checkout of this repo at
dc75038866a3966b5bf5c82ff57c7a758bb4070a; they will drift, but the enclosing symbol should locate each one. - hermes integration: two post-handoff fixes from the bundled hermes-agent copy are missing (append-mode turn buffer bound, gated root warning) #4640 already tracks the two post-handoff fixes from the bundled copy (append-mode turn-buffer bound, gated root warning); this list is the community-PR backlog on top of that.
- One entry is caveated: fix(hindsight): clear the shutdown latch on session switch so long-lived processes keep retaining NousResearch/hermes-agent#69001's premise (shutdown latch never cleared on session switch) was disputed in its review and is unreproduced; the code it patches is identical at the pin, so it is listed for completeness.
- If any of these is already handled on your
mainsince the pin, a SHA-bump PR against hermes-agentplugin-catalog/hindsight.yamlis all it takes to get users onto it.
Activity
UniGood commentedon Sep 23, 2026
Taking the
__init__.py::prefetch/queue_prefetchentries (NousResearch/hermes-agent#42232).I'll port both halves to
hindsight-integrations/hermesand open a PR here shortly:prefetch_join_timeoutconfig key (guarded float parser, schema, README) — the joins at__init__.py:1235and:1525are still hardcoded to 3.0s while a typical recall takes 1.5-4.4s, so prefetch often returns empty._prefetch_generationguard inqueue_prefetch— closes the race where a timed-out switch join lets an old-session worker write stale recall into the new session's cache (same class as #117244 / #70019 / #64745).Will self-review against the pin before sending. Shout if someone is already on this and I'll stand down.
fix(hermes): retain conversation turns as a flat message array
fix(hermes): pass retain_async=True to aretain_batch in tool handler
fix(hermes): retain conversation turns as a flat message array
yaozhen commentedon Sep 23, 2026
Adding measurements from a live install, plus two things that are not yet in your list. Environment: hermes-agent
v0.21.3(editable install),hindsight-embed 0.9.2,local_embedded, profilehermes, macOS 15.7.7, LLMopenaiprovider → OpenAI-compatible base URL (DeepSeek V4.1 Flash).1. Quantified impact of the drift-compare restart (adds to #83076 / #104324 / #107324 / #118858)
All four of those reports are the same root cause and it is still live at
0.9.2:=== Config changed, restarting daemon ===in~/.hermes/logs/hindsight-embed.log: 18 in a single day (up to 21/day) while the daemon was in active use — each one stopping a healthy, running daemon and paying a cold start._wait_for_retains_drained()with a 10.0s budget (prefetch_retain_drain_timeoutdefault). Measured single retain op duration from the daemon's own*_RETAIN COMPLETE: … in Nslines: median 13.4–14.0s, 66% over 10s (4 days, one install) — i.e. the budget is structurally smaller than the work, and the warningPrefetch: server retain visibility timed out after 10.0s; dropping N unresolved op(s)fires on ~74% of turns. Restarts make this worse but are not the main term (30% of those timeouts land within 180s of a restart, vs 3.75% of wall-clock covered by those windows).2. Not in the list: per-operation LLM keys are unreachable in the embedded path
This matters because several of the listed fixes are "profile env drops keys", but fixing preservation alone does not make the per-op LLM knobs usable:
hindsight_apisupports them (config.py:HINDSIGHT_API_RETAIN_LLM_MODEL/_REASONING_EFFORT/_EXTRA_BODY, parsed withjson.loads).profile,llm_provider,llm_api_key,llm_model,idle_timeout,llm_base_url), so anything inconfig.jsonoutside those is not forwarded.~/.hindsight/profiles/<p>.envdoes not survive: the file is rewritten from the bundled template on every daemon start (the same rewrite as above).daemon_embed_managerbuilds the child env asenv = os.environ.copy()and only then merges the profile config (daemon_embed_manager.py:725and theHINDSIGHT_*filter below it), so aHINDSIGHT_*key in the Hermes.env(loaded into the gateway process, inherited by the daemon) reaches the daemon and survives the rewrite. Verified on the running process (ps -Eww <hindsight-api pid>showsHINDSIGHT_*keys that exist only in the Hermes.env).Request: when porting the env-preservation fixes, please also give the integration a supported config path for the per-operation LLM keys (model / reasoning_effort / extra_body per operation) instead of leaving ambient env as the only route.
3. Not in the list: the retain extraction spends most of its time on hidden reasoning
Same install, same prompt, no truncation, model
deepseek-v4.1-flashserved through an OpenAI-compatible aggregator:reasoning_effort: "none"reasoning_effort: "low"thinking: {"type": "disabled"}Two takeaways for the integration: (a)
reasoning_effortalone does not disable thinking on this OpenAI-compatible backend (DeepSeek's own docs:nonedisables,low/high/maxall enable, defaulthigh), so a "set effort to none" fix is not enough — thethinkingfield (or an equivalent extra_body) is what works; (b) with thinking off, a retain op drops from ~14s to ~2s, which makes the 10s visibility budget meaningful again. Worth considering whether the defaultprefetch_retains_drain_timeout: 10.0should stay coupled to a model-dependent op duration at all, or whether the wait should be opt-in.Happy to re-measure against
hindsight-integrations/hermesonce the port lands.Driskol commentedon Sep 23, 2026
Opened #4694 for the
_daemon_start_worker/_build_embedded_profile_enventry of this list: the drift check in_start_daemonnow compares only the keys_build_embedded_profile_envproduces, so a foreign key living in the shared profile env (HINDSIGHT_API_PORT, written by the daemon itself) no longer rewrites the file and stops a healthy daemon. Reproduced with the plugin's own fixtures (6 keys on disk vs 5 built: old compareTrue, new checkFalse) anduv run pytest tests30/30 green.On the
_reflectentries (#62205,#115629): the tag-filter forwarding is the one that bites in practice. The eager version of it — forward the configuredrecall_tags/recall_tags_matchtoareflect, same as_recallalready does — ran in production on a bank with anany_strictspeciality filter for a while, and the symptom it removes is the whole-bank scan: 105k-156k tokens retrieved against a 100k context, split synthesis, 300s wall-clock timeout and an empty reply; with the filter, 55s and a real 11k-character synthesis. If you would rather not change the default, the opt-inreflect_tagsvariant from NousResearch/hermes-agent#115501 (closed with the handoff) is on the table too — the config normalization it uses comes from the retained-tag path. Happy to port either one if that helps; I did not want to open a competing PR against a_reflectentry that is already on this list.balloon5 commentedon Sep 25, 2026
Windows data point for the
config_schema.py::CONFIG_SCHEMAentry (carried over from hermes-agent #84572 / #83644 / #84586) — the confirmations so far are Linux-only:v0.21.3 (2026.9.14)/ upstreambc655bfb· still on the bundled provider (pre-migration):<profile>/hindsight/config.jsonholds"mode": "local_embedded", the agent log showsHindsight initialized: mode=local_embedded, the embedded daemon serves127.0.0.1:9177and recall/retain work — while the panel renders Mode = Cloud with the cloud API URL.provider.config_schema()) returnslocal_embeddedcorrectly, so the stored value is intact; only the declared-schema path coerces it.local_embeddedtocloud.Checked the default branch today:
hindsight-integrations/hermes/config_schema.py(f38d53b0ad) still declares onlycloud/local_externalat lines 22–25, so the five-line fix from #83644 still applies as-is. Has anyone picked this one up? If a PR is welcome, I'm happy to test a build on Windows 11 + v0.21.3 and report back.woshicby commentedon Sep 26, 2026
Taking the
__init__.py::unavailable_reasonentry (NousResearch/hermes-agent#86078) — ported it intohindsight-integrations/hermes: #4808.At the current pin, cloud mode still returned
""fromunavailable_reason(), so the host's "provider unavailable" warning (which already explains that a gateway/systemd unit does not inherit~/.hermes/.env) never named the variables to set.The port keeps the local branches untouched —
local_embeddedkeeps thehindsight-allinstall hint,local_externalstays hint-free because it is available without cloud credentials — and adds the cloud case with the same three inputsis_available()gates on (apiKey,api_url,HINDSIGHT_API_URL).Patch + tests: 7 new cases,
uv run pytest tests -q→ 33 passed.Happy to fold this into an existing PR if someone is already on it.
slyvixenops commentedon Sep 29, 2026
Two additions to the carried-fix hotspots from running this integration on a PM-managed Hermes install (py3.14 generation env):
1.
_find_api_commanduv fallback — silent ABI death on every auto-start. When the fallback resolves thehindsight-apiconsole script from the uv archive cache, that binary's interpreter is a different Python minor than the generation env whose site-packages the spawn inherits (pydantic_coreand other compiled deps load from the gen env). Result:ModuleNotFoundError: No module named 'pydantic_core._pydantic_core'on every auto-start, each attempt burning the full start-timeout window before failing. The fix shape that holds up in production here: resolve the command from the importedhindsight_embedpackage's own environment — its siblingbin/hindsight-api, or<that env's python> -m hindsight_api.main— so the spawned daemon is independent of the parent'ssys.path/PYTHONPATHentirely.2. Profile-env rewrites have two writers with different formats — user lines silently dropped. The Hermes-side plugin materializes a minimal
KEY=VALUEprofile env, while the embed manager'screate_profilerenders the full documented template (render_config) — and each rewrite drops every active line the other side didn't include. Any user-added profile-env setting (we lost a concurrency-tuning line this way) survives only until the next rewrite by whichever writer runs. Suggest folding into the 17-PR profile-env-rewrite hotspot: whichever component owns the file long-term, the other's format must merge rather than replace, or the file needs a "user lines" section both writers preserve.fleet2jeep commentedon Sep 29, 2026
Dropping the O_TRUNC write-path case here per the scoping in #4694 — the compare fix deliberately leaves this half alone, so pinning it red while it's still unfixed.
The case (from the production host that ran the compare fix as a local patch since 2026-08-31): when a genuine drift rewrite does fire — model changed, base URL rotated, whatever the governed compare correctly flags —
_secure_write_profile_envopens the fileO_TRUNCand writes only the keys_build_embedded_profile_envproduces. Every operator-owned or daemon-registered key that isn't in that build disappears in one write. On the live host this ateHF_HUB_OFFLINE=1andHINDSIGHT_EMBEDDINGS_LOCAL_FORCE_CPU=true— a silently changed embedder behavior with nothing in the logs to say so.The red test (runs green against
fix/profile-env-drift-compareas a failing assertion, i.e. it documents today's behavior):The flip side: when the compare fix lands, rewrite frequency drops to "only on genuine drift," which makes each rewrite more costly to get wrong — the rare rewrite that silently drops an operator's
HF_HUB_OFFLINEis arguably harder to notice than the old every-start churn everyone learned to watch for. So the two halves compound: the compare fix makes restarts rare, and this half decides whether those rare rewrites are lossy.Happy to rework it into a merge-preserving formulation (e.g.
_materializeseeding foreign keys first, rewrite preserving keys outside the governed set) if that matches where you want to take it. Full context and the other three production cases: #4694 (comment)test(hermes): port the profile-env production cases from vectorize-io…
fix(hermes): pass retain_async=True to aretain_batch in tool handler
fix(hermes): fence prefetch publication to the owning session generation
fix(hermes): bind retain work to enqueue-time session identity
8 remaining items
dudu7615 commentedon Oct 6, 2026
Hi @carlosrve , thanks for keeping this tracked and for making the session-end boundary explicit in #5218 — that is what prompted me to measure it.
Here is the deterministic, provider-level measurement you asked for — four cases at
retain_every_n_turns=8with two turns buffered below cadence, driven through the recording client (no live server, no private bank):cc806c4d6on_session_endoverrideon_session_endalone (no switch, no shutdown)on_session_switchshutdownaloneon_session_end→on_session_switch(CLI/neworder)Case 1 is the session-end gap you flagged in #5218: with no override the base hook is a no-op and
shutdown()only drains the writer queue, so the buffered tail is lost. Case 4 is the idempotence guard — clearing the buffer on flush means the following switch has nothing to re-send. RED contrast: commenting out onlyself._session_turns.clear()makes case 4 fail withassert 2 == 1, both retains carrying byte-identical payloads (the #41911 duplicate).Case 3 I record as not fixed by this:
shutdown()never touches the buffer, so it belongs to the durable-outbox work tracked in #5218, not to theon_session_endoverride — asserting the loss keeps that boundary explicit.Branch
dudu7615/hindsightfix/hermes-session-end-boundary-evidence@e46e92699; tests intests/test_session_end_boundary.py. Also independently confirmed on a live self-hosted install (real daemon, scratch bank):end→switchyields the tail once, no duplicate document. Happy to run more cases or adjust the assertions if that would help.carlosrve commentedon Oct 7, 2026
Thanks @dudu7615 — the four-case measurement and RED contrast make the session-end boundary much clearer. Your distinction between the end-hook fix and the shutdown-only tail is correct.
Our reference fork now covers both boundaries in the merged outbox feature. At the validated composed revision, test_retain_outbox.py includes shutdown below cadence, session-end without switch, repeated end followed by switch/shutdown in append and legacy modes, failed staging preserving the buffer, and API-outage recovery by a new provider without a new turn. The pinned real-Hermes callback probe and scripted-model API/Postgres tests also passed in the final CI run.
These are deterministic reference tests, not a claim that upstream main has the fix or that transport delivery is exactly once after an ambiguous acknowledgement. API acceptance is distinct from later extraction completion. #5218 remains the focused durability report; your independent evidence is valuable for its acceptance criteria. No additional measurements are needed from you for the four cases above.
yingliang-zhang commentedon Oct 8, 2026
Verification: are the fixes from the closed hermes stack (#4671–#4676) on
main?Ran the regression tests from those PRs against
main(fb11ddf) inhindsight-integrations/hermes(provider source frommain, fixtures fromtests/conftest.py): 7 passed, 11 failed.main?test_client_lifecycle.pygreen;_client_lockpresent in__init__.pyretain_asyncin_tool_retainKeyError: 'retain_async'shutdown()new-tagafter a mid-flight session switch (['new-tag', 'session:session-1']instead of['old-tag', 'session:session-1'])Memory 1/2into the child's slot</memory-context><forged>…and a code-fencesystemblock reach the prompt verbatimNotes:
maintoday.main/ passes against its fix — they are in the diffs of fix(hermes): pass retain_async=True to aretain_batch in tool handler #4671–fix(hermes): frame recalled context as untrusted data #4676 (tests/test_client_lifecycle.py,tests/test_retain_async_forwarding.py,tests/test_retain_identity_isolation.py,tests/test_prefetch.py,tests/test_untrusted_recall_framing.py) and drop intohindsight-integrations/hermes/tests/as-is.carlosrve commentedon Oct 9, 2026
Independently reproduced the manual-retain policy and stale-prefetch publication cases against our pinned reference integration (
e211b2b00). They are now separated in maintained-fork reference PRs #7 and #8; credit to @yingliang-zhang for #4671/#4672/#4675 and the independent regression report above. No new external PR is being submitted.The prefetch tests explicitly release blocked workers after switch, end, shutdown and a newer empty response; owner admission is also checked before spawning. A bounded join is not treated as worker cancellation. Manual retain tests record the forwarded
retain_asyncvalue for both settings. The temporary combination passed 81 deterministic provider tests, including the pinned real-Hermes callback probes. API clients are mocked for these tests; acceptance is not extraction completion.The separate framing report is now #5424. These references establish a reproducible patch/test path, not that upstream main contains the fixes or that a production instance was updated.
yaozhen commentedon Oct 9, 2026
Follow-up from the install I reported on 2026-09-23 (same host, still
local_embedded): the drift-compare fix is merged and correct, but it is not reachable from the Hermes plugin catalog yet — catalog installs are still getting the pre-fix tree.The fix (confirmed merged)
fd70557c6— fix(hermes,embed): local_embedded daemon start, restarts and Windows launch (#5360), 2026-10-06 — introduces the owned-key set (_PLUGIN_PROFILE_ENV_KEYS) and compares on those keys only, i.e. the #5222 root cause. Its patch adds the key set, and the same code path is present in the v0.10.3 tree (2026-10-08) and onmain.Why it doesn't arrive
The catalog pin for this provider is
release(hermes): v1.2.1=d56c4acd, 2026-09-30 — 257 commits behind v0.10.3.hindsight-integrations/hermes/plugin.yamlonmainstill declaresversion: 1.2.1, the same string as that release commit, so nothing downstream can distinguish the fixed tree from the unfixed one. Verified on a live install:hermes plugins browseshows1.2.1 @ d56c4acd, andhermes plugins updatere-installs it —grep -c _PLUGIN_PROFILE_ENV_KEYSin the installed tree = 0.Measured today (2026-10-09, after updating the plugin v1.0.1 → v1.2.1 and restarting the gateway)
=== Config changed, restarting daemon ===keeps firing: 217 cumulative, 20 of them on 2026-10-08 (a busy session day; 3–6/day on quiet days). Each one SIGTERMs a healthy, running daemon.create_profiletemplate again (701 lines, 46,321 B, containingHINDSIGHT_API_PORT=9177) — that port line is exactly what the full-dict compare reads as drift.--idle-timeout 1800survives, and the local embedder/reranker (BAAI/bge-small-zh-v1.5, dim 512, cpu;BAAI/bge-reranker-v2-m3) load cleanly on each cold start (11:31:17 → 11:31:51). The profile env keeps the LLM provider/key/base-url/model, so no keyless boot. The cost is availability (~16 s per restart), not correctness.Ask: is a new
release(hermes)(or a bump to 1.2.2) planned so catalog pins can carryfd70557c6? Otherwise every catalog consumer stays on the unfixed tree until the pin is re-pointed manually — happy to re-verify on this host once a pin carrying it is published. /cc @nicoloboschiSlaventuss commentedon Oct 11, 2026
Still live at 1.2.1 /
main— the_tool_retainentry on this list is a user-visible failure, not a cosmetic gap.Two PRs already target exactly this (
#4671,#4670) and both were closed without merge, whilehindsight-integrations/hermes/__init__.pyatmainstill ships the synchronous call:_tool_retain→self._retain_batch(item, bank_id=self._bank_id)—retain_asyncnever forwarded_retain_batch→kwargs = {k: v for k, v in kwargs.items() if v is not None}— drops theNone, so the client's own default (aretain_batch(..., retain_async: bool = False)) applies → SYNC_make_turn_retain_job) always passesretain_async=self._retain_async, which is why only agent-chosen retains breakMeasured on a live install (Hermes profile + plugin 1.2.1, daemon healthy, idle queue):
retain_async=True: 0.46–0.52 s, returns anoperation_idHINDSIGHT_TIMEOUTis the cliffConsequence for users: past 120 s the plugin's
_run_syncraisesconcurrent.futures.TimeoutError, whosestr()is empty, and the tool handler renders it as the opaque user-visibleFailed to store memory:with no reason. ~18 episodes logged on one install over two months, while/health, parked-ops and the memory-LLM probes all stayed green — the failure is purely client-side, so no server-side monitoring can see it.Minimal carry-over (this is #4671's diff; it still applies cleanly at
main):logger.debug("Tool hindsight_retain: bank=%s, ...", ...) - self._retain_batch(item, bank_id=self._bank_id) + resp = self._retain_batch(item, bank_id=self._bank_id, retain_async=self._retain_async) + if self._retain_async: + self._track_retain_ops(resp, self._bank_id) logger.debug("Tool hindsight_retain: success")plus a belt-and-braces default in
_retain_batchbefore the kwargs dict is built:Happy to test a patch branch against a real install (postgres, 17k-fact bank, Hermes 0.21.4,
local_externalmode) if that helps. Filed as a follow-up to the closed PRs rather than a new issue, since this list is the right home for it. Unrelated to #5486 (env-compare restart loop), which I filed separately.hugetoucan commentedon Oct 11, 2026
Independent live-deployment confirmation that the prefetch identity / session-race fixes tracked here (from #4672, #4675, #4664) still apply at current
main.Environment. Windows host;
hindsight-integrations/hermesplugin (local copy reports 1.2.1); Hindsight daemon 0.10.3;recall_sync=false,prefetch_waits_for_retain=true. Measured recall latency on this box: p50 39 s, max 116 s (266 completed recalls).Checked against current main today. In
hindsight-integrations/hermes/__init__.py(main, still reportingversion: 1.2.1):queue_prefetch()still spawns a thread per call without checking whether the previous worker is alive, still publishes into the shared_prefetch_resultunconditionally, andon_session_switch()still only_join_prefetch(3.0)before clearing the slot. No generation / epoch / slot-owner guard is present, so the ported fixes are still not in the tree.Reproduction (no live server, no LLM, no network: the installed methods are AST-extracted and driven with an in-memory fake thread that completes only when instructed):
MEMORY_FOR:newer-turn-B; A completes afterwards and overwrites the slot withMEMORY_FOR:older-turn-A; the nextprefetch()consumes A — i.e. the stale result wins.Why the bounded joins do not cover it. The worker budget is up to 10 s in
_wait_for_retains_drained()plus the recall itself, while both joins are 3.0 s. Any recall slower than the join outlives a session switch, and with overlapping spawns an older completion can overwrite a newer one.Supporting live signal (approximate, derived from daemon log finish time + duration across two rotated log files): 67 overlapping adjacent complete recall blocks out of 511 — e.g. two recalls finishing 15 s apart with ~41.9 s of overlap.
Would maintainers consider carrying the fence into the plugin — publication fenced to the owning session generation, plus the spawn gate on the slot owner? A bare
if thread.is_alive(): returnis not sufficient (it drops the pending query). Happy to re-submit or re-open either PR, or to attach the reproduction harness if that would help.plugin-catalog: re-pin hindsight to v0.10.3 (embedded-daemon fix)
plugin-catalog: re-pin hindsight to v0.10.3 (embedded-daemon fix)
teknium1 commentedon Oct 12, 2026
Heads-up for the catalog side of this: the Hermes plugin catalog now pins hindsight at
eb6df499(v0.10.3), which includesfd70557c6(local_embedded daemon start, restarts and Windows launch). Landed in NousResearch/hermes-agent#137085 (bcec4b56d9). Thanks @yaozhen for flagging that catalog installs were still getting the pre-fix tree.Existing installs pick it up with
hermes plugins update hindsight. Clients cache the catalog for a few hours, so runhermes updatefirst, or delete~/.hermes/cache/plugin-catalog.json, if the update isn't offered yet.fix(hermes): batch of small plugin bugs from #4662 (#5534)
carlosrve commentedon Oct 12, 2026
Follow-up after #5534: independently checked upstream
f843f173bb41f001e322cc83c917371639e47fbfwith SDK/embed 0.10.3, simulated clients only. The upstream provider suite passes 139 tests. From our directed reference regressions, both manual-retainretain_asyncsettings now pass; synchronous current-query recall and the three late-completion cases across switch/shutdown/newer-empty-query also pass. These are deterministic provider measurements, not live latency/extraction results.One narrower ownership boundary still fails: after switching from session S1 to S2,
queue_prefetch(..., session_id=S1)admits a new worker. The new generation fence blocks an already-running old worker, butqueue_prefetchstill ignores its supplied session owner, so a late queued callback can create a new current-generation request with S1's query. The existing public reference regression reproduces this without a server or LLM (test_superseded_owner_cannot_spawn_prefetch). Our narrowed run: 6 passed / 1 failed, with the separate session-end and outbox-flush fixture cases excluded because upstream does not expose those same callbacks/helpers.This is a remaining admission/owner guard, not a request to restore the already-merged generation fix or reopen an external PR. Thanks for carrying the manual policy and publication fence into upstream. The catalog availability update is also verified independently; it does not by itself establish that every newer main change is already in a catalog install.
Xipong commentedon Oct 15, 2026
Follow-up for my transferred NousResearch/hermes-agent#117513 contribution: opened focused bug #5583 after checking standalone plugin 1.2.2 at f843f17 and current main d5b1d42. A no-server probe still confirms automatic recall, human-cache consumption, queued recall and retention-buffer admission for runtime-shaped inputs. The new report separates Hindsight-owned policy from the missing supported host provenance contract and explicitly requests compatibility with unmodified supported Hermes versions, not a hard import of the unpublished local agent.turn_origin module. It includes the executable reproducer, current source links and human → notification → human acceptance criteria.
nicoloboschi commentedon Oct 15, 2026
Thanks for the careful handoff, this list was very useful. Closing it now that the bulk is fixed and released in hermes plugin v1.2.2:
HINDSIGHT_API_*keys you add to the profile env now survive. @yaozhen, that covers the per-operation LLM keys (HINDSIGHT_API_RETAIN_LLM_MODELetc.) too.hindsight_retaintool honoursretain_async, stale prefetch can't land in a newer session,recall_tagsaccepts a comma-separated string, bad numeric settings fall back to defaults, the reconnect closes the old client,shutdownunregisters its atexit hook, and more.What was still open on
mainis now split into self-contained issues:/versioncheck has no User-Agent and reads an unbounded bodyHINDSIGHT_BANK_IDis ignored whenconfig.jsonexistsbank_mission/bank_retain_missionare never sent to the bankrecall_tagsscopelocal_embeddedwithopenai_compatibleand no API key fails to startIf you hit something from the list that isn't covered by the above, please open a new issue.