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

# CrewAI

CrewAI gives agents a plan and the tools to execute it. It does not give them a
memory that survives the process. Khwan is that memory — and because Khwan never
runs a model, your `Agent`'s LLM stays exactly as you configured it.

CrewAI's seams are ordinary Python, so the integration is a small client you call
around `kickoff()` — nothing has to be patched or monkey-patched.

  A packaged version of everything below is available as
  [`khwan-crewai`](https://github.com/khwanlabs/khwan-crewai) — the three tools and
  a `prepare` → `record` wrapper, so you can install it rather than copy it. The
  page still spells out what it does, because that is what you are trusting.

## Where the three calls go

```
Flow step / before kickoff ──► prepare(turn)   → grounding block, turn_token  (no LLM)
       Agent.kickoff(messages)                  ← CrewAI runs your model here
after the answer ────────────► verify(draft)   → optional answer-gate
after you send ──────────────► record(answer)  → persist + learn
```

| CrewAI seam | what to do there |
| --- | --- |
| a `Flow` step before generation | `POST /prepare` — prepend the returned context to the prompt |
| `Agent.kickoff()` / `Crew.kickoff()` | nothing — this is your model |
| after `kickoff` returns | `POST /verify` (optional), then send |
| after the send | `POST /record` |
| `task_callback` / `step_callback` | per-task write-back in a multi-task crew |

  `Crew.kickoff()` and `Agent.kickoff()` are **blocking**. If your app is async
  (FastAPI, a webhook handler), run them in a worker thread —
  `anyio.to_thread.run_sync(lambda: agent.kickoff(messages))` — and keep the Khwan
  calls on the event loop.

## A minimal client

```python

from crewai import Agent

BASE = "https://api.khwan.ai"

def headers(scope: dict) -> dict:
    h = {"X-API-Key": scope["api_key"], "Content-Type": "application/json"}
    if scope.get("core"):
        h["X-Khwan-Core"] = str(scope["core"])
    if scope.get("user"):
        h["X-Khwan-User"] = str(scope["user"])
    return h

async def run_turn(client: httpx.AsyncClient, agent: Agent, text: str, scope: dict) -> str:
    # 1) prepare — no model call
    prep = (await client.post(
        "/prepare", json={"input": text}, headers=headers(scope)
    )).json()
    turn_token = prep.get("turn_token")

    if prep.get("allowed") is False:
        return f"I can't answer that: {prep.get('reason')}"

    grounding = "\n".join(
        m["content"] for m in prep.get("messages", []) if isinstance(m.get("content"), str)
    )

    # 2) your model — CrewAI runs it, Khwan never touches it
    answer = await anyio.to_thread.run_sync(lambda: agent.kickoff(grounding + "\n\n" + text))

    # 3) record — consume the token exactly once, and never raise
    if turn_token:
        try:
            await client.post(
                "/record",
                json={"turn_token": turn_token, "answer": str(answer)},
                headers=headers(scope),
            )
        except Exception:
            pass  # a failed learn must never break the send

    return str(answer)
```

## Gating what the crew is about to say

CrewAI plans, then acts. If an action is "send this to a customer", you usually want
one check between the draft and the send. That is
[`POST /verify`](/api-reference#post-verify):

```python
verdict = (await client.post(
    "/verify",
    json={"answer": draft, "turn_token": turn_token},
    headers=headers(scope),
)).json()

if not verdict.get("ok"):
    ...  # regenerate, or route to a human with verdict["reason"]
```

`/verify` is non-destructive — it never consumes `turn_token`, so the later `/record`
still works. Make it **fail open**: if the call errors, treat the draft as supported
rather than blocking the send on a transport hiccup.

## Five rules, each one a bug we shipped

These come from running this integration in production. They are not style advice.

### Hold `turn_token` on the turn, not in a session dict

Keying pending tokens by a session id the framework owns means the answer of turn N
can be recorded against the input of turn N-1. Put the token on a per-turn object
that travels with the request; the mispairing then cannot be expressed.

### Consume the token before you dispatch

Set `turn.turn_token = None` *before* awaiting `/record`, not after. Otherwise a
retry or a concurrent path records the same turn twice.

### `record` must never raise

Wrap it and swallow. A failed learn costs one turn of memory; a raised exception
costs the user their reply.

### In multi-tenant, a missing tenant key is an error — never a fallback

Do not fall back to the process-wide `KHWAN_API_KEY` when a tenant's key is absent.
That key belongs to a different workspace, so the fallback reads and writes one
customer's brain on behalf of another. Fail the call instead.

### Record the text that actually shipped

If a human approves or edits the draft before it goes out, record the **edited**
text. The correction is the single most valuable thing the brain can learn — and
recording the draft instead teaches it the opposite of what happened.

## Scoping a crew to your customers

If your crew runs on behalf of many end users, you do not build that isolation
yourself — it is two headers:

- `X-Khwan-Core` — one brain per tenant, project, or client
- `X-Khwan-User` — a sub-brain per end user inside that core (`account::core::@user`)

A shared functional agent can point several people at one sub-brain by sending the
same `X-Khwan-User`; a personal agent sends the individual's id. See
[Cores](/api-reference#cores).

## Related

- [Connect your agent](/connect-your-agent) — the framework-agnostic version of this page.
