Building apps
Limits
What the platform deliberately does not do, and the runtime and data constraints to design around.
When a requirement hits a row below, say so up front and take the nearest supported shape. Do not build an approximation that cannot work.
Platform shape
| Not possible | Why, and the nearest supported path |
|---|---|
| Public or customer-facing apps | Every viewer must be a signed-in org member. There is no anonymous access and no self-signup. These are internal tools |
| Inbound webhooks or public API endpoints | Your backend function only runs on an authenticated app request or your own cron. Poll the source on a cron instead of receiving events |
| Arbitrary outbound calls | Egress is an allow-list. Declare hosts under egress: (exact names or one wildcard level; no schemes, ports, or paths). The default is the data plane only |
| Real-time push (websockets, presence) | No push surface; UIs poll. LLM streaming is the only streaming response |
| Next.js | Its SSR model does not map to the bounded single backend function. Any other bundler that emits one self-contained ES module works |
| Custom domains, native mobile, push notifications | Apps are responsive web apps at <app>.<org>.<serving-domain> |
| Bring-your-own API keys in frontend code | The frontend holds nothing. Use secrets in the backend function, or a connector |
Backend runtime
| Constraint | Value |
|---|---|
| The backend function must be one self-bundled ES module | A code-split, CJS, or dependency-referencing module deploys and then crashes on the first request. The CLI guarantees this for its templates; bring-your-own is your responsibility |
| Subrequest budget | About 100 per invocation. Use files.urls() for batches, not a loop of files.url() |
| Module size | 5 MB soft cap |
| Secrets | 64 per app, 5 KB per value, write-only |
| Daily caps | LLM tokens and emails per app; both return a typed 429 |
| Invocation logs | Kept about 14 days |
Data
| Constraint | Detail |
|---|---|
db is one flat store | No joins, transactions, or aggregations. Partitioning is your key design. Keep heavy data in a warehouse and read it through saved queries |
query() returns one page | Default 100, max 500. Paginate in the backend function or you silently drop the tail |
| No embeddings or vector search | The LLM gateway is text-in, text-out |
Agents and cron
Scheduled routes are alpha and will change. The refusals below are deliberate, but the limits are under review.
| Constraint | Detail |
|---|---|
| A cron invocation has no caller | ctx.user is null, ctx.trigger is "cron". Every limit below follows from this |
| Cron cannot start an agent run | A run is owned by (app, caller), so a cron-started run would have no owner. 409. Give the agent its own schedule |
| Cron cannot poll a run either | agents.get() matches the same pair, so a cron cannot read back a run that an http invocation started. Also 409 |
| Your own authz code must handle a null caller | A route that reads ctx.user.roles throws under cron, or returns everything. Guard with if (!ctx.user) |
| Cron caps | 5 schedules per app, 1-minute minimum. A schedule pauses with a visible reason if the current deploy has no backend function |
| Cron is at least once and may overlap | ctx.invocationId is your idempotency key. Never promise "exactly once" |
| Cron sends POST | A route declared GET-only will 404 on every fire and look like a broken schedule |
| Agent runs are never awaited while the request is open | agents.start() returns a queued run; agents.get(request_id) reads it back. There is no call that waits, and a get() loop is not one |
| Not exposed to the backend function | The org's role list, and design-system guidance. ctx.user.roles gives the caller's own roles. Fetch design guidance at build time with railcode design-system |
Managed agents
See agent limits for what an agent cannot do regardless of its manifest: reaching the open web, running continuously, reacting to events, or invoking another agent.