Skip to content
Get Started

The Engine

Architecture Overview

What the Mirobody Python engine is made of: three steps side by side over one shared infrastructure layer and one thin HTTP layer, running as two processes.

Mirobody is a Python engine (3.12 and up). It is made of three parts, one per step: Collect takes data in, Standardize resolves readings to canonical codes, and Answers is the agent working on top of them. All three share one infrastructure layer (accounts, task queue, MCP, configuration), with a thin HTTP layer above.

It runs as two processes over one PostgreSQL database and one Redis: mirobody serve serves HTTP, mirobody worker consumes background queues. All the code lives in one importable package, so a deployment is a config file and one docker compose up — there is nothing to build.

LangChain and its related dependencies are allowed only inside the Answers part, enforced at build time by an import contract. That is why the Collect and Standardize halves installed by pip install mirobody bring numpy as their only third-party package — and why they work as an ordinary library. See The Engine as a Library.

The split is deliberate: an embedding sweep that runs for minutes has no business running in the process that serves HTTP requests.

ProcessCommandWhat it does
HTTP servicemirobody serveLoads configuration, bootstraps the database schema, assembles routes and middleware, and serves on HTTP_HOST:HTTP_PORT.
Background workermirobody workerReads the same configuration and consumes background tasks off Redis: backfilling indicator codes and embeddings, rebuilding the user profile. It listens on no port.

Both need the [agents] extra; without it the command prints one line about what to install and exits. On SIGINT / SIGTERM the worker finishes what it holds before exiting.

In compose.yaml these are two containers: one publishes 18080 and runs the HTTP service, the other publishes no port and runs the worker.

In the order a request crosses them, with the behaviour and the config key each one reads:

  1. Response compression Responses over 10,000 bytes are gzipped.
  2. CORS Read from HTTP_HEADERS. Setting Access-Control-Allow-Origin: * together with credentials logs a warning, because browsers reject that combination.
  3. JWT Verifies the bearer token and resolves the caller (0 when anonymous), and takes language, timezone and trace_id from the headers. Enabled only when JWT_KEY is set.
  4. Rate limiting Counts per user per path per minute; over the threshold it returns 429 with Retry-After. Driven by REQUEST_RATE_LIMITER, default /api/chat: 6 and /api/session: 6.
  5. User preference write-back For the paths listed in USER_INFO_UPDATER, writes the caller's language and timezone back to their user record.

At startup, unless ENV is one of TEST / GRAY / PROD / TEST-INLOCAL, the engine creates the schemas named in the configuration and runs the bundled table scripts. That is the local bootstrap path; production migrations are done by hand.

PartDirectoryWhat is in it
① Collectpulse/Device providers, on-device batch import, file parsing, the normalized write path, daily rollups and the insight engine. See Data Flow.
② Standardizeindicator/The concept graph, embedding-based resolution, UCUM unit families and clinical taxonomies. See Health Indicators.
③ Answersagent/The two agents, the /api/* chat surface, the MCP tool surface, Agent Skills and the prompts. See Agent Types.
Shared infrastructureserver/ · mcp/ · task/ · user/ · utils/HTTP assembly, the MCP service, background tasks, accounts and auth, configuration and database access.

Five config keys are directory-driven: MCP_TOOL_DIRS, AGENT_DIRS, PROVIDER_DIRS, MCP_RESOURCE_DIRS and SKILL_DIRS each name directories scanned at startup. Adding a tool, an agent, a provider or a skill means dropping a file in and restarting — nothing has to be registered anywhere. Each key defaults to the one directory inside the package; to use your own, list it first.

The two route families follow different prefix rules, which matters before you put the engine behind a path-routing proxy:

  • Chat, MCP, login and OAuth routes take the prefix when HTTP_URI_PREFIX is set.
  • Pulse, sharing, files and indicators declare their prefixes themselves and are not affected by HTTP_URI_PREFIX.

The OAuth discovery documents, the SPA fallback, /charts and /mirobody.json are unprefixed either way.

Full route list
AreaRoutes
Health checkGET /api/health
ChatGET /api/models · /api/agents · /api/providers · /api/prompts, POST /api/session, GET /api/history · /api/history_by_person, POST /api/history/delete · /api/rating · /api/chat, GET /api/beneficiary-users
Per-user configurationGET / POST /api/user/mcp, POST /api/user/mcp/set · /api/user/mcp/delete, GET / POST /api/user/prompt, POST /api/user/prompt/set · /api/user/prompt/delete
MCPPOST / GET /mcp, /mcp/{secret}, POST /personal/mcp
OAuth/.well-known/oauth-authorization-server, /.well-known/oauth-authorization-server/mcp, /.well-known/mcp-configuration, /oauth/register · /oauth/authorize · /oauth/token · /oauth/introspect, /oauth2/authorize, /oauth2/check_state/{state}
LoginPOST /email/login · /email/verify · /email/bind, POST /apple/verify · /google/verify, POST /user/del · /user/update_name
WebAuthn/auth/webauthn/{register,login,upgrade}/{options,verify}, /auth/session/renew, /auth/session/reauth/{options,verify}
FilesPOST /files/upload, GET /files/{file_path}, GET /api/v1/data/uploaded-files · /api/v1/data/data-distribution, POST /api/v1/data/delete-files, WebSocket /ws/upload-health-report
IndicatorsGET /api/v1/health-indicators, POST /api/v1/health-indicators/reading
Pulse (public)GET /api/v1/pulse/providers · /user/providers · /user-data-sources · /user/insights · /indicators · /units, POST /api/v1/pulse/user/providers/link · /unlink, POST /api/v1/pulse/{platform}/webhook · /{platform}/token, GET /api/v1/pulse/{platform}/{provider}/callback
On-device batchesPOST /apple/health · /apple/statistics · /apple/cda — also mounted under /api/v1/pulse/apple/*, since an uploader may point at either
Pulse (management)/api/v1/manage/pulse/* (indicators, units, user-health-data, monitor, insight, data-quality), /api/v1/manage/theta/pull/*, POST /api/v1/manage/aggregate/recalculate-range
SharingPOST /api/share/create, GET /api/share/{share_session_id}, /invitation/shared-by-me/*, /invitation/shared-with-me/*, /invitation/permissions/list
User settingsGET / POST /api/user/settings, POST /api/user/virtual
Web clientAnything unmatched above and outside the backend’s own prefixes falls back to the SPA shell; /mirobody.json, /__/auth/init.json and /charts are registered explicitly

The SPA fallback returns 404 for unmatched GETs under /api, /mcp, /oauth, /oauth2, /invitation, /apple, /google, /email, /personal, /auth/session, /auth/webauthn and /.well-known rather than the shell: a mistyped backend path is an error, not a client deep link.

  • Database: PostgreSQL with the vector, pg_trgm and pgcrypto extensions, default schema theta_ai. The compose file pins pgvector/pgvector:pg17-trixie and publishes 18082:5432. Connection keys are PG_HOST / PG_PORT / PG_USER / PG_PASSWORD / PG_DBNAME / PG_SCHEMA / PG_MIN_CONNECTION / PG_MAX_CONNECTION.
  • Cache and queues: Redis (redis:7.0-alpine, published on 18089:6379). It backs rate limiting, the background task queues and the locks provider pulls take.
  • Object storage: a configured cloud backend when there is one, otherwise local disk. The S3 backend reads S3_KEY / S3_TOKEN / S3_REGION / S3_BUCKET / S3_PREFIX / S3_CDN.
  • Configuration: three layers — the repository template config.yaml, your own config.{env}.yaml, and .env (ENV + CONFIG_ENCRYPTION_KEY). Environment variables win over both YAML files. CONFIG_SERVER + CONFIG_TOKEN point at a remote config service. See Configuration.
  • Auth: multi-user JWT, an OAuth 2.0 authorization server with discovery documents for MCP clients, email one-time codes, Apple and Google sign-in, and WebAuthn.
  • Web client: a prebuilt SPA served from HTTP_ROOT, read from disk per request, so replacing the build needs no restart. It sits outside the Python package on purpose: the wheel installs the engine, not the JavaScript. Its pages are /data (documents and readings) and /ask (the agent), plus Care Circle sharing.