Repository navigation
Description
What needs verifying
The gateway returns x-correlation-id on responses — docs/verified-apis.md,
row "Gateway identity on response", VERIFIED (LIVE), captured in
python/tests/fixtures/anypoint/llm_proxy/responses.success.headers.txt with the
value d34a1c5a-a3af-4bcc-8fe1-217682d7d0e0. What is not captured is whether
that value is the one the client sent, or one the gateway minted independently.
Also unverified: the canonical header name and format for the platform's announced
End-to-End Correlation ID Support ("trace one request across every agent, MCP,
API and LLM hop it touches"), and whether it is this same header or a second one.
Why it blocks work
core/transport.py declares CORRELATION_HEADER = "X-Correlation-Id" as a
hard-coded string literal — it is not behind a _verify.Unverified(...) guard,
so it emits no UnverifiedValueWarning and has no row in the ledger.
core/telemetry.py::ensure_correlation_id always self-generates a uuid4; there is
no code path that adopts an inbound value.
#195 (per-call and per-run correlation IDs, BG §1.7) has not started yet. BG §1.7
promises that "client-side and gateway-side records join". If the gateway mints its
own ID and ignores the client's, that join silently never happens and the failure is
invisible — the SDK would keep emitting a correlation ID that correlates nothing.
Answering this before #195 is built avoids baking an unverified header name into the
six-piece minimum.
Done when
- A live round-trip confirms whether a client-sent
x-correlation-idis echoed back unchanged. - The multi-hop correlation header name/format is confirmed, or confirmed to be the same header.
-
docs/verified-apis.md"Gateway identity on response" row is updated with the echo semantics. - If the name is not confirmed,
CORRELATION_HEADERis moved behind_verify.Unverified(...).
Cross-surface impact
- core:
transport.py(CORRELATION_HEADER,_apply_base_headers) andtelemetry.py(ensure_correlation_id,run_context). - llm / registry / tools / integrations: not affected — they inherit the shared transport.
- TypeScript parity (planned): flag — the TS port must use the same header name; a divergence here would be invisible until a customer joins the two datasets.
- Provisioning CLI: not affected.
- Nextra docs site: the correlation-ID concept page will need the echo semantics stated.
- docs/verified-apis.md: updates the existing "Gateway identity on response" row.
Related
- telemetry: per-call and per-run correlation IDs via donkey.run() #195 — consumes this answer; should not ship before it.
- verify: whether Anypoint Monitoring or Agent Visualizer ingests OTLP GenAI spans #257 — whether Anypoint Monitoring or Agent Visualizer ingests the resulting OTLP spans.
Activity
amirkhan-ak-sf commentedon Sep 11, 2026
Partial evidence from a live probe, plus a concrete way to finish the verification.
A live
200through a governed proxy returnedx-correlation-id: 4733fc2e-…(a UUID)on a request sent from Postman that carried no correlation header. All three
committed captures in
python/tests/fixtures/anypoint/llm_proxy/likewise show ax-correlation-idUUID on the response, andpolicy_list.openai-sdk.jsonlistsx-correlation-idin the API's CORSexposedHeaders.So: the gateway mints one when the client sends none, and
x-correlation-idisconfirmed to be the name it speaks on the response. That does not answer the question in
this issue, because a minted value is indistinguishable from an echoed one when nothing
was sent.
The remaining test is cheap and needs no SDK: send
X-Correlation-Id: test-12345andinspect the response.
x-correlation-id: test-12345→ the gateway echoes, and_verify.CORRELATION_ID_HEADERcan be flipped to VERIFIEDignored, and this becomes an upstream gap rather than a config default
Worth doing under the same probe as #352/#354 since it is the same request. Related:
the
UnverifiedValueWarningpair this currently produces is noisy enough that itappears on every demo run before any output.
amirkhan-ak-sf commentedon Sep 11, 2026
Two of the premises here have gone stale since filing, and there is a piece of
evidence in the committed capture that narrows the question. Recording both so
whoever picks this up does not re-investigate.
Stale: the header name is already behind
UnverifiedThe description says:
That is no longer the case. It is now
_verify.CORRELATION_ID_HEADER, anUnverified(...)with adocs/verified-apis.md §3ref, alongside a second onefor the per-call id:
https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/python/src/donkey_kit/core/_verify.py#L84-L101
It warns on read, resolved once per client in
_resolve_header_names(), andboth names have §3 ledger rows (the two "#195" rows). The warning is live — a
default
Donkey.from_env()run prints both:So the fourth Done when box — "if the name is not confirmed,
CORRELATION_HEADERis moved behind_verify.Unverified(...)" — is alreadysatisfied and can be ticked independently of the live round trip. #195 has also
since closed, so "should not ship before it" is moot; what remains is confirming
the semantics for a feature already shipped on a placeholder.
Still accurate: nothing adopts an inbound correlation id. That is #308.
Evidence: the captured value is not in the client's format
new_correlation_id()returnsuuid.uuid4().hex— 32 hex chars, no dashes.The captured gateway response carries a dash-formatted UUID:
https://github.com/Donkey-Development-Kit/donkey-development-kit/blob/main/python/tests/fixtures/anypoint/llm_proxy/responses.success.headers.txt#L30
A gateway echoing a client-sent value verbatim would return the dashless form.
This is consistent with Envoy minting its own id — but it is not conclusive,
because we do not know whether that particular probe sent a correlation header
at all. If it did not, Envoy generating one is the expected behaviour and tells
us nothing about the echo case. Worth checking against however the capture was
taken before treating it as an answer.
Repro recipe for the live round trip
The first Done when box needs only this, and it does not need a special
harness —
with_raw_responseexposes both directions of one call:Three outcomes and what each means:
echoed == sentX-Correlation-Id; §3 row flips to VERIFIED andverified=Trueechoedis a fresh UUID each runechoedabsentWorth running the same probe with a deliberately wrong name
(
DONKEY_CORRELATION_HEADER=X-Nonsense-Id) in the same session. If the responsex-correlation-idis a fresh UUID in that case but matches in the default case,that isolates the read to the specific header name rather than to any inbound
header.
Knock-on already filed
Separately from the verification: the config override that exists because this
is unverified currently breaks
DonkeyError.correlation_idread-back, since_sent_ids()looks up the placeholder name rather than the configured one.Filed as #363 — independent of the outcome here, but it means the documented
workaround for this issue is not currently safe to recommend.
feat(skeleton): define the last-call record once at donkey.last_call (#…
docs(verify): record x-correlation-id echo semantics on §3 (#1037)
docs(verify): record x-correlation-id echo semantics on §3 (#1037)