feat(gateway): one frozen RoutingIdentity per inbound event (#88715 phase 1) - #115665
Merged
Merged
Conversation
A multiplexed gateway answered "which bot received this / who may admit it / where does it run" in three places (`_transport_owner`, `_authorization_home_for_source`, `_resolve_profile_home_for_source` + `_session_key_profile`) that agreed only because they read the same fallback chain. `gateway/session_identity.py` answers them once: `resolve_identity()` folds `_admit_primary_source` + `_stamp_routed_profile` + the transport-owner lookup and pins a frozen `RoutingIdentity` (transport_profile, runtime_profile, authorization_home, runtime_home, weak transport ref) on the source as a wire-invisible attribute, like `_transport_adapter_ref`. Under multiplexing a route to an unserved profile raises `IdentityUnresolved` instead of a `None`-means-default return; `"default"` is spelled out inside the object. Additive: the existing helpers become thin readers of the identity when it is present and keep their fallback chain when it is not, `source.profile` stays the serialized runtime profile (None on the wire ⇔ default) and every historical `agent:main` key is byte-identical. `replace_source()` copies a source without losing its provenance (run_topics used to hand-copy the transport ref). Phase 1 of #88715; the gateway rows of #90142 / #93943.
૮ >ﻌ< ა ci reviewran on f41046b — feat(gateway): RoutingIdentity — one frozen identity per inb debug infoCI timingsCI timings · View report · View jobWall time 5m32s vs 4m50s (+14.5%). 5 job(s) slower, 7 faster, 1 unchanged.
|
|
Independent verification on the PR head (f41046b): session-identity suite 3/3 green on Linux. Freezing one RoutingIdentity per inbound event kills the mid-dispatch identity-shift class: authz, topics, and session keys all read the same frozen snapshot. No findings. |
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.
Every inbound gateway event now carries one frozen
RoutingIdentity— which bot received it, who may admit it, where it runs — resolved once instead of re-derived in three places that only agreed by accident.What changed
gateway/session_identity.py(nothing appended tosession.py/run.py):RoutingIdentity— frozen dataclass:transport_profile,runtime_profile,authorization_home,runtime_home,multiplexed, weaktransportref (excluded from equality: provenance, not identity); propertiesnamespace(byte-identical to_session_key_namespace),store_path,session_key_profile."default"is spelled out;Nonenever means default inside the object.resolve_identity(source, *, runner, adapter=None, transport_profile=None, primary_home=None)— folds_admit_primary_source+_stamp_routed_profile+ the transport-owner lookup. Under multiplexing a rejected route raisesIdentityUnresolved(callers drop the event) instead of aNone-means-default return.identity_of(source)/replace_source(source, **changes)— the latter isdataclasses.replacethat keeps the wire-invisible provenance (run_topics.pyused to hand-copy the transport ref)._transport_owner,_authorization_home_for_source,_resolve_profile_home_for_source,BasePlatformAdapter._session_key_profile,SessionStore._resolve_profile_for_keyread the pinned identity when present and fall back to today's chain when absent.run_adapters.py):_admit_primary_sourceand_stamp_event_profilenow go throughresolve_identity.source.profilestays the serialized runtime profile (Noneon the wire ⇔ default) — no wire change, no key change.gateway/AGENTS.md§ Profile scope,website/docs/developer-guide/multiplexing-gateway.md§ Per-bot session lanes.Validation
tests/gateway/test_session_identity.py(3 invariants: one frozen value all readers agree on;IdentityUnresolvedunder multiplex vs explicit default outside it withagent:mainbyte-stable;replace_sourcekeeps identity wheredataclasses.replacedrops it)_primary_message_handler,_make_profile_message_handler), two homes, shared-bot route: identity / ambientHERMES_HOME/ authorization home / adapter key / runner key agree on all four events, no contamination after the secondary turnscripts/run_tests.sh tests/gateway tests/scripts tests/plugins/platformscheck_profile_scope_patterns.py --base origin/mainagent:mainruff,check-windows-footguns,check_compat_pointers,git diff --checkRoot cause in one sentence: the runner stamped the routed profile after the adapter had already derived a key, and authorization/transport/runtime homes each came from their own getattr chain, so nothing held the three answers together.
Phase 1 of #88715 (canonical profile identity); satisfies the gateway rows of #90142 and #93943. PR-2 (
fix/adapter-session-key-seam) is stacked on this branch. Decisions D1–D5 from the audit taken as recommended (preserve today's behaviour, make it explicit).Infographic