---
title: Architecture
path: concepts/architecture
status: published
---

# Architecture

```mermaid
flowchart TB
  Canvas["Canvas (SolidJS + SVG)<br/><code>apps/canvas</code><br/>drag/drop, property panels, live preview,<br/>CRDT real-time editing, debugger overlay"]
  API["ScaiFlow API (FastAPI)<br/><code>apps/api</code><br/>/v1/flows, /v1/admin, /v1/catalog,<br/>/v1/scaicore proxy, /v1/scaiqueue,<br/>/v1/scaibunker, /v1/scaigrid/models"]
  DB[("DB<br/>MariaDB Galera<br/>flow metadata<br/>(alembic-managed)")]
  Store[("Object store<br/>filesystem or S3<br/>per-version flow JSON")]
  ScaiGrid["ScaiGrid (control plane)<br/>POST /v1/modules/scaicore/cores<br/>POST /v1/modules/.../{id}/publish<br/>GET  /v1/modules/scaicore/events"]
  ScaiCore["ScaiCore<br/>runtime that actually executes invocations"]

  Canvas -->|ScaiKey JWT via BFF<br/>/v1/auth/exchange| API
  API --> DB
  API --> Store
  API -->|ScaiCore YAML manifest| ScaiGrid
  ScaiGrid --> ScaiCore
```

## Pieces

### Canvas (`apps/canvas`)

A SolidJS single-page app. Hand-written SVG renderer (no `xyflow`, no `reactflow`). Edits run through `flowStore`, which keeps the canonical `FlowGraph` JSON in memory; persistence is autosave-to-localStorage plus an explicit Save to the backend.

When ScaiKey OIDC is configured, the canvas authenticates via PKCE and exchanges the auth code for tokens through the backend's BFF (`POST /v1/auth/exchange`). A dev-token fallback exists for environments without ScaiKey.

### ScaiFlow API (`apps/api`)

FastAPI backend exposing `/v1/*`. Owns:

- **Flow CRUD + deploy** (`/v1/flows`) — list, get, create, update, delete, deploy, run-tests, list versions.
- **Auth** (`/v1/auth/*`) — runtime ScaiKey config, BFF token exchange + refresh, `/v1/me`.
- **Admin** (`/v1/admin/*`) — ScaiKey-driven user/group sync, super_admin role grants, tenant flow visibility for tenant admins.
- **Catalog** (`/v1/catalog/*`) — per-tenant private flow packages.
- **Lookups** (`/v1/scaiqueue/*`, `/v1/scaibunker/*`, `/v1/scaigrid/*`, `/v1/scaicore/*`) — read-only proxies into ScaiGrid so the canvas never holds tenant API keys.
- **Webhooks** (`/v1/webhooks/scaikey`) — ScaiKey-signed events that update the local user/group mirror.
- **CRDT relay** (`/v1/flows/{id}/sync` WebSocket) — Yjs document fan-out for real-time editing.

Persistence is split: SQLAlchemy async on MariaDB for metadata (Alembic-managed schema), filesystem or S3 for the actual flow JSON content (keyed by `flows/{tenant_id}/{flow_id}/v{version}.flow.json`, never updated in place).

### Compiler (`apps/compiler`)

A Python library, not a service. `compile_flow(flow_json, output_format="yaml")` turns flow JSON into either:

- A **YAML manifest** for ScaiGrid (`POST /v1/modules/scaicore/cores`) — the default, the deploy artifact.
- A **SCIR MessagePack IR bundle** (`output_format="ir"`) — the upstream-canonical binary form, useful for verification and direct-runtime experiments.
- A `.scai` **text source** (`output_format="scai"`) — deprecated, mostly for inspection.

The same compiler runs in the API backend (for the live preview + deploy paths) and in the standalone CLI.

### ScaiGrid (control plane)

External — see the [ScaiGrid docs](/docs/scaigrid). Accepts the YAML manifest, persists the Core, can optionally publish it as a chat model. ScaiFlow talks to ScaiGrid via the `scaigrid-client` SDK.

### ScaiCore (runtime)

External — see the [ScaiCore docs](/docs/scaicore). Actually executes invocations against the Core that ScaiGrid persisted. ScaiFlow doesn't talk to ScaiCore directly; the deployed Core does the runtime work.

## Auth flow at a glance

```mermaid
sequenceDiagram
  participant Canvas as Canvas (browser)
  participant API as ScaiFlow API (BFF)
  participant ScaiKey
  participant ScaiGrid

  Canvas->>API: 1. PKCE code + verifier
  API->>ScaiKey: 2. add client_secret, exchange
  ScaiKey-->>API: access_token + refresh_token
  API-->>Canvas: 3. user JWT (aud = client_id)
  Canvas->>API: 4. bearer on every subsequent call
  API->>ScaiGrid: forward user JWT
  Note over API,ScaiGrid: Background jobs fall back to<br/>per-tenant API key from ScaiVault
```

The single ScaiKey client_id is shared by the canvas (browser) and the backend. The backend keeps the client_secret server-side; the canvas hands the auth code over for exchange.

ScaiGrid accepts the user JWT directly (its audience is the ScaiKey `client_id`, which ScaiGrid trusts), so the same token flows end-to-end. For non-user contexts (cron jobs, background sync), the backend falls back to a per-tenant `sgk_*` API key stored in ScaiVault under `tenants/{tenant_id}/scaigrid_api_key`.
