Skip to content

feat: client session identity for sticky routing and usage observability #53

Description

@sususu98

Summary

Home already supports optional session-sticky credential routing (routing.session-affinity), but the current implementation is incomplete for real multi-client production use:

  1. Session ID extraction misses several native client headers used by Claude Code, Codex, OpenCode, pi, and gateway-injected clients.
  2. Sticky bindings are process-local (SessionCache), so multi-Home deployments do not share binding state.
  3. Usage records / Management API do not persist a first-class client session identity, so operators cannot filter one conversation across many requests.

This issue proposes a Home-owned session identity model that serves both:

  • credential sticky binding
  • request/usage observability by conversation/session

Motivation

When debugging production issues, operators usually have a client session id from Claude Code / Codex / pi logs, not only a single request_id.

Today Home can answer “what happened to request X?”, but cannot reliably answer:

  • which credential did conversation/session S stick to?
  • show all turns of session S in order
  • did failures start after compact / model switch / subagent fork?
  • which request logs belong to the same client conversation?

Current gaps

Extraction (internal/cliproxy/auth/selector.go)

Current priority roughly:

metadata.user_id (Claude formats)
→ X-Session-ID
→ Session_id
→ X-Client-Request-Id
→ bare metadata.user_id
→ conversation_id
→ message content hash

Missing / incomplete high-value sources from measured client behavior:

x-gateway-session-id
x-claude-code-session-id
session-id / thread-id          # current Codex form (hyphen)
x-opencode-session / x-session-id / x-session-affinity
prompt_cache_key (carefully)
body.conversation / previous_response_id chain mapping

Also:

  • X-Client-Request-Id is request/transport scoped and should not be treated as a root sticky session by default
  • low-confidence fingerprints (message hash / IP / UA) must not silently merge conversations

Binding store

  • in-memory only
  • not multi-Home safe
  • routing key is not clearly namespaced by authenticated API key / tenant

Observability

usage has request_id, provider/model/auth/node metadata, but no durable:

  • session_id
  • thread_id
  • session_source
  • session_confidence
  • client_type

So Management filters and HomeUI cannot build a conversation timeline from DB-backed usage data.

Proposed design

Design doc in this repo:

  • docs/design/session-binding-and-observability.md

1) Extract with source + confidence

ExtractedSession {
  client_session_id
  thread_id?
  parent_thread_id?
  client_type
  source
  confidence   # high | medium | low
  scope        # session | thread | user | transport
  client_provided
}

Recommended priority (simplified):

x-gateway-session-id
→ native client headers (Claude Code / Codex / OpenCode / Hermes / ...)
→ Claude metadata.user_id session fields when multi-signal confirmed
→ protocol continuity maps (conversation / previous_response_id)
→ no sticky when only low-confidence signals exist

Do not use full messages hash, IP, or User-Agent as high-confidence session keys.

2) Bind with namespaced routing key

routing_key = HMAC(
  secret,
  api_key_or_tenant + client_type + client_session_id + provider + model
)

Rules:

  • sticky is preference + TTL, not infinite hard lock
  • first writer wins (SETNX / conditional insert)
  • refresh TTL on use
  • rebind when bound auth is unhealthy / model-incompatible
  • invalidate bindings by auth id on cooldown/disable

Prefer shared DB-backed bindings for multi-Home clusters.

3) Persist session fields on usage

Add indexed columns (MVP):

column purpose
session_id operator-facing client session id
thread_id optional thread/subagent id
session_source where extracted from
session_confidence high/medium/low
client_type claude-code / codex / opencode / pi / ...

Accept these fields from CPA usage payloads with backward-compatible aliases.

4) Management API

Extend existing usage observability filters:

GET /usage/records?session_id=...&thread_id=...&client_type=...

Add session-centric read APIs:

GET  /sessions
GET  /sessions/:sessionId
GET  /sessions/:sessionId/requests
GET  /sessions/:sessionId/bindings
POST /sessions/:sessionId/bindings/expire   # optional operator action

/sessions/:id/requests should return ordered usage records so UI can render a conversation timeline and jump into existing request-log download paths.

Non-goals (this issue)

  • building a full product chat transcript store
  • inventing fake high-confidence sessions when clients send nothing
  • treating session id as auth
  • replacing request-event work; this complements request_id based diagnostics

Suggested phases

  1. Extractor + confidence policy + tests
  2. Cluster-safe sticky binding store
  3. Usage schema + ingest + /usage/records filters
  4. /sessions* Management APIs + docs
  5. Coordinate with HomeUI for session filter / session workbench

CPA dependency

Edge reporting should eventually include extracted session fields in usage payloads. Home parsers should tolerate missing fields so schema/API can land first.

If CPA extractor/payload work is tracked separately, link it here.

Acceptance criteria

  • Claude Code session is extracted from x-claude-code-session-id and/or confirmed metadata session fields
  • Codex session prefers hyphen session-id / thread-id; underscore forms remain compatibility only
  • OpenCode / gateway headers are recognized
  • Low-confidence signals do not create sticky bindings by default
  • Sticky bindings are namespaced by API key/tenant and survive multi-Home (or documented single-node limitation with store abstraction)
  • Usage records store queryable session_id (+ source/confidence/client_type)
  • Management API can filter usage by session_id
  • Management API can list a session timeline of requests for one session id
  • Docs updated in docs/management/api.md (+ CN if maintained)
  • Focused tests for extractor fixtures, binding race, and usage filter queries

References

  • Design: docs/design/session-binding-and-observability.md
  • Current selector: internal/cliproxy/auth/selector.go
  • Current usage model: internal/cluster/usage.go
  • Current observability query: internal/cluster/usage_observability.go
  • Client header research / probe captures (local): agent session id gateway research

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions