Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions docs/verified-apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,30 +176,35 @@ no `UnverifiedValueWarning`.
| Gateway identity on response | `core/transport.py` (`x-llm-proxy-llm-provider` → `gen_ai.system`, #192); `core/lastcall.py` **parses** `x-envoy-decorator-operation` and `x-request-id` (#362) | VERIFIED (LIVE) | `x-envoy-decorator-operation: api-instance-21133858.3e6ce455-…svc`; `x-correlation-id`; `x-llm-proxy-llm-provider/-llm-model/-routing-type` | 2026-08-28 | `responses.success.headers.txt` |
| Agent→agent egress attribution header | `core/_verify.py` → transport | VERIFIED (build) | `x-anypoint-api-instance-id` → `agent-connection-telemetry` policy `sourceAgentId`; `tracing` labels `mulesoft.api.instance.id`, `mulesoft.api.type=llm` | 2026-08-28 | built `connection.json` (§12.6) |
| Business-group attribution header name | `core/_verify.py` → transport | UNVERIFIED | not surfaced as a request header in the direct-proxy path | — | — |
| Run correlation id **request** header (`X-Correlation-Id`, #195) | `core/_verify.py` `CORRELATION_ID_HEADER` → transport | UNVERIFIED | `x-correlation-id` is verified as a **response echo** (row above); that the gateway **reads** an inbound `X-Correlation-Id` as the run/trace join key is NOT confirmed. Placeholder, overridable via `correlation_header` / `DONKEY_CORRELATION_HEADER`. | — | — |
| Per-call id **request** header (`X-Donkey-Request-Id`, #195) | `core/_verify.py` `CALL_ID_HEADER` → transport | UNVERIFIED | client-generated per logical request, stable across that request's retries; no evidence the gateway reads this name yet. Placeholder, overridable via `call_id_header` / `DONKEY_CALL_ID_HEADER`. | — | — |
| Cost tag: team **request** header (`X-Anypoint-Cost-Team`, #196) | `core/_verify.py` `COST_TEAM_HEADER` → transport | UNVERIFIED | the gateway-side cost-attribution header name is the highest-priority unknown; placeholder, overridable via `cost_team_header` / `DONKEY_COST_TEAM_HEADER`. Value always carried on the `donkey.cost.team` span attribute regardless (SDK owns the span). | — | — |
| Cost tag: project **request** header (`X-Anypoint-Cost-Project`, #196) | `core/_verify.py` `COST_PROJECT_HEADER` → transport | UNVERIFIED | placeholder, overridable via `cost_project_header` / `DONKEY_COST_PROJECT_HEADER`; full value on `donkey.cost.project` span attribute. | — | — |
| Cost tag: env **request** header (`X-Anypoint-Cost-Env`, #196) | `core/_verify.py` `COST_ENV_HEADER` → transport | UNVERIFIED | placeholder, overridable via `cost_env_header` / `DONKEY_COST_ENV_HEADER`; full value on `donkey.cost.env` span attribute. | — | — |
| Cost tag: enduser id **request** header (`X-Anypoint-Cost-Enduser-Id`, #196) | `core/_verify.py` `COST_ENDUSER_HEADER` → transport | UNVERIFIED | placeholder, overridable via `cost_enduser_header` / `DONKEY_COST_ENDUSER_HEADER`; full value on `donkey.cost.enduser.id` span attribute. | — | — |
| Run correlation id **request** header (`X-Correlation-Id`, #195, #522) | `core/_verify.py` `CORRELATION_ID_HEADER` → transport | VERIFIED (LIVE) | the gateway **reads** the inbound `X-Correlation-Id` and echoes it **verbatim** on the response `x-correlation-id` — a probe sending `X-Correlation-Id: ddk522-corr` got `ddk522-corr` back on both the 200 and 400 paths. So this IS the client→gateway run/trace join key. Overridable via `correlation_header` / `DONKEY_CORRELATION_HEADER`. | 2026-09-22 | live probe against `ddk-multi-route-fallback` (instance 21179672, DDK/Sandbox) |
| Per-call id **request** header (`X-Donkey-Request-Id`, #195, #522) | `core/_verify.py` `CALL_ID_HEADER` → transport | VERIFIED (LIVE) | **negative** — the gateway consumes NO inbound per-call-id header (not echoed; absent from all 10 applied system policies). It is a **client-owned** id surfaced on `DonkeyError.call_id`, stable across a request's retries; the gateway need not read it. Name is the SDK's own convention, overridable via `call_id_header` / `DONKEY_CALL_ID_HEADER`. | 2026-09-22 | live probe + `view_api_instance_policies` (instance 21179672) |
| Cost tag: team **request** header (`X-Anypoint-Cost-Team`, #196, #522) | `core/_verify.py` `COST_TEAM_HEADER` → transport | VERIFIED (LIVE) | **negative** — the LLM Gateway has NO inbound cost-tag ingestion: the name is not echoed and appears in none of the 10 applied system policies. Cost/usage is collected by the `llm-proxy-core` policy from token usage and attributed by API instance + consuming client application, never by a client header. The authoritative carrier is the `donkey.cost.team` span attribute (SDK owns the span). The `X-Anypoint-Cost-Team` name is a forward-looking, overridable (`cost_team_header` / `DONKEY_COST_TEAM_HEADER`) convention. | 2026-09-22 | live probe + `view_api_instance_policies` (instance 21179672) + `ddk-create-llm-proxy-*` system-policy set |
| Cost tag: project **request** header (`X-Anypoint-Cost-Project`, #196, #522) | `core/_verify.py` `COST_PROJECT_HEADER` → transport | VERIFIED (LIVE) | **negative** — same finding as the team row: no gateway ingestion; carrier is the `donkey.cost.project` span attribute. Overridable via `cost_project_header` / `DONKEY_COST_PROJECT_HEADER`. | 2026-09-22 | live probe + `view_api_instance_policies` (instance 21179672) |
| Cost tag: env **request** header (`X-Anypoint-Cost-Env`, #196, #522) | `core/_verify.py` `COST_ENV_HEADER` → transport | VERIFIED (LIVE) | **negative** — same finding: no gateway ingestion; carrier is the `donkey.cost.env` span attribute. Overridable via `cost_env_header` / `DONKEY_COST_ENV_HEADER`. | 2026-09-22 | live probe + `view_api_instance_policies` (instance 21179672) |
| Cost tag: enduser id **request** header (`X-Anypoint-Cost-Enduser-Id`, #196, #522) | `core/_verify.py` `COST_ENDUSER_HEADER` → transport | VERIFIED (LIVE) | **negative** — same finding: no gateway ingestion; carrier is the `donkey.cost.enduser.id` span attribute. Overridable via `cost_enduser_header` / `DONKEY_COST_ENDUSER_HEADER`. | 2026-09-22 | live probe + `view_api_instance_policies` (instance 21179672) |

The two #195 rows are **request** headers the SDK *sends* (the client→gateway
join keys behind `donkey.run()` and `DonkeyError.correlation_id`/`.call_id`).
The verified `x-correlation-id` above is the gateway's **response** echo — a
different direction. Until an inbound-read name is confirmed against a sandbox,
both request-header names stay `Unverified(...)` placeholders and emit the verification-discipline
one-time warning; a customer whose gateway reads different names points the SDK
at them via config rather than the SDK guessing.

The four #196 cost-tag rows follow the same discipline: the fixed dimensions
(`team` / `project` / `env` / `enduser.id`, set once via
`Donkey.from_env(team=…)` / `[donkey.cost]` / `DONKEY_COST_*`, overridable per
run via `donkey.run(team=…)`) are emitted as `X-Anypoint-Cost-*` placeholder
**request** headers *and* as `donkey.cost.*` **span** attributes. Because the
SDK controls the span end to end, the span attribute always carries the full
value regardless of the header question — so cost attribution works in tracing
today, and flips to on-the-wire headers the moment a customer sets the
`cost_*_header` overrides or the gateway-side names are verified here.
join keys behind `donkey.run()` and `DonkeyError.correlation_id`/`.call_id`). As
of #522 both are verified against the live gateway: the gateway **reads** the
inbound `X-Correlation-Id` and echoes it verbatim, so the request and response
`x-correlation-id` are the same value and a client-side log line joins to the
gateway's own record; `X-Donkey-Request-Id` is confirmed to be a **client-owned**
per-call id the gateway does not consume (it lives on `DonkeyError.call_id`).
Both names are still overridable via config for a gateway that expects different
ones, but neither is a guess and neither emits a warning.

The four #196 cost-tag rows are verified **negative** as of #522: the LLM Gateway
has no inbound cost-tag ingestion, so the `X-Anypoint-Cost-*` request-header names
are a NON-CONTRACT — the deployed proxy's applied system policies do not read
them, and cost/usage is metered by the `llm-proxy-core` policy from token usage,
attributed by API instance + consuming client application. The fixed dimensions
(`team` / `project` / `env` / `enduser.id`, set once via `Donkey.from_env(team=…)`
/ `[donkey.cost]` / `DONKEY_COST_*`, overridable per run via `donkey.run(team=…)`)
are still emitted as `X-Anypoint-Cost-*` request headers (harmless — nothing reads
them) *and*, authoritatively, as `donkey.cost.*` **span** attributes. Because the
SDK controls the span end to end, cost attribution works in tracing regardless of
the header question; a customer whose own gateway *does* read a cost header can
point the SDK at it via the `cost_*_header` overrides.

## 4. Policy rejection response shapes (capture as fixtures, BG §1.5)

Expand Down Expand Up @@ -390,7 +395,7 @@ provisioning API. Exact REST calls behind the CLI are now recorded in §12
|---|---|---|---|---|---|
| LangGraph | `langchain_openai.ChatOpenAI(base_url, api_key, default_headers, http_async_client, use_responses_api=True)` | UNVERIFIED (constructor); endpoint VERIFIED | The kwargs/class name are still §8-pending, but the deep adapter (#198) pins `use_responses_api=True` so it calls the **live-verified** `/responses` data plane (§4) rather than ChatOpenAI's default `/chat/completions` route. Overridable via `chat_model(..., use_responses_api=False)`. | 2026-09-11 | #198 |
| Google ADK | `google.adk.models.lite_llm.LiteLlm(model="openai/…", api_base, extra_headers)` | UNVERIFIED | — | — | — |
| MS Agent Framework | `agent_framework.openai.OpenAIChatClient` name + `model_id` kwarg | UNVERIFIED | — | — | — |
| MS Agent Framework | `agent_framework.openai.OpenAIChatClient(model, base_url, api_key, default_headers)` | VERIFIED | Class path confirmed; the constructor kwarg is `model=` — `model_id` is **not** accepted. `base_url`/`api_key`/`default_headers` all accepted, so `connection_kwargs()` is unchanged. Pinned to agent-framework 1.19.0. | 2026-09-22 | #520 (`verify_frameworks.py --only agent_framework`) |
| OpenAI Agents SDK | `agents.OpenAIChatCompletionsModel(model, openai_client=AsyncOpenAI(...))` | UNVERIFIED | — | — | — |
| Anthropic SDK | `anthropic.AsyncAnthropic(base_url, api_key, default_headers, http_client)` — model id per-call; proxy Anthropic-native route UNVERIFIED | UNVERIFIED | — | — | — |
| CrewAI | `crewai.LLM(model="openai/…", base_url, api_key, extra_headers)` | UNVERIFIED | — | — | — |
Expand Down
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "donkey-kit"
version = "0.1.0.dev4"
version = "0.1.0.dev5"
description = "An SDK for consuming Agent Fabric capabilities (governed model and tool access) from your own agent framework."
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion python/src/donkey_kit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
PublicationAssetType,
)

__version__ = "0.1.0.dev4"
__version__ = "0.1.0.dev5"

__all__ = [
"STRICT",
Expand Down
59 changes: 43 additions & 16 deletions python/src/donkey_kit/core/_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,54 +81,81 @@ def blocked(what: str) -> NotImplementedError:
doc_ref="docs/verified-apis.md §3",
)

# --- Cost-attribution tag headers (docs/verified-apis.md §3, the HIGHEST-priority
# unknown, #196) ---
# --- Cost-attribution tag headers (docs/verified-apis.md §3, #196) ---
# The fixed cost dimensions (team / project / env / enduser.id) are emitted as
# request headers so the gateway can group spend per dimension. The gateway-side
# header NAMES are the single most important unverified value (docs/verified-apis.md §3): the
# direct-proxy path did NOT surface them, so these are loud, overridable
# placeholders (config: ``cost_*_header``), and the value ALWAYS lands on the
# ``donkey.cost.*`` span regardless (the SDK controls the span end to end, #196
# AC #4). The ``X-Anypoint-Cost-*`` shape mirrors the attribution placeholders
# above; it is a guess, not a confirmed name.
# request headers AND as ``donkey.cost.*`` span attributes (the SDK controls the
# span end to end, #196 AC #4).
#
# NEGATIVE result — VERIFIED (LIVE 2026-09-22, #522): the deployed Omni Gateway
# LLM proxy has NO inbound cost-tag ingestion. A live probe sending
# ``X-Anypoint-Cost-Team/Project/Env/Enduser-Id`` saw none of them echoed, and
# none of the names appears in ANY of the 10 platform-managed system policies
# applied to the proxy (checked via ``view_api_instance_policies`` on instance
# 21179672, DDK / Sandbox, and against the ``ddk-create-llm-proxy-*`` provisioning
# skills' system-policy set). Cost/usage is collected by the ``llm-proxy-core``
# system policy from token usage (the ``x-llm-proxy-*-tokens-cost-per-1m``
# response headers) and attributed by API instance + consuming client application
# — never by a client-sent header. So the gateway-side names are confirmed to be
# a NON-CONTRACT; the authoritative carrier is the ``donkey.cost.*`` span
# attribute. The SDK still emits these ``X-Anypoint-Cost-*`` names as a
# forward-looking, overridable (``cost_*_header``) convention — harmless because
# nothing reads them — so each is ``verified=True``: there is nothing left to
# discover, and leaving the warning on would lie by silence.
COST_TEAM_HEADER = Unverified(
key="cost.team_header",
placeholder="X-Anypoint-Cost-Team",
doc_ref="docs/verified-apis.md §3",
verified=True,
)
COST_PROJECT_HEADER = Unverified(
key="cost.project_header",
placeholder="X-Anypoint-Cost-Project",
doc_ref="docs/verified-apis.md §3",
verified=True,
)
COST_ENV_HEADER = Unverified(
key="cost.env_header",
placeholder="X-Anypoint-Cost-Env",
doc_ref="docs/verified-apis.md §3",
verified=True,
)
COST_ENDUSER_HEADER = Unverified(
key="cost.enduser_header",
placeholder="X-Anypoint-Cost-Enduser-Id",
doc_ref="docs/verified-apis.md §3",
verified=True,
)

# --- Correlation / call-id request headers (BG §1.1, #195) ------------------
# The gateway ECHOES `x-correlation-id` on RESPONSES (VERIFIED LIVE 2026-08-28,
# docs/verified-apis.md §3). Whether it READS an INBOUND correlation header —
# and under what name — is UNVERIFIED, as is any per-call request-id header. Both request-header
# names are therefore placeholders, overridable per-Donkey via config
# (``DonkeyConfig.correlation_header`` / ``.call_id_header``) so a customer can
# point them at the real names without waiting for us. ``X-Correlation-Id`` is
# the best guess precisely because it mirrors the verified response echo.
# VERIFIED (LIVE 2026-09-22, #522) against the deployed Omni Gateway proxy
# ``ddk-multi-route-fallback`` (API instance 21179672, DDK / Sandbox):
# * X-Correlation-Id (request) — POSITIVE: the gateway READS the inbound value
# and echoes it verbatim on the response ``x-correlation-id`` (probe sent
# ``X-Correlation-Id: ddk522-corr``; the response echoed ``ddk522-corr`` on
# both the 200 and the 400 paths). This is the client→gateway run/trace join
# key the SDK sends, so the name is confirmed — not a guess. It matches the
# already-verified response echo (docs/verified-apis.md §3, 2026-08-28).
# * X-Donkey-Request-Id (request) — NEGATIVE: no gateway policy reads a per-call
# id header (not echoed; absent from all 10 applied system policies). It is a
# CLIENT-OWNED per-call id surfaced on ``DonkeyError.call_id``, stable across a
# request's retries; the gateway does not need to consume it. The name is the
# SDK's own convention.
# Both are overridable per-Donkey via config (``DonkeyConfig.correlation_header``
# / ``.call_id_header``) and both are ``verified=True``: the correlation name is
# confirmed read by the gateway, and the call-id name is confirmed to be a
# client-side construct the gateway ignores — neither is an open worklist item,
# and leaving the warning on would lie by silence. See docs/verified-apis.md §3.
CORRELATION_ID_HEADER = Unverified(
key="correlation.request_header",
placeholder="X-Correlation-Id",
doc_ref="docs/verified-apis.md §3",
verified=True,
)
CALL_ID_HEADER = Unverified(
key="correlation.call_id_header",
placeholder="X-Donkey-Request-Id",
doc_ref="docs/verified-apis.md §3",
verified=True,
)

# --- Control-plane token endpoint (docs/verified-apis.md §1) ----------------
Expand Down
19 changes: 11 additions & 8 deletions python/src/donkey_kit/core/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ def cost_headers(cfg: DonkeyConfig, tags: CostTags) -> dict[str, str]:
(docs/verified-apis.md §3, BG §1.7, #196).

Each header NAME is the config override (``cost_*_header``) if set, else the
UNVERIFIED placeholder from ``core/_verify`` — the gateway-side cost header
name is the single highest-priority unknown (docs/verified-apis.md §3), so
an un-overridden name emits the one-time verification-discipline warning.
Values are pre-validated by
placeholder from ``core/_verify``. As of #522 the gateway-side cost header
names are a VERIFIED-NEGATIVE result (docs/verified-apis.md §3): the LLM
Gateway ingests no cost-tag header, so the name is a forward-looking
convention and reading the placeholder emits no warning — the authoritative
carrier is the ``donkey.cost.*`` span attribute. Values are pre-validated by
:class:`CostTags`, so they are always header-safe."""
override = {
field: getattr(cfg, attr) for field, attr, _placeholder in _COST_HEADER_SOURCES
Expand Down Expand Up @@ -204,10 +205,12 @@ def proxy_api_key(cfg: DonkeyConfig) -> str:

def _resolve_header_names(cfg: DonkeyConfig) -> tuple[str, str]:
"""The ``(correlation, call_id)`` request-header NAMES for this config (BG §1.1,
#195): each is the config override if set, else the UNVERIFIED placeholder
from ``core/_verify``. Called ONCE per client at construction, so the
one-time unverified warning for an un-overridden name fires there, not on
every request."""
#195): each is the config override if set, else the placeholder from
``core/_verify``. As of #522 both are verified (docs/verified-apis.md §3): the
gateway reads the inbound ``X-Correlation-Id`` (echoed verbatim), and
``X-Donkey-Request-Id`` is a confirmed client-owned per-call id the gateway
does not consume — so neither placeholder warns. Called ONCE per client at
construction, not on every request."""
correlation = cfg.correlation_header or _verify.CORRELATION_ID_HEADER.get()
call_id = cfg.call_id_header or _verify.CALL_ID_HEADER.get()
return correlation, call_id
Expand Down
Loading
Loading