> ## 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.

# AI Composer in the builder

> Build and edit your workflow hands-on by chatting with the AI Composer copilot inside the canvas: stream edits live, answer its questions, and save or revert in one click.

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>;
};

The **AI Composer** is the copilot built into the [workflow builder](/workflow-builder/overview). You describe what you want in plain English from a panel beside the canvas, and the Composer builds it for you — adding [nodes](/workflow-builder/nodes/overview), wiring them together, and setting up inputs while you watch each edit land on the canvas in real time.

This page is the hands-on guide to using the Composer *inside the builder*. For the bigger picture of what the Composer is and how it differs from the [Assistant](/concepts/assistant), start with [AI Composer: text to workflow](/concepts/ai-composer).

<MediaEmbed id="MX-MEDIA-3020" type="app_video" caption={"The Composer panel editing the open canvas live, from prompt to a saved workflow."} />

## Where the Composer lives

The Composer is the **chat tab** in the builder's right-hand panel, alongside the node inspector, outputs, and runs. It is always pointed at — *focused on* — the workflow you currently have open, so anything it builds goes straight onto the canvas in front of you.

<CardGroup cols={2}>
  <Card title="Open the panel" icon="panel-right">
    On any workflow, open the right panel and switch to the chat tab. The Composer starts a conversation tied to the workflow you're viewing.
  </Card>

  <Card title="It edits this workflow" icon="diagram-project">
    The Composer is focused on the open canvas. The nodes and edges it creates appear here, on the workflow you're looking at.
  </Card>

  <Card title="Your conversations follow you" icon="clock-rotate-left">
    Composer chats belong to you, not to a single workflow, so your history is there when you come back. A brand-new workflow opens a fresh chat.
  </Card>

  <Card title="You can keep editing by hand" icon="hand">
    The Composer and manual editing work on the same canvas. Drag nodes yourself, then ask the Composer to extend what you built.
  </Card>
</CardGroup>

<MediaEmbed id="MX-MEDIA-3021" type="screenshot" caption={"The workflow builder with the Composer chat tab open in the right panel."} />

## Build a workflow step by step

A single message is enough to get started. The Composer reads your request, plans the workflow, and applies the edits to the canvas as it goes.

<Steps>
  <Step title="Describe what you want">
    In the message box, write the outcome in plain English — for example, "Summarize new support emails and post a digest to Slack every morning." Specific requests ("post to the `#support` channel") give better results than vague ones.
  </Step>

  <Step title="Watch it build on the canvas">
    The Composer streams its work as it plans, adds nodes, and connects them. New nodes appear on the canvas live, and a build-step indicator shows where it is in the process. You don't need to refresh anything.
  </Step>

  <Step title="Answer any questions">
    If the Composer needs a decision or a credential, it pauses and asks inline. Respond in the chat and it picks up where it left off. See [Answer the Composer's questions](#answer-the-composers-questions).
  </Step>

  <Step title="Review the pending changes">
    When the turn finishes, the Composer's edits sit on the canvas as **pending changes** with a save/revert action bar. Nothing is locked in yet — look over the result first.
  </Step>

  <Step title="Save or revert">
    **Save** keeps the changes and updates the workflow's saved snapshot. **Revert** discards them and restores the workflow to where it was. See [Save or revert changes](#save-or-revert-changes).
  </Step>
</Steps>

<Tip>
  You don't have to build everything in one message. Ask for a first version, review it, then send follow-up requests like "now add a step that filters out spam first." The Composer keeps working on the same canvas across the conversation.
</Tip>

## What you see while it works

The Composer doesn't just drop a finished workflow on you — it narrates as it builds, so you can follow along and stop it if it heads the wrong way.

<AccordionGroup>
  <Accordion title="A live build-step indicator" icon="list-check">
    As the Composer works, a step indicator advances through the build — planning the workflow, laying out its skeleton, setting up the data it passes between steps, then adding nodes and connecting them. It's a quick read on how far along the Composer is.
  </Accordion>

  <Accordion title="The tools it's using" icon="wrench">
    Each step the Composer takes shows up in the chat — for example adding a node, looking up your connected integrations, or doing a test run. You can see exactly what it did and what came back.
  </Accordion>

  <Accordion title="Nodes appearing on the canvas" icon="diagram-project">
    Every edit the Composer makes is applied to the canvas as it happens, not at the end. The graph fills in while the chat is still streaming.
  </Accordion>

  <Accordion title="A written summary" icon="message-lines">
    Alongside its actions, the Composer streams a plain-English explanation of what it built and why, so you understand the workflow it produced.
  </Accordion>
</AccordionGroup>

<Tip>
  If the Composer is going in the wrong direction, you can stop the current turn from the chat and start over with a clearer request. Stopping a turn doesn't undo edits it already made — use **Revert** for that.
</Tip>

## Answer the Composer's questions

The Composer doesn't guess on important decisions. When it needs your input, it pauses and asks **inline in the chat** — this is called [human-in-the-loop](/realtime/hitl). While it's waiting, the conversation stays open and nothing else changes until you respond.

<AccordionGroup>
  <Accordion title="A choice between options" icon="list">
    The Composer offers a short list and you pick one or several — for example, which model provider to use for a step.
  </Accordion>

  <Accordion title="A yes or no" icon="thumbs-up">
    A quick confirmation before it does something, such as approving a sensitive action during a test run.
  </Accordion>

  <Accordion title="Some free text" icon="keyboard">
    A short written answer, like the exact wording of a message or the name of a channel.
  </Accordion>

  <Accordion title="A credential for an integration" icon="key">
    If a step needs a service you haven't connected, the Composer asks you to connect it. For services that use OAuth, this opens the provider's sign-in flow and the Composer **continues on its own** as soon as you finish — you don't need to resend your request.
  </Accordion>
</AccordionGroup>

Two rules keep these questions tidy:

<Note>
  Only **one question** can be open per conversation at a time, and only the **person who was asked** can answer it. If you try to send a new request while a question is still open, the Composer asks you to answer the open one first.
</Note>

<Tip>
  Most questions are optional — the Composer shows a **Skip for now** action when it can proceed without an answer. Skip it and the Composer makes a reasonable choice and keeps building.
</Tip>

<MediaEmbed id="MX-MEDIA-3022" type="screenshot" caption={"A Composer human-in-the-loop question shown inline in the builder chat."} />

## Save or revert changes

Composer edits are never silently committed. After a turn finishes, the edits sit on the canvas as **pending changes**, and an action bar gives you two choices.

<CardGroup cols={2}>
  <Card title="Save" icon="floppy-disk">
    Keeps the Composer's edits and refreshes the saved snapshot of your workflow. This snapshot becomes the new baseline — the point a later revert will return to.
  </Card>

  <Card title="Revert" icon="rotate-left">
    Discards the Composer's edits from this turn and restores the workflow to its last saved snapshot. A clean undo of the latest changes.
  </Card>
</CardGroup>

<Tip>
  You can iterate without committing: send another request, see the new result, and only save once you're happy. Each save sets the baseline that a later revert returns to.
</Tip>

<MediaEmbed id="MX-MEDIA-3023" type="screenshot" caption={"The pending-changes action bar after a Composer turn."} />

## Building alongside your team

The Composer works on the same canvas as your teammates. When it edits a workflow you're co-editing, the changes are shared with everyone in the room automatically, so collaborators see the Composer's nodes appear just as you do.

You don't need to do anything special for this — the Composer's edits are persisted and broadcast for you. For how live co-editing, presence, and external changes sync, see [Realtime co-editing & external sync](/workflow-builder/realtime-coediting) and [Canvas collaboration](/platform/collaboration/canvas).

<Tip>
  If a teammate is editing the same node, the canvas may lock it while they work. Wait for the lock to clear, or ask the Composer to work on a different part of the workflow in the meantime.
</Tip>

## Run a Composer turn from code

Most people use the Composer through the builder. If you'd rather drive it from code, a turn starts with a single call that creates or continues a chat and kicks off the agent. The response gives you a `stream_url` you follow to watch the build live.

Every request needs your API key and your organization context — see [Authentication](/api-reference/authentication). The example targets the workflow you want the Composer to edit with `workflow_id`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/composer/chat \
    -H "Authorization: Bearer mx_live_8Kqp2wZ7nR4tVx1aB6cD3eF" \
    -H "X-Organization-ID: 7f3a2b10-9c4d-4e8a-b1f6-2d5c8e0a9b41" \
    -H "Content-Type: application/json" \
    -d '{
      "workflow_id": "a1b2c3d4-5e6f-4708-9a1b-2c3d4e5f6a7b",
      "message": "Add an LLM node that summarizes the input text",
      "llm": {
        "integration_name": "anthropic",
        "provider_id": "anthropic",
        "model_id": "claude-haiku-3.5"
      }
    }'
  ```

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

  client = Modulex(
      api_key="mx_live_8Kqp2wZ7nR4tVx1aB6cD3eF",
      organization_id="7f3a2b10-9c4d-4e8a-b1f6-2d5c8e0a9b41",
  )

  turn = await client.composer.chat(
      "Add an LLM node that summarizes the input text",
      workflow_id="a1b2c3d4-5e6f-4708-9a1b-2c3d4e5f6a7b",
      llm={
          "integration_name": "anthropic",
          "provider_id": "anthropic",
          "model_id": "claude-haiku-3.5",
      },
  )
  print(turn.stream_url)
  ```

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

  const client = new Modulex({
    apiKey: "mx_live_8Kqp2wZ7nR4tVx1aB6cD3eF",
    organizationId: "7f3a2b10-9c4d-4e8a-b1f6-2d5c8e0a9b41",
  });

  const turn = await client.composer.chat({
    workflowId: "a1b2c3d4-5e6f-4708-9a1b-2c3d4e5f6a7b",
    message: "Add an LLM node that summarizes the input text",
    llm: {
      integration_name: "anthropic",
      provider_id: "anthropic",
      model_id: "claude-haiku-3.5",
    },
  });
  console.log(turn.stream_url);
  ```
</CodeGroup>

A successful response confirms the turn is running and tells you where to stream it. The `stream_url` carries the agent's steps, any human-in-the-loop questions, and the final result.

```json Response theme={null}
{
  "status": "running",
  "composer_chat_id": "f2c1a890-3b47-4d62-8e15-6a9c0d2e4f73",
  "workflow_id": "a1b2c3d4-5e6f-4708-9a1b-2c3d4e5f6a7b",
  "run_id": "8e7d6c5b-4a39-4218-9f0e-1d2c3b4a5e6f",
  "thread_id": "f2c1a890-3b47-4d62-8e15-6a9c0d2e4f73",
  "stream_url": "/composer/chat/f2c1a890-3b47-4d62-8e15-6a9c0d2e4f73/listen/8e7d6c5b-4a39-4218-9f0e-1d2c3b4a5e6f"
}
```

After the stream reaches its end, you confirm the changes with a save or revert call, the same way the action bar does in the builder. The full developer reference — the streamed event types, answering questions, and saving or reverting over the API — lives in [Build a workflow with Composer](/guides/build-with-composer) and [Human-in-the-loop resume](/realtime/hitl).

## Access and credits

<CardGroup cols={2}>
  <Card title="Who can use it" icon="user-shield">
    The Composer is available to organization **owners and admins**. The previous **member** role has been retired, so any account that can open the Composer can also use it. See [Roles & permissions](/security/roles-permissions).
  </Card>

  <Card title="What it costs" icon="coins">
    The Composer runs on managed AI usage, so it consumes [credits](/billing/credits) — one credit per turn, plus the cost of the AI it uses. See [Usage gating & limits](/billing/usage-gating).
  </Card>
</CardGroup>

<Note>
  If your organization is out of credits or over a limit, a Composer turn is refused **before any work begins** with a billing response — a `402`, `403`, or `429`. The full shapes are documented in [Errors & status codes](/api-reference/errors).
</Note>

## Known limitations

A couple of related behaviors aren't fully wired today. We list them so you don't build a flow that depends on them.

<AccordionGroup>
  <Accordion title="Bulk deleting nodes on a shared canvas" icon="triangle-exclamation">
    Deleting nodes **one at a time** is fully supported and syncs to everyone editing the canvas with you. When collaborating, delete nodes individually — confirming each — rather than in a single bulk action.
  </Accordion>

  <Accordion title="Reconnecting an expired OAuth integration" icon="triangle-exclamation">
    When the Composer asks for a credential and you connect a service via OAuth, the connect-and-continue flow works. To refresh an expired OAuth credential, reconnect the integration — that re-runs the sign-in flow and issues fresh tokens. See [Managing credentials](/integrations/managing-credentials).
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="AI Composer: text to workflow" icon="lightbulb" href="/concepts/ai-composer">
    The concept behind the Composer, and how it compares to the Assistant.
  </Card>

  <Card title="Build a workflow with Composer" icon="map" href="/guides/build-with-composer">
    A full walkthrough from a plain-English prompt to a running workflow.
  </Card>

  <Card title="Node types overview" icon="shapes" href="/workflow-builder/nodes/overview">
    The nine node types the Composer can add to your canvas.
  </Card>

  <Card title="Canvas & controls" icon="arrows-up-down-left-right" href="/workflow-builder/canvas">
    Navigate the canvas, the detail panel, and keyboard shortcuts.
  </Card>
</CardGroup>
