<!-- Source: https://docs.khwan.ai/byom -->
<!-- The whole documentation in one file: https://docs.khwan.ai/llms-full.txt -->

# Bring Your Own Model

Khwan is a cognition layer, not a model. In the BYOM flow **you** call the LLM;
Khwan only prepares context and records the result. This page is the deep dive on
that loop.

## The contract

```python
turn   = kw.prepare(user_input)     # Khwan → you: a ready-to-send brief
answer = my_own_llm(turn.messages)  # you → your model → you: the reply
kw.record(turn, answer)             # you → Khwan: persist + learn
```

Two things cross the boundary:

- **Out of Khwan:** a `Turn` — most importantly `turn.messages` (the brief) and
  `turn.turn_token` (an opaque handle that ties `record` back to this `prepare`).
- **Back into Khwan:** the plain-text `answer` your model produced.

Khwan never sees your model provider key and never makes the generation call —
there is no hosted model path; you always call your own model.

## `prepare()` — what you get

`kw.prepare(input)` returns a `Turn`. It does **not** call an LLM; it assembles
context on the server and hands it back.

| Field | Type | Meaning |
| --- | --- | --- |
| `turn.messages` | `list[{role, content}]` | The brief to feed your model. |
| `turn.coherence` | `float \| None` | Coherence signal for this turn. |
| `turn.sources` | `list` | Memory / retrieval sources used. |
| `turn.allowed` | `bool` | `False` if Khwan gated the turn. |
| `turn.reason` | `str \| None` | Why it was gated (when `allowed` is `False`). |
| `turn.turn_token` | `str \| None` | Opaque handle; pass to `record()`. |
| `turn.raw()` | `dict` | The full server payload. |

  Always check `turn.allowed` before calling your model. When it's `False`, skip
  generation and surface `turn.reason` — the turn conflicts with the core's
  constitution or memory.

### What's inside `turn.messages`

A standard chat array with Khwan's value baked into the **system** message:

```python
[
  {
    "role": "system",
    "content": "<constitution> + <retrieved memory> + <learned lessons> + <coherence>"
  },
  {"role": "user", "content": "remember I prefer short answers in Thai"}
]
```

Don't parse it — just pass it through. The constitution (identity + rules),
retrieved memory for this core, distilled lessons, and coherence framing are
already composed into it.

## `record()` — closing the loop

```python
kw.record(turn, answer)
```

Pass back the same `Turn` and your model's answer as a string. Khwan uses
`turn.turn_token` to match it to the original `prepare`, then persists the exchange
and updates what it has learned. Skip `record` and Khwan learns nothing from that
turn.

## Writing `my_own_llm`

`my_own_llm` is just your normal LLM call: it takes `turn.messages` and returns a
string. Drop-in versions for common providers follow.

### Anthropic (Claude)

Anthropic takes the system prompt as a separate argument, so split it out:

```python

client = anthropic.Anthropic(api_key="sk-ant-...")  # your key, Khwan never sees it

def my_own_llm(messages):
    system = next((m["content"] for m in messages if m["role"] == "system"), "")
    chat   = [m for m in messages if m["role"] != "system"]
    r = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system=system,
        messages=chat,
    )
    return r.content[0].text
```

### OpenAI

OpenAI accepts the `system` message inline, so pass `messages` straight through:

```python
from openai import OpenAI

client = OpenAI(api_key="sk-...")  # your key

def my_own_llm(messages):
    r = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,   # system + user, as-is
    )
    return r.choices[0].message.content
```

### Local Ollama

Fully local generation — the input and the answer never leave your machine except
for the Khwan memory calls:

```python

def my_own_llm(messages):
    r = ollama.chat(
        model="llama3.1",
        messages=messages,   # Ollama uses the same {role, content} shape
    )
    return r["message"]["content"]
```

### Anything else

Any callable that maps `list[{role, content}] -> str` works. Streaming, tool use,
and multi-model routing are all fine — Khwan only cares about the final answer
string you hand to `record()`.

## Isolate a core

The BYOM loop above runs against one brain. To run several fully separate brains —
each with its own memory, identity, and learning — pass `core` per client:

```python
test    = Khwan(api_key="kwk_live_xxx", user_id="alice", core="test")
client1 = Khwan(api_key="kwk_live_xxx", user_id="alice", core="client1")
# test and client1 share no memory or lessons.
```

`core` is sent as the `X-Khwan-Core` header. Omit it for the account's default
core. `user_id` still identifies the end user on each request — it does not create
a separate brain. Quota is pooled at the account level. See
[Isolated cores](/) and the [cores API](/api-reference#cores).
