# Chat

> Embeddable web chat over MoQT. You get AI text agents, human handoff, forms, and skill queues on one connection.

Add one custom element to a web page to put a support or sales chat on it.
The visitor gets an AI text agent. The agent can answer, collect a form, and
hand off to a human. All of this runs over a **single QUIC connection**, with a
WSS fallback. The **same visual flow builder that your voice IVR uses** drives
it. You build the conversation once as a flow of steps. The flow runs the same
when the caller reaches you by phone or by the chat bubble.

The chat modality is **widget-first**. You embed a web component and configure
it with attributes. You author everything else in the flow builder: routing,
AI or human handling, forms, and queueing. The engine serves it. For a custom
client, you can speak the envelope protocol directly over raw MoQT. There is no
separate polyglot SDK for chat today (see [Honest limits](#honest-limits)).

## When to use it

  - **Support / sales chat** — Put a chat bubble on your marketing site or app. It greets visitors and
    answers with an AI text agent. It escalates to a human when necessary.
  - **One flow, phone + chat** — Reuse the announcements, forms, and skill queues that you built for voice.
    Voice and chat share the step engine. Chat is a text-mode surface over it.
  - **Lead capture** — Present a form in the middle of a conversation. The flow blocks until the
    visitor returns a validated result. You get structured intake, and the
    visitor does not leave the chat.
  - **Concurrent human agents** — One human agent handles several conversations at the same time from the
    console. Routing is skill-based. If the agent misses an offer, the engine
    offers the conversation again.
  - **Files &amp; reactions** — Visitors and agents attach files (images and documents). They react to
    messages with emoji or a quote-reply. The AI text agent sees uploaded files
    as context.

## Embed the widget

The web component tag is your brand name in lowercase, plus `-chat`. The
element that you place on the page is `<clutchcall-chat>`.

  1. **Load the widget script**
Add the loader `<script type="module">` from your workspace's widget URL to
    the page `<head>`. The script registers the custom element and defines
    `<clutchcall-chat>`.
  2. **Place the element**
Put `<clutchcall-chat org="…" flow="…">` anywhere in the body. The
    widget renders a launcher bubble. When the visitor opens it, the widget
    renders a chat panel.
  3. **It connects on its own**
When the visitor opens it, the widget mints a **namespace-scoped token**.
    The widget opens a **MoQT session** to the relay. The transport is **QUIC
    first, with a WSS fallback**. The widget then subscribes to its
    conversation's `server` track. You do not write connection code.

- **`org`** (`string`, required) — Your workspace or organization id. It scopes the conversation namespace and   the minted token.
- **`flow`** (`string`, required) — The published flow (step-engine graph) that drives the conversation. The flow   contains the announcements, the forms, and the queue-to-skill step. You   author it in the flow builder, the same builder that voice uses.
- **`theme`** (`string`, default `auto`) — `light`, `dark`, or `auto`. This attribute is cosmetic. It does not change   routing.

> **NOTE:**
> The widget subscribes to **exactly one** track (`server`). It publishes to
> **one** track (`client`). Everything that the visitor sees arrives on that
> single `server` subscription: AI replies, human replies, system announcements,
> and forms. The UI never reconciles multiple streams. See
> [Tracks](#the-three-tracks).

## The three tracks

The address of each conversation is `chat/<org>/<conv>`. Each conversation
carries three MoQT tracks. The split keeps the widget's job simple: publish one
track and subscribe to one track. A human agent can join without a change on
the widget side.

| Track | Direction | Who publishes | Who subscribes |
| ----- | --------- | ------------- | -------------- |
| `client` | visitor → engine | the widget | the engine |
| `server` | engine → visitor | the engine | **the widget (only this one)** |
| `agent`  | human agent → engine | the Agent desk | the engine |

A human agent publishes on `agent`. The engine **relays** those envelopes onto
`server`. That is why the widget needs only one subscription. AI replies and
human replies both arrive on `server`. The transport layer does not tell them
apart. The `role` field in the envelope identifies the author.

## The envelope protocol

Every MoQT object on a chat track is **one JSON envelope**. This is the wire
contract for a custom client. Speak it if you build your own UI over raw MoQT
instead of the widget.

```json
{
  "v": 1,
  "seq": 42,
  "ts": 1720800000000,
  "kind": "message",
  "role": "user",
  "data": { "text": "Hi, I need help with billing" }
}
```

- **`v`** (`number`, required) — Envelope version. Currently `1`.
- **`seq`** (`number`, required) — A monotonic sequence number per track. Use it to order envelopes and to find   gaps.
- **`ts`** (`number`, required) — The epoch timestamp in milliseconds. The sender sets it.
- **`kind`** (`string`, required) — One of `message` · `typing` · `form` · `form_result` · `reaction` · `event`.
- **`role`** (`string`, required) — The author. Examples: `user`, `agent` (AI or human), `system`.
- **`data`** (`object`, required) — The payload for the given kind: message text, a form schema, a submitted   result, or an event body.

| `kind` | Meaning | Typical `data` |
| ------ | ------- | -------------- |
| `message` | A chat message. It can carry attachments or a quoted reply | `{ mid, text, attachments?, reply_to? }` |
| `typing` | Typing indicator | `{ active }` |
| `form` | The engine presents a form and waits for a result | a field schema |
| `form_result` | The visitor's submitted answers, which the engine validates | the field values |
| `reaction` | Add or remove an emoji reaction on a message | `{ op, emoji, target_mid }` |
| `event` | A lifecycle or control signal | e.g. queued, assigned, closed, file uploaded |

### Message ids, attachments, and replies

The `data` of a `message` envelope carries more fields than `text`:

- **`data.mid`** (`string`) — An **author-owned message id**. It is unique in the conversation. The engine   stamps it and relays it. Reactions and quoted replies address a message by   its `mid`.
- **`data.attachments`** (`array`) — Zero or more attachment metas for files sent with this message. Each meta is   `{ id, name, size, mime, url }`. An attachment-only message can have empty   `text`. See [File uploads](#file-uploads).
- **`data.reply_to`** (`object`) — A quoted reply. The object `{ mid, preview }` points at the quoted message.

## File uploads

Visitors and agents can attach **images and documents** to a message. The file
**bytes travel out-of-band, not through MoQT.** Upload the bytes over HTTPS.
The response is an attachment meta. Then publish a normal `message` envelope
that references the meta.

  1. **POST the bytes**
Send the raw file bytes to the control-plane API with the session token.
    The server permits only an allowed set of MIME types and a maximum size. It
    rejects all other uploads.

    ```http
    POST https://engine.firstbyte.fast/v1/chat/upload
    Authorization: Bearer <session-token>
    x-cc-filename: quarterly-report.pdf
    Content-Type: application/pdf

    <raw file bytes>
    ```
  2. **Read the attachment meta**
The response is the attachment meta. Store the whole object. You attach it
    to the message verbatim.

    ```json
    { "id": "att_9f2…", "name": "quarterly-report.pdf", "size": 184320,
      "mime": "application/pdf", "url": "…" }
    ```
  3. **Publish a message that references it**
Publish a normal `message` envelope. Set `data.attachments` to an array of
    metas. For an attachment-only message, the text can be empty.

    ```json
    { "v": 1, "seq": 51, "ts": 1720800001000, "kind": "message", "role": "user",
      "data": { "mid": "m_abc", "text": "",
        "attachments": [ { "id": "att_9f2…", "name": "quarterly-report.pdf",
          "size": 184320, "mime": "application/pdf", "url": "…" } ] } }
    ```

Download files from the engine's download URL at
`GET /v1/chat/attachment/<conv>/<id>/<name>`. Object storage serves the bytes.

> **NOTE:**
> On upload, the engine emits a `chat.file.uploaded` event `{ role, count }`. The
> transcript records `"[file: <names>]"`. Thus an **AI text agent has context
> about the file**, although the bytes never crossed the AI's text channel.

## Reactions

With emoji reactions and quoted replies, each side can annotate a specific
message. The [`mid`](#message-ids-attachments-and-replies) identifies the
target message. A reaction is its own envelope `kind`:

```json
{ "v": 1, "seq": 60, "ts": 1720800002000, "kind": "reaction", "role": "agent",
  "data": { "op": "add", "emoji": "👍", "target_mid": "m_abc" } }
```

- **`data.op`** (`string`, required) — `add` or `remove`.
- **`data.emoji`** (`string`, required) — The emoji to apply.
- **`data.target_mid`** (`string`, required) — The `mid` of the target message.

The engine relays the reaction to the other side. It emits a `chat.reaction`
event `{ role }`.

> **NOTE:**
> **On the widget, these features work with no code.** The built-in UX includes a
> paperclip attach button, drag-and-drop, a multi-file picker (with `accept`
> filtering), an emoji picker, a hover quick-reaction strip, and reaction pills
> that show counts. The envelope and upload details above are for **custom
> clients** and for readers who study the wire. See
> [Widget &amp; API](/modalities/chat/sdk-methods) and the
> [Cookbook](/modalities/chat/cookbook) for procedures.

## Same flow engine as voice IVR

Every conversation runs through the **same visual flow builder and step engine
as voice**. You do not learn a second product. The steps that you know from IVR
apply directly to text:

- **Announcements** — send a scripted `message`, for example a greeting,
  business hours, or a policy notice.
- **Waits + queue position** — hold the visitor and show their queue position
  while they wait for an agent. The engine delivers the position as `event`
  envelopes.
- **Present a form** — emit a `form` and **block until a validated
  `form_result` arrives**. Use this for lead capture and structured intake.
- **Queue to a skill** — route the conversation to a skill queue. This is the
  same model as an ACD queue on the voice side.

A **queue-to-skill** step resolves to one of two handlers:

  
    The engine hands the conversation to a **text conversation DAG**. This is
    the same agent runtime as voice, in **text mode** instead of speech. The
    agent answers and can call tools. It can also decide to escalate, and then
    it queues the conversation to a human skill.
  
  
    The engine **offers** the conversation to an available human on the agent
    console. If the human does not accept (ring-on-no-answer), the engine
    **re-offers** it to the next eligible agent. A human handles **multiple
    concurrent chats**. You can set the concurrent chat capacity for each
    agent.
  

> **TIP:**
> AI and human handlers both publish through the engine onto `server`. Thus the
> widget does not see a mid-conversation AI→human handoff. The visitor keeps the
> same panel and history. The next envelope carries `role: "agent"` from a person
> instead of the model.

## Honest limits

> **WARNING:**
> There is **no polyglot SDK for chat today**. The supported surfaces are the
> embeddable web widget and the agent-side console. You can build a custom client
> that speaks the [envelope protocol](#the-envelope-protocol) over raw MoQT. But
> that path is JS/widget-first, and only the `v` field versions it. Treat it as
> advanced usage, not as a stable client library.

## Related

  - **[Widget & API](/modalities/chat/sdk-methods)** — Embed the widget. Configure its attributes. Speak the envelope protocol
    from a custom client.
  - **[Cookbook](/modalities/chat/cookbook)** — Task snippets: embed on a page, present a form, route to a skill, hand off
    to a human.
  - **[Recipes](/modalities/chat/recipes)** — End-to-end builds: a support widget with AI-to-human handoff, and a
    lead-capture form.
  - **[Agent runtime](/modalities/voice/runtime/overview)** — The same runtime that the AI text agent uses, here in text mode.
  - **[Human handoff](/modalities/voice/concepts/handoffs)** — How escalation to a human works across modalities.
  - **[Skill queues (ACD)](/modalities/voice/transport-telephony/pbx-acd)** — The skill-based routing model that the queue-to-skill step shares with
    voice.
