A walk through the parts of the system that matter when you're evaluating it: the two-database split, the plugin sandbox, the snapshot & branching model, the provider abstraction layer, and the request pipeline from edit to publish.
Caelo runs as a small set of cooperating processes. Nothing exotic — Postgres, a Node app server, a static-file bucket, and an optional plugin sandbox. Each piece is replaceable without rewriting the others.
Serves the static production bucket. Caddy in self-hosted mode; any CDN in cloud mode. Terminates TLS, applies redirects, forwards /api/* + /admin to the app server.
Handles /edit, /security, the chat agent, write tools, and plugin operation endpoints. Stateless — scale horizontally behind the edge.
One DB for content (pages, modules, snapshots, plugin tables). One DB for system state (users, roles, proposals, audit, deploys). See §02.
Tier 2 plugins execute here with --no-read --no-write --no-net. The app server is the only client. See §03.
One staging bucket, one production bucket. Stage merges the chat branch and rebuilds staging; promote does an atomic pointer swap to production.
Caelo keeps content and system state in separate Postgres databases. The boundary is enforced — no foreign keys cross it, no joins span it. Operationally it means you can restore a content backup without disturbing user/role/audit history, and vice versa.
pages, modules, module_placementstemplates, layouts, template_blockssnapshots + snapshot_payloadsstructured_sets (nav, taxonomies, theme tokens)users, roles, user_rolesproposals + proposal_eventsaudit_log (every write, with cost in microcents)deploys, builds, domainsai_providers, mcp_tokens, chat_sessionsEvery row in the content DB carries a branch_id. Chat sessions write to their own branch; Stage merges into main. The system DB has no branching — proposals and audit are the source of truth.
Plugins extend Caelo without forking it. Two tiers, with very different trust models. Pick the lowest tier that covers what you need.
Ships in the Caelo repo. Runs in the app-server process, can declare workers and AI tools, can request capabilities (file system, network, raw SQL). Used for first-party plugins: auth, forms, comments, ratings, search.
Submitted via submit_plugin, validated, and Owner-approved. Runs in a Deno sandbox with --no-read --no-write --no-net. Imports limited to @caelo-cms/plugin-sdk. No fetch, no Deno, no dynamic imports, no raw SQL.
page_id must declare locale{kind, hint} the agent can self-correctEvery content row carries a branch_id. Every write emits a snapshot with the full diff payload. Together they give you per-chat isolation, one-click revert at three scopes, and a Stage step that's just a merge.
Each chat session writes to its own branch_id. The editor iframe reads that branch so you see your in-progress work. The published site only ever reads main.
Clicking Stage merges every row from the chat branch into main, then rebuilds the staging bucket. No magic — just a transactional row copy.
Three revert granularities: revert_module (one module), revert_page (one page + its layout), revert_site (everything to a point in time). Each is a proposal.
revert_chat_changes files a single revert_site proposal targeting the snapshot right before your session began. One click rewinds the whole chat.
External services are reached through a thin Provider Abstraction Layer (PAL). Each capability has one interface and N adapters. Swap adapters by config — no plugin or page-level code changes.
Credentials are encrypted at rest in the system DB. Owners paste API keys inline at proposal-approve time — the AI agent never sees the plaintext. Per-provider cost caps in microcents kill runaway spend before it happens.
Every editor action follows the same path. The pipeline is deliberately boring; the interesting parts are the gates between stages.
Editor types in chat or clicks a chat-chip. The agent reads the system-prompt context block (layouts, templates, pages, locales, users) and plans tool calls.
Agent invokes a write tool (edit_module, create_page, compose_page_from_spec, …). Bulk variants collapse N round-trips into one.
Hard-to-revert ops (locale add/delete, layout delete, plugin activate, site_defaults, AI provider config) route through the proposal queue. Owner clicks Approve at /security/<kind>/pending.
Row mutated with branch_id. Snapshot emitted with diff payload. Audit row recorded with user, tool, args hash, cost in microcents.
Live editor iframe rebinds. inspect_page_render and screenshot_page let the agent self-verify before reporting back.
Operator clicks Stage. Branch rows merge into main; the staging bucket rebuilds. Stage URL is a 1:1 preview of what production would render.
Owner approves a deploy.promote proposal — staging → production. Per-kind checkboxes (Pages, Templates, Layouts, Plugins, Redirects, SEO) allow partial promotion. Atomic bucket swap.