Skip to content

feat(gateway): one frozen RoutingIdentity per inbound event (#88715 phase 1) - #115665

Merged
teknium1 merged 1 commit into
mainfrom
feat/routing-identity-object
Sep 19, 2026
Merged

teknium1 merged 1 commit into
mainfrom
feat/routing-identity-object

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

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

  • New sibling gateway/session_identity.py (nothing appended to session.py/run.py):
    • RoutingIdentity — frozen dataclass: transport_profile, runtime_profile, authorization_home, runtime_home, multiplexed, weak transport ref (excluded from equality: provenance, not identity); properties namespace (byte-identical to _session_key_namespace), store_path, session_key_profile. "default" is spelled out; None never 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 raises IdentityUnresolved (callers drop the event) instead of a None-means-default return.
    • identity_of(source) / replace_source(source, **changes) — the latter is dataclasses.replace that keeps the wire-invisible provenance (run_topics.py used to hand-copy the transport ref).
  • Thin readers, old fallbacks kept (additive — every existing multiplex test stays green): _transport_owner, _authorization_home_for_source, _resolve_profile_home_for_source, BasePlatformAdapter._session_key_profile, SessionStore._resolve_profile_for_key read the pinned identity when present and fall back to today's chain when absent.
  • Runner ingress (run_adapters.py): _admit_primary_source and _stamp_event_profile now go through resolve_identity. source.profile stays the serialized runtime profile (None on the wire ⇔ default) — no wire change, no key change.
  • Docs: gateway/AGENTS.md § Profile scope, website/docs/developer-guide/multiplexing-gateway.md § Per-bot session lanes.

Validation

Check Result
tests/gateway/test_session_identity.py (3 invariants: one frozen value all readers agree on; IdentityUnresolved under multiplex vs explicit default outside it with agent:main byte-stable; replace_source keeps identity where dataclasses.replace drops it) green
A→B→A E2E with real runner handlers (_primary_message_handler, _make_profile_message_handler), two homes, shared-bot route: identity / ambient HERMES_HOME / authorization home / adapter key / runner key agree on all four events, no contamination after the secondary turn PASS
scripts/run_tests.sh tests/gateway tests/scripts tests/plugins/platforms 9846 passed, 0 failed, 0 flaky
check_profile_scope_patterns.py --base origin/main 2 advisory hits, both docstring mentions of agent:main
ruff, check-windows-footguns, check_compat_pointers, git diff --check clean

Root 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

routing-identity

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.
@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on f41046b — feat(gateway): RoutingIdentity — one frozen identity per inb

debug info

CI timings

CI timings · View report · View job

Wall time 5m32s vs 4m50s (+14.5%). 5 job(s) slower, 7 faster, 1 unchanged.

  • All required checks pass: +36.0s
  • Check contributors / check-attribution: -28.0s
  • Python lints / Windows footguns (blocking): +16.0s
  • OS-specific tests / macOS-only tests: -9.0s
  • OS-specific tests / Windows-only tests: +7.0s

@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Sep 19, 2026
@whyyagswhy

Copy link
Copy Markdown

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants