On this page

Architecture and data

Services, state locations, and the path from an uploaded file to a queryable log.

Services#

ServiceTechnologyRolePort
apiPython 3.12, FastAPIRoutes, module loader, jobs, ingest, MATE AI proxy, MCP server8000
webNext.js 15, React 19Interface, plus esbuild bundling of module frontends3000
keycloakKeycloak 26OIDC login, realms, roles8080
app-dbPostgres 16Application metadata — the only database the platform readsinternal
keycloak-dbPostgres 16Keycloak's own storeinternal
neo4jNeo4j 5Sidecar for graph-backed modules, behind the graph compose profileloopback

A host development API runs on SQLite instead, so make dev and the test suite need no database server.

Browser
Next.js server componentscookie-backed sessionDirect API callsNEXT_PUBLIC_API_URL, CORS, Bearer token
webrenders the interface, bundles module frontends
apiroutes, modules, jobs, ingest, MCP
app-dbusers, logs, jobs, settings, sharesParquetevent logs and module results under data/DuckDBin-process SQL over Parquet
Outside the host, only when configured
Keycloaklogin and JWKS token validationLLM providersMATE AI and module AI featuresS3-compatible bucketSTORAGE_MODE=s3
How a request travels. Two client paths into one API. The API owns the stores, and reaches outside the host only for what you configured.

Repository layout#

Repository
mate/
├── apps/api/                # FastAPI backend (src/mate/api)
├── apps/web/                # Next.js frontend (app/, components/, lib/)
├── modules/                 # bundled modules – discovered at startup
├── packages/
│   ├── module-sdk-py/       # Python SDK (mate.sdk)
│   ├── module-sdk-ts/       # frontend SDK for panels and widgets
│   ├── module-sdk-jvm/      # Java SDK
│   └── shared-types/        # generated TypeScript types
├── data/                    # bind-mounted state
├── docs/                    # design spec, deploy runbook, MCP reference
├── infra/                   # Caddyfile, Keycloak realm and scripts
└── landing/                 # public site and this manual

modules/ sits outside apps/ on purpose: it is the extension point, and a module is authored and shipped on its own. Folder names are arbitrary — the manifest's id is authoritative.

Technology choices#

ConcernChoiceWhy
MetadataPostgres (SQLite in dev and tests)Transactions and concurrency where they matter, zero setup where they do not.
Event dataParquetColumnar, compressed, 5–20× smaller than XES, readable by every Python data tool.
QueriesDuckDBEmbedded SQL over Parquet; no service to run, sub-second aggregation.
Jobsasyncio queue + job rowsNo broker, no extra container, state that survives a restart.
Live updatesServer-Sent EventsThe production proxy chain carries HTTP streaming but drops WebSocket upgrades.
TypesPydantic → OpenAPI → openapi-typescriptOne schema, generated types, no drift.

Metadata tables#

GroupTables
Identity and accessusers (a mirror of the Keycloak subject), api_tokens, user_settings, system_settings, control_policies
Logsprocess_logs, process_folders, watched_folders, watched_folder_files, event_edits
Workjobs (status, progress, payload, parent job)
Modulesmodule_configs, module_installs, module_layouts
Sharingdashboards, teams, team_members, dashboard_shares
Usage (optional)analytics_sessions, analytics_events, plus the OCEL-shaped analytics_objects, analytics_object_relations, analytics_event_objects

Logs, folders, watched folders, and teams use soft deletes: the row stays for audit and cascade bookkeeping while the data is removed.

On-disk layout#

data/
data/
├── users/{user_id}/
│   ├── event_logs/{log_id}/
│   │   ├── meta.json         # source format, ingest stats, detected schema, roles
│   │   ├── events.parquet    # event table, sorted by (case, timestamp)
│   │   ├── cases.parquet     # cached per-case aggregates
│   │   ├── original.*        # the untouched upload, for re-import and export
│   │   └── ocel/             # events, objects, relations, o2o (OCEL only)
│   └── module_results/{log_id}/{module_id}/
├── staging/{user_id}/{token}/     # uploads awaiting confirmation (swept after 2 h)
├── uploaded_modules/{module_id}/  # modules installed by upload
└── uv-python/                     # cached interpreters and wheels

Log ids and job ids are UUID v7: time-ordered, so they sort and index well without coordination. Display names never appear in URLs, so renaming never breaks a link.

The import pipeline#

StageModuleDoes
Detectingest/detect.pyFormat family from the extension, refined by content sniffing (including OCEL vs case-centric).
Decompressingest/compression.pygzip, bzip2, xz, zip.
Stageingest/staging.pyBytes land under data/staging/{uid}/{token}/ so probing and confirming never re-upload.
Probeingest/probe.pyA bounded sample proposes column roles.
Parseingest/{xes,csv_parser,xml_parser,json_parser,ocel}.pyFormat-specific parsing into one frame; XES streams.
Map · coerce · aggregatemapping.py, parquet_coerce.py, aggregation.pyApply roles, type the columns, normalise timestamps, compute case aggregates.
Store · dispatchstorage.py, dispatch.pyWrite the Parquet set, bump the log's data revision, flip the status to ready, emit log.imported.

Automatic repairs (duplicate columns, text that should be numeric, mixed timestamp formats) are recorded and reported rather than rejected.

Storage modes#

ModeBehaviour
local (default)One copy of everything under data/; the directory is the backup.
s3An S3-compatible bucket is authoritative and local disk is a bounded cache with an eviction reaper.

Details, including migration and quota: Backup, storage and limits.