Skip to content
Get Started

Building on Mirobody

Development Setup

Run the Mirobody Python engine from a source checkout: venv, editable install, Postgres and Redis in Docker, and the test suites.

Mirobody is a Python service (requires-python = ">=3.12"). Developing on it means creating a virtual environment, installing the package in editable mode, starting Postgres and Redis with Compose, and running mirobody serve. There is no compile step.

Python ≥ 3.12

pyproject.toml declares requires-python = ">=3.12"

Docker + Compose

For the Postgres (pgvector/pgvector:0.8.6-pg17-trixie) and Redis (redis:8.2-alpine) containers

Git + Git LFS

mirobody/res/*.bin, *.npz, *.npy and *.gz are LFS objects

Clone the repository

Terminal window
git clone https://github.com/thetahealth/mirobody.git
cd mirobody

Start Postgres and Redis

compose.yaml defines four services — pg, redis, mirobody and mirobody_worker. For local development you only want the two backing stores; the application runs on your host:

Terminal window
docker compose up -d pg redis

They publish 18062 → Postgres and 18069 → Redis, on loopback only.

Create a virtual environment and install

Terminal window
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install --upgrade pip
pip install -e .

pip install -e . gets the engine — ① Collect and ② Standardize as a library, with no database driver and no HTTP server. Working on the chat server or the agents means the [agents] extra, which pulls [server] in with it:

Terminal window
pip install -e '.[agents,test]' # what you want for development
pip install -e '.[cn]' # Aliyun OSS + Volcengine Ark
pip install -e '.[indicator-build]' # only to regenerate the terminology bundles

There is no Node.js dependency anywhere in the repository.

Write .env and your config override

.env carries three variables: which config file to load, the key that encrypts sensitive config values at rest, and the key that encrypts sensitive log fields.

Terminal window
echo "ENV=localdb" > .env
echo "CONFIG_ENCRYPTION_KEY=$(openssl rand -hex 16)" >> .env
echo "LOG_ENCRYPTION_KEY=$(openssl rand -hex 16)" >> .env

config.yaml is the read-only template shipped with the repository. Your edits go into config.<ENV>.yaml — with ENV=localdb, that is config.localdb.yaml:

config.localdb.yaml
JWT_KEY: 'a 32-byte random string'
EMAIL_PREDEFINE_CODES:
caregiver@mirobody.ai: '111111'
OPENROUTER_API_KEY: 'sk-or-...'

Any key whose name contains _KEY, _PASSWORD, _PASS, _PWD, _SECRET, _SK or _TOKEN is encrypted automatically on first load, using CONFIG_ENCRYPTION_KEY. The full key list is in Configuration.

Point the app at the published ports

config.yaml ships the container-network addresses (PG_HOST: 10.108.0.2, REDIS_HOST: 10.108.0.9) because that is where the app container finds them. A process running on your host reaches the same containers through the published ports instead, so override them:

config.localdb.yaml
HTTP_PORT: 18060
PG_HOST: localhost
PG_PORT: 18062
REDIS_HOST: localhost
REDIS_PORT: 18069

Run

Terminal window
mirobody serve

The server listens on http://localhost:18060 with the override above (without HTTP_PORT, a source run falls back to 80). Log in with caregiver@mirobody.ai and the code you put in EMAIL_PREDEFINE_CODES.

Background work — the IndicatorSync and ProfileRefresh task queues — runs in a second process, not in the web server:

Terminal window
mirobody worker

Five config keys tell the engine where to look for your own code. Each is a list of directories shipping exactly one entry — the packaged one. Add your own and list it first, so it is scanned ahead of the built-in one:

KeyDefault
MCP_TOOL_DIRSmirobody/agent/tools
MCP_RESOURCE_DIRSmirobody/agent/resources
AGENT_DIRSmirobody/agent
PROVIDER_DIRSmirobody/pulse/providers
SKILL_DIRSmirobody/agent/skills

Because everything is discovered at runtime, adding a tool, agent or provider means adding a file and restarting the process — there is no registry to edit and nothing to rebuild.

Tests live beside the code they cover — the test for mirobody/user/care_circle.py is mirobody/user/test_care_circle.py — and testpaths is set, so the whole suite is bare pytest:

Terminal window
pip install -e '.[agents,test]'
pytest
# passes with no failures, in seconds

No database, no network, no API key. '.[test]' without [agents] is a supported smaller install: the collection step skips the packages whose extras are missing and prints a header naming what was skipped.

The published suite is deliberately the set of tests that are evidence for a public claim, so a clone runs it green in seconds. The core of it:

SuiteCovers
mirobody/test_engine.pygolden LOINC codes — pins the whole chain: alias index → commonness prior → axis table
mirobody/test_engine_coverage.pythe published accuracy number, 211/211, with a coverage floor that fails the build if it drops
mirobody/test_readme_numbers.pyevery figure the four READMEs publish, re-derived from the artifact or code that defines it
mirobody/test_one_key_defaults.pythe one-key promise: the shipped defaults chat, see and embed with a single OPENROUTER_API_KEY or DASHSCOPE_API_KEY
mirobody/user/test_care_circle.py · mirobody/demo/test_member_seed.pythe care-circle isolation and demo seed the front page demonstrates
mirobody/server/test_bootstrap_guard.pythe PRODUCTION / BOOTSTRAP_SCHEMA posture switches
Terminal window
lint-imports # the two import-linter contracts

lint-imports enforces the two import-linter contracts that keep langchain*, deepagents and langgraph out of everything except agent/ and server/ — see The Engine as a Library.

A provider’s tests are ordinary pytest modules in its own package directory, asserting on format_data_v2 with recorded payloads — no server, no database, no network:

Terminal window
pytest mirobody/pulse/providers/mirobody_acme -v

How to structure those tests, and the live verification that complements them, is in Provider Testing.

The annotated tree — packages, the extension directories, and the deployment files — is in Installation. It is the single copy, so this page doesn’t repeat it.