OpenMAIC (THU-MAIC/OpenMAIC, MIT licensed, the top trending repo on GitHub) turns "one prompt in, a whole course out" into an open-source multi-agent interactive classroom: an AI teacher lectures, AI classmates discuss, the whiteboard draws derivations, quizzes run live, and project-based learning (PBL) keeps learners hands-on. For engineering teams the value is neutrality and self-hosting — you bring your own models, storage, and deployment. But "it runs" and "it is production-ready" differ by a full procedure: which path, what goes in .env, when to use Docker, how to persist, and how to wire it into Feishu or Slack.
This SOP walks five steps, from zero-deploy to production to agent workbench. The companion roundup is at OpenMAIC open-source resources; for another procedure see Claude API migration SOP. Every command is quoted verbatim from the official README.
Zero-Deploy Path: Hosted Mode, Grab a Code and Go
If you only want a course now, or to validate fit, skip the command line. Hosted mode is the cheapest route: grab an access code at open.maic.chat, deploy nothing, paste it into config, and it works.
This is the README "Hosted mode": grab an access code from open.maic.chat, save it in your config, and generate classrooms instantly — no local setup required. The official project hosts a complete service; you only consume the capability. It fits three cases: an individual trying it, an internal demo, or a pre-flight check before self-hosting.
Inside OpenClaw / ClawHub, Hosted mode is the skill default. Write the code into ~/.openclaw/openclaw.json:
{
"skills": {
"entries": {
"openmaic": {
"config": {
// Hosted mode: paste your access code from open.maic.chat
"accessCode": "sk-xxx",
// Self-hosted mode: local repo path and URL
"repoDir": "/path/to/OpenMAIC",
"url": "http://localhost:3000"
}
}
}
}
}In Hosted mode accessCode is a credential from open.maic.chat, not your LLM key — the official side resolves model calls, so you set no provider key. But course data and generation live on the official side; teams that require data in their own data center should use the self-hosted path below.
Local Standard Deployment: The pnpm Route (clone -> install -> .env -> dev)
To truly self-host, the standard route is a pnpm-spawned Next.js service. First the gate — the first failure point.
Prerequisites (verbatim):
- Node.js >= 22.19
- pnpm >= 10
The pnpm version is a hard gate; below 10 dependencies will not install. Confirm:
node -v
pnpm -vThen clone and install:
git clone https://github.com/THU-MAIC/OpenMAIC.git
cd OpenMAIC
pnpm installAfter install, configure .env. Copy the sample:
cp .env.example .env.localFill in at least one LLM provider key (README: Fill in at least one LLM provider key). Supported providers are numerous — OpenAI, Azure OpenAI, Anthropic, Amazon Bedrock, Google Gemini, DeepSeek, Qwen, Kimi, MiniMax, Grok, OpenRouter, Doubao, Tencent Hunyuan, Xiaomi MiMo, GLM, Ollama, Lemonade, FunASR, and any OpenAI-compatible API. Configure at least one, e.g.:
OPENAI_API_KEY=sk-...
AZURE_OPENAI_API_KEY=...
AZURE_OPENAI_BASE_URL=https://YOUR-RESOURCE.openai.azure.com/openai
AZURE_OPENAI_MODELS=YOUR-DEPLOYMENT-NAME
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=...
GROK_API_KEY=xai-...
OPENROUTER_API_KEY=sk-or-...
TENCENT_API_KEY=sk-...
XIAOMI_API_KEY=...Key point: "all providers are optional" means configure just one, not none. It does not mean "run with every key empty and still get a course." Blank keys make generation fail — the reading is "pick any one," not "leave all blank" — the second pitfall.
The README also offers declarative server-providers.yml (openai/azure/anthropic/bedrock). Pick one; .env.local is most direct.
Start dev:
pnpm devOpen http://localhost:3000. Dev mode has hot reload and suits tuning config. Do not stop here for production — next step.
Production and Containerization: build+start / Vercel / Docker
Once dev works, three ways hand the service out, in rising ops complexity.
Option 1: Local production build. Lightest production form (single-node, no persistence):
pnpm build && pnpm startpnpm build runs the Next.js production build; pnpm start starts the server. No hot reload, optimized artifacts — better throughput and stability. Suits a long-running single machine on an internal network.
Option 2: Vercel one-click deploy. The README ships a vercel.com/new/clone button; the repository URL is https://github.com/THU-MAIC/OpenMAIC, and the clone flow forces at least one LLM provider key (envDescription: "Configure at least one LLM provider API key"). Manual flow:
- Fork this repository
- Import into Vercel
- Set environment variables (at minimum one LLM API key)
- Deploy
Fits teams on Vercel wanting global edge deployment without servers. Note Vercel is stateless by default — no automatic PostgreSQL; for persistence use the profile below.
Option 3: Docker deployment. Recommended production form: isolated, reproducible:
cp .env.example .env.local
# Edit .env.local with your API keys, then:
docker compose up --buildThe image is based on node:22-alpine. In China or on weak networks add two build arguments to accelerate npm/Alpine pulls (note: these only accelerate Alpine and npm source — NOT Docker Hub pulls, including the Dockerfile frontend and node:22-alpine):
ALPINE_MIRROR=mirrors.tuna.tsinghua.edu.cn \
NPM_REGISTRY=https://registry.npmmirror.com \
docker compose up --buildA direct docker build supports the same build-arg:
docker build \
--build-arg ALPINE_MIRROR=mirrors.tuna.tsinghua.edu.cn \
--build-arg NPM_REGISTRY=https://registry.npmmirror.com \
-t openmaic:local .The third pitfall: some assume ALPINE_MIRROR / NPM_REGISTRY speed up the Docker Hub node:22-alpine pull — they do not. Those arguments only govern the Alpine packages and npm source inside the image. For a slow Hub, configure a Docker daemon registry mirror separately. Never put usernames, passwords, or tokens into these build-arg values; Docker may record them in image metadata.
Advanced: Persistence, Access Code, Video Export, and Localization
For real production, four advanced configs are nearly mandatory.
1. PostgreSQL persistence (server-persistence profile). By default OpenMAIC needs no database; documents, runtime records, KV, and assets use browser storage. For server-backed persistence (survives restart, shareable), use this profile:
cp .env.example .env.local
printf '\nDATABASE_URL=postgres://openmaic:openmaic-dev@postgres:5432/openmaic\nPERSISTENCE_DEV_TOKEN=openmaic-local-dev\n' >> .env.local
NEXT_PUBLIC_PERSISTENCE=1 NEXT_PUBLIC_PERSISTENCE_TOKEN=openmaic-local-dev docker compose --profile server-persistence up --buildTwo deadly details. First, NEXT_PUBLIC_PERSISTENCE is a build-time switch compiled into the browser bundle — once enabled you must pair it with a working DATABASE_URL and PERSISTENCE_DEV_TOKEN, and NEXT_PUBLIC_PERSISTENCE_TOKEN must match the server token at build time, or the home page shows a persistence-unavailable toast and the library appears empty. Second, to change the Postgres password you must first drop the volume: docker compose --profile server-persistence down -v, set the new password and matching DATABASE_URL, then start again — because PERSISTENCE_POSTGRES_PASSWORD only initializes the role when the data directory is empty; changing the env var later does not rotate the password in an existing openmaic-postgres volume. That is the fourth pitfall.
Warning: PERSISTENCE_DEV_TOKEN / NEXT_PUBLIC_PERSISTENCE_TOKEN are not real secrets — the latter is compiled into the public JS bundle, extractable by anyone, who can then read or write every learner partition and all documents. It suits only localhost or trusted-network, single-user deployments. Before production replace lib/persistence/server-auth.ts with real session verification.
2. ACCESS_CODE (site-level password for shared deployments). When exposing the service to a team, add one line to .env.local:
ACCESS_CODE=your-secret-codeOnce set, visitors see a password prompt and all API routes are protected; unset, it behaves as before. For small internal shared deployments this is strongly recommended.
3. MP4 video export (video-export profile). To export a classroom as a video you need Chromium + FFmpeg, run separately in a render-service container:
docker compose --profile video-export up --buildThe app auto-detects the service via RENDER_SERVICE_URL (preset in docker-compose.yml) and enables one-click MP4 rendering. The fifth pitfall: if this profile is not started, or RENDER_SERVICE_URL is unset, export does not error — it degrades to downloading the project ZIP for local CLI rendering. "I clicked export and got no video" is usually a missing render-service, not a breakage.
4. Localization (Lemonade / FunASR / extraction). For teams refusing to send data to the cloud:
- Lemonade local AI (LLM/image/TTS/ASR, no key):
LEMONADE_BASE_URL=http://localhost:13305/v1
TTS_LEMONADE_BASE_URL=http://localhost:13305/v1
ASR_LEMONADE_BASE_URL=http://localhost:13305/v1
IMAGE_LEMONADE_BASE_URL=http://localhost:13305/v1- FunASR local speech recognition (SenseVoiceSmall / Paraformer / Fun-ASR-Nano, no key):
python -m pip install torch torchaudio
python -m pip install "funasr==1.4.0" fastapi uvicorn python-multipart
# Add vLLM for Fun-ASR-Nano on NVIDIA GPUs
python -m pip install vllm
funasr-server --device cuda --model fun-asr-nanoASR_FUNASR_BASE_URL=http://localhost:8000/v1CPU-only: funasr-server --device cpu --model sensevoice. Local audio/video extraction needs system ffmpeg (ffmpeg and ffprobe on PATH); the app resolves executables at extraction time. ffmpeg is not an npm dependency — if absent, only local extraction is skipped and startup is unaffected.
Wiring Into an Agent Workbench: Generate Classrooms From Feishu/Slack
OpenMAIC's killer feature is being invoked as a Skill by your agent workbench. In chat tools you already use (Feishu, Slack, Discord, Telegram), tell your assistant "teach me quantum physics" and the classroom is generated, no terminal required.
The skill package lives at skills/openmaic/ in standard SKILL.md format. Beyond OpenClaw it works with Codex, DeepSeek, WorkBuddy, and similar workbenches. Two integration styles:
Style 1: OpenClaw + ClawHub, one-line install. Inside OpenClaw:
clawhub install openmaicOr tell your Claw "install OpenMAIC skill." After install, follow the README skill flow: Clone (detect an existing checkout or ask before cloning/installing) -> Startup (choose pnpm dev, pnpm build && pnpm start, or Docker) -> Provider Keys (recommend a path; you edit .env.local yourself) -> Generation (submit an async job and poll until complete). Every step asks for confirmation first — no black-box automation.
Style 2: Import the skills/openmaic/ folder. In Codex, DeepSeek, or WorkBuddy, import the skills/openmaic/ folder (or its zip). The built-in SOP covers the live demo, local setup, classroom generation, and secondary development on the @openmaic/* SDK.
Once connected, Hosted mode is fastest: grab a code at open.maic.chat, store it, generate with zero local setup. Self-hosted mode clones, installs, configures keys, and starts the server with the skill guiding each step. Generation is asynchronous — the skill polls and sends the link when ready. This turns "making a course" into a reusable workflow: a newcomer joins the Feishu group, @-mentions the assistant, and pulls up an on-spec training course without everyone learning to deploy.
Pitfall Log
Pitfall 1: pnpm version too low. The README states pnpm >= 10; with an old version pnpm install fails on dependency resolution. Run pnpm -v first; if short, corepack prepare pnpm@10 --activate. Node must also be >= 22.19 — neither gate is skippable.
Pitfall 2: running with every provider key empty. "All providers are optional" is misread as "configure none." Optional means pick one, not zero — but you must fill at least one LLM provider key (OPENAI_API_KEY or ANTHROPIC_API_KEY), or generation fails with no model reachable. Land one provider in .env.local first.
Pitfall 3: Docker build args do not accelerate Hub pulls. ALPINE_MIRROR and NPM_REGISTRY only govern the Alpine packages and npm source inside the image; they cannot reach the Docker Hub node:22-alpine pull. For a slow Hub, configure a Docker daemon registry mirror separately. Never embed accounts, passwords, or tokens in these build-arg values; Docker may write them into image metadata.
Pitfall 4: changing the Postgres password without down -v first. PERSISTENCE_POSTGRES_PASSWORD initializes the role only when the data directory is empty. Changing the env var later does not rotate the password in an existing openmaic-postgres volume. To change it you must docker compose --profile server-persistence down -v, then set the new password and matching DATABASE_URL and restart.
Pitfall 5: RENDER_SERVICE_URL unset degrades export. When the video-export profile is not started, or RENDER_SERVICE_URL is unset, MP4 export does not error — it degrades to downloading the project ZIP for local CLI rendering. "Export is broken" is usually just a missing render-service; start it with docker compose --profile video-export up --build.
Pitfall 6: NEXT_PUBLIC_PERSISTENCE is a build-time switch. Enabling it without a working DATABASE_URL, or with NEXT_PUBLIC_PERSISTENCE_TOKEN mismatched against the server token, shows a persistence-unavailable toast and an empty library. Because it is compiled into the bundle at build time, changing the env var does nothing — you must rebuild.
Ten-Point Pre-Launch Checklist
- Confirm
node -v>= 22.19 andpnpm -v>= 10; without clearing these gates nothing else matters. - Copy
.env.exampleto.env.local; never edit configuration directly into.env.example. - At least one LLM provider key is in
.env.local(OPENAI_API_KEYorANTHROPIC_API_KEY); never run with all keys empty. - Dev validation uses
pnpm devand opens http://localhost:3000 to confirm a test course generates. - Production delivery uses
pnpm build && pnpm startor Docker; do not treat dev as production. - Internal shared deployments add
ACCESS_CODE=your-secret-codefor site-level password protection. - If persistence is needed, enable the server-persistence profile and keep
NEXT_PUBLIC_PERSISTENCE,NEXT_PUBLIC_PERSISTENCE_TOKEN, andDATABASE_URLconsistent. - To export MP4, start the
video-exportprofile and confirmRENDER_SERVICE_URLis detected; otherwise accept the ZIP degradation. - Before changing the Postgres password, always
docker compose --profile server-persistence down -v, or the password will not rotate. - For agent workbench integration: OpenClaw runs
clawhub install openmaic, or importskills/openmaic/into Codex/DeepSeek/WorkBuddy; Hosted mode grabs an open.maic.chat access code for zero-deploy.
FAQ
Q1: I do not want to write any code. What is the fastest way to use OpenMAIC?
Use Hosted mode. Open open.maic.chat, grab an access code, paste it into your OpenClaw ~/.openclaw/openclaw.json config (or any compatible workbench skill config), and generate classrooms directly inside Feishu/Slack — zero local deployment. Validate course quality first, then decide whether to self-host.
Q2: The README says providers are optional. Can I run with no key configured?
No. "Optional" means pick one of them, not configure none — but you must fill at least one LLM provider key (OPENAI_API_KEY or ANTHROPIC_API_KEY), or generation fails with no model reachable. Running with every key empty is the biggest beginner pitfall.
Q3: For Docker in China, do ALPINE_MIRROR and NPM_REGISTRY help slow pulls?
They only help the Alpine packages and npm source inside the image, accelerating build-time installs; they cannot reach the Docker Hub node:22-alpine pull. For a slow Hub, configure a Docker daemon registry mirror separately. Never put accounts or passwords into these two build-arg values.
Q4: After enabling server-persistence, can I just change the env var to rename the Postgres password?
No. PERSISTENCE_POSTGRES_PASSWORD initializes the role only when the data directory is empty; changing the env var later does not rotate the password in the existing volume. Correct move: docker compose --profile server-persistence down -v to drop the volume, set the new password and matching DATABASE_URL, then restart. Note down -v clears that volume's data; for important data, change the password online with ALTER ROLE instead.
Q5: I clicked export video and got no MP4. Is it broken or misconfigured?
Most likely the render-service is not up. MP4 export depends on the video-export profile (the app auto-detects it via RENDER_SERVICE_URL). If that profile is not started, or RENDER_SERVICE_URL is unset, export degrades to downloading the project ZIP for local CLI rendering rather than erroring. Start docker compose --profile video-export up --build once and it resolves.
References
- OpenMAIC official README (THU-MAIC/OpenMAIC; commands and environment variables quoted verbatim)
- Companion roundup: OpenMAIC open-source resources
- Related procedure on this site: Claude API migration SOP