CMS Assistant
The constrained authoring agent — skills, proposal operations, guardrails, durable execution, and turn budgets
The assistant is a session-based agent that authors CMS pages through the same core services and validators the layout editor uses. It is not a free-form HTML generator: the model never writes JSONB directly, and every mutation is a typed operation that passes the ordinary validation path.
It reads the current page, the owner-scoped block palette, field schemas, theme, SEO, and media library, then proposes draft-only mutations.
Two authoring flows
- Create from a prompt — build a page from scratch, mixing existing palette modules with new owner-scoped custom block types and a complete SEO record. Validated create-from-scratch chains auto-apply in order to mint their drafts.
- Edit in place — create or modify a specific section, row, column, or module, rewrite block content, or revise page SEO. Every existing-page or component proposal stays pending until an editor explicitly applies it.
Proposal chains are canonical and idempotent: one page create, stable custom-schema identities, fresh-state validation, deterministic supersession, and terminal handling for failed dependent proposals. The server is authoritative for user-visible creation state, so the assistant cannot describe a pending proposal as live or applied.
Skills
CMS contracts and authoring guidance are versioned skills loaded into the cached system prompt rather than improvised per turn.
| Skill | Covers |
|---|---|
cms-composition | the layout hierarchy and grid math |
cms-blocks | block schemas and field kinds |
cms-modules | the module compiler template dialect |
cms-seo | the seven fields the admin SEO form exposes |
cms-theme-kit | owner-scoped brand values |
cms-page-theme | per-page theme overrides |
cms-owner | owner scoping and palette resolution |
cms-design | visual composition |
cms-ux | page purpose, narrative flow, readability, actions, accessibility, responsive composition, motion, and trustworthy guest-facing content |
cms-inspiration | reference and inspiration media handling |
cms-member-data | member data tokens and personalization |
Operations
A proposal is a list of typed operations. Ten are permitted:
| Operation | Effect |
|---|---|
create_page | new page with owner, slug, type, and optional member gate |
ensure_draft | clone the published row to a draft before editing |
patch_page | toggle memberAuthRequired |
replace_layout | replace the layout tree |
update_block_content | rewrite one module's content |
create_block_schema | register an owner-scoped custom type with html / optional css |
update_block_schema | revise an owned type |
delete_block_schema | remove an owned type |
patch_theme | page theme colors, fonts, typography, buttons, grid |
replace_seo | write the cms_page_seo row |
Seven operations are explicitly forbidden and rejected rather than merely omitted from the prompt:
publish archive unpublish delete_page
discard_draft delete_section delete_subtreeThe agent therefore cannot put anything live, take anything down, or destroy an author's work. It also cannot mint platform-wide block types, add a new field kind, or emit raw CSS.
A custom block type is registered only when the live palette cannot express
the request. It is always owner-scoped and composed from existing field kinds.
create_block_schema uses a proposal-local alias so a replace_layout in the
same proposal can reference the type before it exists; apply mints the real
ULID block_type.
Brand grounding
Brand context comes from one approved, effective theme kit snapshot rather than free research inside the turn. Research and review happen in the owner-scoped theme-kit workflow; page generation consumes the approved kit and reuses the associated profile version, with no research controls in the page builder.
Official-site discovery uses same-origin links, sitemap and navigation discovery, redirect and challenge detection, and CSS/font/logo extraction, with bounded official-domain search — not guessed paths.
Inspiration media is staged against the assistant session and deduplicated on import; unused session-created assets are cleaned up after a failed or cancelled build.
Preview and apply
Before apply, the proposal tree drives the site iframe using the same
postMessage render contract the layout editor already uses for unsaved work.
Custom types preview through a schema-driven field-kind fallback on
journey-website. Previews are built from the same canonicalized content shape
apply uses, so what is previewed is what lands.
Apply persists; preview does not.
Durable execution
Every user message gets a durable execution record, kept separate from
conversation and session state, so a worker restart cannot destroy in-flight
work while leaving a session visibly running.
- Executions are claimed with renewable leases, heartbeats, attempt ownership, and guarded terminal transitions. Lease renewal is independent of model stream traffic.
- Model, tool, capture, proposal, and apply checkpoints persist in order as work completes, rather than flushing the whole turn at the end.
- Every consequential tool effect carries a stable idempotency identity and request digest, so replay cannot duplicate a CMS write.
- Abandoned executions are reconciled after worker loss or deployment, resuming only from an unambiguous checkpoint and otherwise failing explicitly with a retryable reason.
- Harness, prompt, tool-schema, and worker versions are recorded so deployments can drain or resume compatible executions.
The admin UI reconstructs progress from an authoritative execution snapshot and
reconnects to ordered events, so it never shows an indefinite Working… state.
Budgets
| Limit | Value |
|---|---|
| Message length | 8,000 characters |
| Images per turn | 2, up to 5 MB each |
| Model to tool rounds per send | 24 (a ceiling, not a target) |
| Synchronous turn | 4 minutes |
| Asynchronous turn | 25 minutes |
| Execution attempts | 3 |
| Total execution elapsed | 30 minutes |
Server-side context compaction starts well before the model's one-million-token limit, preserving author intent, current proposal identity, and durable restart state.
Multi-page requests are recognized before generation. Supported page sets get a shared site plan and coordinated page jobs; anything else stops early and presents the one-page boundary before spending research or generation budget.
Real event timing, rounds, tool latency, token usage, capture status, proposal
lineage, and apply outcomes are persisted for evaluation and support, with an
eval-cms-assistant suite covering the authoring cases.
Source: api/src/core/cms/assistant/,
api/src/core/cms/assistant/operations/operations.ts,
api/src/core/cms/assistant/skills/skills.ts.