# ThoughtML — a guide for the AI that writes it
You are most likely the author. ThoughtML is a plain-text language for recording
**reasoning you keep** — claims, evidence, decisions, and how they change over time —
as a graph a machine can read back. It is written far more often by an AI agent
transforming prose (a report, a case study, a debate, a postmortem, a decision) into
structured thought than by hand. This file is the whole language in one place: read it
once and you can author correct ThoughtML and interpret everything the toolchain gives
back. It is the exact spec the `thoughtml guide` command prints and the site serves — one
source, so it cannot drift from the tool.
The single governing idea: **ThoughtML is a mirror, not an oracle.** It does not decide
for you. It reflects the reasoning you put in — and *surfaces where that reasoning
disagrees with itself*: a confidently-held claim your own evidence defeats, or one belief
defined two incompatible ways. Your job when authoring is to record the reasoning
faithfully so the mirror stays honest. The fastest way to break it is to **overstate** —
e.g. use an evidence relation to write a plain list. The rules below exist to prevent
exactly that.
Everything here is derived from the reference parser's vocabulary and compute code, not
from prose docs. The closed sets (kinds, relations, fields, postures) are **exhaustive** —
if a word is not listed, it does not exist, and inventing one earns a warning (or, worse,
silently changes how a line parses). When unsure, prefer the smaller, more literal
construct.
---
## 1. The 60-second model
A document is a flat or nested list of **records**, one per header line, each with an
indented block beneath it. There are six things you can record:
- **focus** — a node you reason about (a claim, an observation, an option, a goal…).
- **question** — an open question the document is trying to resolve.
- **link** — a typed, directed edge between two nodes (`X supports Y`).
- **stance** — an *agent's* attitude toward a node or edge (the only place `confidence` lives).
- **scope** — a named container grouping related records.
- **profile / import** — advanced: dialect extensions and multi-document namespaces.
From these, the toolchain **derives** readings you never write by hand: a confidence
propagated along evidence, a grounded argument status (which claims survive attack), a
timeline, expected values over a decision, and a **conflict report** (the mirror). You
author the graph; you read back the derived readings (§13). Nothing is computed until you
ask (`--compute` / `--audit`).
Nothing is ever deleted. A superseded belief, an abandoned branch, a divergent
redefinition — all are kept with their reason. **"Fold, don't forget."**
---
## 2. Syntax rules
- **Indentation is structure.** A record's block is the lines indented under its header.
Nesting a record under a `scope`/`focus`/`question` makes it a *member* (§9). Use
consistent spaces.
- **Ids are `lowercase-kebab-case`** — `[a-z][a-z0-9-]*`, starting with a letter:
`cache-is-safe`, `error-rate-up`. One id = one node; mentioning an id again *merges*
into that same node (§8). A namespaced reference is `ns.id` (only after `import`, §9).
- **Comments are whole-line only**: a line whose first non-space character is `#`. There
are **no** trailing/inline comments — `focus x # note` is not a comment, it's a parse
error.
- **A header is one line**; its body prose, `field value` lines, `= formula` lines, and
nested records follow, indented.
- **Body prose** is an indented line that is neither a `field` (`name value…`), a
`= formula`, nor a nested header — it becomes the node's description. Write bodies as
ordinary sentences (a leading capital or trailing punctuation keeps an all-lowercase
line from being misread as a field). First mention of a body wins on merge.
Minimal shape — two typed foci and a link, so nothing dangles (strict-clean):
```thml
focus load-test-passed
kind observation
Held p99 under 2x peak traffic.
focus cache-is-safe
kind claim
Safe to ship the new cache layer today.
link load-test-passed supports cache-is-safe
```
---
## 3. Records and headers
The **first word** of a header decides what it is: seven reserved header keywords, plus
three sugar forms.
| Header form | Makes | Example |
|-------------|-------|---------|
| `focus ` | a focus | `focus cache-is-safe` |
| `question ` | a question | `question cause-of-shift` |
| `link [alias:] ` | a link | `link load-test supports cache-is-safe` |
| `stance ` | an agent stance (longhand) | `stance payments-agent holds root-cause` |
| `scope ` | a container | `scope incident-742` |
| `profile ` | a dialect (advanced, §5/§6) | `profile appsec` |
| `import as ` | a namespace (advanced, §9) | `import control-library as baseline` |
| **``** (typed header, sugar) | a focus with that kind | `observation error-rate-up` |
| **``** (evidence bundle, sugar) | many links | see §10 |
| **` …`** (readable action) | a stance (+ maybe a focus/link) | `team noticed shift` |
If a header's first word is a **kind** (§5) it's a typed focus; if it's a **relation**
(§6) it's a bundle; otherwise it's read as an ` …` action (§7) — and the
**second** word must then be a posture, or the line errors (`unknown record kind or
header`). So the first word (and an action's second word) is overloaded across the closed
sets — which is why inventing a kind / relation / posture doesn't merely warn, it can
change how a line parses. (`stance` longhand and the readable `` form
build the *same* stance object; use whichever reads better — `merge-conflict-beliefs`
mixes both to prove it.)
---
## 4. What can be linked
A link's `from`/`to` may resolve to a **focus, question, or link** (yes, links can target
links — that's how `undercuts` attacks an inference). Targeting a **stance** or **scope**
is an error (`TML202` — "links may only connect …"). Targeting an id that doesn't exist
yet is a warning (`TML201` unresolved reference) — declare it or fix the typo.
---
## 5. Foci and the ten kinds
A focus's `kind` is its semantic category. Set it with a `kind` field, a typed header
(`claim cache-is-safe`), or let a posture infer it (§7). The **ten** kinds are exhaustive:
| Kind | Meaning | Typically introduced by |
|------|---------|-------------------------|
| `observation` | Something seen or measured | `noticed`, or a typed header |
| `claim` | An assertion put forward as true | `infers`, or a typed header |
| `hypothesis` | A proposed explanation, not yet settled | typed header |
| `option` | A choice on the table | `considers`, or a typed header |
| `decision` | A choice to be made or recorded | `holds` / `chooses` |
| `outcome` | A result an option `leads-to` | edge inference, or a typed header |
| `goal` | A desired end | typed header |
| `assumption` | Something taken as given | typed header |
| `memory` | A recollection carried forward | `remembers` |
| `action` | A thing one *does* — a plan, intervention, mitigation | typed header only (no posture infers it) |
An unknown kind warns (`TML101`, with a nearest-spelling suggestion) unless a `profile`
declares it (§6). Two *explicit* kinds disagreeing on one focus keeps the first and warns.
---
## 6. Relations — the fourteen edges (READ THIS TWICE)
Relations are the heart of the language and the easiest thing to get wrong. Grouped by
role. **Only the first three carry evidence polarity** — they, and *only* they, move
derived confidence and argument status (§13).
### Evidence (defeasible — these move the numbers)
| Relation | Polarity | Use for |
|----------|----------|---------|
| `supports` | **+** | source is *evidence for* target |
| `opposes` | **−** | source rebuts a *node*: "that claim is false" |
| `undercuts` | **−** | source attacks an *inference*: "that step doesn't follow" (target is usually a **link**) |
### Structural & causal (no polarity — describe, don't argue)
| Relation | Meaning |
|----------|---------|
| `causes` | source brings about target (expected acyclic) |
| `enables` | source makes target possible |
| `prevents` | source stops target |
| `depends-on` | target is required for source (expected acyclic) |
| `blocks` | source holds target up (what `until … answered` desugars to) |
| `answers` | source *resolves* a question (the one settled answer) |
| `revises` | source supersedes target (nothing deleted; §11) |
### Decision
| Relation | Meaning |
|----------|---------|
| `leads-to` | an option leads to an outcome; carries a `probability` |
| `option-of` | an option belongs to a decision |
### Membership & candidacy (non-evidential — how you list things)
| Relation | Meaning |
|----------|---------|
| `part-of` | source is *one item in* the target collection |
| `candidate-for` | source is a *proposed* answer to a question (not resolved) |
**The mistake that breaks the mirror.** To *list* the items of something — SWOT strengths,
competitors, success factors, sub-points, friction points — use **`part-of`**, never
`supports`. `supports` means "evidence this is true," so enumerating a claim's parts with
it silently inflates that claim's confidence: "Netflix has strengths" reads as
100%-certain just for *naming* them. `part-of` has no polarity, so enumerations stay
structure and the numbers keep meaning something. Likewise use `candidate-for` for a
question's proposed options and reserve `answers` for the one that actually resolves it.
(`roadmap-priorities` and `control-library` show `part-of` done right; `differential-dx`
shows `candidate-for` vs `answers`.)
**Other easy confusions:**
- `opposes` (a claim is false) vs `undercuts` (a step doesn't follow — target the **link**).
`pr-feedback` sets them side by side.
- `weight` (evidential strength, any evidence/structural link, `0..1`) vs `probability`
(outcome likelihood, `leads-to` **only**, `0..1`). Putting one where the other belongs
is ignored with a warning.
- `answers` doubles as a **field** and a **relation**: as an indented line,
`answers ` on a stance/focus points it at the question it settles.
- There is **no** `rejects` or `mitigates` relation in the core. A hard rejection is
`opposes`; defending X is attacking X's attacker (`guard opposes risk`). (`rejects`
exists only as a *posture*, §7. A domain that truly wants `mitigates` declares it in a
`profile` — see `threat-model`.)
**Profiles add relations (and kinds/fields/postures).** A `profile` header opens a dialect
whose declared words are then legal below it; anything it doesn't declare still warns.
Comma-separated lists:
```thml
profile appsec
kinds threat, control, weakness
relations mitigates, aggravates, exposes
fields likelihood, severity
postures flags
```
---
## 7. Stances and the twelve postures
A **stance** is an agent's attitude toward a node or edge — the only place a `confidence`
lives. You almost never write `stance` longhand; you write the readable form **``**, where `` is any id you choose (`team`, `analyst`, `oncall`,
`classifier`). The **twelve** postures are exhaustive:
| Posture | Creates | Kind inferred | Shape |
|---------|---------|---------------|-------|
| `noticed` | focus + stance | `observation` | `team noticed metric-shift` |
| `considers` | focus + stance | `option` | `team considers rollback` |
| `holds` | focus + stance | `decision` | `team holds rollback-decision` |
| `chooses` | focus + stance | `decision` | `team chooses option-a` |
| `remembers` | focus + stance | `memory` | `team remembers last-outage` |
| `infers` | `claim` focus + `supports` links from the listed sources + stance | `claim` | `team infers X from a b c` |
| `suspects` | a **link** (`from rel to`) + a stance over it | — | `team suspects deploy causes shift as deploy-cause` |
| `asks` | a stance on an (existing) question | — | `team asks cause-of-shift` |
| `doubts` | a stance | — | `team doubts vendor-claim` |
| `accepts` | a stance | — | `team accepts postmortem` |
| `rejects` | a stance (rules a target out) | — | `team rejects option-b` |
| `revises` | a stance superseding this agent's *prior* stance on the target | — | `analyst revises on-track-claim` |
Confidence and rationale ride on the stance: put `confidence`, `note`, `because`, and
`asserted-at` in the block under a posture line. `confidence` may be a scalar
(`confidence 0.72`), a **range** (`confidence 0.25..0.70`, read at its midpoint), or
genuinely **unknown** (`confidence ?` — see `assistant-memory`). `suspects` takes a full
edge (` [as ]`); `infers` takes ` from …`
and wires each source with a `supports` link, so the reasoning is explicit.
**Which postures declare a node vs. only reference one** (this decides strict-clean, §12):
the focus-creating postures declare their *target*; `suspects` declares *both* endpoints
of its edge; `infers` declares only its *target* — its **sources** are `supports`
endpoints you must declare elsewhere. `asks` / `doubts` / `accepts` / `rejects` /
`revises` create *only* a stance, so **their target must already exist**, or you get an
unresolved-reference warning.
---
## 8. Merging — one id, one node
An id mentioned more than once is **one** node; the mentions merge. `body`, `quantity`,
`formula` are first-wins; `fields` accumulate; `kind` follows priority (explicit >
posture-inferred). This lets you define a focus once in full, then refer to it by id
anywhere — including from another agent's stance, or a later scope.
**Divergence is kept, not dropped.** If a later mention gives a *different*
body/quantity/formula (not a repeat), the alternative is retained on the node and the
mirror raises a `definition-divergence` conflict (§13). Concurrent authors never clobber
each other — see `merge-conflict-beliefs`, where two teams define one `root-cause` two
ways and both survive.
---
## 9. Scopes, thought-trees, and imports (nesting)
Indent records under a `scope` to group them, or under a `focus`/`question` to make a
**thought-tree** — a claim (or question) and the reasoning that hangs off it, as one unit.
Nested members are recorded on the container's `includes` and **inherit its provenance and
temporal context** — an explicit value on the child wins. (`prod-outage` puts `source
pagerduty` and `observed-at` on a scope and lets the observations inherit; `roadmap-
priorities` nests candidate options under a question.)
```thml
focus ship-decision
kind decision
Ship the new cache layer this week.
observation load-test-passed
p99 held under 2x peak traffic.
assumption rollback-ready
One-command rollback is wired up.
```
**Imports (multi-document).** `import as ` pulls another document's nodes in
under a namespace; you then reference them as `ns.id`. The importer is checked as a
**project** (importer + library together) so cross-document references resolve. A library
must itself be strict-clean as a standalone document. See `control-library` (the library)
and `compliance-rollout` (`import control-library as baseline`, then `depends-on
baseline.encryption-at-rest`).
---
## 10. Numbers, provenance, formulas, and evidence bundles
**Ranges & clamping.** `confidence` / `weight` / `probability` are `0..1`. `weight` and
`probability` outside that range are **clamped** with a warning (`TML402`). `confidence`
is *not* clamped: it accepts a scalar, an ordered range `low..high` (unordered → error),
or `?` for genuinely unknown. `quantity` is a typed measure with a unit (`quantity 30 GB`).
**Provenance basis.** Any number may declare *how it was arrived at*, inline, from the
exhaustive set `measured` / `estimated` / `assumed`:
```thml
confidence 0.9 assumed
quantity 30 GB measured
weight 0.85 estimated
```
A number with no basis is silent about it; the opt-in `--strict-provenance` lint flags the
omission (`TML401`). Prefer stating a basis — an unbased number reads as fact without
saying on what footing it stands.
**Formulas (`= expr`).** A focus can compute its `quantity` from other foci with a
`= expression` line over `+ - * /` and referenced ids. The parser **checks units** as it
goes (USD/hour × hour = USD; USD ÷ user = USD/user), so a dimensional mistake is caught,
not shipped. `--compute` evaluates them into `computed_quantity`. See `cloud-bill` (a
self-computing monthly bill) — change one input and the whole thing recomputes.
```thml
observation instance-price
quantity 0.096 USD/hour
observation instance-hours
quantity 21600 hour
focus compute-cost
kind claim
= instance-price * instance-hours
```
`compute-cost` computes to `2073.6 USD` — `USD/hour × hour` is unit-checked to `USD`, so a
dimensional slip is an error, not a silent wrong number.
**Evidence bundles** collapse the common "many sources → one target" repetition. A bare
`` header lists sources one per line, each ` [weight ]
[basis]`, each desugaring to an ordinary link:
```thml
supports customer-pain-points
late-fees-problem weight 0.90 assumed
limited-availability weight 0.85
long-travel-time
```
Bundles work with **any** relation — including `part-of` and `candidate-for`, the
idiomatic way to write a collection:
```thml
part-of netflix-strengths
strong-global-brand
large-subscriber-base
advanced-algorithms
```
**A bundle links; it does not declare.** Every member id *and* the bundle target must be a
node declared somewhere (a `focus`/typed header/`question`, or a focus-creating posture).
An id that appears only inside a bundle is an unresolved reference and warns — so it breaks
strict-clean. Declare the nodes first (see gold example B, §15), then bundle them.
---
## 11. Time — optional, and how belief changes
**Time is optional. Do not invent timestamps to force an ordering.** A dateless document
parses and renders fine — the viewer reveals it in *document order* (narrative replay).
Reach for dates only when *when* actually matters.
When it does, three ISO-8601 fields (partial dates like `2026` or `2026-06` are fine):
- `observed-at` — when something was seen.
- `asserted-at` — when a belief was put on the record.
- `valid-during ..` — a span over which something holds (`launch-readiness`).
**Lifecycle (`status`).** A focus can carry a `status`: `open` (default, live), `settled`
(resolved), `superseded` (replaced by a later belief), `abandoned` (a dead end). A
settled/superseded/abandoned branch is kept with its reason and folds in the viewer —
never deleted. (`choose-datastore` parks a rejected option as `abandoned`; `launch-
readiness` walks a call through `superseded`.)
**Revision.** `link new revises old` supersedes a *node*; the `revises` posture supersedes
the same agent's *prior stance*. Either way the old belief stays in the graph (it just
stops counting as live evidence). A revision asserted *earlier* than what it revises warns
(`TML304`).
**Replay.** `--as-of