Skip to content

The Intent File Specification

Version 1 · vendor-neutral · normative source: IntentFile/intent-specification

A single .intent file at a project root is the source of truth for a whole application. It is authored one altitude above the models a platform generates from: instead of hand-authoring a data model, process definitions, forms, reports, roles and seed data separately, you author all of them from one YAML document, and a conforming generator produces them for you.

The intent never emits application code. It stops at the model layer. Schema, persistence, APIs, user interface, jobs, listeners, processes and security are produced from those models by the platform's own generation step. That boundary is non-negotiable.

The three altitudes

AltitudeArtefactAuthored byTransform below it
1 — Intentone *.intent per projecta human, or an AI assistant proposing patchesdeterministic generation
2 — Modelsthe platform's model artefacts (data model, processes, forms, reports, roles, seed data)the intent generatorsthe platform's template engine
3 — Applicationschema, persistence, APIs, UI, jobs, listeners, processes, securitythe platform's application templatesbrought live by the runtime

Each layer is the deterministic input to the one below it. The only fallible, supervised step is turning natural language into an Intent File; every transform below the top layer is a pure function.

The scope boundary

The altitude table is also a statement of what the format deliberately does not model. Three kinds of requirement recur in every real application and are how, not what — they belong below the model layer, and each has a designated hand-off point that the intent wires in rather than describes:

Beyond the boundaryWhy it is not intentThe hand-off
Protocol adaptation — conversation-shaped integration with an external system: certificates, acknowledgments, retries and their backoff, batch and file transportsintegrations and inbound are one-line call-outs by design; a protocol has state and failure semantics no declaration should pretend to carryan integration route in the platform's integration technology, feeding the entity's ordinary write path
Algorithms — checksums, fuzzy matching, scoring, policy-driven tie-breakingthe format already draws this line for pattern: a format check, not a semantic onea calculated-field call-out or a service-task delegate, hand-written in the project's custom folder
Statutory and designed form — the exact mandated layout of a printed documentthe print template is written create-if-absent by design: a formatted, audited artefact adapted by handthe authored template itself

The boundary is a feature, not a shortfall. Everything inside it is deterministic, regenerable and reviewable; everything outside it enters through a first-class, documented hand-off instead of a workaround. A format that models the what completely and hands the how to explicit extension points is more trustworthy than one that pretends to cover everything — and a requirement that falls outside the line is exactly the signal worth reporting, because that is how the vocabulary learns which construct to grow next. The framing, the extension-point inventory and the honesty this implies are on their own page.

The altitude table names two authors: a human, and an AI assistant proposing patches. The assistant is held to the same honesty this specification demands of generators, which must report what they cannot resolve rather than ignore it:

Normative

An authoring assistant that cannot express a requirement in this format MUST say so rather than silently substituting weaker semantics — a manual step proposed where automation was requested is a changed contract, not a smaller change. It MUST NOT drop a stated requirement from a proposal without reporting it. It SHOULD name the category of the gap and the designated hand-off point, and it MUST NOT imply that hand-off code will be generated when it is the developer's to write.

Editor-first, not a runtime artefact

The Intent File is an authoring artefact, not a runtime one. It gets an editor and an explicit Generate step; it is not silently reconciled from a repository behind your back.

  • Generation happens in your workspace project, visible immediately, before anything is published.
  • A published Intent File is inert source, exactly like any other authored model. The generated models and code are what run.
  • There is no intent daemon, no intent database table - the file is read only when you ask the generator to run.

The workflow

1. Create a project.
2. Author app.intent (any *.intent) at the project root - by hand or with an
   AI assistant that proposes reviewable patches.
3. Open it in the intent editor: structured YAML, a live read-only diagram, and
   inline validation.
4. Generate. The generators write the derived model artefacts NEXT TO app.intent:
       the data model            entities + relations + UI metadata
       process definitions        workflows
       forms                      task data-entry pages
       reports                    aggregations, charts, dashboard tiles
       roles                      permissions
       glue                       triggers, notifications, schedules, roll-ups, ...
       seed data                  initial / reference rows
       custom-action descriptors  actions, generates, transitions (buttons)
       document templates         printable documents
       a test manifest            UI-test descriptor
5. Generate once more, one level down: the template engine turns the models into
   the full-stack application.
6. Publish. The runtime brings it live exactly as for any hand-modelled project.

Project layout

The folders layer cleanly, each owned by exactly one tool:

FolderOwned byLifecycle
app.intentyou (and the AI assistant)the only hand-authored artefact
project-root model filesthe intent generators' Generatere-emitted and scrubbed on every Generate
the generated code folderthe template enginewiped wholesale on every regeneration
the custom folderyouthe escape hatch - touched by nobody

Do not hand-edit the generated model files. Changes are overwritten, and a file no longer backed by the intent is scrubbed on the next Generate. Adding an app.intent to a classic project hands ownership of its root-level model files to the intent generators; migrate them into the intent first.

The file is YAML, not JSON

Comments, multi-line strings and friendly diffs matter for an artefact a human reviews and an AI patches. The parser loads the document safely - type tags (!!type) are blocked, because an Intent File often arrives from generated output or paste and must never be a code-execution surface.

Every top-level collection defaults to empty, so a partial file (entities only) is valid. Field names are camelCase; entity names are PascalCase.

A minimal complete file

yaml
name: orders
description: Order management with an approval workflow
version: 1

entities:
  - name: Customer
    fields:
      - { name: id,   type: integer, primaryKey: true, generated: true }
      - { name: name, type: string,  required: true, length: 200 }
    relations:
      - { name: orders, kind: oneToMany, to: Order }

  - name: Order
    fields:
      - { name: id,        type: integer, primaryKey: true, generated: true }
      - { name: orderDate, type: date,    required: true }
      - { name: total,     type: decimal }
    relations:
      - { name: customer, kind: manyToOne, to: Customer }
      - { name: items,    kind: oneToMany, to: OrderItem }

  - name: OrderItem
    fields:
      - { name: id,       type: integer, primaryKey: true, generated: true }
      - { name: quantity, type: integer, required: true }
    relations:
      - { name: order, kind: manyToOne, to: Order, composition: true }

processes:
  - name: OrderApproval
    trigger: { onCreate: Order }
    steps:
      - { name: managerReview, kind: userTask, args: { assignee: manager, form: ApproveOrder } }
      - { name: done,          kind: end }

forms:
  - name: ApproveOrder
    forEntity: Order
    fields: [orderDate, total]
    actions: [approve, reject]

reports:
  - name: OrdersByCustomer
    source: Order
    dimensions: [customer]
    measures: ["count(*)", "sum(total)"]

permissions:
  - { role: Sales,   can: [Customer:read, Order:create] }
  - { role: Manager, can: [Order:approve] }

seeds:
  - name: order-statuses
    entity: OrderStatus
    rows:
      - { id: 1, name: DRAFT }
      - { id: 2, name: ISSUED }

Authoring rules

Normative

These rules keep the file diff-stable, safe to parse, and friendly for both human review and AI patching.

  • Comments are encouraged. No tool rewrites the file, so developer comments stay put; an AI patch path is expected to preserve them.
  • No anchors or aliases (&foo / *foo). They make diffs harder to read and harder for an AI to patch minimally. Prefer a defaults: block if duplication hurts.
  • No multi-document YAML (---). One file, one document.
  • No type tags. Blocked by the safe parser.
  • Quote unquoted braces in scalars. to: {member.email} is parsed by YAML as an object, not a string - write to: member.email. Braces are only for {...} interpolation inside subject / body text.
  • An event-binding key is event:, never on: - YAML 1.1 resolves a bare on (and off / yes / no) to a boolean. An action key is do:.
  • Only the keys this specification declares exist, and they are case-sensitive. An invented key, or a case slip (Required: for required:), is an authoring error - never a key that is accepted and ignored.

Unrecognised keys

A typed mapping normally drops a key it does not know. That silence is the worst failure this format can have: the file is accepted, generation succeeds, the application deploys, and the only symptom is that the promise the author wrote is absent at runtime - with every step of the pipeline reporting success. The rule is therefore the same one the format applies to a reference it cannot resolve.

Normative

A conforming generator MUST report a key it does not recognise as an authoring error rather than ignoring it, and the report MUST name the key, where it appears, and - where one exists - the nearest declared name. Key names are case-sensitive: a key differing from a declared one only in case is unrecognised, and the report SHOULD say so, since it is the slip hardest to see by eye. This applies equally to a seed row, whose keys are the target entity's own names rather than this specification's. A map whose keys are drawn from the model being described (a map: projection, a relation's where:, a widget's at:, a delegate's injected fields:) is validated against that model, not against this vocabulary.

Being written as a mapping does not make a block free-form. A process trigger:, an abortOn:, a glue event: binding, a step's args: and the blocks nested inside them are each a fixed vocabulary, and a key outside it is unrecognised like any other.

Normative

A step's args: are recognised per step kind: an argument declared on a kind that does not read it (a decision's if on a user task, a boundary timeout on a service task) MUST be reported like an unrecognised one, and the report SHOULD name the kind that does read it. This is the same failure and not a lesser one - the step reads nothing, so the argument does nothing.

In this section

  • Entities & fields — the data model: fields, types, calculated values, document numbering, labels, checks, immutability, hierarchies, attachments and snapshots.
  • Relations & multi-model — relations, composition, many-to-many, and referencing entities owned by another model.
  • Processes & forms — workflows with user tasks, decisions, waits, boundary timers and abort-on-status; task forms and custom action buttons.
  • Presentation — reports, charts, dashboard widgets, calendar/range/slot views, chat-threaded documents, and printable documents.
  • Declarative glue — notifications, schedules, integrations, webhooks, roll-ups, settlements, expansions, generates, transitions and postings.
  • Scoped surfaces & roles — per-user and per-partner row-scoped surfaces, sensitive-field stripping, and permissions.
  • Data, seeds & naming — seed data, multilingual data and UI, and the physical naming rules.

For a one-line-per-construct lookup, see the DSL reference.

Released under the Apache License 2.0.