Skip to content

feat: context-scoped multi-workspace routing (workspace() scope + routing exporter) - #68

Merged
PabloPardoGarcia merged 1 commit into
mainfrom
pablo/rius-646-workspace-routing
Sep 4, 2026
Merged

feat: context-scoped multi-workspace routing (workspace() scope + routing exporter)#68
PabloPardoGarcia merged 1 commit into
mainfrom
pablo/rius-646-workspace-routing

Conversation

@PabloPardoGarcia

Copy link
Copy Markdown
Member

RIUS-646. One process, many workspaces: init(workspaces={alias: api_key}) plus with rius.workspace(alias): routes every span started in scope (observe, generations, sessions, and auto-instrumented spans, which inherit the OTel context) to that workspace's destination. Spans outside any scope keep using the default key.

Design mirrors sessions end to end:

  • workspace(alias) context manager on an OTel context key (copy of session())
  • WorkspaceSpanProcessor stamps a transient rius.workspace attribute at span start (copy of SessionSpanProcessor), and warns when a span starts under a different alias than its parent's stamp: one trace, one workspace (the backend derives the workspace from the key)
  • RoutingSpanExporter sits innermost in the export chain (masking and export-health wrap the fan-out), partitions each batch by the attribute, strips it via the same copy-on-write discipline as MaskingSpanExporter (it never reaches the wire), and forwards each partition to a lazily-built per-key OTLP exporter; flush delivery and shutdown fan out

Decisions from the ticket, implemented:

  • Unknown alias routes to the DEFAULT destination with a once-per-alias warning: data is kept in the vendor's own workspace and can never leak to another customer
  • rius.workspace joined PENDING_IDENTITY_ATTRIBUTES, so a partial-span snapshot routes to the same workspace its final span will (covered by test)
  • Dynamic registration: client.register_workspace(alias, key) / module-level rius.register_workspace for keys fetched at runtime; workspaces={} opts into routing with no static routes; re-registering an alias rotates its key
  • Heartbeat unchanged: liveness reports under the default workspace (v1 decision)
  • workspace_exporter_factory injection point for tests, matching the repo's DI style

12 new tests in tests/test_workspace.py; full suite 240 passed, ruff + mypy strict clean.

@PabloPardoGarcia

Copy link
Copy Markdown
Member Author

Verified end-to-end against staging: init() with a pablo-test key as default and a B-test key under alias "acme"; a bare span landed only in pablo-test, the workspace("acme")-scoped root+child trace landed only in B-test, and ClickHouse shows the stored spans carry only openinference.span.kind — the rius.workspace attribute never reached the wire. flush() reported delivery success for the fan-out.

@PabloPardoGarcia
PabloPardoGarcia merged commit 621fab2 into main Sep 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant