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

# Chat in ModuleX: one place to ask, run, and search

> Chat is the conversational surface in ModuleX. Talk to the agentic Assistant, run a workflow from a message, and ask questions answered from your knowledge bases — with answers that stream back live.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

Chat is where you talk to ModuleX in plain language. You open a thread, type what you want, and the answer streams back as it is produced. The same chat surface does three different jobs: it runs the agentic [Assistant](/concepts/assistant), it can trigger a [workflow](/concepts/workflows-and-runs) and show its progress, and it can answer questions from your [knowledge bases](/concepts/knowledge-rag).

This page is the map. It explains what chat is, the three ways a conversation can be answered, and how streaming works — then points you to the page for each one.

<MediaEmbed id="MX-MEDIA-3310" type="app_video" caption={"A short walkthrough of the chat surface — opening a new chat, sending a message, and watching the answer stream back with tool steps and a final reply."} />

## What chat is

A chat is a single conversation thread. Every message you send and every reply you get back is kept in that thread, so you can scroll up, pick up where you left off, and come back to it later from your history.

You reach chat the moment you sign in — the app opens on a fresh chat. From there the composer at the bottom is where you type, and the left sidebar holds every conversation you have had. For where chat sits relative to the rest of the app, see [Tour the ModuleX app](/platform/overview).

<CardGroup cols={2}>
  <Card title="Start typing" icon="message-square">
    Open a chat and send a message. No setup, no workflow to build first.
  </Card>

  <Card title="Watch it stream" icon="radio">
    Answers appear as they are written, including any tool steps the Assistant takes along the way.
  </Card>

  <Card title="Keep your history" icon="folder">
    Every conversation is saved to your sidebar. Organize threads with [history and folders](/platform/chat/history-folders).
  </Card>

  <Card title="Stay in context" icon="users">
    Chats can be private to you or shared with your organization. See [private and org chat](/platform/chat/private-org-chat).
  </Card>
</CardGroup>

<Note>
  Who can use chat: the chat surface requires an **owner** or **admin** role in the organization. The `member` role has been retired, so a plain member cannot open or use chat. See [roles and permissions](/security/roles-permissions).
</Note>

## The three ways a chat answers you

A single chat can answer in three ways depending on what you ask and what you have connected. They share one input box — you do not switch modes by hand.

<CardGroup cols={3}>
  <Card title="Assistant chat" icon="bot" href="/concepts/assistant">
    The default. An agentic chat that reasons, calls your connected tools step by step, and pauses for your approval when needed.
  </Card>

  <Card title="Run a workflow" icon="play" href="/platform/chat/workflow-run">
    Trigger one of your saved workflows from a message and watch the run unfold inside the thread.
  </Card>

  <Card title="Chat with knowledge" icon="book-open" href="/platform/chat/knowledge-chat">
    Ask a question and get an answer drawn from your connected knowledge bases, with the sources it used.
  </Card>
</CardGroup>

### Assistant chat

When you just start typing, you are talking to the [Assistant](/concepts/assistant) — a workflow-independent agentic chat. You describe a task in plain language and it decides which of your connected [integration tools](/integrations/overview) to call, runs them one at a time, drafts the result, and pauses to ask you whenever it needs a decision or an approval. There is no workflow to build first.

The Assistant is the modern replacement for the old "LLM mode" of running a workflow. Plain conversational requests now go through the Assistant rather than a workflow run.

<Accordion title="How is the Assistant different from the AI Composer?">
  Both are agentic chats, but they do different jobs. The [Assistant](/concepts/assistant) gets work done using your connected tools and never touches a workflow graph. The [AI Composer](/concepts/ai-composer) is a copilot that builds and edits a workflow on the canvas. They are two surfaces over the same underlying agent engine, so they behave consistently — but the Assistant has no workflow-editing tools at all.
</Accordion>

### Run a workflow from chat

If you have a saved workflow, you can run it straight from a message instead of opening the [workflow builder](/workflow-builder/overview). The run streams its progress into the thread, step by step, and the finished output stays in the conversation. See [run a workflow from chat](/platform/chat/workflow-run) for how this works, and [run from chat](/workflow-builder/execution/run-on-chat) for exposing a workflow this way.

A chat is also how a workflow run is kept on record: when you run a workflow, ModuleX stores the conversation and the run together, so the thread doubles as the run's history.

### Chat with your knowledge

Ask a question and ModuleX can answer it from the documents you have loaded into a [knowledge base](/platform/knowledge/overview). It retrieves the most relevant passages, writes an answer grounded in them, and shows the sources it drew from. This is retrieval-augmented generation (RAG) — covered in [chat with your knowledge](/platform/chat/knowledge-chat) and explained end to end in [knowledge and RAG](/concepts/knowledge-rag).

## Streaming responses

Answers do not arrive all at once. ModuleX streams them so you see the reply being written, and — for the Assistant — the steps it takes on the way.

<Steps>
  <Step title="You send a message">
    Your message appears in the thread and an empty reply is added below it, marked as running.
  </Step>

  <Step title="The answer streams in">
    Text arrives in small pieces and fills the reply live. If the Assistant calls a tool, a step appears showing what it is doing.
  </Step>

  <Step title="It may pause to ask you">
    When the Assistant needs a decision, an approval, or a credential, it pauses and shows you a question inline. Answer it and the run continues. This is [human-in-the-loop](/assistant/human-in-the-loop).
  </Step>

  <Step title="The reply finishes">
    When the run completes, the reply is marked done and saved to the thread.
  </Step>
</Steps>

<Note>
  While an answer is streaming you can stop it at any time. A chat can have only one run in progress at once — if you send a new message while a run is still going, you are asked to wait or cancel first.
</Note>

### Streaming over the API

The app uses streaming under the hood, and so can your own code. Chat replies stream over [Server-Sent Events (SSE)](/realtime/sse-streaming): the response starts a run, and you open a stream to receive its events live. Each event is a single `data:` line carrying JSON — you read the `type` field on each one to know what it is (`response_chunk` for text, `tool_call` and `tool_result` for tool steps, `done` when it finishes). A `heartbeat` event keeps the connection open during quiet stretches.

You start an Assistant chat the same way in cURL, Python, and JavaScript. Every request authenticates with `Authorization: Bearer mx_live_…` plus your `X-Organization-ID` header — see [authentication](/api-reference/authentication).

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/assistant/chat \
    -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
    -H "X-Organization-ID: org_a1b2c3d4e5f6" \
    -H "Content-Type: application/json" \
    -d '{
      "message": "List my three newest GitHub issues",
      "llm": {"integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o"}
    }'

  # The response includes a stream_url. Open it to receive events live:
  curl -N https://api.modulex.dev/assistant/chat/{chat_id}/listen/{run_id} \
    -H "Authorization: Bearer mx_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
    -H "X-Organization-ID: org_a1b2c3d4e5f6"
  ```

  ```python Python theme={null}
  from modulex import Modulex

  client = Modulex(
      api_key="mx_live_xxxxxxxxxxxxxxxxxxxxxxxx",
      organization_id="org_a1b2c3d4e5f6",
  )

  # Start an Assistant chat turn.
  chat = await client.assistant.chat(
      message="List my three newest GitHub issues",
      llm={"integration_name": "openai", "provider_id": "openai", "model_id": "gpt-4o"},
  )

  # Stream the run's events live (response_chunk, tool_call, tool_result, done).
  async for event in client.assistant.listen(chat.chat_id, chat.run_id):
      print(event)
  ```

  ```javascript JavaScript theme={null}
  import { Modulex } from "modulex-js";

  const client = new Modulex({
    apiKey: "mx_live_xxxxxxxxxxxxxxxxxxxxxxxx",
    organizationId: "org_a1b2c3d4e5f6",
  });

  // Start an Assistant chat turn.
  const chat = await client.assistant.chat({
    message: "List my three newest GitHub issues",
    llm: { integrationName: "openai", providerId: "openai", modelId: "gpt-4o" },
  });

  // Stream the run's events live (response_chunk, tool_call, tool_result, done).
  for await (const event of client.assistant.listen(chat.chatId, chat.runId)) {
    console.log(event);
  }
  ```
</CodeGroup>

<Note>
  Chat runs go through the ModuleX billing gate. If your organization is out of credits or over a limit, starting a chat returns a `DenialEnvelope` as a `402`, `403`, or `429` rather than running. See [usage gating and limits](/billing/usage-gating) and [errors and status codes](/api-reference/errors).
</Note>

<Warning>
  The app does not send chat messages by writing to a chat thread directly. There is **no** endpoint for posting a message into an existing `/chats` thread — chat threads created by workflow runs are read-only records, and the route some older client code points at (`POST /chats/{id}/messages`) does not exist on the server. To send a message, start an Assistant turn (`POST /assistant/chat`) or run a workflow. This is tracked in [known limitations](/reference/known-limitations).
</Warning>

## Make chat your own

A few settings change how every chat behaves.

<CardGroup cols={2}>
  <Card title="Choose the model" icon="cpu" href="/platform/chat/model-selection">
    Pick which language model answers, from ModuleX-managed models to your own provider keys (BYOK).
  </Card>

  <Card title="Send a voice message" icon="mic" href="/platform/chat/voice-message">
    Speak instead of type when that is faster.
  </Card>

  <Card title="Private or shared" icon="lock" href="/platform/chat/private-org-chat">
    Keep a chat to yourself or make it visible to your whole organization.
  </Card>

  <Card title="History and folders" icon="folder-tree" href="/platform/chat/history-folders">
    Find past conversations and group them into folders.
  </Card>
</CardGroup>

## Where to go next

<CardGroup cols={2}>
  <Card title="Meet the Assistant" icon="bot" href="/concepts/assistant">
    The agentic chat behind the default experience — how it reasons, calls tools, and asks for approval.
  </Card>

  <Card title="Chat with your knowledge" icon="book-open" href="/platform/chat/knowledge-chat">
    Attach a knowledge base and get answers grounded in your own documents.
  </Card>

  <Card title="Run a workflow from chat" icon="play" href="/platform/chat/workflow-run">
    Trigger a saved workflow from a message and watch it run in the thread.
  </Card>

  <Card title="Streaming responses (API)" icon="radio" href="/realtime/sse-streaming">
    The SSE event format behind live answers, for building your own chat experience.
  </Card>
</CardGroup>
