feat(session): unify session identity and identify sub-agents from real client signals - #4644
Open
sususu98 wants to merge 2 commits into
Open
feat(session): unify session identity and identify sub-agents from real client signals#4644sususu98 wants to merge 2 commits into
sususu98 wants to merge 2 commits into
Conversation
Treat every recognized explicit root session identifier as an equivalent alias while keeping the representative ID display-only. Keep thread, user, transport, derived, and message-hash fallbacks isolated from explicit roots. Namespace local bindings by caller, provider, and model. Resolve cold and overlapping alias groups atomically with striped alias/group locks so the same session's concurrent first requests return one auth without serializing unrelated sessions. Merge conflicts symmetrically, rebind unavailable auths, and retain bounded TTL/LRU behavior. Send Home both the legacy session ID and structured identity metadata, including aliases, source, confidence, scope, client type, and thread relationships. Scope CPA's compatibility alias cache by caller and propagate the exact canonical Home dispatch session into direct, count-token, and streaming usage contexts. Confidence remains observational and never affects routing. Make SessionCache shutdown idempotent and stop replaced affinity selectors during routing reconfiguration. Expand extraction, transition, caller isolation, cache-bound, lifecycle, and concurrent first-pick coverage.
Captured live traffic from Claude Code 2.1.220, Codex 0.145.0, OpenCode 1.18.4 and pi 0.82.1 against a local capture gateway that replies with the client's own sub-agent tool, so a real child agent request is observed rather than inferred. Three conventions turned up, and only Codex was already handled correctly. Claude Code repeats the root session at every depth and marks a sub-agent with X-Claude-Code-Agent-Id, which the main agent omits entirely; the parent identifier only appears from depth 2, because a depth-1 sub-agent's parent is the main agent and has no identifier of its own. Neither header was read, so thread identity was always empty for Claude Code. Both now feed ThreadID/ParentThreadID. OpenCode inverts the convention: a sub-agent gets its own X-Session-Id and reports the root in X-Parent-Session-Id. The parent is now collected under the same "header:" prefix as X-Session-Id, so a child request joins the parent's alias group and reuses its credential instead of routing as an unrelated session and losing the parent's prompt cache. X-Client-Request-Id becomes a last resort instead of an unconditional root identifier. A Codex sub-agent sends its own thread identifier there while session-id stays on the root, so collecting it alongside a real root added one throwaway alias per sub-agent to the root alias group. It still provides affinity for a client that sends nothing else. Claude Code reports background work as X-App: cli-bg, which a strict "cli" comparison dropped, costing one of the two signals header-only detection needs. Codex also reports per-turn observations in X-Codex-Turn-Metadata. RequestKind, ThreadSource and TurnID carry them to Home, usage records and request logs so a session timeline can separate real conversation turns from compaction, title generation and other housekeeping, and can group the upstream requests belonging to one client turn. They are observation only and never enter a routing key; client-supplied JSON is bounded and rejected on control characters. Every changed file is covered by a test that fails when the change is reverted, including the field-copy paths into Home and usage that would otherwise publish empty values silently, and the interaction between the OpenCode parent alias and the per-group alias cap.
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.
Two commits. The first unifies session extraction and affinity routing; the second
identifies sub-agents from signals captured off real clients.
Why
#4554landed native client session signals. Two follow-up problems remained.Routing still treated every recognized session identifier as a separate session,
so a request that arrived through a different signal than the one that created the
binding started a second binding on another credential. And sub-agent traffic was
not identified at all, because the headers that carry it were never read.
To find out what clients actually send, rather than infer it, requests from
Claude Code 2.1.220, Codex 0.145.0, OpenCode 1.18.4 and pi 0.82.1
were captured against a local gateway that replies with each client's own
sub-agent tool, so a real child agent request is observed. Three different
conventions turned up.
Commit 1 — unified extraction and alias affinity
Every recognized explicit root identifier in one request is treated as an alias of
the same session, so a request seen through any of them resolves to one binding.
Thread, user, transport, derived and message-hash fallbacks stay isolated from
explicit roots. Local bindings are namespaced by caller, provider and model. Cold
and overlapping alias groups resolve atomically under striped alias/group locks,
conflicts merge symmetrically, and unavailable auths rebind. The cache is bounded
by TTL and LRU. Home receives both the legacy session ID and structured identity
metadata. Confidence stays observational and never affects routing.
Commit 2 — sub-agent identification
Claude Code repeats the root session at every depth and marks a sub-agent with
X-Claude-Code-Agent-Id, which the main agent omits entirely. The parentidentifier only appears from depth 2, because a depth-1 sub-agent's parent is the
main agent and has none of its own:
Neither header was read, so thread identity was always empty for Claude Code.
Both now feed
ThreadID/ParentThreadID.OpenCode inverts the convention: a sub-agent gets its own
X-Session-Idandreports the root in
X-Parent-Session-Id, so it was routed as an unrelatedsession and lost the parent's prompt cache. The parent is now collected under the
same
header:prefix asX-Session-Id, so a child joins the parent's alias groupand reuses its credential.
Codex was already correct:
session-idstays on the root whilethread-idmoves to the child. But
x-client-request-idfollows the thread, not the session,so collecting it as an unconditional root added one throwaway alias per sub-agent
to the root alias group. It is now a last resort, still providing affinity for a
client that sends nothing else.
pi exposes no sub-agent tool at this version.
Claude Code also reports background work as
X-App: cli-bg, which a strictclicomparison dropped, costing one of the two signals header-only detection needs.
Codex reports per-turn observations in
X-Codex-Turn-Metadata.RequestKind,ThreadSourceandTurnIDcarry them to Home, usage records and request logs so asession timeline can separate real conversation turns from compaction, title
generation and other housekeeping, and can group the upstream requests belonging
to one client turn. They are observation only, never enter a routing key, and
client-supplied JSON is bounded and rejected on control characters.
Compatibility
New wire fields are additive and
omitempty. Home reads the auth dispatch andusage payloads with gjson, which ignores unknown fields, so a new CPA runs against
an older Home with no ordering constraint. This was verified by reading Home's
decoders, not by an end-to-end run against a live Home.
Verification
gofmtclean, builds, and the full suite passes (86 packages). Under-racetheonly failing packages are
internal/runtime/executorandinternal/watcher,which fail identically on an unmodified tree.
Every changed production file was reverted individually to confirm a test fails or
the build breaks, including the field-copy paths into Home and usage that would
otherwise publish empty values silently. Two cases are worth calling out:
extraction change makes it pick a different one
binding, and an early sub-agent whose own aliases were squeezed out still
resolves through the parent header
The one change that alters routing rather than only observation is the OpenCode
parent alias. Its semantics were verified on OpenCode only.