fix(gateway): identity survives restore, relay, callbacks and thread hops (#88715 phase 5) - #115847
Merged
Merged
Conversation
૮ >ﻌ< ა ci reviewran on 7b31433 — fix(gateway): a restored lane whose receiving bot is offline debug infoCI timingsCI timings · View report · View jobWall time 19m17s vs 24m35s (-21.6%). 7 job(s) slower, 4 faster, 2 unchanged.
|
teknium1
force-pushed
the
fix/identity-survives-boundaries
branch
2 times, most recently
from
September 19, 2026 07:27
84a2aee to
8f2e99f
Compare
…es deliver through it or fail closed After a restart the routing index rebuilt every lane from `SessionEntry.origin`, which carries the runtime profile (key namespace) but not the bot that received the conversation. Delivery then fell to `_is_shared_bot_satellite`: a lane owned by a secondary bot whose runtime profile is ALSO a satellite of the default bot was handed to the default bot, and authorization read the wrong allowlist. - `SessionEntry.transport_profile` (routing JSON) + nullable `sessions.transport_profile` (SCHEMA_SQL, reconciled by the existing column path; `agent:main` keys untouched, standalone gateways write nothing). Stamped from the pinned `RoutingIdentity` at create, reset/switch, DB recovery and every peer refresh; compression forks inherit it like the other routing columns. - `session_identity.restore_identity()` re-pins a `RoutingIdentity(transport=None)` from the persisted transport profile; `authz_mixin._restored_source(entry)` is the one seam every revive path uses (auto-resume, heartbeat restore, plugin injection, background-process events). - `_adapter_for_source` / `_adapter_profile_for_source` honour a restored identity: the persisted bot's adapter or None — never the default bot by heuristic. Entries written before the column exist keep the old chain. Phase 5 of #88715.
…w_up The connector stamps `profile` on inbound and passthrough_forward frames but the gateway never sent it back, so the connector had nothing to stamp on the NEXT interaction of a routed chat. `_capture_scope` now remembers the routed profile per chat, `_with_scope` echoes it as `metadata.profile` on chat-addressed frames, and `send_follow_up` derives it from the `agent:<profile>:` key namespace. A single-profile gateway emits no key — frames stay byte-identical. Contract §4 documents the round-trip.
…t profile reads `_session_key_for_source` reads the pinned identity before falling back to `source.profile` / the active profile. The remaining `get_active_profile_name()` reads in gateway/ run before any event exists (adapter boot, cron ticker homes, startup log, advertised model name) and are marked `# launch profile, pre-identity`.
…story The page-level salvage lane infers a salvaged store's physical column order from SCHEMA_HISTORY; a column added to SCHEMA_SQL without an event here makes the newest rows of every upgraded store map to nothing and take the positional fallback.
… nowhere Composing PR-4 (intake/delivery split) with PR-5 (persisted transport_profile): the delivery fallback to the runtime profile's unique adapter is only for sources with NO identity. A pinned identity names the receiving bot; if that bot has no adapter it is offline and the lane fails closed, never answering from the runtime profile's bot. Also: tests and docs reference the split helper, not the removed _adapter_for_source.
teknium1
force-pushed
the
fix/identity-survives-boundaries
branch
from
September 19, 2026 08:55
8f2e99f to
7b31433
Compare
This was referenced Sep 19, 2026
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.
A multiplexed gateway now remembers which bot received each conversation across a restart, so a revived lane delivers through that bot (or fails closed) instead of falling back to the default bot by heuristic — and the relay echoes the routed profile so the connector can keep a routed chat's next interaction in the same profile.
Phase 5 of #88715 (stacked on PR-1
ad651b8250/ PR-2c07708671d, both onmain).What changed
SessionEntry.transport_profile(routing JSON) + nullablesessions.transport_profile(SCHEMA_SQL, added by the existing column-reconcile path — no version bump;agent:mainkeys and standalone gateways untouched: they write nothing). Stamped from the pinnedRoutingIdentityat create, reset/switch, DB recovery and every peer refresh; compression forks inherit it with the other routing columns.session_identity.restore_identity()re-pins aRoutingIdentity(transport=None)from the persisted bot (the restored/synthetic row of PR-4's transport matrix).authz_mixin._restored_source(entry)is the one seam every revive path readsentry.originthrough: auto-resume (run_startup), heartbeat restore, plugin injection (run_inbound), background-process events (run_notifications)._adapter_for_source/_adapter_profile_for_sourcehonour a restored identity — the persisted bot's adapter orNone, never the default bot. Entries written before the column exist (transport_profileabsent) keep the old_is_shared_bot_satellitechain._capture_scoperemembers the routed profile per chat;_with_scopeechoesmetadata.profileon every chat-addressed frame andsend_follow_upderives it from theagent:<profile>:key namespace. Single-profile gateways emit no key (frames byte-identical). Contract doc §4 documents the round-trip.run.py::_session_key_for_sourcereads the identity first; the remainingget_active_profile_name()reads ingateway/are boot-only and marked# launch profile, pre-identity(adapter boot, cron ticker homes, startup log, advertised model name). Callback capture (/modelpicker →profile_homeat command time) and_run_in_executor_with_contextalready carry the scope; verified, not rewritten.gateway/AGENTS.md§ Profile scope,multiplexing-gateway.md§ Restore, relay, callbacks and thread hops,session-storage.md,relay-connector-contract.md§4.Root cause
The routing index persisted the runtime profile (key namespace) but not the receiving bot, so after a restart the only way to pick an adapter was a heuristic on the runtime profile — wrong whenever the runtime profile is a satellite of a different bot than the one that received the lane.
Validation
tests/gateway/test_session_identity_restore.py(2 invariants: team_b-received lane routed toops→ after restart delivers via team_b, fails closed when team_b is offline, satellite lane keeps default egress, pre-column entry unchanged; standalone control: nothing persisted,agent:mainbyte-stable)AttributeError: transport_profile), green on headtests/gateway/relay/test_relay_passthrough.py::test_routed_profile_round_trips_on_every_egress_framemetadatahas noprofile), green on headGatewayRunnerhandlers_stamp_event_profile/_admit_primary_source, realSessionStoreover two homes'state.db, fresh runner reload fromgateway_routing, executor hop keepsHERMES_HOMEscope)None; legacy entry → heuristic unchangedscripts/run_tests.sh tests/gateway tests/hermes_state tests/plugins/platforms__new__relay adapters: lazy_profile_by_chat; 1 test lambda signature;test_session_hygiene2.4s>2.0s timing under 40 workers — passes alone) ; re-run oftests/hermes_state+ session files: 1475 passedcheck_profile_scope_patterns.py --base origin/mainagent:mainruff,check-windows-footguns --all,check_compat_pointers,git diff --checkNot covered (lane
NOT_COVERED.md): connector-side stamping of the echoedprofile(connector repo); backfill oftransport_profileon pre-existing rows (PR-6repair-profiles); live two-token Telegram rig.Infographic