Docs · Reference
Architecture
Two databases with row-level security forced on every table, one Query API in front of them, snapshots on every write, a static site on a CDN and an admin behind an identity proxy.
This page describes how Caelo fits together in the current release (0.10.x, pre-1.0). The canonical sources are ARCHITECTURE.md, CLAUDE.md and CMS_REQUIREMENTS.md in the repository.
What runs where
Admin app (apps/admin, SvelteKit on Bun) authoring UI, /edit, /settings, chat, MCP endpoints
-> admin-core operations, AI tools, chat loop, audit, snapshots
-> Query API (packages/query-api) the only way to the database
Validator: actor scope + Zod
Adapter: one transaction, actor bound for RLS
-> Postgres
cms_admin (admin_role) pages, modules, snapshots, chat, audit
cms_public (public_role) plugin public tables, visitor sessions
Static generator (apps/static-generator) runs at deploy, writes the site as files
API gateway (apps/api-gateway) public visitor writes; connects to cms_public only
Plugin host (packages/plugin-host) loads active plugins, brokers their grants
MCP server (packages/mcp-server) stdio shim to the admin's /api/mcp/*
Two databases, row-level security everywhere
Authoring data lives in cms_admin. Visitor-facing plugin data and visitor sessions live in cms_public. Each has its own Postgres role, and the roles hold no privileges on each other's database.
- Row-level security is enabled and
FORCEd on every table in both databases, so it applies to table owners too. Role isolation alone is not considered enough. - The API gateway connects only to
cms_public, aspublic_role. It holds no login tocms_adminat all and refuses to start if its configuration offers one. - What the gateway needs to serve
/api/*(its settings, the staging gate, rate limits, the active plugins, approved comments) the admin publishes intocms_public, one way. Anything the admin reads back fromcms_publicis treated as untrusted input.
The write path
Every change, whether a click in the admin, an AI tool call or a deploy trigger, is a named operation and goes through the same chain:
- The operation is looked up in the registry. Unknown names fail.
- The actor scope is checked: human, AI or system. Some operations are human-only; the AI gets
ActorScopeRejectedwith a next step. - Zod validates the input.
- The adapter opens a transaction and binds the actor to it, so row-level security sees who is writing.
- The handler runs. The audit record and the snapshot are written in the same transaction.
- Commit. If anything throws, the write, its audit record and its snapshot roll back together.
Some host code that brokers for plugins still issues SQL directly; the requirements track it as a defect. See Plugin permissions.
Pages, modules, snapshots
Four primitives compose a page by reference:
- A layout is the site shell (header, footer, navigation) with named slots.
- A template defines a page type and fills the layout's content slot.
- A page is a URL; it binds to one template.
- A module is HTML, CSS and JS with semantic fields. Pages and layouts reference modules; they never contain raw HTML themselves.
The editor preview and the static generator use the same composer, so the same input gives the same output. There is no separate production renderer.
Every write emits a snapshot. Snapshots are grouped and tagged with the chat that made the change, which is what makes Undo per chat work. Reverting a site snapshot restores pages and modules together.
Draft, staging, live
All chats share one draft of the site and see each other's unstaged changes. Isolated branches exist only for explicit experiments and site migrations. Within the draft, a stale write is a conflict and the AI re-reads instead of overwriting.
The AI may stage finished work: merge, staging build, quality audit. Staging is not indexed (robots.txt plus an X-Robots-Tag header). Publish live is always a person's click, and an AI stage never publishes automatically. A small set of settings (SEO fields, redirects, site settings) are documented exceptions that write live. See Publishing and Quality gate.
Approvals for hard-to-revert actions
Most AI actions apply immediately and can be undone. Actions that cannot be undone with one more call (deleting a layout, promoting a deploy, changing users or roles, minting MCP tokens, and others) are approval-gated: the AI proposes, the operator clicks Approve or Reject on a card in the chat. The AI cannot bypass the click and there is no auto-approve.
AI providers
Every model call goes through the Vercel AI SDK behind one provider abstraction. Supported providers are Anthropic, OpenAI, Google and local OpenAI-compatible endpoints. The chat loop runs on the SDK's multi-step tool loop; snapshots, audit, the cost cap and subagents hook into it. The editor UI says "AI" and never shows the provider's brand. External agents can drive the same tools over MCP.
Plugins
Seven plugins ship with Caelo. Runtime-installed plugins, including those the AI writes, run in a Deno subprocess with no file, network or environment access and get capabilities only through Owner grants bound to the exact artifact. Shipped plugins currently run in-process. Every plugin waits for an Owner to activate it. See Plugins and Plugin permissions.
Deployment shape
Every install has the same tiers. What protects each tier depends on the provider:
| Tier | gcp | gcp-firebase | self-hosted |
|---|---|---|---|
| Static site | Cloud Storage + Cloud CDN behind a load balancer | Firebase Hosting | Caddy serving files, automatic TLS |
| Admin | Cloud Run behind Identity-Aware Proxy | Cloud Run behind Identity-Aware Proxy | Container behind Caddy; Caelo's own login |
| API gateway | Cloud Run behind Cloud Armor (WAF) | Cloud Run via Hosting rewrites; no Cloud Armor | Container behind Caddy |
| Database | Cloud SQL, private IP only | Cloud SQL, private IP only | Postgres on the Compose network only |
- On Google Cloud, IAP checks every admin request against Google identity before Caelo sees it. Caelo's own login is the second line. MCP calls pass IAP with a short-lived service-account token.
- On
gcp-firebasethe gateway has no WAF; the mitigations are Firebase's edge protection, Cloud Run instance caps, Caelo's in-app rate limiting and proof-of-work on forms. - Self-hosted installs have no identity proxy in front of the admin by default.
- Background workers run inside the admin process, not as separate services. Admin and gateway scale to zero by default.
- AWS and Azure stacks exist in the repository but the installer does not support them yet.
What is not in Caelo
- No plugin marketplace. Plugins ship with a release or are installed on your site.
- No block editor. The chat is the editor; Live Edit is the visual surface.
- No multi-site mode. One install is one site.
- No silent fallbacks before 1.0. Missing data fails loudly with an error that names what is missing.
Something wrong or missing on this page? Open an issue on GitHub