Schemas as documents
Take any document type — say task. Its schema is itself a document:
sys:schema:task, of type sys:schema. It is created and updated through the same API as user
data, synced to clients like user data, and validated like user data — against
a meta-schema.
What a schema describes
Section titled “What a schema describes”A schema document declares a document type’s shape, plus the rules that govern it:
- Field types — objects (fixed, named keys), records (open string-keyed
maps), arrays, strings, numbers, booleans, and enums. And
json— a field that holds any JSON value, the escape hatch from a declared shape. - References — fields that point at other documents (or at records within the same document), with integrity rules for what happens when the target disappears. See References & integrity.
- Defs — reusable type definitions, referenced from fields so a shape can be declared once and shared across the schema.
- Access rules — the declarative per-type role rules the engine evaluates on every write and read. See Authorization.
- Migrations — an append-only log of schema evolutions (rename, remove, remap). A document written under an old schema version is brought forward on its next read and the migrated data is persisted back — the full story is on Schema evolution.
Every document type has a schema — there is no schemaless document. When you
need loose or open-ended data, reach for a json field: it rides inside a
validated type and syncs and patches like any other field — accepting any
JSON-encodable value, just without a declared shape.
Why schemas want to be documents
Section titled “Why schemas want to be documents”One API. Evolving the data model is a document write, not a deploy. The same subscription machinery that gives you live user data gives you live schema changes.
The model is editable at runtime. Introducing a document type is a document write — no deploy, no code generation, no out-of-band tooling. A user building types from a UI and an AI agent that decides it needs a new type take the very same path.
One timeline, not two. As documents, schemas have their history in the same event log as the data they govern — introspecting a document as it was at any point in time means looking in exactly one place, not correlating deploy times against data versions. It’s also what makes the data portable: a folder’s history carries its own interpretation with it.
Schemas version like data. Sequence numbers, event history, and guarded writes apply to schema changes exactly as they do to data changes.
Where the schema truth lives
Section titled “Where the schema truth lives”Schemas being documents doesn’t mean giving up TypeScript. There are three ways to source them, and they compose:
Authored in TypeScript. A schema can be defined in code, and the document data types are inferred from the schema literal — compile-time validation and typed reads/writes with no code-generation step. The defined value is a plain schema document’s data; the types are phantom.
Upserted at startup. The host application writes its TypeScript-defined
schemas into the sys:schema:* documents when a folder boots — a no-op when
nothing changed. Rolling out a schema change in code becomes a document
update in each folder, still subject to the server’s append-only
migration rules. This is what makes
TypeScript authoring compatible with the one-timeline argument above: the
upsert turns every code change back into document history, so even when git
is where schemas are written, the schema documents remain where their
history lives — the repository never becomes a second timeline you’d have
to consult.
Document-driven only. Types created at runtime — by a user, or by an agent — exist purely as schema documents, with no TypeScript counterpart.
Whatever the source, the server’s write path always validates against the
schema documents — the TypeScript definitions are never a second,
shadow truth. Clients then choose per app: bundle the static TypeScript
schemas (typed, instant optimistic validation, no waiting for schemas to
sync) or, with dynamic schemas enabled, subscribe to sys:schema:* and
validate against whatever the folder currently declares — required when
document types are born at runtime.