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:pg17-trixie) and Redis (redis:7.0-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 18082 → Postgres and 18089 → Redis on the host.

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 exactly two variables: which config file to load, and the key used to encrypt sensitive values at rest.

Terminal window
echo "ENV=localdb" > .env
echo "CONFIG_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:
exp1@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
PG_HOST: localhost
PG_PORT: 18082
REDIS_HOST: localhost
REDIS_PORT: 18089

Run

Terminal window
mirobody serve

The server listens on http://localhost:18080. Log in with exp1@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/mcp/service.py is mirobody/mcp/test_protocol.py — and testpaths is set, so the whole suite is bare pytest:

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

No database, no network, no API key. '.[test]' without [agents] is a supported smaller install: it runs the engine tests and prints a header saying the agent-layer tests were skipped.

Two suites carry the project’s public claims, and they are the ones to run after touching either half of ② Standardize:

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, 116/116, with a coverage floor that fails the build if it drops
mirobody/mcp/test_protocol.pyMCP wire behaviour: version negotiation, resultType, server/discover
mirobody/pulse/gate_tests/one snapshot per vendor payload → StandardPulseData
mirobody/pulse/aggregate/daily rollups, CGM indicators, source priority — the only suite that touches config
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.

The provider pipeline’s acceptance suite at mirobody/pulse/gate_tests/ replays recorded vendor payloads through format_data() and compares the resulting StandardPulseData against stored snapshots — no server, no database, no network:

Terminal window
pytest mirobody/pulse/gate_tests -v
pytest mirobody/pulse/gate_tests --update-snapshots # after an intentional shape change

Details, including how to add a case, are 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.