Reference
Deploying
One decision dominates every other on this page: the kernel is never public. It holds every credential you have — model keys, the Composio key, the vault master key — and its /v1 surface trusts whoever presents a project key. If it is reachable from the internet, nothing else here matters.
founders ──► cloud / portal (public) ──┐
├──► kernel (PRIVATE) ──► Postgres
customers ──► your product (public) ──┘Private subnet, security group admitting only those two services. Everything below assumes that is true.
What has to change from your laptop
| Set | Or else |
|---|---|
MYCEL_DATABASE_URL | Everything is in memory and every task runs in the API process. The banner says store=memory queue=inline |
MYCEL_SECRET_KEY | Every stored credential becomes unreadable at the next restart |
MYCEL_API_KEY, MYCEL_OWNER_* | Regenerated on every boot, so your product and your login break on every deploy |
MYCEL_SANDBOX=docker (or daytona) | local runs agent commands on the host. It is not an isolation boundary |
MYCEL_PUBLIC_URL | The sandbox cannot call back to the harness, so gates and the action proxy fail |
MYCEL_SECRET_KEY is the one you cannot lose. 32 bytes, base64. It encrypts every connection credential. Lose it and every customer re-authorises from scratch; rotate it and the same thing happens. Back it up somewhere that is not the same account as the database — a database backup without that key is unreadable ciphertext.
Database
Set MYCEL_DATABASE_URL and the kernel migrates on boot: tasks, events, the service surface, tenants, the audit chain and the vault all become durable. graphile-worker creates its own schema in the same database. One datastore, one pool, no broker.
If you are using a pooler, use the session pooler, not the transaction pooler. The kernel issues real transactions — FOR UPDATE SKIP LOCKED in the scheduler, and the row lock that allocates audit sequence numbers. The transaction pooler supports neither, and the failure is subtle rather than loud.
Scale workers, not the API
Both are the same image. A worker just has no port and no load-balancer target.
MYCEL_WORKER=0— an API-only replica, serving HTTP and SSEMYCEL_WORKER_CONCURRENCY(default 10) — concurrent runs per worker
Runs are what cost money and what saturate first: each concurrent run holds a sandbox, so memory is the limit. Roughly 120–300 runs per hour per 2GB worker at the default concurrency.
Let workers overlap during a rolling deploy. graphile-worker drains in-flight jobs on SIGTERM, so an overlapping replacement finishes what it started rather than abandoning a run mid-approval.
Health checks: GET /health on the kernel.
Running more than one kernel replica
The queue, the store, the audit chain and the scheduler are all multi-instance clean. Several smaller things are not, and they are all in process memory:
| Process-local | Consequence |
|---|---|
| Approval TTL timers | A restart orphans pending rows; nothing sweeps expires_at |
| Cancel registry | POST /v1/tasks/:id/cancel only works on the replica running the task |
| Policy counters | max_per_day is effectively multiplied by the replica count |
| Portal links and sessions | Customers are signed out on restart, and links break across replicas |
| Idempotency map | Idempotency-Key only deduplicates within one process |
| Restart recovery | A booting replica fails every unfinished task, including ones running elsewhere |
Approvals themselves survive this: the blocked run polls the approval row every 700ms, so a decision landing on another replica still unblocks it. But until the list above is backed by shared state, the supported shape is one kernel replica plus additional workers.
Sandboxes in production
docker needs a Docker socket the kernel can reach and MYCEL_PUBLIC_URL set to something resolvable from inside a container — often http://host.docker.internal:4000. daytona needs DAYTONA_API_KEY and a publicly resolvable MYCEL_PUBLIC_URL, and is the least exercised of the three.
Whichever you choose, confirm it in the boot banner. An unrecognised MYCEL_SANDBOX value falls back to local without an error.
What is not done
- The Terraform in
infra/has never been applied. It is written from verified container behaviour — both app images build and run against a live kernel — but no AWS account has seen it. Expect the usual first-apply friction: a certificate in the wrong region, health-check grace periods, service discovery timing. - No mail sender in the kernel. Password resets and client portal links return a token for your product to deliver. Nothing is posted for you.
- No per-customer provisioning. Standing up an app instance per customer is manual.
- No image build pipeline, WAF, or Redis in the shipped infrastructure. Redis is only needed for a second kernel replica.
Read these in the repo
DEPLOY.md— the full AWS walk-through, service by serviceCOST.md— a real monthly estimate and what breaks firstinfra/— Terraform, with an honest status section at the top
What to read next
Limitations & roadmap — the rest of what is not built, in one place.