App Runtime
Choose a static or executable workspace, promote it safely, configure app commands and tabs, and run bounded jobs with recovery.
Use a static workspace for files that a browser can open without server-side code. Use an app runtime when the artifact needs a build, application state, registered commands or bounded compute. Runtime compute is isolated workspace compute; it is not a persistent local development server.
Choose a template
| Template | Best for | Compute at creation | Next step |
|---|---|---|---|
html | A self-contained page, report or walkthrough | None | Edit, push and share |
wiki | Connected pages for notes, guides and knowledge | None | Edit, push and share |
empty | A Vite-style runtime workspace | Runtime template | Push, inspect and run through the sandbox |
nextjs | A Next.js app | Runtime template | Push, inspect and run through the sandbox |
react-vite | Converting a pushed static workspace | Promotion target only | grain upgrade --target react-vite |
react-vite is not a new-workspace template. The current selectable creation choices are html, wiki, empty, and nextjs; use grain init --template <template> or grain new --template <template>.
Promote a static workspace
Promotion is a long-running operation. Push a clean checkout first, use a stable idempotency key, retain the returned operation ID, and poll until the response says runtimeReady: true:
grain --json --workspace <workspace-id> upgrade \
--target react-vite --idempotency-key first-runtime
grain --json --workspace <workspace-id> operation status \
--kind promotion --operation <operation-id>Only a completed promotion is ready. Pending, running, failed, cancelled and cleanup states are not ready. If a response is lost, inspect the workspace and known operation before retrying. Reuse the same idempotency key; there is no lookup-by-key endpoint for a lost operation ID.
Push before compute
In a checkout, edit locally and publish before installing packages, building, testing or running code:
grain --workspace <workspace-id> push
grain --workspace <workspace-id> exec npm testgrain exec accepts an argv command and runs in the workspace sandbox. A static HTML workspace has no compute runtime. Never assume a local npm test proves what the remote runtime will execute. For longer work, use a bounded job:
grain --json --workspace <workspace-id> job start \
--request-id test-2026-09-13 --timeout-ms 900000 -- npm test
grain --json --workspace <workspace-id> job status <operation-id>
grain --json --workspace <workspace-id> job cancel <operation-id>Jobs default to 15 minutes and accept at most one hour. Output is capped at 1 MiB. The job record is local to the current CLI profile and host; it is not a durable cross-host scheduler. A disconnected worker can become unknown; inspect the sandbox and acknowledge it with job acknowledge <operation-id> --remote-stopped only after confirming the remote command stopped.
Sandbox commands may change authored files such as lockfiles. After execution ends, pull and review those changes before editing or checkpointing. Do not use force to bypass an unfinished or unknown job.
App configuration
An app’s manifest lives at /project/workspace/.grain/grain.json in the runtime. Use grain config to inspect, validate and update it; updates require the expectedHash returned by inspection so concurrent edits do not silently overwrite one another:
grain --json --workspace <workspace-id> config inspectMerge the intended change into the existing commands and/or tabs maps returned by inspection. Validate the merged patch, then send the same patch with the inspected hash:
printf '%s' '{"action":"validate","patch":{"commands":{"echo":{"type":"script","command":"node","args":["echo.mjs"],"cwd":"app","title":"Echo","description":"Return text","enabled":true,"params":{"text":{"type":"string","description":"Text to return","required":true}},"safety":{"sideEffects":"read","description":"Returns validated input"}}},"tabs":{"home":{"title":"Home","route":"/"}}}}' |
grain --json --workspace <workspace-id> config --json-input
printf '%s' '{"action":"update","expectedHash":"<64-character hash from config inspect>","patch":{"commands":{"echo":{"type":"script","command":"node","args":["echo.mjs"],"cwd":"app","title":"Echo","description":"Return text","enabled":true,"params":{"text":{"type":"string","description":"Text to return","required":true}},"safety":{"sideEffects":"read","description":"Returns validated input"}}},"tabs":{"home":{"title":"Home","route":"/"}}}}' |
grain --json --workspace <workspace-id> config --json-inputReplace the placeholder with the exact lower-case 64-character hash from the immediately preceding inspection; do not use an app name or workspace ID. Only supplied commands and tabs sections are replaced, so include existing entries you want to keep. A stale hash is a remote-drift condition: inspect again, merge, validate, and retry. Saving configuration does not open a tab. The example assumes echo.mjs is already in the app checkout and the / route is served by the app.
Registered commands are discovered with command list, detailed with command schema <id>, and executed with a JSON input batch. A command script reads JSON from stdin; inspect every per-command result because an outer HTTP/MCP success can contain a command failure.
Desktop content tabs
The Grain desktop can keep several content tabs for the same workspace mounted while you switch between them. Editor buffers, undo history, scroll position and guest navigation can remain available, and saves are addressed to the tab that produced them. This is a navigation convenience, not durable crash recovery: checkpoint important work and do not treat an inactive tab as a second workspace or a backup.
Runtime data and unsupported assumptions
The current released empty, nextjs and react-vite templates use SQLite at /project/workspace/data.db for application data. The SQL API and Front Door examples target that file. Grain’s control-plane Postgres database is separate, managed by Grain and not exposed as an app database.
An app-specific login boundary, app-hosted Postgres and scheduled jobs may appear in product or internal discussions, but this public release has no general self-service setup path or published contract for them. Do not promise or design around those capabilities from this page. grain login authenticates the CLI; it does not add app users. job start is a bounded, manually requested worker and is not a recurring scheduler. Treat any workspace-specific first-party or unreleased runtime instructions as such until they are published here.