# Code study — K3 API protocol

Until open weights exist, the **implementation surface** is the API.  
Sources: `reference/docs/kimi-k3-quickstart.md`, `use-thinking-effort.md`, `context-caching.md`.

---

## Client skeleton

```python
from openai import OpenAI
import os

client = OpenAI(
    api_key=os.environ["MOONSHOT_API_KEY"],
    base_url="https://api.moonshot.ai/v1",
)
```

Model string: **`kimi-k3`**.

---

## Message lifecycle (multi-turn + tools)

```
user → model
       ├─ reasoning_content  (streamable)
       ├─ content
       └─ tool_calls?
user/tool results
       → must include prior assistant message WHOLE
```

**Rule:** never keep only `content`. Preserved thinking is part of the training distribution.

---

## Reasoning effort

```python
client.chat.completions.create(
    model="kimi-k3",
    reasoning_effort="max",  # only supported value at launch
    messages=[...],
)
```

| Do | Don’t |
|---|---|
| Use top-level `reasoning_effort` | Use K2.x `thinking` object |
| Expect always-on reasoning | Assume effort=none exists yet |

---

## Streaming

```python
stream = client.chat.completions.create(
    model="kimi-k3",
    messages=[...],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta
    if getattr(delta, "reasoning_content", None):
        print(delta.reasoning_content, end="")
    if delta.content:
        print(delta.content, end="")
```

---

## Vision parts

```python
messages = [{
    "role": "user",
    "content": [
        {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
        {"type": "text", "text": "Describe this image."},
    ],
}]
```

- `content` is an **array**, not a string  
- No public `https://…` image URLs  
- Video: Files API → `{"type":"video_url","video_url":{"url": f"ms://{file_id}"}}`  

---

## Tools

### Static tools + required first call

```python
first = client.chat.completions.create(
    model="kimi-k3",
    messages=messages,
    tools=tools,
    tool_choice="required",
)
messages.append(first.choices[0].message)  # full object
for tc in first.choices[0].message.tool_calls or []:
    messages.append({
        "role": "tool",
        "tool_call_id": tc.id,
        "content": execute(tc),
    })
```

### Dynamic tool loading

System message **without** `content`, with `tools: [...]` — takes effect from that position onward; keep it in history (server does not store it).

---

## Context cache pattern

```python
knowledge = open("kb.md").read()
for q in questions:
    client.chat.completions.create(
        model="kimi-k3",
        messages=[
            {"role": "system", "content": knowledge},  # keep identical prefix
            {"role": "user", "content": q},
        ],
    )
```

Unchanged long prefix → automatic cache hit attempts. Pricing: **$0.30** hit vs **$3.00** miss per MTok input.

---

## Structured output

`response_format` with `json_schema` + `strict: true` constrains final **`message.content`** only — parse that, not `reasoning_content`.

---

## Fixed parameters checklist

Omit from requests (platform-fixed):

- `temperature=1.0`  
- `top_p=0.95`  
- `n=1`  
- `presence_penalty=0`  
- `frequency_penalty=0`  

---

## Agent harness compatibility

Prefer harnesses that:

1. Persist full thinking traces  
2. Don’t strip tool payloads  
3. Don’t mid-flight swap models  

Official recommendations include **Kimi Code** and platform guides for Claude Code / Codex / OpenClaw / Hermes.

Next: [02-kda-background.md](02-kda-background.md)