--project [--parent ]` | create a folder |
| `druntime folder rename` · `move` · `delete ` | rename, move or delete a folder |
| `druntime link --project [--env ]` | link this folder to a project and environment |
| `druntime unlink` | remove this folder's link |
## Environments [#environments]
| Command | |
| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `druntime env create --project [--folder ] [--mode dev\|prod] [--client-org ]` | create an environment; prints its handle |
| `druntime env open [--env ] [--once] [--no-clients]` | start or reattach the development session |
| `druntime push [--env ] [--yes] [--no-site] [--delete ] [--rename ]` | put the folder's domain live on its environment: publish to a development environment, [release](#releasing-to-production) to a production one. `--yes` confirms removals of what the domain declared (and, for production, the release itself), `--delete` confirms one by name, `--rename` keeps an attribute and its data under a new name, `--no-site` releases the domain without the folder's [Site](#the-folders-site). See [Changing the schema](/docs/data/modeling-data#changing-the-schema) |
| `druntime schema deleted` | what was removed from the schema in the last 2 days |
| `druntime schema restore ` | bring a removed entity or attribute back, with its data |
| `druntime env pull [] [--project ] [--no-code]` | bring the environment's code and public configuration into this folder |
| `druntime env run -- ` | run a command with the environment's configuration |
| `druntime env stop [--env ]` | stop the development machine; data is kept |
| `druntime env delete [--yes] [--confirm=] [--force]` | [delete an environment](#deleting-an-environment); restorable for 2 days |
| `druntime env restore ` | bring a deleted environment back, with its data |
## Common options [#common-options]
| Option | Applies to |
| ---------------- | ------------------------------------------------------------------------------------ |
| `--env ` | push, run, query and environment commands; otherwise the folder's linked environment |
| `--org ` | project, folder and environment commands; otherwise the selected organization |
| `--json` | most commands: structured output for scripts and agents |
The development session (`druntime`, `env open`) never targets production. `push` releases to production only when the target is explicit — `--env prod-…` or the folder's link — and asks first (`--yes` in CI).
## Releasing to production [#releasing-to-production]
```bash
druntime push --env prod-acme-main # asks, then releases
druntime push --env prod-acme-main --yes # CI
```
```text
✔ Built locally src/domain.ts · 3 files · 2 actions
✔ Uploaded build 3f0c1b2e
⟳ Building on the platform building
✔ Built on the platform 52 s
✔ Live https://prod-acme-main.domainruntime.cloud
├─ environment prod-acme-main
├─ build 3f0c1b2e-…
├─ released by ada@acme.dev (you)
└─ schema +2 attrs
```
1. The domain is built and loaded on your machine, the same check a development push makes.
2. Its files are uploaded as the release's source: the folder that holds them keeps its layout, and the packages they import are pinned to the versions you have installed. The release is built from your folder as it is, not from the project's repository.
3. The platform builds it. If the build fails, you see the compiler's messages at your files' paths, then the last lines of the build log; the current release keeps serving.
4. Activating the release applies its schema with the same checks and confirmations as in development, then prints the live URL.
```text
✘ The build failed on the platform; the current release keeps serving.
├─ src/domain.ts:3:19
│ └─ An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
└─ log
├─ Next.js build worker exited with code: 1 and signal: null
└─ Error: Command "npx --yes pnpm@10.6.1 build" exited with 1
```
Imports must be relative (no path aliases such as `@/lib/x`) and domain files `.ts` or `.json`; `push` names any file that is not. A production release serves actions, so the domain needs at least one.
### The folder's Site [#the-folders-site]
When the folder's `package.json` depends on Next.js or Vite, a production push releases that app with the domain as the environment's **Site**, served at `https://.domainruntime.site`. Domain and Site go live, and roll back, together. The app is declared in `.domainruntime/site.json`:
```json
{ "access": "organization", "root": "app", "enabled": true }
```
| Key | Meaning |
| --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `access` | who the Site serves: `organization` (default, members of the environment's organization), `users` (anyone signed in), `public` |
| `root` | the app's folder, when it is not the folder itself |
| `enabled` | `false` releases the domain alone on every push from this folder (default `true`) |
To release only the domain once, pass `--no-site`:
```bash
druntime push --env prod-acme-main --yes --no-site
```
```text
✔ Built locally src/domain.ts · 3 files · 2 actions
◇ Site not released --no-site · the environment's Site stays as it is
✔ Uploaded build 3f0c1b2e
```
A release without a Site does not remove one: the environment's Site keeps serving what was last released with it.
Who can release: an **owner or admin** of the organization, signed in as themselves, or the organization's [service key](/docs/auth/service-keys) in CI (`DOMAIN_ORGANIZATION_KEY`, with `--yes`). A member without that role is told so. Every release records who pushed it and who made it live, and `push` prints it (`released by`).
The first release of an environment also sets up its runner, the service that runs your actions. The platform gives the runner a credential of its own, valid for that environment only; you never see it, and your organization's key never leaves the platform. `push` says so once:
```text
├─ released by ada@acme.dev (you)
├─ runner provisioned · environment credential
```
## Deleting an environment [#deleting-an-environment]
```bash
druntime env delete dev-orders-main-3f0c1b2e # asks first; --yes in CI
druntime env delete prod-acme-main # type the handle to confirm
druntime env delete prod-acme-main --confirm=prod-acme-main # CI
```
```text
✔ Deleted prod-acme-main
├─ mode production
├─ deleted by ada@acme.dev (you)
└─ restorable until 2026-09-25 14:02 UTC 2 days, then its data is purged
❯ druntime env restore prod-acme-main brings it back with its data
```
* It stops at once: its URL, actions and queries answer *not found*, its development machine stops, and it leaves every listing.
* Its data — records, files, streams and executions — is kept for **2 days**. `druntime env restore ` brings it all back, including a production environment's last release. After 2 days it is purged and cannot be restored.
* Only owners and admins of the organization (or its service key) can delete or restore an environment. Who did it, and when, is recorded.
* A production environment asks you to type its handle; `--yes` alone never deletes one. While custom domains still point at it, it is not deleted unless you pass `--force`.
* Its name is free for a new environment at once; its handle stays reserved until it is purged, so it can be restored.
If a deletion is interrupted, the environment has already stopped serving; run the same command again to finish it.
## Data and actions [#data-and-actions]
| Command | |
| ------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `druntime run [--input '' \| @file.json \| -] [--env ]` | run an action as you and print its output |
| `druntime query '' [--env ]` | run a [query](/docs/data/reading-data) as you and print the rows |
Both use `--env` or the folder's linked environment, and act as you: an action sees you as `runtime.auth.user`, a query returns what the environment's rules let you read.
```text
$ druntime run orders.place --input '{"orderId":"8f1c…","total":10}'
✔ Ran orders.place dev-orders-alice-7f3k9x2m · 412 ms
└─ execution 0f5a…
{
"orderId": "8f1c…"
}
$ druntime query '{"orders_order": {"$": {"limit": 5}}}'
✔ orders_order 2 rows
id total status
8f1c… 10 placed
91d0… 25 paid
```
If the action does not run, `run` says why: an unknown action, the parts of the input that do not fit its schema, or the error your action threw. A lost answer is retried with the same request id, so the action never runs twice. `--json` prints the call's record for `run` and the objects for `query`.
## Releases *(planned)* [#releases-planned]
| Command | |
| ------------------------------------- | -------------------------------- |
| `druntime release rollback --env ` | re-activate the previous release |
## Keys *(planned)* [#keys-planned]
| Command | |
| -------------------------------------------------------------- | ---------------------------------------- |
| `druntime keys create --name [--env \| --project ]` | a [service key](/docs/auth/service-keys) |
| `druntime keys list` · `revoke ` | list or revoke keys |
---
# MCP
URL: https://docs.domainruntime.dev/docs/platform/mcp
Status: Guide
Reviewed: 2026-09-23
Every environment is an [MCP](https://modelcontextprotocol.io) server at `https://.domainruntime.cloud/mcp`. An agent connected to it can read your data and run your domain's actions, as the person or service it signed in as, with the same permissions and the same record as any other caller. There is no adapter to write per agent.
```bash
druntime mcp --env=prod-orders-main
```
prints the URL and what to paste in Claude Code, Codex, Cursor, Claude and ChatGPT (`--client=codex` for one, `--json` for scripts).
## What the agent gets [#what-the-agent-gets]
| Tool | What it does |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.`, one per action of the active release | Runs the action as the caller, exactly like [`POST /actions/{name}/invoke`](/docs/platform/admin-http-api#run-an-action): an execution is recorded, `runtime.auth` is the caller, the input is checked against the action's schema. The tool's description is the action's `description`. |
| `query` | Reads with InstaQL as the caller: `{"query": {"orders_order": {"$": {"where": {"status": "open"}, "limit": 20}, "items": {}}}}`. Your [permissions](/docs/auth/permissions) decide what comes back. |
| `describe` | The release's domains, entities, links and actions, so the agent can find its way before it reads or acts. Also the resource `domainruntime:///schema`. |
Write each action's `description` for the agent: what it does, when to use it, what it refuses.
```ts
placeOrder: defineAction({
name: "orders.placeOrder",
description: "Place an order for a customer. Refuses a customer on credit hold.",
input: z.object({ customerId: z.string(), items: z.array(z.object({ sku: z.string(), qty: z.number().int() })) }),
output: z.object({ orderId: z.string() }),
async execute({ input, runtime }) { /* … */ },
}),
```
## What the agent sees back [#what-the-agent-sees-back]
* A finished function answers its output (as `structuredContent` when the output is an object).
* A workflow answers once its run is admitted, with the execution to follow.
* Every call names its execution in `_meta["dev.domainruntime/execution"]` (`id`, `requestId`, `status`, `url`): the same execution you see in the platform and with `GET /executions/{id}`.
* A refusal is a tool error the model can read: an input that does not fit (with the part that does not), `access_denied`, a failed action with your message.
Actions carry no read-only hint yet, so clients ask the person before running one.
## Signing in [#signing-in]
A person connects from the client: the client reads `/.well-known/oauth-protected-resource/mcp` from the environment, registers with the DomainRuntime authority, and opens the sign-in page. The agent then acts as that person.
A server connects with a key, sent as a bearer:
```toml
# ~/.codex/config.toml
[mcp_servers.prod-orders-main]
url = "https://prod-orders-main.domainruntime.cloud/mcp"
bearer_token_env_var = "DOMAINRUNTIME_TOKEN"
```
An organization's [service key](/docs/auth/service-keys) acts as an administrator; a [user token](/docs/auth/users) acts as that user of the environment. Keep keys in the environment of the process, never in a config file.
> Interactive sign-in from clients that send an OAuth resource indicator (the official MCP SDKs do) is being enabled on the authority. Until then connect with a key.
## Limits [#limits]
* One JSON message per request; no server-initiated stream (`GET /mcp` answers `405`).
* A function call waits up to 110 s, then the tool answers `execution_result_timeout`; the execution keeps running and can be followed by its `url`.
* Inputs with files answer `execution_files_required`: send files through the [HTTP API](/docs/platform/admin-http-api#files).
---
# Platform SDK
URL: https://docs.domainruntime.dev/docs/platform/platform-sdk
Status: Planned
Reviewed: 2026-09-22
**Planned public API.**
`@domainruntime/platform`
exists and powers the CLI, but it is not yet on npm and today's calls differ:
`init({ auth: { token }, runtime, sandbox })`
,
`platform.environments.resolve / pull / pushDomain(handle, domain, options)`
and
`platform.sandboxes.up / pull / run / down`
. Errors are
`PlatformApiError`
, with the code in
`message`
, plus
`status`
and
`body`
.
The Platform SDK is how you manage DomainRuntime from code: build a product on top of it, give an agent its own environment, run tests in sandboxes, or automate releases in CI. The [CLI](/docs/platform/cli) is built on it.
```bash
npm i @domainruntime/platform
```
## Authenticate [#authenticate]
```ts
import { init } from "@domainruntime/platform";
// on a backend or in CI
const platform = init({ serviceKey: process.env.DOMAIN_SERVICE_KEY! });
// as a person, reusing the CLI's login
const platform = init({ auth: "cli" });
```
## Projects and environments [#projects-and-environments]
```ts
const project = await platform.projects.create({ name: "Orders" });
const env = await platform.environments.create({
project: project.id,
name: "staging",
mode: "dev",
});
// env.handle, env.url
const same = platform.environments.get("dev-orders-staging-3k9x2m7q");
await platform.environments.list({ project: project.id });
await env.delete();
```
## Publish a domain [#publish-a-domain]
```ts
import domain from "../src/domain";
const result = await env.pushDomain(domain);
// result.pushId, result.digest
```
`pushDomain` takes the domain itself: schema, actions and their contracts come from your code, not from a file you maintain by hand.
## Run things [#run-things]
```ts
await env.query({ orders_order: { $: { limit: 10 } } });
await env.actions.run("orders.place", { orderId: crypto.randomUUID(), total: 10 });
const { token } = await env.users.mintToken({ id: userId, email });
```
## Sandboxes [#sandboxes]
```ts
const sandbox = await platform.sandboxes.create({ env: "dev-orders-e2e-4k2m9x1p" });
await sandbox.pushDomain(domain);
// … the same API as an environment …
await sandbox.destroy();
```
See [Sandboxes](/docs/environments/sandboxes).
## Releases [#releases]
```ts
const release = await env.releases.create(); // build the project's source and activate it
await env.releases.rollback();
```
## Keys [#keys]
```ts
const { key } = await platform.keys.create({ name: "backend", env: env.handle });
await platform.keys.revoke(keyId);
```
## Errors [#errors]
Every method throws `PlatformApiError` with `status` and a stable `code`, such as `environment_not_found` or `catalog_migration_required`. See [Errors](/docs/reference/errors).
---
# Errors
URL: https://docs.domainruntime.dev/docs/reference/errors
Status: Verified
Reviewed: 2026-09-23
```json
{ "ok": false, "error": "environment_not_found" }
```
Codes are stable; messages may change. Runtime codes use `snake_case`. Codes from the query engine use `kebab-case`.
## Calling actions [#calling-actions]
| Code | Status | Meaning | Do |
| ---------------------------------------------------------------------- | ------ | -------------------------------------------------------------------- | ----------------------------------- |
| `execution_envelope_invalid` | 400 | the body is not `{ requestId, input }`, or `requestId` is not a UUID | fix the request |
| `action_contract_validation_failed` | 400 | the input does not match the action's schema; your code did not run | fix the input |
| `action_not_found` | 404 | no action with that id in the active release | check the id; publish the domain |
| `access_denied` | 403 | you may not run this action or see its execution | |
| `environment_release_missing` | 409 | the environment has no active release yet | publish a domain |
| `REQUEST_CONFLICT` | 409 | this request id was already used for another action or input | use a new request id for a new call |
| `CONTRACT_MISMATCH` | 409 | the contract hash you sent does not match the active release | reload the contract |
| `execution_result_timeout` | 504 | the action did not finish within 110 s; it may still finish | follow `GET /executions/{id}` |
| `execution_total_file_size_exceeded` · `execution_file_limit_exceeded` | 400 | file inputs over 128 MiB in total, or more than 16 files | |
| `execution_file_digest_mismatch` | 422 | a file's content did not match its declared checksum | upload it again |
| `execution_not_found` · `run_not_found` | 404 | no execution or run with that id, or not yours | |
## Inside an execution [#inside-an-execution]
These do not come back as an HTTP error: the call answers `200` and the execution carries the code.
| Code | Meaning |
| -------------------------- | -------------------------------------------------------------------------------- |
| `action_failed` | your `execute` threw; the execution holds your message, up to 1000 characters |
| `execution_output_invalid` | your action returned something that does not match `output`; writes it made stay |
| `workflow_failed` | a workflow run failed or was cancelled |
## Authentication [#authentication]
| Code | Status | Meaning | Do |
| ---------------------------- | --------------------------- | --------------------------------------------------------------- | --------------------------------------- |
| `invalid_credentials` | 401 | the token or key is missing, expired or revoked | sign in again, or mint a new user token |
| `organization_access_denied` | 403 | the caller is not a member of the environment's organization | check the organization and key |
| `session_access_denied` | WebSocket close 4401 / 4403 | the live-query socket's token is not valid for this environment | mint a token for this environment |
## Environments [#environments]
| Code | Status | Meaning | Do |
| ------------------------------------------ | ------ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `environment_not_found` | 404 | no environment with that handle, or not visible to you | check the handle and organization |
| `environment_release_unavailable` | 502 | the active release could not be loaded | retry |
| `catalog_migration_required` | 409 | the organization still uses the previous project model | migrate it in the console |
| `managed_build_required` | 410 | direct publishing is not allowed on this environment | release it instead |
| `schema_change_refused` | 409 | the stored data does not allow a schema change; `details.issues` lists each one; nothing was applied | fix the data or the schema; see [Changing the schema](/docs/data/modeling-data#changing-the-schema) |
| `removal_unconfirmed` | 409 | the publish removes something the domain declared | confirm with `druntime push --yes` or `--delete ` |
| `development_workflow_adapter_unavailable` | 409 | workflow actions are not available in development yet | test the workflow in a production release |
## Queries and writes [#queries-and-writes]
| Type | Status | Meaning |
| ------------------- | ------ | ------------------------------------------------------------------------ |
| `permission-denied` | 400 | a permission rule rejected the write |
| `validation-failed` | 400 | the query or write is malformed |
| `record-not-unique` | 400 | a write would give two objects the same value of a `.unique()` attribute |
| `rate-limited` | 429 | this environment is blocked from further requests; contact us |
| `timeout` | 429 | the query ran longer than 30 s; add an index or narrow it |
| `result-too-large` | — | a live query result exceeded 8 MiB; paginate it |
Over HTTP (`POST /query`) these arrive as `{ "ok": false, "error": "data_provider_rejected" }` with the status above. On the live-query socket they arrive as an `error` frame with the type.
## Files [#files]
| Code | Status | Meaning |
| ------------------------ | ------ | -------------------------------------------------- |
| `storage_body_invalid` | 400 | the signed-upload-url request is not a JSON object |
| `storage_body_too_large` | 400 | the signed-upload-url request is over 1 MiB |
## From the Platform SDK [#from-the-platform-sdk]
The Platform SDK throws `PlatformApiError` with `status`, and today the code in `message`.
---
# Limits
URL: https://docs.domainruntime.dev/docs/reference/limits
Status: Verified
Reviewed: 2026-09-22
## Requests [#requests]
| | Limit |
| ------------------------------ | --------------------------------------------------------------------------------------------------- |
| Query request body | 4 MiB |
| Action call body | 8 MiB (files are uploaded separately) |
| Query response over HTTP | 16 MiB |
| One live query result | 8 MiB; a larger result is refused with `result-too-large` and your other subscriptions keep working |
| Query time | 30 s, then `timeout` |
| Waiting for an action's result | 110 s per request, then `execution_result_timeout` |
## Actions and files [#actions-and-files]
| | Limit |
| ------------------------------------ | ------------------------------------------------------------------------------- |
| Action file inputs | 32 MiB per file, 16 files, 128 MiB per call |
| Error message returned to the caller | 1000 characters |
| Execution lease | 30 s; an attempt that stops renewing is superseded, and its writes are rejected |
| User token claims | 1 KB |
| Streams | written to storage every 1 MiB and when they finish |
## Environments [#environments]
| | Limit |
| ------------------------------------- | ---------------------------------------------------- |
| Development machine | stops after 30 minutes idle; lives at most 7.9 hours |
| Development machines per organization | 2 running at a time |
| Environment handle | 63 characters |
| Folder nesting | 8 levels |
| Region | São Paulo, AWS `sa-east-1` |
Limits on the number of environments and requests per organization depend on your plan.
---
# Status
URL: https://docs.domainruntime.dev/docs/reference/status
Status: Verified
Reviewed: 2026-09-23
These docs describe the product we are building, and every page says how much of it you can use today:
* **Verified** — copy the first code block and it runs in production today.
* **Guide** — the mechanism runs today; some helpers shown are planned and marked inline, with a *Today* block.
* **Planned** — designed, not available yet.
## Running in production [#running-in-production]
| | |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Runtime | one Rust service serving every environment: queries, live subscriptions, permissions, actions, executions, files, streams |
| Data | PlanetScale Postgres in `sa-east-1`, a primary and two replicas |
| Files | Amazon S3 in `sa-east-1`, private, encrypted, versioned |
| Query language | `where` with equality, `$ne`, `$in`, `$gt`/`$gte`/`$lt`/`$lte`, `$isNull`, `$like`/`$ilike`, `and`/`or`, link paths; `order`; `limit`/`offset`; cursors (`first`/`after`, `last`/`before`, top level); nested links; `fields` |
| Permissions | per-entity CEL rules for `view`, `create`, `update`, `delete`, `link`, `unlink`; `auth`, `data`, `newData`, `data.ref`, `auth.ref`, `ruleParams`, `bind`, `fields`, `$default` |
| Actions | Zod input and output checked by the runtime; execution records; one run per request id; the caller captured at admission and read with `runtime.auth`; reads and writes as the caller, in development and production |
| Schema on publish | applied the same way in development and production, checked against stored data; confirmed removals are soft-deleted and restorable for 2 days |
| Workflows | durable runs in production releases, started and followed through the runtime |
| Identity | people, organizations and one service key per organization; user tokens per environment, validated on every request |
| Projects and folders | organizations, projects, folders and environments in the console |
| Development loop | `druntime` publishing on save to a development environment (CLI not on npm yet) |
| HTTP API | every route on the [HTTP API](/docs/platform/admin-http-api) page |
## Built, not yet public [#built-not-yet-public]
| | Missing to be public |
| ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| CLI (`druntime`): development session, projects, folders, environments, releases with `push`, `run`, `query` | npm release; `query` and input issues also need the kernel release with `/query?format=objects` |
| Platform SDK (`@domainruntime/platform`) | npm release, and the public shape on its page |
| Testing helpers (`@domainruntime/testing`) | npm release |
| Sandboxes | self-service access and per-organization limits |
| Workflows inside DomainRuntime's own runtime | production adoption |
## Designed, not built [#designed-not-built]
| | Page |
| -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `runtime.tx`, `runtime.query` inside actions (today: `runtime.db()`) | [Actions](/docs/data/actions) |
| `@domainruntime/react`: `init({ domain })`, `db.useQuery`, `db.actions..`, `db.auth` | [Reading data](/docs/data/reading-data), [Users](/docs/auth/users) |
| Action scopes: who may run an action, by permissions, roles or custom scopes (today any signed-in user can run any action) | [Actions](/docs/data/actions#who-may-call-an-action) |
| Workflow actions in development environments | [Local development](/docs/environments/local-development) |
| `withSchema` without empty `links` and `rooms` | [Modeling data](/docs/data/modeling-data) |
| `withPermissions` in the domain | [Permissions](/docs/auth/permissions) |
| `db.storage`, `runtime.files`, `runtime.streams`, `db.streams` | [Files](/docs/data/files), [Streams](/docs/data/streams) |
| Error codes of your own on thrown errors; conditional writes | [Error handling](/docs/data/error-handling), [Guarantees](/docs/data/guarantees) |
| `env.users.mintToken`; several scoped service keys and rotation | [Users](/docs/auth/users), [Service keys](/docs/auth/service-keys) |
| `druntime release rollback`, `druntime keys` | [CLI](/docs/platform/cli) |
| The project layout generated by `create-app` | [Project structure](/docs/introduction/project-structure) |
## Later [#later]
Presence, rooms and cursors for collaborative apps; offline support; server-side rendering helpers; scheduling an action for later.