Problem
The per-request routing JSONL (--routing-log-file, crates/switchyard-server/src/routing_log.rs) records task, trial_id, and session_id, but nothing that identifies who/what sent the request. A gateway serving several harnesses (Claude Code, Codex CLI, custom agents, direct API callers) can't answer "which client produced this traffic?" from the durable log alone.
Downstream consumers tail the JSONL and can only show fields that exist in the record — origin cannot be recovered after the fact, because request headers aren't persisted anywhere durable.
Proposed change
Add an optional origin field to RoutingRecord, extracted in RoutingLogContext::from_metadata exactly like the existing task/trial_id headers:
- Primary source: new
x-switchyard-origin request header.
- Fallback (optional, mirrors the
LEGACY_SESSION_ID_HEADER precedent): derive a coarse origin from User-Agent when the header is absent — e.g. claude-code, codex-cli, openai-sdk.
RoutingRecord already uses #[serde(default)], so old and new records coexist on read (snapshot()) with no migration.
const ORIGIN_HEADER: &str = "x-switchyard-origin";
// in RoutingLogContext:
origin: Option<String>,
// in from_metadata:
origin: headers
.and_then(|headers| nonempty_header(headers, ORIGIN_HEADER))
.map(str::to_string),
Why this is cheap
RoutingLogContext::from_metadata (routing_log.rs, commit c665174) already plucks x-switchyard-intake-task / x-switchyard-trial-id out of metadata.http_headers — origin is the same pattern one line over. The field rides into the record in RoutingLog::append; no new plumbing beyond that.
Consumers
At least one known consumer: switchyard-studio tails this JSONL into SQLite for a routing-visibility console. It parses a field allowlist, so an additive field is ignored by existing readers and picked up with a small parse change on the consumer side.
We run the routing log enabled in production (x-switchyard-session-id already flows end-to-end), so we can validate the field live immediately after it lands, and are happy to test a prototype build.
Problem
The per-request routing JSONL (
--routing-log-file,crates/switchyard-server/src/routing_log.rs) recordstask,trial_id, andsession_id, but nothing that identifies who/what sent the request. A gateway serving several harnesses (Claude Code, Codex CLI, custom agents, direct API callers) can't answer "which client produced this traffic?" from the durable log alone.Downstream consumers tail the JSONL and can only show fields that exist in the record — origin cannot be recovered after the fact, because request headers aren't persisted anywhere durable.
Proposed change
Add an optional
originfield toRoutingRecord, extracted inRoutingLogContext::from_metadataexactly like the existingtask/trial_idheaders:x-switchyard-originrequest header.LEGACY_SESSION_ID_HEADERprecedent): derive a coarse origin fromUser-Agentwhen the header is absent — e.g.claude-code,codex-cli,openai-sdk.RoutingRecordalready uses#[serde(default)], so old and new records coexist on read (snapshot()) with no migration.Why this is cheap
RoutingLogContext::from_metadata(routing_log.rs, commitc665174) already plucksx-switchyard-intake-task/x-switchyard-trial-idout ofmetadata.http_headers— origin is the same pattern one line over. The field rides into the record inRoutingLog::append; no new plumbing beyond that.Consumers
At least one known consumer: switchyard-studio tails this JSONL into SQLite for a routing-visibility console. It parses a field allowlist, so an additive field is ignored by existing readers and picked up with a small parse change on the consumer side.
We run the routing log enabled in production (
x-switchyard-session-idalready flows end-to-end), so we can validate the field live immediately after it lands, and are happy to test a prototype build.