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

# How ModuleX works

> The end-to-end mental model behind ModuleX: you author a workflow, you run it, and you observe the run live as each step streams its progress.

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

ModuleX runs on one loop you will see everywhere in these docs: **you author a workflow, you run it, and you observe the run as it happens.** Once that loop clicks, every other feature is a deeper version of one of those three steps.

This page is the mental model. It does not teach you every setting — it shows you the shape of the whole system so the rest of the docs have somewhere to hang. For the term-by-term map, see [Core concepts at a glance](/get-started/core-concepts).

<MediaEmbed id="MX-MEDIA-1080" type="image" caption={"A single end-to-end flow diagram showing the author → run → observe loop."} />

## The loop in one line

<CardGroup cols={3}>
  <Card title="1. Author" icon="pen-line" href="#step-1-author-a-workflow">
    Describe a process to the AI Composer, or build it on the visual canvas. Both produce the same editable **workflow** graph.
  </Card>

  <Card title="2. Run" icon="play" href="#step-2-run-it">
    Start a **run** from chat, the API, or an SDK. ModuleX checks your credit and capacity, then executes the graph step by step.
  </Card>

  <Card title="3. Observe" icon="wave-pulse" href="#step-3-observe-the-run">
    Watch the run stream its progress event by event, pause it for your input when needed, and inspect the final result.
  </Card>
</CardGroup>

There is a shortcut, too: the [Assistant](/concepts/assistant) collapses all three steps into one chat. You give it a goal, it picks and runs the tools itself, and you watch the same live stream — no workflow to author first. More on that below.

## Step 1 — Author a workflow

A **workflow** is an editable graph that connects tools, data, and agents into a repeatable process. You build it one of two ways, and both ways edit the same graph.

<CardGroup cols={2}>
  <Card title="Describe it with the AI Composer" icon="wand-magic-sparkles" href="/concepts/ai-composer">
    Tell the **AI Composer** what you want in plain English. It turns your description into a complete, editable workflow graph, then keeps editing the canvas as you chat with it.
  </Card>

  <Card title="Build it on the canvas" icon="diagram-project" href="/workflow-builder/overview">
    Open the **workflow builder** and place nodes by hand. You connect steps, set their options, and pass data between them with references.
  </Card>
</CardGroup>

A workflow is assembled from a fixed set of **nine node types** — each step does one thing and writes its result back into the run so later steps can use it. You connect steps with edges, and you move data between them with a **reference** written `{{node_id.field}}`. You do not need the details now; the [workflow engine](/concepts/workflow-engine) and the [node types overview](/workflow-builder/nodes/overview) cover them in depth.

<Note>
  Authoring is multi-user. Several people can edit the same canvas at once, with live cursors, presence, and node locks so edits never collide. See the [realtime and collaboration model](/concepts/realtime-model).
</Note>

## Step 2 — Run it

A **run** is one execution of a workflow. You can start a run from three places, and the result is the same execution either way.

<CardGroup cols={3}>
  <Card title="From chat" icon="comments" href="/platform/chat/workflow-run">
    Trigger a workflow inside a chat and watch it run there.
  </Card>

  <Card title="From the API" icon="terminal" href="/workflow-builder/execution/api-endpoint">
    Run a workflow programmatically over REST and stream the result.
  </Card>

  <Card title="From an SDK" icon="code" href="/sdks/overview">
    Run from your own code with the official JavaScript or Python client.
  </Card>
</CardGroup>

### What happens when you press run

<Steps>
  <Step title="ModuleX checks you can run">
    Before any work starts, ModuleX runs a quick admission check: do you have the credit and the capacity to run right now? If yes, the run proceeds. If not, the request is declined cleanly with a billing error — nothing partial executes. This check applies to runs, AI Composer turns, Assistant turns, and managed-knowledge calls.
  </Step>

  <Step title="The run starts and you get a run id">
    ModuleX accepts the request and hands back a **run id** along with a stream you can listen on. The actual execution happens in the background, so the response comes back immediately.
  </Step>

  <Step title="Each node executes in turn">
    The engine walks the graph from its entry point. Every node does its work — calling a model, retrieving from a knowledge base, calling an integration tool, branching, and so on — and writes its result into the run state for the next step to read.
  </Step>

  <Step title="The run reaches a final state">
    The run ends in one of a small set of outcomes — it completes, it fails, or it is cancelled. Some runs pause partway to ask you a question first (see [Observe](#step-3-observe-the-run)).
  </Step>
</Steps>

<Note>
  The admission check is real and live on the run, AI Composer, Assistant, and managed-knowledge surfaces. When it declines a request you get a `402`, `403`, or `429` response carrying a structured reason ([usage gating & limits](/billing/usage-gating)). Plain create, read, update, delete, and organization-settings calls are not gated this way — they return a simpler error shape. See [Errors & status codes](/api-reference/errors) for both.
</Note>

### What a run costs

ModuleX meters **managed** usage in [credits](/billing/credits). Each run is a flat charge, plus the managed model and managed-retrieval usage it consumes along the way. If you bring your own model keys, that usage is billed by your provider and is not metered as credits. The [credits and billing model](/concepts/credits-billing) explains exactly what consumes credits.

## Step 3 — Observe the run

You do not wait blindly for a result. While a run executes, it **streams** its progress to you event by event over Server-Sent Events (SSE) — so you can watch each step happen live, in the app or from your own code.

<CardGroup cols={2}>
  <Card title="Live event stream" icon="list-timeline" href="/realtime/sse-streaming">
    The run emits a sequence of events as it goes: a metadata event when it starts, a node-update event each time a step finishes, and a terminal event when it ends. You read them in order.
  </Card>

  <Card title="Pause for a human" icon="hand" href="/realtime/hitl">
    A run can stop to ask you a structured question — a choice, a yes or no, free text, or a request to connect a credential. You answer, and the run continues. This is called human-in-the-loop.
  </Card>
</CardGroup>

<Accordion title="What the events look like">
  Each event arrives as a small JSON object with a `type` field that tells you what it is. A run typically streams a `metadata` event, then a `node_update` event per step, and finishes with a `done` event. If a step needs your input, you get an `interrupt` (workflows) or a `user_input_request` (the AI Composer and Assistant), the stream stays open, and you respond to continue.

  A `heartbeat` event arrives periodically during quiet stretches — for example while a run is paused waiting on you — so the connection stays open. You can ignore heartbeats; they carry no run data. For the full event taxonomy, see the [realtime overview](/realtime/overview).
</Accordion>

<Accordion title="Pause, resume, and cancel">
  When a run pauses for your input, it does not lose its place — its state is checkpointed. You answer the question and the run resumes from exactly where it stopped. Answering a pause never charges you a second time; a run is charged once.

  You can also cancel a running or paused run. Cancellation is graceful: the current step finishes, then the run stops. For the precise pause-and-resume contract, see [human-in-the-loop resume](/realtime/hitl).
</Accordion>

<Note>
  Run progress (SSE) and canvas collaboration (which keeps several editors in sync) are two **separate** realtime systems that travel over different channels. The [realtime and collaboration model](/concepts/realtime-model) keeps them straight — and notes which sync paths are live versus retired.
</Note>

## Run a workflow from code

The same loop works programmatically. You authenticate, start a run, then open the stream and read events until a terminal one. Every request carries two headers: your API key as a bearer token, and the organization the request runs in.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Start the run
  curl -X POST https://api.modulex.dev/workflows/run \
    -H "Authorization: Bearer mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76" \
    -H "X-Organization-ID: 8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c" \
    -H "Content-Type: application/json" \
    -d '{"workflow_id": "wf_3d8b9a1c-2e4f-4a6b-8c0d-1e2f3a4b5c6d", "input": {"topic": "weekly sales summary"}}'
  # → {"status":"running","run_id":"6a7b...","thread_id":"550e...","chat_id":"..."}

  # 2. Observe the run (SSE stream of events until a terminal one)
  curl -N https://api.modulex.dev/workflows/listen/6a7b8c9d-0e1f-2a3b-4c5d-6e7f8a9b0c1d \
    -H "Authorization: Bearer mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76" \
    -H "X-Organization-ID: 8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c"
  ```

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


  async def main():
      async with Modulex(
          api_key="mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76",
          organization_id="8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c",
      ) as client:
          # 1. Start the run
          run = await client.executions.run(
              workflow_id="wf_3d8b9a1c-2e4f-4a6b-8c0d-1e2f3a4b5c6d",
              input={"topic": "weekly sales summary"},
          )

          # 2. Observe the run, event by event
          async with client.executions.listen(run.run_id) as stream:
              async for event in stream:
                  print(event.event)
                  if event.is_terminal:
                      break


  asyncio.run(main())
  ```

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

  const client = new Modulex({
    apiKey: "mx_live_2f9c4a1b8e7d6c5a4b3f2e1d0c9b8a76",
    organizationId: "8a1f3c2e-9b4d-4e7a-bc11-7d6e5f4a3b2c",
  });

  // 1. Start the run
  const run = await client.executions.run({
    workflowId: "wf_3d8b9a1c-2e4f-4a6b-8c0d-1e2f3a4b5c6d",
    input: { topic: "weekly sales summary" },
  });

  // 2. Observe the run, event by event
  for await (const event of client.executions.listen(run.runId)) {
    console.log(event.type);
    if (event.type === "done" || event.type === "error" || event.type === "cancelled") {
      break;
    }
  }
  ```
</CodeGroup>

<Note>
  Wire fields stay snake\_case (`run_id`, `thread_id`). The SDKs accept camelCase in your code and convert it for you on the request. For the full run-from-code walkthrough, see [Run a workflow (REST + SDK)](/guides/run-a-workflow); for authentication, see [Authentication](/api-reference/authentication).
</Note>

## The shortcut: the Assistant

Not every task is worth building a workflow for. The [Assistant](/concepts/assistant) is an agentic chat that authors, runs, and observes in a single conversation: you state a goal, it decides which connected tools to call, it calls them step by step, and you watch the same kind of live stream. It pauses for your approval before sensitive actions, just like a run can.

<CardGroup cols={2}>
  <Card title="AI Composer" icon="wand-magic-sparkles" href="/concepts/ai-composer">
    Use the **AI Composer** when you want a reusable workflow you can run again and again. It produces a graph you own and can edit.
  </Card>

  <Card title="Assistant" icon="robot" href="/concepts/assistant">
    Use the **Assistant** for one-off or open-ended tasks. It acts directly with your tools — no workflow to set up first.
  </Card>
</CardGroup>

## Where everything lives

The whole loop happens inside an **organization** — the shared workspace and billing boundary. Your workflows, credentials, knowledge bases, plan, and credits all belong to one organization, and every run is metered against it. Each member has a **role**; the live roles are **owner** and **admin**, and actions such as using the AI Composer, the Assistant, and schedules require one of them.

<CardGroup cols={2}>
  <Card title="Organizations, roles & membership" icon="building" href="/concepts/organizations-roles">
    How tenancy works: the organization context, membership, and what owners and admins can do.
  </Card>

  <Card title="Credits & the billing model" icon="coins" href="/concepts/credits-billing">
    How managed usage is metered in credits, and where the admission check applies.
  </Card>
</CardGroup>

## Keep going

<CardGroup cols={3}>
  <Card title="Workflows & runs" icon="diagram-project" href="/concepts/workflows-and-runs">
    A closer look at what a workflow is, what a run is, and the run identities you will meet.
  </Card>

  <Card title="Realtime & collaboration model" icon="satellite-dish" href="/concepts/realtime-model">
    How runs stream over SSE and how the canvas stays in sync across collaborators.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/get-started/quickstart">
    Create an organization, get an API key, and make your first authenticated call.
  </Card>
</CardGroup>
