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

# Architecture overview

> How ModuleX fits together: one FastAPI backend, a Next.js app, a Socket.io realtime server, two SDKs, and an installable tool catalog, bound by managed storage, a shared in-memory store, and Clerk.

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 is a system of seven repositories built around a single backend. One FastAPI service owns the database, the credit ledger, the tool runtime, and every REST and SSE surface. A Next.js app is the only first-party human surface; it talks to the backend over REST and SSE and to a separate Socket.io server for realtime canvas collaboration. Two SDKs wrap the same REST surface, and the integration tool catalog ships as an installable Python package the backend loads at startup.

This page maps the components, the wires between them, the boot order, and the auth context that threads through every hop.

<MediaEmbed id="MX-MEDIA-1060" type="image" caption={"ModuleX component-and-data-flow diagram covering all seven repositories."} />

## The seven repositories

Seven repositories make up ModuleX, but only five of them run as processes — two are libraries and one is a static marketing site.

| Repository             | Stack                              | Role                                                                                                                                     | Listens on              |
| ---------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `modulex`              | Python 3.12, FastAPI (Pydantic v2) | The backend: all REST and SSE, auth, billing, the tool runtime, the workflow, Composer, and Assistant engines, and the background worker | `:8000` (dev canonical) |
| `modulex-ui`           | Next.js 15 / React 19 (App Router) | The first-party web app and a thin backend-for-frontend (BFF) proxy layer                                                                | `:3000`                 |
| `modulex-ws`           | Node 20 / TypeScript, Socket.io    | The realtime multi-user canvas collaboration server                                                                                      | `:3001`                 |
| `modulex-integrations` | Python 3.12 package                | The installable tool catalog (175 integrations) loaded by the backend                                                                    | — (library)             |
| `modulex-js`           | TypeScript SDK                     | The JavaScript/TypeScript client wrapping the REST surface                                                                               | — (library)             |
| `modulex-python`       | Python SDK                         | The async Python client wrapping the REST surface                                                                                        | — (library)             |
| `modulex-www`          | Static marketing site              | The public marketing and pricing site                                                                                                    | — (static)              |

The backend serves on `http://localhost:8000` in development; that is the canonical base URL for every example in these docs. (A `python -m app.main` shortcut binds `8001` to avoid local conflicts, but the standard `uvicorn` command and all tooling use `8000`.) The SDKs default to the production REST host `https://api.modulex.dev`.

<Note>
  The integration count is **175 live integrations** — the figure to use anywhere a tool count appears. A vendored `dist/manifests.json` snapshot in `modulex-integrations` lags at 136 entries; do not quote it. See the [integration catalog](/integrations/catalog) for the authoritative list.
</Note>

## The three shared backplanes

Every component above is wired together by exactly three external services.

<CardGroup cols={3}>
  <Card title="Managed datastore" icon="database">
    The authoritative datastore. The backend is the only authoritative writer; `modulex-ws` does its own reads and writes against the same storage. The Procrastinate job queue lives in a dedicated `procrastinate` schema in the same datastore.
  </Card>

  <Card title="In-memory store" icon="bolt">
    Used by the backend for run-event pub/sub, history replay, rate-limit counters, caches, and credit reservation; used by `modulex-ws` as the Socket.io adapter and for presence and lock keys.
  </Card>

  <Card title="Clerk" icon="key">
    The external identity provider. It issues the user JWTs that the backend, the app, and the realtime server all verify against Clerk's JWKS.
  </Card>
</CardGroup>

The datastore and in-memory store are shared, but the ownership boundaries are strict:

* The backend **never** reads or writes the `presence:*` or `lock:workflow:*` keys. Those belong exclusively to `modulex-ws`.
* Beyond those keys, the two backends share only a narrow pub/sub surface; external workflow sync runs over the Socket.io `workflow:external-sync` event.

## Component and data-flow map

```text theme={null}
                                 ┌──────────────────────────┐
                                 │   Clerk (external IdP)    │
                                 │   JWT issue + JWKS        │
                                 └───┬──────────────┬────────┘
                       sign-in / JWT │              │ JWKS verify
                                     │              │
           ┌─────────────────────────▼─┐        ┌──▼──────────────────────┐
  browser  │ modulex-ui (Next.js :3000) │        │ modulex-ws (Socket.io   │
 ─────────►│ • App Router pages         │        │ :3001)                  │
           │ • app/api/* BFF proxies    │        │ • canvas collaboration  │
           │   (add Bearer + X-Org-ID)  │        │ • presence / locks      │
           └───┬───────────────┬────────┘        └────┬───────────┬────────┘
  REST + SSE   │               │ Socket.io             │ Socket.io │ shared
  (proxied)    │               └───────────────────────┘           │ datastore + store
               │                                                    │ (own reads)
               ▼                                                    │
   ┌───────────────────────────────────────────────────────────────┴──────────┐
   │  modulex (FastAPI :8000)                                                   │
   │  • 21 routers (REST)        • SSE: workflow:run:{run_id}:events            │
   │  • auth/org dependencies    • workflow / Composer / Assistant engines      │
   │  • tool runtime ───────────────────────► loads @tool from modulex-integr.  │
   │  • billing / credit ledger  • Procrastinate worker (separate process)      │
   └───┬───────────────┬───────────────────┬──────────────────────┬────────────┘
       │ datastore     │ in-mem store      │ entry-point group     │ Stripe
       ▼               ▼                   ▼ "modulex.tools"       ▼ (external)
   ┌────────┐    ┌──────────┐     ┌──────────────────────┐
   │managed │    │ in-mem   │     │ modulex-integrations │
   │datastore    │ store    │     │ (installed package,  │
   │(+ proc-│    │(pub/sub, │     │  175 tools)          │
   │ rastin-│    │ cache,   │     └──────────────────────┘
   │ ate)   │    │ rate-lim)│
   └────────┘    └──────────┘

  SDKs:  modulex-js / modulex-python  ──REST──►  modulex (:8000)
  Docs:  modulex-www (static marketing site, no runtime wire)
```

## The five deployables

There are five things that actually run. The other repositories are libraries (`modulex-integrations`, `modulex-js`, `modulex-python`) or a static site (`modulex-www`).

| Unit                 | From repository                                                       | Process                                                           |
| -------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------- |
| API server           | `modulex`                                                             | `uvicorn app.main:app` (`:8000`)                                  |
| Background worker    | `modulex`                                                             | `python -m app.workers.run_worker --queue default --queue ingest` |
| App server           | `modulex-ui`                                                          | Next.js (`:3000`)                                                 |
| Realtime server      | `modulex-ws`                                                          | `node dist/index.js` (`:3001`)                                    |
| (libraries / static) | `modulex-integrations`, `modulex-js`, `modulex-python`, `modulex-www` | not a process                                                     |

<Warning>
  The background worker is a separate process from the API server and does **not** run the API's startup lifespan. Tasks that need the in-memory store (such as organization-usage refresh and the credit-reservation sweeper) connect to the store themselves rather than relying on a shared client wired during API startup.
</Warning>

### Boot order

The two long-lived servers each have a deterministic startup sequence.

<Tabs>
  <Tab title="API server (modulex)">
    <Steps>
      <Step title="Configure logging">
        The root log level comes from `LOG_LEVEL` (default `INFO`).
      </Step>

      <Step title="Validate security settings">
        In `production` or `staging`, a failed security check aborts startup. In development it logs a warning and continues.
      </Step>

      <Step title="Run the startup sequence">
        Applies migrations and syncs the integration catalog from the installed `modulex-integrations` package into the database.
      </Step>

      <Step title="Connect the in-memory store">
        Falls back to a local in-process implementation if the store is unreachable.
      </Step>

      <Step title="Start the event worker and pub/sub subscriber">
        Wires the run-event delivery machinery (multi-instance aware).
      </Step>

      <Step title="Initialize the checkpointer and pre-warm the DB pool">
        The state checkpointer is always the managed saver.
      </Step>

      <Step title="Register routers">
        All 21 REST routers mount at their resource prefixes. There is no `/v1` segment.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Realtime server (modulex-ws)">
    <Steps>
      <Step title="Connect the in-memory store">
        Opens three store clients (default, subscriber, publisher).
      </Step>

      <Step title="Test the datastore">
        Runs `SELECT 1` against the shared storage.
      </Step>

      <Step title="Start the HTTP server">
        Exposes `/health` and `/admin/*`.
      </Step>

      <Step title="Start Socket.io and attach the store adapter">
        The adapter lets multiple realtime instances broadcast to one another.
      </Step>

      <Step title="Install the auth middleware">
        The handshake gate verifies the Clerk JWT and resolves org membership before any event handler runs.
      </Step>

      <Step title="Register per-socket handlers and start listening">
        Binds node, edge, lock, cursor, presence, and patch handlers, then listens on `:3001`.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## The REST surface

The backend exposes 21 routers, mounted directly at their resource prefixes (`/workflows`, `/composer`, `/assistant`, `/knowledge-bases`, `/credentials`, and so on). There is **no API version segment** — no `/v1` or `/api/v1`. Trailing-slash redirects are disabled, so a path is matched exactly as written.

Every response body is **snake\_case** JSON (Pydantic v2 default); request headers are PascalCase-hyphen (`X-Organization-ID`, `X-API-KEY`). The full request lifecycle, content types, and base URLs are covered in the [API overview](/api-reference/overview); the environments and versioning policy live in [Base URLs, environments & versioning](/get-started/environments).

### Standard error envelopes

The backend emits **three** error-envelope shapes, and you must branch on all of them. Each is owned by a different surface:

| Shape                                                         | Example                                                                                                                                  | Where it comes from                                                  |
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `{"detail": "<string>"}`                                      | `{"detail": "X-Organization-ID header is required"}`                                                                                     | The common FastAPI `HTTPException` on CRUD and org-settings routes   |
| `{"detail": {…}}`                                             | `{"detail": {"code": "rate_limited", "layer": "rate", "key": "api", "current": 300, "limit": 300}}`                                      | The org-level rate-limit deny                                        |
| Flat `{"code", "layer", "key", "current", "limit", "reason"}` | `{"code": "credit_plan_exhausted", "layer": "credit", "key": "run", "current": 5000, "limit": 5000, "reason": "plan_credits_exhausted"}` | The billing/credit admission gate (`DenialEnvelope`) on run surfaces |

The complete shape catalog, status mapping, and which surface emits each lives in [Errors & status codes](/api-reference/errors).

<Warning>
  The billing/credit admission gate is **live** on the run, [Composer](/concepts/ai-composer), [Assistant](/assistant/overview), and managed-knowledge surfaces. Calls to those surfaces can return a flat `DenialEnvelope` as **402** (credit or wallet), **403** (quota), or **429** (rate) — alongside the existing rate-limit-header 429. Plain CRUD and org-settings routes do **not** return this envelope; they return the `{"detail": …}` `HTTPException` shape. See [Usage gating & limits](/billing/usage-gating).
</Warning>

## Auth and org context across every hop

The same two pieces of context — a **Clerk JWT** (or an `mx_live_` API key) and an **organization id** — thread through every wire, but the transport differs per hop.

| Hop                       | Identity token                                      | Org context                                              |
| ------------------------- | --------------------------------------------------- | -------------------------------------------------------- |
| Browser → app             | Clerk session                                       | `selected_organization_id` cookie                        |
| App BFF → backend (REST)  | `Authorization: Bearer <clerk jwt>`                 | `X-Organization-ID` header                               |
| Backend dependency        | Bearer JWT **or** `mx_live_*` API key               | `X-Organization-ID` header (or `?organization_id` query) |
| Browser → realtime server | `auth.token` (Clerk JWT) in the Socket.io handshake | `auth.organizationId` in the handshake                   |
| SDK → backend             | `mx_live_*` API key (or Clerk JWT)                  | `X-Organization-ID` header                               |

<Warning>
  The auth header is `Authorization: Bearer mx_live_…`, **not** `X-Authorization`. Both SDKs send `Authorization: Bearer` plus `X-Organization-ID`; the backend also accepts the key in an `X-API-KEY` header as an alternative. There is no `X-Authorization` header anywhere in the system. See [Authentication](/api-reference/authentication).
</Warning>

The org context header is `X-Organization-ID` (exact casing) on every REST and SSE hop. The realtime server resolves org membership independently, through its own cached SQL lookup. The full model — Clerk JWT versus API key, roles, and the org header — is in [Auth model: JWT vs API key](/security/authentication) and [Org context & X-Organization-ID](/security/org-context).

Live org roles are **`owner`** and **`admin`** only — the `member` role is retired. Composer, Assistant, and managed-knowledge actions require owner or admin. See [Roles & permissions](/security/roles-permissions).

## Two realtime planes

ModuleX has two realtime systems that do not share a transport.

<CardGroup cols={2}>
  <Card title="Run-event streaming" icon="signal-stream" href="/realtime/sse-streaming">
    Owned by the backend, delivered over **SSE** on top of server-side pub/sub. Carries workflow, Composer, and Assistant run progress: `metadata`, `node_update`, `tool_call`, `interrupt`, `done`, and more. Run endpoints emit flat `data: {"type": …}` frames; the discriminator is the `type` key inside the JSON, with no `event:` line.
  </Card>

  <Card title="Canvas collaboration" icon="users" href="/realtime/socket-events">
    Owned by `modulex-ws`, delivered over **Socket.io**. Carries presence, cursors, node locks, node and edge edits, and external sync. The handshake takes the Clerk JWT and the org id; events are `node:add`, `patch`, `lock`, and the like.
  </Card>
</CardGroup>

On reconnect, the SSE path is replay-safe: the listener first re-yields the run's buffered history list (one-hour TTL) in order, then tails live. Run-event delivery is pub/sub-native, so any backend replica subscribed to `workflow:run:{run_id}:events` receives events published by any other replica. The realtime server scales horizontally through the Socket.io store adapter. The end-to-end model is in [Realtime overview & event taxonomy](/realtime/overview) and the [realtime & collaboration model](/concepts/realtime-model).

<Note>
  Not all SSE in the system uses the same frame convention. Run and agent streams use flat typed-JSON `data.type` frames; the sidebar chat-list stream (`GET /chats/stream`) uses **named** SSE events (`event: chat_list_updated`). A consumer that crosses both must expect both conventions.
</Note>

## The tool runtime seam

The most architecturally significant internal seam is how the backend turns a catalog name such as `github.create_issue` into an executed, billed action against the installed [integrations](/integrations/overview) package.

<Steps>
  <Step title="Discover">
    At startup, the backend queries Python entry points in the `modulex.tools` group from the installed `modulex-integrations` package and caches the result for the process lifetime. Restart the worker to pick up a freshly installed package version.
  </Step>

  <Step title="Sync">
    The startup sequence syncs the catalog into the database. The runtime then reads auth schemas from the **database** catalog, not from live package manifests — so a new manifest field is inert until the catalog is re-synced against the bumped package.
  </Step>

  <Step title="Load">
    The backend imports each tool's `@tool`-decorated function and wraps it so that credential fields are stripped before the model ever sees them.
  </Step>

  <Step title="Execute">
    The executor resolves and decrypts the credential (refreshing OAuth2 tokens that are close to expiry), injects auth into the call, and invokes the tool. Tools run only inside workflow runs and Composer or Assistant turns — there is no REST endpoint for direct tool execution.
  </Step>

  <Step title="Bill">
    Managed (`modulexai`) usage is metered in [credits](/billing/credits); bring-your-own-key (BYOK) credentials are tracked but not credit-limited.
  </Step>
</Steps>

Most integrations come from the `modulex-integrations` package, but not all: legacy JSON-defined integrations such as `mcp_server` (which loads tools dynamically from a [custom MCP server](/integrations/building/custom-mcp) credential) are also loaded, and the package wins on a name collision. The full tool contract is in [Build an integration](/integrations/building/overview).

## SDK client configuration

Both SDKs wrap the same REST surface and send the same headers. The constructor shapes differ in one structural way: the Python client reads environment-variable fallbacks; the JavaScript client does not.

<ParamField path="apiKey / api_key" type="string" required>
  An `mx_live_…` API key. In Python this falls back to the `MODULEX_API_KEY` environment variable; in JavaScript it must be passed to the constructor or the client throws.
</ParamField>

<ParamField path="organizationId / organization_id" type="string">
  The organization id sent as `X-Organization-ID`. In Python it falls back to `MODULEX_ORGANIZATION_ID`; in JavaScript it is constructor-only.
</ParamField>

<ParamField path="baseUrl / base_url" type="string" default="https://api.modulex.dev">
  The REST host. In Python it falls back to `MODULEX_BASE_URL`; in JavaScript it is constructor-only.
</ParamField>

<ParamField path="timeout" type="number" default="30000 ms (JS) / 30.0 s (Python)">
  Per-request timeout. No environment fallback in either SDK.
</ParamField>

<ParamField path="maxRetries / max_retries" type="number" default="3">
  Retry budget for retryable requests. No environment fallback in either SDK.
</ParamField>

Configure a client and trigger one workflow run. The run accepts either `workflowId` (a deployed workflow) or an inline `workflow` definition, plus `input`, `config`, and `stream`.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/workflows/run \
    -H "Authorization: Bearer mx_live_2f8c9d1a4b6e7f0c3d5a8b2e" \
    -H "X-Organization-ID: 7c1e9a4b-2d6f-4a83-9b15-0e2c8f6a1d34" \
    -H "Content-Type: application/json" \
    -d '{
      "workflow_id": "wf_3a9c1e7b-5d24-4f81-bc06-2e8a9d4f1b73",
      "input": { "topic": "Q3 release notes" },
      "stream": true
    }'
  ```

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


  async def main():
      # api_key, organization_id, and base_url fall back to
      # MODULEX_API_KEY / MODULEX_ORGANIZATION_ID / MODULEX_BASE_URL.
      client = Modulex(
          api_key="mx_live_2f8c9d1a4b6e7f0c3d5a8b2e",
          organization_id="7c1e9a4b-2d6f-4a83-9b15-0e2c8f6a1d34",
      )
      async with client:
          run = await client.executions.run(
              workflow_id="wf_3a9c1e7b-5d24-4f81-bc06-2e8a9d4f1b73",
              input={"topic": "Q3 release notes"},
              stream=True,
          )
          print(run)


  asyncio.run(main())
  ```

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

  // apiKey and organizationId are constructor-only (no env fallback in JS).
  const client = new Modulex({
    apiKey: "mx_live_2f8c9d1a4b6e7f0c3d5a8b2e",
    organizationId: "7c1e9a4b-2d6f-4a83-9b15-0e2c8f6a1d34",
  });

  const run = await client.executions.run({
    workflowId: "wf_3a9c1e7b-5d24-4f81-bc06-2e8a9d4f1b73",
    input: { topic: "Q3 release notes" },
    stream: true,
  });

  console.log(run);
  ```
</CodeGroup>

One wire-casing detail to keep in mind: the JavaScript SDK takes camelCase parameters and converts them to snake\_case before sending, but it does **not** convert responses back — response fields stay snake\_case (`run_id`, `created_at`). The Python SDK is snake\_case in both directions. The full route-to-method mapping, naming differences, and parity gaps are in the [SDK ⇄ API parity matrix](/sdks/parity).

## Realtime & SDK notes

A couple of notes on the realtime and SDK surfaces.

<AccordionGroup>
  <Accordion title="External sync uses Socket.io">
    Live external canvas sync — changes from the REST API, the Composer, or a deployment reaching open canvases — flows over the **Socket.io `workflow:external-sync`** event. See [Socket.io collaboration events](/realtime/socket-events) and [Realtime co-editing & external sync](/workflow-builder/realtime-coediting).
  </Accordion>

  <Accordion title="The subscriptions SDK resource is Python-only">
    The `subscriptions` resource exists only in the Python SDK; the JavaScript SDK has no equivalent. See [Subscriptions & Stripe](/billing/subscription-lifecycle) and the [parity matrix](/sdks/parity).
  </Accordion>
</AccordionGroup>

## How repositories stay in contract

The repositories deploy independently, so ModuleX coordinates breaking changes through a file-based "brief" protocol: the backend holds inbound brief directories (one per sibling repository), each a markdown file with an acknowledgment block. Two conventions hold across them:

* Breaking changes merge in the order **backend → realtime server → app**, with each side holding its branch until the upstream field lands.
* Any new integration manifest field (a new env var or OAuth setting) is inert until the catalog sync re-reads the bumped package into the database — the database catalog, not live manifests, is the runtime source.

## Where to go next

<CardGroup cols={2}>
  <Card title="How ModuleX works" icon="diagram-project" href="/concepts/overview">
    The end-to-end mental model, from a prompt or canvas to a running, observable workflow.
  </Card>

  <Card title="Realtime overview" icon="tower-broadcast" href="/realtime/overview">
    The two realtime planes and their event taxonomies in full.
  </Card>

  <Card title="API overview" icon="code" href="/api-reference/overview">
    The request lifecycle, content types, and how every operation is shown three ways.
  </Card>

  <Card title="Base URLs & environments" icon="globe" href="/get-started/environments">
    Hosts, environments, and the no-`/v1` versioning policy.
  </Card>
</CardGroup>
