Conversation
…nch state.db A multiplexed gateway connects each served profile's adapter inside _profile_runtime_scope(<profile home>). The receive loop an adapter starts while connecting inherits that home override, so every final reply the bot sends is recorded from it. The ledger resolved its path through get_hermes_home(), which follows the override, and the rows landed in profiles/<name>/state.db. The boot sweep (sweep_recoverable) and the boot flood-timer arming (pending_retries) run in the launch context and open the launch state.db, so they never saw those rows. A served bot's reply cut off by a crash or SIGKILL between finalize and platform ACK was never redelivered, a flood-refused reply that spanned a restart was never retried, and resume_pending was not cleared for a session whose answer sat in the ledger. The ledger is meant to be one shared store: the boot sweep already scopes rows by (platform, adapter_profile), and the profile purge terminalizes rows in the shared store. _db_path now resolves from get_process_hermes_home(), as the gateway's other process-level files do (gateway.status). It deliberately skips the get_hermes_home() fallback that lifecycle_ledger uses when HERMES_HOME is unset: a default gateway started in the foreground has no HERMES_HOME, and that fallback would follow the override again. Rows an earlier build already wrote to a profile's state.db stay where they are.
This was referenced Sep 23, 2026
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
On a multiplexed gateway, each served profile's adapter is connected inside that profile's
_profile_runtime_scope. The receive loop the adapter starts while connecting inherits the profile's home override, so every final reply that bot sends is recorded from that context. The delivery ledger resolved its path withget_hermes_home(), which follows the override, so those rows landed inprofiles/<name>/state.db.The boot sweep (
sweep_recoverable) and the boot flood-timer arming (pending_retries) run in the launch context and open the launchstate.db, so they never saw the rows. A served bot's reply cut off by a crash or SIGKILL between finalize and the platform's ACK was never redelivered. A flood-refused reply whose wait spanned a restart was never retried.resume_pendingstayed set for a conversation whose answer was in the ledger.The ledger is already designed as one shared store: the boot sweep scopes rows by
(platform, adapter_profile), and the profile purge terminalizes rows in the shared store. Only the path resolver was out of step. This PR resolves_db_path()fromget_process_hermes_home(), which is howgateway/status.pyresolves the gateway's other process-level files.I left out the
HERMES_HOME-unset fallback toget_hermes_home()thatlifecycle_ledgeruses on purpose. A default gateway started in the foreground has noHERMES_HOME, and that fallback would follow the override again. The test covers that case.Rows an earlier build already wrote to a profile's
state.dbstay where they are. This PR does not migrate them.Related Issue
Fixes #119973
Type of Change
Changes Made
gateway/delivery_ledger.py:_db_path()now resolves fromget_process_hermes_home()instead ofget_hermes_home(). Theget_hermes_homeimport this left unused is gone.tests/gateway/test_delivery_ledger_process_home.py(new):BasePlatformAdapter._record_delivery_obligation, from a task created inside the real_profile_runtime_scope, then kills the owner.GatewayRunner._redeliver_pending_obligationsfrom the launch context. It checks that the served profile's adapter sends the reply with the recovered-reply marker and thatresume_pendingis cleared.HERMES_HOMEset and unset (the platform default)._db_pathunpatched. Every other ledger test replaces it, which is why the existing suite never saw this bug.website/docs/user-guide/messaging/index.md: one sentence on where the ledger lives on a multiplexed gateway.How to Test
pytest tests/gateway/test_delivery_ledger_process_home.py -q: 2 passed. Withgateway/delivery_ledger.pyfrom main, both cases fail withassert 0 == 1, because the boot sweep claims nothing.lifecycle_ledgerpattern (get_process_hermes_home() if HERMES_HOME is set else get_hermes_home()). Only theplatform-defaultcase fails.profiles/research/state.dband the boot sweep claims[].state.dband is claimed as('ob-1', 'research', True).BasePlatformAdaptersubclass whoseconnect()spawns its poll loop, connected through_connect_initial_adapter_with_timeoutinside_profile_runtime_scope. The inbound message went throughhandle_message, and a send never returned. On this branch the row is in the launch store, and_claim_pending_obligationsclaims it with the marker and clearsresume_pending, the same as a single-profile gateway.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass. I did not run the full suite. I ran these files one at a time, all green:tests/gateway/:test_delivery_ledger_process_home.py,test_delivery_ledger.py,test_delivery_ledger_producer.py,test_delivery_ledger_single_connection.py,test_delivery_ledger_fd_leak.py,test_queued_final_ledger.py,test_delivery_flood_invariants.py,test_diagnostic_wake_presentation.py,test_discord_missed_message_backfill.py,test_multiplex_adapter_registry.py,test_owner_liveness_drift.py,test_platform_reconnect.py,test_restart_notice_replay.py,test_restart_resume_pending.py,test_silent_partial_delivery.py,test_profile_isolation_runtime.pytests/hermes_state/:test_journal_mode_config.py,test_purge_profile_state.py,test_rekey_profile_state.pytests/hermes_cli/:test_sqlite_util_canonical.py,test_session_recovery.pyDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/Aget_process_hermes_home()helper, which already handles the per-platform default home.Screenshots / Logs
Repro script from the issue, on main:
On this branch: