Core Concepts
Nine ideas that explain everything in IntentText.
1. One line, one intent
Write plain prose as you normally would — a bare line is simply text. When a line carries a specific meaning, give it a keyword:
keyword: value | property: x | property: y
This is a plain paragraph — no keyword needed.
task: Review the contract | owner: Ahmed | due: 2026-04-15
No nesting, no indentation rules, no closing tags. A keyword tells you what the line is; the value after the colon is its content; properties follow the pipes. Prose needs no keyword at all.
Even code follows this rule. Triple backticks delimit the value — think of it like a stringified JSON object placed as the keyword's value:
code: ```const total = items.reduce((s, i) => s + i.price, 0);``` | lang: js
Multi-line code is the same pattern — the backticks wrap the value across lines:
code: ```
SELECT *
FROM users
WHERE active = true
``` | lang: sql
The code: keyword isn't special. Every block is keyword: value | properties. The backticks are just value delimiters — like quotes around a string.
And the keyword is optional for prose. text: is the default, so a bare line with no
keyword is read as a text block — write a document as ordinary prose and reach for keywords
only when a line needs a specific meaning. Bare prose re-emits without the text: prefix,
so natural source round-trips byte-for-byte. See Bare prose.
2. Keywords
IntentText has a stable 40-keyword canonical contract, plus localized keyword names and extension keywords for specialized domains.
The canonical keywords are tiered — a small everyday core set plus opt-in profiles:
| Tier | Keywords | Use for |
|---|---|---|
| core (13) | title: summary: meta: section: sub: text: info: quote: code: image: link: task: done: | Everyday documents: notes, READMEs, plans |
| agent (9) | step: decision: gate: trigger: result: policy: audit: ask: context: | AI / workflow documents |
| contract (8) | track: approve: sign: freeze: certify: amendment: route: require: | Signed, frozen, auditable documents |
| data (3) | headers: row: metric: | Tabular / metric data |
| print (7) | page: header: footer: watermark: style: break: toc: | Print / PDF layout |
headers: is the canonical table-header keyword (write headers:; columns: is not reserved — it resolves to an ordinary custom block). route: and require: declare a document's in-file approval policy and certify: records an authority certification — all three are reserved contract-tier keywords (see Approval Workflows and Trust & Signing).
Beyond the canonical set: localized keyword names (32 Arabic keywords like عنوان: for title: that round-trip as written), extension keywords (deadline:, contact:, def:, ref:, figure:, and the x-ns: namespaces), and custom keywords — any word: ... line you invent parses as a typed custom block, never an error. There are no synonym aliases: every word that isn't a canonical keyword, an Arabic keyword, or a namespaced extension is reliably your custom block — collision-free, never silently reinterpreted.
Every keyword has a purpose. Use text: for text, task: for trackable work, metric: for measurable values, deadline: for dates with consequences.
3. Pipe properties
Properties follow the content, separated by pipes:
task: Review the contract | owner: Ahmed | due: 2026-04-15 | status: pending
The first value after the colon is always the content. Everything after a | is a property. Properties are key: value pairs.
Some keywords have specific properties — task: understands owner:, due:, status:. Others have general properties — any keyword can use style properties like color:, weight:, align:.
Two conventions keep lines unambiguous: dates in date-bearing properties (date:, due:, at:, expires:, issued:) are ISO 8601 (2026-04-15) so date queries and sorting just work, and a literal pipe in content or a value is escaped as \| (colons never need escaping). See Reserved characters & escaping.
4. Sections
Sections organize blocks into groups:
section: Scope
The project covers phases 1 through 3.
section: Timeline
deadline: Phase 1 complete | date: 2026-06-01
deadline: Phase 2 complete | date: 2026-09-01
section: renders as H2. sub: renders as H3. Blocks belong to the section above them.
5. The history boundary
The history: keyword separates the document from its history:
title: Consulting Agreement
Terms and conditions...
approve: Reviewed | by: Sarah Chen | role: Legal
sign: Ahmed Al-Rashid | role: CEO
freeze: | status: locked
history:
revision: | version: 1.0 | at: 2026-03-06 | by: Ahmed | change: Initial draft
Everything above history: is the document. Everything below is machine-managed history. You read history. You don't edit it.
6. Templates vs documents
Same format, different intent.
A document has real data:
title: Invoice INV-2026-042
contact: Acme Corp | role: Client | email: billing@acme.com
A template has placeholders:
title: Invoice {{invoice.number}}
meta: | type: template
contact: {{client.name}} | role: {{client.role}} | email: {{client.email}}
Merge a template with data:
dotit invoice-template.it --data client-data.json --html
The same parser handles both. Templates are just documents with {{variables}}.
7. Forms
A template is filled by code; a form is filled by a person. Mark a document meta: | type: form and declare input: fields, and the file becomes a fillable, signable record:
meta: | type: form
title: Vendor Onboarding
input: Legal name | key: legal_name | type: text | required: yes
input: Country | key: country | type: choice | options: KW, SA | required: yes
input: VAT number | key: vat | type: text | show-if: country = SA
input: Quantity | key: qty | type: number | value: 4
input: Total | key: total | type: number | compute: qty * 250
output: Net total | value: {{total}}
input:declares a field (types: text, textarea, date, number, choice, checkbox, signature, table, attachment).show-if:reveals a field only when a condition holds;compute:derives a value from other fields with a safe arithmetic evaluator — nevereval.output:displays a computed/summary value. A complete form (all required fields answered) stops being a template and becomes a final, signable record;buildSubmission/submitFormpackage and post the answers to a backend.
Forms get the full trust model — a sent form has two-party trust (the author seals the blank structure; the filler seals the answers). See Forms, Review & Compliance.
8. The trust chain
Documents follow a lifecycle: draft → tracked → approved → signed → frozen → certified → amended.
track: | version: 1.0 | by: Ahmed // activate history
approve: Legal review | by: Sarah Chen // named approval
sign: Ahmed Al-Rashid | role: CEO // integrity hash seal
freeze: | status: locked // seal — no more edits
certify: UTS | entity: Acme Corp // authority binds the signer to a verified org (optional)
amendment: Payment terms | section: Payment | was: 30 days | now: 15 days
Once frozen, a document can only change through formal amendment: blocks. The original seal is preserved. The amendment carries its own approval chain. certify: is an optional authority layer — a certification authority binds the signing key to a verified organization (see Trust & Signing).
9. The .it file is yours
IntentText is an open format. Your .it files are plain text — readable in any editor, storable in any VCS, parseable with any language.
No proprietary format. No vendor lock-in. No binary blobs. The file is yours.