# Quickstart

Make your first governed model call, then see what a plain `base_url` can't
give you — a **typed refusal**, a **budget** and a **span** — in a few
minutes, on your laptop. You start against DDK's **local gateway simulator**,
so you need no Anypoint account and no credentials. Switching to your real
Omni Gateway later is an environment change, not a code change.

## What you can use

| Capability | Status | Learn more |
|---|---|---|
| Governed model access for 8 frameworks | Live | [Frameworks](https://donkey-development-kit.github.io/donkey-development-kit/frameworks.md) |
| Typed refusals | Live | [Typed refusals](https://donkey-development-kit.github.io/donkey-development-kit/errors.md) |
| Budget & pacing | Live | [Budget & pacing](https://donkey-development-kit.github.io/donkey-development-kit/budget.md) |
| OpenTelemetry spans & cost attribution | Live | [Telemetry & cost](https://donkey-development-kit.github.io/donkey-development-kit/telemetry.md) |
| Local simulator & `simulate()` | Live | [Local simulator](https://donkey-development-kit.github.io/donkey-development-kit/simulator.md) |
| Conformance testing with pytest | Live | [Testing & conformance](https://donkey-development-kit.github.io/donkey-development-kit/testing.md) |
| CLI (`init`, `doctor`, `mock`, `test`) & decorators | Live | [CLI & decorators](https://donkey-development-kit.github.io/donkey-development-kit/cli.md) |
| Governed tool access (MCP) | Roadmap | [Tool access](https://donkey-development-kit.github.io/donkey-development-kit/tool-access.md) |
| A2A agents (`serve`, `expose`, `dev`) | Roadmap | [A2A agents](https://donkey-development-kit.github.io/donkey-development-kit/a2a.md) |
| On-behalf-of identity | Roadmap | [Identity](https://donkey-development-kit.github.io/donkey-development-kit/identity.md) |
| Human-in-the-loop | Roadmap | [Human-in-the-loop](https://donkey-development-kit.github.io/donkey-development-kit/hitl.md) |
| Scan & publish to the registry | Roadmap | [Scan & publish](https://donkey-development-kit.github.io/donkey-development-kit/publishing.md) |
| Policy handshake | Roadmap | [Policy handshake](https://donkey-development-kit.github.io/donkey-development-kit/policies.md) |

## Your first governed call

### Install

```bash
pip install "donkey-kit[llm,local,otel]"
```

`llm` adds the OpenAI client, `local` the gateway simulator, `otel` the
OpenTelemetry SDK. Quote the extras — zsh treats unquoted brackets as a glob.

### Start the local gateway

In one terminal:

```bash
donkey mock
```

The simulator listens on `127.0.0.1:8080` and replays real gateway responses,
including every refusal shape. It enforces no policy and ignores
credentials, and every response carries `x-donkey-simulator: true`.

### Point DDK at it

In a second terminal:

```bash
export DONKEY_LLM_PROXY_URL="http://127.0.0.1:8080"
export DONKEY_LLM_PROXY_CLIENT_ID="local"       # placeholder — the simulator ignores auth
export DONKEY_LLM_PROXY_CLIENT_SECRET="local"
```

### Make the call

```python filename="hello.py"
from donkey_kit import Donkey

with Donkey.from_env() as donkey:
    client = donkey.llm.client(sync=True)      # a real openai.OpenAI, governed
    reply = client.responses.create(
        model="gpt-5.1",
        input="Tell me a one-sentence bedtime story about a unicorn.",
    )
    print(reply.output_text)
    print("budget remaining:", donkey.budget.remaining, "tokens")
```

```text
A sleepy unicorn named Luma painted soft silver stars across the night sky with her glowing horn, then curled up on a moonbeam so all the children below could fall asleep beneath her gentle, sparkling light.
budget remaining: 99500 tokens
```

`donkey.llm.client()` returns the OpenAI SDK's own client, routed through
DDK — so credentials, correlation and attribution headers are injected, and
`donkey.budget` is updated from the gateway's response. The simulator replays
a response captured from a real gateway, so you get this story whatever you
ask; against your Omni Gateway the model answers your actual prompt.

  Prefer one command? Run `python -m examples.quickstart.main` from the
  `python/` directory of the
  [SDK repository](https://github.com/Donkey-Development-Kit/donkey-development-kit/tree/develop/python/examples/quickstart):
  it boots the simulator for you and runs the steps above end to end.

## Catch a typed refusal Live

Ask the simulator for a specific rejection by using the model id
`donkey-sim/<shape>`, then bridge the raw response into DDK's taxonomy with
`classify()`:

```python
import openai
from donkey_kit import PIIDetected
from donkey_kit.core.errors import classify

try:
    client.responses.create(
        model="donkey-sim/pii-detected",
        input="Email the report to jane.doe@example.com.",
    )
except openai.APIStatusError as exc:
    governed = classify(exc.response)
    if isinstance(governed, PIIDetected):
        print("blocked, entities:", governed.entities)
```

```text
blocked, entities: ['Email']
```

A PII block is not an auth error, and a policy `429` must never be retried —
typed refusals let the agent react correctly. The framework adapters
(`donkey.langgraph`, …) classify for you. See [Typed refusals](https://donkey-development-kit.github.io/donkey-development-kit/errors.md).

## Pace against the budget Live

The gateway's token window is a first-class object. Keep a reserve in hand
and wait for the window to reset instead of failing:

```python
from donkey_kit import BudgetReserveReached

async with Donkey.from_env() as donkey:
    while True:
        try:
            async with donkey.budget.pace(reserve=0.05):
                await enrich(batch)
            break
        except BudgetReserveReached:
            await donkey.budget.wait_for_reset()
```

See [Budget & pacing](https://donkey-development-kit.github.io/donkey-development-kit/budget.md).

## See a span Live

With `otel` installed, every governed call emits an OpenTelemetry GenAI span.
Print them to the console:

```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor

provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)   # set before the first call
```

Put these lines at the top of `hello.py` and run it again. Alongside the
story, the console prints the span:

```text
{
    "name": "donkey.llm.chat",
    "context": {
        "trace_id": "0x604e2f805df7f12cfdc402f83a66c8f4",
        "span_id": "0x212735c846ec8f18",
        "trace_state": "[]"
    },
    "kind": "SpanKind.INTERNAL",
    "parent_id": null,
    "status": {
        "status_code": "UNSET"
    },
    "attributes": {
        "gen_ai.request.model": "gpt-5.1",
        "gen_ai.system": "openai",
        "gen_ai.response.model": "gpt-5.1",
        "donkey.routing.type": "ModelBased",
        "donkey.routing.fallback": false,
        "gen_ai.usage.input_tokens": 17,
        "gen_ai.usage.output_tokens": 51,
        "donkey.usage.cached_tokens": 0,
        "donkey.usage.cache_write_tokens": 0,
        "donkey.usage.reasoning_tokens": 0,
        "donkey.policy.decision": "allow",
        "donkey.budget.remaining": 99000,
        "donkey.correlation_id": "e2fec0b686694055846da9446fa24c96"
    },
    ...
}
```

Each `donkey.llm.chat` span carries `gen_ai.usage.*` token counts,
`donkey.policy.decision`, `donkey.budget.remaining` and the correlation ID.
Set `OTEL_EXPORTER_OTLP_ENDPOINT` and DDK exports over OTLP with no code at
all. See [Telemetry & cost](https://donkey-development-kit.github.io/donkey-development-kit/telemetry.md).

## Attribute cost to a run Live

Group one logical task under a single correlation ID and cost tags, so the
gateway record, your logs and your spans all join up:

```python
async with donkey.run(id=ticket.id, team="support", project="triage"):
    await triage_agent.ainvoke(ticket)
```

Or fold it into one line with a decorator:

```python
@donkey.governed(team="support")
async def handle_ticket(ticket): ...
```

See [CLI & decorators](https://donkey-development-kit.github.io/donkey-development-kit/cli.md).

## Test the refusal branch Live

Inject a real gateway refusal in-process — no server, no network:

```python
from donkey_kit import PIIDetected

with donkey.simulate(PIIDetected):
    await agent.ainvoke(...)      # the next call fails as a real PIIDetected
```

Then grade your own agent against every refusal shape with the pytest
conformance suite. See [Testing & conformance](https://donkey-development-kit.github.io/donkey-development-kit/testing.md).

## Connect to your Omni Gateway

The same code runs against your real gateway — stop the simulator and set
three values. The proxy authenticates on a `client_id` / `client_secret`
header pair (consumer auth), not a bearer token.

```bash
export DONKEY_LLM_PROXY_URL="https://<ingress-gw>/<instance>/"   # note: no /v1
export DONKEY_LLM_PROXY_CLIENT_ID="<consumer client id>"
export DONKEY_LLM_PROXY_CLIENT_SECRET="<consumer client secret>"
```

Where the values come from:

- **[Create the model proxy](https://docs.mulesoft.com/general/model-proxy-create-model-proxy)**
  on Omni Gateway with **Format=OpenAI**. Its consumer endpoint is your
  `DONKEY_LLM_PROXY_URL`.
- **[Request access](https://docs.mulesoft.com/exchange/to-request-access)**
  to the proxy in Exchange. That registers a client application and issues the
  `client_id` / `client_secret` pair.

Then check the setup:

```bash
donkey doctor
```

```text
[ok] config       env (3 fields)
[ok] gateway      reachable, responded
[ok] credentials  client_id accepted
[ok] model        accepted by the proxy
[i]  budget       99,000 / 100,000 remaining, resets in 59s, observed 0s ago
```

`donkey doctor` tells a wrong URL from wrong credentials from a model that is
not on the proxy's allow-list, instead of one opaque failure.

## Next steps

- **[Pick your framework](https://donkey-development-kit.github.io/donkey-development-kit/frameworks.md)** — get a native LangGraph, ADK,
  Strands, LlamaIndex, CrewAI, OpenAI Agents SDK, Anthropic SDK or Agent
  Framework object in three lines.
- **[Examples](https://donkey-development-kit.github.io/donkey-development-kit/examples.md)** — runnable demos for every capability on this page.
- **[Scenarios](https://donkey-development-kit.github.io/donkey-development-kit/scenarios.md)** — support triage, a nightly batch and an internal
  copilot, built end to end.
