Skip to content
Get Started

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.

Docker Compose

One command, four containers. The path the Quickstart takes.

Local Python

Run the engine from source, keep the database and cache in Docker.

PyPI package

pip install mirobody and embed it in your own service.

RequirementNeeded forNotes
Python ≥ 3.12Local development, PyPI packagerequires-python = ">=3.12". The Docker image brings its own interpreter.
Docker + Docker ComposeAll three pathsEven the local-Python path uses Docker for PostgreSQL and Redis.
Git + Git LFSCloning the repositorymirobody/res/ holds the terminology bundles and indicator resources as LFS objects. Run git lfs install once before cloning.
Terminal window
git lfs install
git clone https://github.com/thetahealth/mirobody.git
cd mirobody
./deploy.sh

deploy.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:

ServiceImageHost portRole
pgpgvector/pgvector:pg17-trixie18082 → 5432PostgreSQL with pgvector
redisredis:7.0-alpine18089 → 6379Cache and task queues
mirobodybuilt locally18080HTTP server: python -m mirobody serve
mirobody_workersame imageBackground 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.

Terminal window
docker compose ps # what is running
docker compose logs -f mirobody # server log
docker compose restart mirobody mirobody_worker # pick up a config change
docker compose down # stop everything

On 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.

Run the engine from source while PostgreSQL and Redis stay in Docker.

Start the backing services

Terminal window
docker compose up -d pg redis

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 '.[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.

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

Point 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.

config.localdb.yaml
HTTP_PORT: 18080
PG_HOST: 127.0.0.1
PG_PORT: 18082
REDIS_HOST: 127.0.0.1
REDIS_PORT: 18089

Run

Terminal window
mirobody serve # HTTP server
mirobody worker # background worker, in a second terminal

python -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.

ExtraInstallPulls in
serverpip install -e ".[server]"FastAPI, uvicorn, psycopg, SQLAlchemy, Redis, aioboto3, WebAuthn, email — the HTTP surface
agentspip install -e ".[agents]"[server] plus LangChain, deepagents, langchain-quickjs, the LangGraph Postgres checkpointer
cnpip install -e ".[cn]"Aliyun OSS (oss2) and the Volcengine Ark SDK
testpip install -e ".[test]"pytest, pytest-asyncio, pytest-sugar, import-linter; see Development Setup
indicator-buildpip install -e ".[indicator-build]"Rebuilding the terminology bundles themselves; consumers of the bundles need none of it

The engine is published to PyPI as mirobody:

Terminal window
pip install mirobody

This 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:

app.py
import asyncio
from 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.

The package layout is the three stages, plus the infrastructure they stand on.

Health check

Terminal window
curl http://localhost:18080/api/health

Returns JSON with the service name and version plus the number of tools, resources and agents discovered at startup.

MCP discovery

Terminal window
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.

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.