# 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