Getting Started
Installation
Three ways to install the Mirobody Python engine: Docker Compose, a local Python environment, or the PyPI package.
Mirobody is a Python application: a Starlette/FastAPI HTTP server plus a background worker, on PostgreSQL (with pgvector) and Redis. There are three ways to install it.
One command, four containers. The path the Quickstart takes.
Run the engine from source, keep the database and cache in Docker.
pip install mirobody and embed it in your own service.
Prerequisites
Section titled “Prerequisites”| Requirement | Needed for | Notes |
|---|---|---|
| Python ≥ 3.12 | Local development, PyPI package | requires-python = ">=3.12". The Docker image brings its own interpreter. |
| Docker + Docker Compose | All three paths | Even the local-Python path uses Docker for PostgreSQL and Redis. |
| Git + Git LFS | Cloning the repository | mirobody/res/ holds the terminology bundles and indicator resources as LFS objects. Run git lfs install once before cloning. |
Docker Compose
Section titled “Docker Compose”git lfs installgit clone https://github.com/thetahealth/mirobody.gitcd mirobody./deploy.shdeploy.sh does four things, all idempotent (existing files and an unchanged image are left alone):
Creates .env
ENV (defaults to localdb) and a generated 32-character CONFIG_ENCRYPTION_KEY.
Creates config.{env}.yaml
Seeded with a random JWT_KEY, the demo login codes, and commented placeholders for the LLM keys and MCP_PUBLIC_URL.
Builds the image
An inline Dockerfile on ubuntu:24.04 with a Python virtualenv (there is no Dockerfile in the repository, and no Node.js — the engine has no JavaScript dependency at runtime). If hub.docker.com is unreachable, images come from the docker.1ms.run mirror.
Starts the stack
Brings the previous stack down, frees ports 18080 / 18082 / 18089, then docker compose up -d --remove-orphans and tails the logs.
Four services come up, defined in compose.yaml:
| Service | Image | Host port | Role |
|---|---|---|---|
pg | pgvector/pgvector:pg17-trixie | 18082 → 5432 | PostgreSQL with pgvector |
redis | redis:7.0-alpine | 18089 → 6379 | Cache and task queues |
mirobody | built locally | 18080 | HTTP server: python -m mirobody serve |
mirobody_worker | same image | — | Background tasks: python -m mirobody worker |
The containers sit on a fixed bridge network, 10.108.0.0/24, which is why config.yaml ships PG_HOST: 10.108.0.2 and REDIS_HOST: 10.108.0.9. The repository is bind-mounted into /app, so editing a .py file or config.{env}.yaml on the host and restarting the container is enough; no rebuild.
docker compose ps # what is runningdocker compose logs -f mirobody # server logdocker compose restart mirobody mirobody_worker # pick up a config changedocker compose down # stop everythingOn the first start against an empty database the server creates the schema and applies the SQL under mirobody/schema/ itself; there is no separate migration step.
Local Python development
Section titled “Local Python development”Run the engine from source while PostgreSQL and Redis stay in Docker.
Start the backing services
docker compose up -d pg redisCreate a virtual environment and install
python3 -m venv venvsource venv/bin/activate # Windows: venv\Scripts\activate
pip install --upgrade pippip install -e '.[agents]' # engine only: pip install -e .pip install -e . gets the engine: ① Collect and ② Standardize as a library. The chat server, the MCP endpoint and the agents live in the [agents] extra, which pulls [server] in with it. See The Engine as a Library.
Write .env
ENV must be set; the server reads it on startup to pick the config file.
echo "ENV=localdb" > .envecho "CONFIG_ENCRYPTION_KEY=$(openssl rand -hex 16)" >> .envPoint the config at the published ports
The defaults in config.yaml are the container addresses on the compose network. A process on the host reaches the same services through the published ports, and needs a port of its own: outside Docker, HTTP_PORT falls back to 80.
HTTP_PORT: 18080
PG_HOST: 127.0.0.1PG_PORT: 18082
REDIS_HOST: 127.0.0.1REDIS_PORT: 18089Run
mirobody serve # HTTP servermirobody worker # background worker, in a second terminalpython -m mirobody serve is the same thing, and is what the containers run.
Both commands take config filenames as arguments; with none, they fall back to config.yaml plus config.{env}.yaml from the working directory. Without the [agents] extra they exit with a one-line message naming what to install, rather than a ModuleNotFoundError from deep inside an import chain.
Optional extras
Section titled “Optional extras”| Extra | Install | Pulls in |
|---|---|---|
server | pip install -e ".[server]" | FastAPI, uvicorn, psycopg, SQLAlchemy, Redis, aioboto3, WebAuthn, email — the HTTP surface |
agents | pip install -e ".[agents]" | [server] plus LangChain, deepagents, langchain-quickjs, the LangGraph Postgres checkpointer |
cn | pip install -e ".[cn]" | Aliyun OSS (oss2) and the Volcengine Ark SDK |
test | pip install -e ".[test]" | pytest, pytest-asyncio, pytest-sugar, import-linter; see Development Setup |
indicator-build | pip install -e ".[indicator-build]" | Rebuilding the terminology bundles themselves; consumers of the bundles need none of it |
PyPI package
Section titled “PyPI package”The engine is published to PyPI as mirobody:
pip install mirobodyThis gives you the importable package, not the repository. compose.yaml and the config.yaml template live at the repository root and are not part of the wheel, so you supply your own config files in the working directory. The wheel does carry a CLI (mirobody serve), and Server.start is there when you want to mount your own routers alongside the built-in ones:
import asynciofrom mirobody.server import Server
async def main(): # Your own FastAPI routers can be mounted alongside the built-in ones. await Server.start(yaml_files=["config.yaml"], fastapi_routers=[])
asyncio.run(main())The worker has the same shape, from mirobody.server import Worker and Worker.start(...). ENV still has to be in the environment before either one starts.
Repository layout
Section titled “Repository layout”The package layout is the three stages, plus the infrastructure they stand on.
resolve() offline, parse_file() in one call mirobody parse | resolve | serve | worker mirobody_garmin_connect · mirobody_oura · mirobody_whoop · mirobody_pgsql · platform/ StandardPulseData — the universal exchange format /mcp SKILL.md) server/routers/) Verify the installation
Section titled “Verify the installation”Health check
curl http://localhost:18080/api/healthReturns JSON with the service name and version plus the number of tools, resources and agents discovered at startup.
MCP discovery
curl -X POST http://localhost:18080/mcp \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Lists the registered tools with their JSON schemas.
Logs
Under Docker, docker compose logs -f mirobody. Running from source, logs go to the console; set LOG_NAME and LOG_DIR to write files instead.
Troubleshooting
Section titled “Troubleshooting”Startup fails on a resource file in mirobody/res/
Git LFS was missing or not initialised when you cloned, so those files are text pointers. Run git lfs install and then git lfs pull in the repository.
Port 18080, 18082 or 18089 already in use
deploy.sh stops containers publishing those ports before it starts, but a non-Docker process holding one is not touched. Free the port, or change the mapping in compose.yaml and HTTP_PORT.
Running from source, the server listens on port 80
HTTP_PORT is commented out in config.yaml, and the fallback is 80. The Docker path sets it through the container environment; from source, set HTTP_PORT in your config.{env}.yaml.
Database connection refused from a local Python run
PG_HOST: 10.108.0.2 and REDIS_HOST: 10.108.0.9 are addresses on the compose bridge network. From the host, use 127.0.0.1 with the published ports 18082 and 18089.
ENV is not set
The server reads ENV on startup to choose config.{env}.yaml. deploy.sh writes it into .env; if you are running from source or from the PyPI package, create that file or export the variable yourself.
Next steps
Section titled “Next steps”The three config layers, what gets encrypted, and the key groups.
The short path from clone to signed-in.
How the pieces above fit together at runtime.
Working on the engine itself.