> ## Documentation Index
> Fetch the complete documentation index at: https://docs.karta.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Tutorial: run a support bot locally

> A complete local Karta-session walkthrough - three personas, a streaming chat widget, no cloud. Operator runs Karta, an agent builder brings a support bot project, an end user chats.

This is the local Karta-session loop on one machine: an **operator** runs
Karta, an **agent builder** brings a support bot project, and an **end user**
chats with it through a browser widget - replies streaming token by token. No
cloud account.

It mirrors the runnable [`support-bot`](https://github.com/karta-sh/examples/tree/main/support-bot).

## The cast

<CardGroup cols={3}>
  <Card title="Operator" icon="user-shield">
    Hosts the platform - locally, `karta dev` plays this part.
  </Card>

  <Card title="Agent builder" icon="user-gear">
    Brings the support bot project and exercises it.
  </Card>

  <Card title="End user" icon="user">
    Opens a chat widget in the browser and talks to the bot.
  </Card>
</CardGroup>

```text theme={null}
browser --HTTP--> web server (:5050) --proxy--> karta dev --turn--> Claude Code
```

## 1. The agent

Our bot is **Beans**, the support agent for a fictional roaster. The whole
agent is three files.

```markdown app/CLAUDE.md theme={null}
# Beans - Karta Coffee Co. support agent

You are Beans, the support agent for Karta Coffee Co.

- Warm, concise, a little playful (one coffee pun per reply, max).
- Greet a brand-new conversation with a short hello.
- Help with orders & shipping (2-3 business days; free over $35),
  our roasts (Sunrise, Midnight, Decaf Dusk), returns (30 days, unopened),
  and brewing tips.
- If you do not know something order-specific (like a tracking number), say so
  honestly and offer a human.
- Keep replies to 1-3 sentences unless asked for detail.
```

```python app/app.py theme={null}
from karta import Karta

# Karta imports this `app` (see entry_point in karta.toml).
app = Karta()
```

```toml app/karta.toml theme={null}
name = "support-bot"
deploy = true
entry_point = "app:app"
buildpack = "python"
```

That's a complete, deployable agent. `CLAUDE.md` is the instructions;
[`karta.toml`](/build/karta-toml) gives it a name, opts it into deploys, and
tells Karta how to load it. (`karta setup` writes this file for you.)

## 2. Run it: one command plays the operator

```bash theme={null}
cd app && karta dev
```

`karta dev` serves the folder behind the
[HTTP session API](/api-reference/introduction) - the uniform surface every
Karta agent speaks - and prints its local URL
(`Local agent API: http://127.0.0.1:<port>/v1`). It also drops you into a
chat REPL; for this tutorial the widget is the client, so keep `karta dev`
running and note the URL.

## 3. End user: a streaming chat widget

A tiny web server hosts the widget and proxies to Karta, so the browser only
ever talks to *your* origin. The two calls it makes:

<Steps>
  <Step title="Open a session">
    ```javascript theme={null}
    // Create a session for this visitor:
    const res = await fetch("/api/spawn", { method: "POST" });
    const { session_id } = await res.json();
    // server-side: POST <karta dev's URL>/v1/sessions { metadata: {...} }
    ```
  </Step>

  <Step title="Send & stream replies">
    ```javascript theme={null}
    const res = await fetch("/api/message", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ session_id, text: userText }),
    });
    // server-side: POST /v1/sessions/{id}/messages { text, stream: true }

    // Read the SSE stream and append text deltas to the reply bubble:
    const reader = res.body.getReader();
    const decoder = new TextDecoder();
    for (;;) {
      const { value, done } = await reader.read();
      if (done) break;
      // parse `event: text` frames and append data.part.text
      bubble.textContent += extractText(decoder.decode(value));
    }
    ```
  </Step>
</Steps>

The widget reads the [SSE stream](/concepts/streaming-events#on-the-wire-sse)
and grows the reply bubble token by token - exactly what the
[Messages endpoint](/api-reference/messages) emits with `stream: true`.

## 4. Try it

Open the widget in a browser. Beans greets you, and a conversation flows:

```text theme={null}
You:   Hi! What roasts do you have?
Beans: Welcome! We have Sunrise (light, citrusy), Midnight (dark,
       chocolatey), and Decaf Dusk (smooth, caffeine-free). Anything catch
       your eye?
You:   Do you ship free?
Beans: Orders over $35 ship free, and most arrive in 2-3 business days.
```

Multi-turn context holds because each message reuses the same `session_id` -
and the history lives in the harness, not the widget.

## What this exercised

* An **agent** (`CLAUDE.md` + `karta.toml`) with zero
  Karta-specific agent code.
* The **operator to developer to end user** persona split, locally.
* The **uniform session API** and **SSE streaming** that a real frontend builds
  against.

## Where to take it next

<CardGroup cols={2}>
  <Card title="Add a sub-agent" icon="users-gear" href="/build/defining-agents">
    Hand billing questions to a dedicated harness role.
  </Card>

  <Card title="Add a skill" icon="wand-magic-sparkles" href="/build/skills">
    Give Beans real order-lookup capability.
  </Card>

  <Card title="Deploy it for real" icon="rocket" href="/deploy/deploy-loop">
    Publish a release behind an agent URL.
  </Card>

  <Card title="Front it with the OpenAI SDK" icon="plug" href="/api-reference/adapters/openai-chat-completions">
    Point an existing client at your agent.
  </Card>
</CardGroup>
