Async human-agent collaboration. Spin up AI agents, step away, check in from your phone.
Mai-Tai is a self-hosted platform that lets you launch AI coding agents as Docker containers, communicate with them via a mobile-first web UI, and stay in the loop from anywhere — no IDE required.
- Spin up agents — create a workspace, pick a template (research, coding, assistant), and Mai-Tai spawns a Docker container running Claude Code connected to your workspace
- Talk to your agents — real-time WebSocket messaging from any device, including mobile
- Step away — agents run autonomously in Docker, sending you updates and asking questions via Mai-Tai when they need you
- Persistent memory — agents remember lessons learned across restarts via a per-workspace mounted volume
- Stash — save and organize links with AI enrichment and
#NNNNissue numbers
Prerequisites: Docker, Docker Compose, Git, Python 3.11+, and
uv
(curl -LsSf https://astral.sh/uv/install.sh | sh).
git clone https://github.com/jmcdice/mai-tai-dev.git && cd mai-tai-dev
uv tool install ./cli
mai-tai initRun init from inside the clone — it locates the repo by walking up from your
working directory. pipx install ./cli works too; a bare pip install usually
does not, because most distributions now ship Python as
externally managed and refuse to install
into it.
init walks the whole path from a bare clone to an agent that talks back: it
starts the stack, builds the agent image, creates your admin account, wires up
the API key the backend needs to spawn containers, and launches a first
Supervisor workspace. It's idempotent — every step it finds already done, it
skips — so it's safe to re-run on a half-finished install.
Then visit http://localhost:3000, log in with the account you just made, and say hi to the Supervisor.
Don't run
cp .env.example .envyourself.dev.shonly generates secrets when.envis missing; hand-copying the example leavesPOSTGRES_PASSWORDempty and the stack refuses to start.
On Vertex AI? Pass --vertex-project <gcp-project> and agents authenticate
off the host's gcloud ADC instead of an API key. Add --model if your project
only has some models enabled — the default is the runtime's, and an agent whose
model is not enabled will connect and greet you before failing its first real
turn. See cli/README.md.
Doing it by hand instead
./dev.sh local up # generates .env secrets, migrates the DB
docker build -t mai-tai-agent:latest -f agents/claude-code/Dockerfile .Then, in the web UI, register an account and copy the API key shown once at registration. The backend reads that key off the host to authenticate the agent containers it spawns, so it has to land here:
mkdir -p ~/.config/mai-tai
cat > ~/.config/mai-tai/config <<EOF
MAI_TAI_API_KEY=mt_your_key_here
MAI_TAI_API_URL=http://localhost:8000
EOF
chmod 600 ~/.config/mai-tai/configMiss this step and every Start Agent click fails with "No Mai-Tai API key
available." Create the directory before ./dev.sh local up — compose
bind-mounts it, and Docker will otherwise create it for you as root.
Finally, give the agents a model credential: either set ANTHROPIC_API_KEY in
Settings → AI, or run on Vertex (CLAUDE_CODE_USE_VERTEX=1), in which case
containers fall back to the host's gcloud ADC automatically.
The core feature. Create an agent workspace, pick a template, and Mai-Tai launches a Docker container running Claude Code connected to your workspace via MCP.
| Template | Description |
|---|---|
| Research | Searches the web, compiles findings, sends reports |
| Coding Agent | Clones a GitHub repo, writes code, opens PRs |
| Personal Assistant | General tasks, daily questions, follow-ups |
| Monitor | Periodic checks, alerts on changes |
| Custom | Your own system prompt and behavior |
You (mobile/browser)
│
▼
Mai-Tai Web UI ──WebSocket──▶ Backend (FastAPI) ──▶ PostgreSQL
│
Docker Socket
│
▼
┌─────────────────────┐
│ Agent Container │
│ (Claude Code + MCP) │
│ /home/agent/memory/ │ ← persistent volume
└─────────────────────┘
Each agent container:
- Runs Claude Code in headless mai-tai mode
- Connects to your workspace via the MCP server
- Has a persistent volume at
/home/agent/memory/for lessons and task notes - Gets template-specific
CLAUDE.mdwith working principles baked in
- Create a workspace → select Coding Agent template
- Enter your GitHub repo URL
- Add a GitHub PAT in Settings → AI tab
- Start the agent — it clones the repo and gets to work
For Claude Code sessions running directly on a machine (not in a Docker container):
# Global credentials (~/.config/mai-tai/config)
MAI_TAI_API_URL=http://localhost:8000
MAI_TAI_API_KEY=mt_your_key_here
# Per-project workspace (.env.mai-tai in project root)
MAI_TAI_WORKSPACE_ID=your-workspace-uuid- Docker-per-agent — each workspace gets its own isolated container
- Persistent memory — agents learn from corrections, lessons survive restarts
- Mobile-first — designed for checking in from your phone
- Real-time — WebSocket-powered live updates
- Multi-workspace — separate workspaces per project/agent
- Auth — email/password + optional OAuth (GitHub, Google)
- Admin panel — user management, registration toggle, impersonation
- Stash — save links with AI enrichment and
#NNNNissue tracking - Self-hosted — runs entirely on your machine, no data leaves your network
┌─────────────────────────────────────────────────┐
│ Mai-Tai Platform │
│ │
│ Frontend (Next.js) ◀──▶ Backend (FastAPI) │
│ │ │ │
│ │ WebSocket PostgreSQL │
│ │ │ │
│ │ Docker Socket │
│ │ │ │
│ │ Agent Containers │
│ │ (Claude Code + MCP) │
└─────────────────────────────────────────────────┘
▲
│
You (anywhere)
./dev.sh local up # Start everything
./dev.sh local logs # View logs
./dev.sh local rebuild # Rebuild after code changes
./dev.sh local migrate # Run database migrations
./dev.sh local down # Stop everything
./dev.sh local nuke-db # Wipe database and start freshSee .env.example for all options. Key settings:
| Variable | Description |
|---|---|
SECRET_KEY |
JWT signing key (required, change in production) |
NEXTAUTH_SECRET |
NextAuth session encryption |
CORS_ORIGINS |
JSON array of allowed origins |
REGISTRATION_ENABLED |
Set false to disable new signups (also toggleable in admin UI) |
AGENT_IMAGE |
Docker image for agent containers (default: mai-tai-agent:latest) |
AGENT_MODEL |
Model agents run (default: sonnet) |
CLAUDE_CODE_USE_VERTEX |
Set 1 to auth agents via Google Vertex AI instead of an Anthropic key |
ANTHROPIC_VERTEX_PROJECT_ID |
GCP project for Vertex (required when Vertex is on) |
CLOUD_ML_REGION |
Vertex region (default: global) |
GITHUB_CLIENT_ID/SECRET |
GitHub OAuth (optional) |
GOOGLE_CLIENT_ID/SECRET |
Google OAuth (optional) |
With Vertex enabled, the backend reads the host's Application Default
Credentials (~/.config/gcloud, mounted read-only) and passes them to each
agent container — no per-user Anthropic key needed. Run gcloud auth application-default login on the host first.
Build from the repo root, not agents/ — the images copy mcp-server/
into the container, so it has to be inside the build context.
# Claude Code runtime (required for agent workspaces)
docker build -t mai-tai-agent:latest -f agents/claude-code/Dockerfile .
# OpenAI Codex runtime (optional — for workspaces using the codex runtime)
docker build -t mai-tai-agent-codex:latest -f agents/codex/Dockerfile .Rebuild after changes to agents/.
Agent containers run a driver loop: each user message triggers one CLI
turn (claude -p --resume), so there is no long-lived agent process to babysit.
Persistent memory lives on the per-workspace volume: MEMORY.md (curated,
size-capped, loaded every session), journal/ (daily notes), and
tasks/lessons.md — plus a search_history tool backed by Postgres full-text
search over the workspace's entire message history.
Tools the agent installs for itself land there too. uv tool install is
redirected to memory/tools/, so a CLI an agent needed once is still there
after a restart — which matters most for scheduled tasks, since a job that
depended on a vanished install fails silently at 5am. The container is
otherwise immutable: the agent is not root, there is no sudo, and apt-get
will not work. memory/tools/bin is appended to PATH rather than prepended,
so a self-installed binary can't shadow python3 or git and survive a
restart doing it.
mai-tai config (the admin CLI) moves a whole deployment —
users, workspaces, agents, and full message history — to another machine.
# On the source host
mai-tai config export mai-tai-backup.tar.gz
mai-tai config inspect mai-tai-backup.tar.gz # peek without restoring
# On the target host
mai-tai config check-env # what's missing from .env
mai-tai config import mai-tai-backup.tar.gz # prompts before wipingBy default the dump is byte-for-byte complete, so the credentials in
users.settings (Anthropic/OpenAI keys, GitHub token, LLM keys) travel with
it. That's deliberate — the target comes up as a working copy with nothing to
re-enter. The tradeoff is that the tarball is credential material. It's
written mode 0600, mai-tai-export-*.tar.gz is gitignored, and you should
delete it once the move is done.
Those settings are Fernet-encrypted at rest, but the key comes from
ENCRYPTION_KEY — or, when that's unset, from SECRET_KEY — so the target
needs the same values or the restored credentials decrypt to nothing. Import
fingerprints both ends and warns on a mismatch before it touches the database.
| Flag | Effect |
|---|---|
| (none) | Full fidelity — credentials included, nothing to re-enter on the target |
--scrub |
Strip credentials from users.settings; you re-enter keys in Settings → AI |
--with-env |
Also bundle .env. Import writes it to .env.imported for review rather than overwriting |
--scrub is not the same as "safe to share." It strips users.settings
and nothing else. Message history is not scrubbed — agents paste keys into
chat and operators paste them back — so every export scans the dump for
credential-shaped strings and tells you what it found. Take that count
seriously; on this repo's own database it found three.
Password hashes and mt_ API-key hashes are always included, so logins and
existing agent configs keep working on the target.
A restored clone is live. Schedules come across enabled, so on the next
tick the target starts waking agents and doing real work — sending messages,
reaching the LAN — from a host that was only ever meant to be a copy. Import
warns when the bundle carries enabled schedules; mai-tai config import --disable-schedules turns them all off as part of the restore.
Either way, copy ~/.config/mai-tai/config across so existing mt_ API keys
still authenticate — and fix its MAI_TAI_API_URL to point at the target.
The bundle is the database plus (optionally) .env. Everything below is
source-host specific and has to be handled on the target by hand:
- The host's address, baked into
.env.NEXT_PUBLIC_API_URL,NEXT_PUBLIC_WS_URL, andNEXTAUTH_URLare compiled into the frontend bundle. Left alone, the target's UI loads fine and then quietly talks to the source host. Rewrite them before./dev.sh local up. - The agent image.
mai-tai-agent:latestis built locally, so a fresh clone has none — agent workspaces fail to start until you build it (see Building the Agent Image). - TLS and LAN DNS.
caddy/certs/is untracked andinfra/dnsmasq.confhardcodes the source host's IP, so both services crash-loop on a new host. Unless you're serving the same domain from the target, leave them stopped:docker compose stop caddy dnsmasq.
Contributions welcome! See CONTRIBUTING.md for guidelines.
MIT — see LICENSE for details.