Skip to content

Specification index

Lute is specified as a stack of versioned proposals under docs/proposals/. Each revision is a compatible refinement or extension of the one before, so the stack reads cumulatively: 0.1.0 is the scene kind + shared kernel, and every later revision cites it.

VersionScopeSource
0.0.1First pre-implementation draft of the authoring language — lexical structure, grammar, and semantics of a .lute scenario.0.0.1.md
0.1.0The language proper — scene kind + shared kernel: logic layer, Lute-CEL, scalar state model, totality, identity/i18n, and reusable content components.0.1.0.md
0.2.0Document-kind system (making .lute polymorphic), the <on> ECA trigger, the quest.* state tier, and the quest kind in full.0.2.0.md
0.3.0Relational fact layer — a closed SVO/n-ary fact database with valid-time intervals, delta assertion/retraction, and a total Datalog derivation layer.0.3.0.md
0.4.0Writer-experience layer — lute trace preview, provable softlock/dead-content diagnostics, param-scoped component <match> dispatch, ceremony sugar, and diagnostic presentation.0.4.0.md
0.5.0Authoring-feedback hardening — diagnostic specificity, trace honesty, the reachability boundary, and lute context completeness.0.5.0.md
0.5.1trace preview of quest-gated reads plus authoring-surface honesty items (delivery flags, lute context reserved paths, event/component diagnostics).0.5.1.md
0.5.2A single new E-UNSET-LITERAL diagnostic catching the most common misspelling of the unset sentinel in a CEL guard.0.5.2.md
0.6.0into= records without persist=; free shot headings — breaking.0.6.0.md
0.6.1Three verification-coverage warnings (W-UNPROVEN-RELATIONAL, W-LUTE-VERSION-STALE, W-TRACE-MOCK-UNPRODUCIBLE) and --deny/--deny-warnings warning promotion; a compatible refinement of 0.6.0.0.6.1.md
0.7.0Version unification — language, IR, and toolchain aligned at 0.7.0; no grammar, semantic, or IR shape change (byte-for-byte 0.6.1 semantics).0.7.0.md
0.8.0The adoption release: ::end termination, after: active(), the reserved quest.<id>.activatedAt anchor, scalar-only author state:, Artifact.shots, the localization round trip, identity: templates, and a uniform-width addr; every item traces to a gap found assessing Lute against a real 777-scene / 583-quest game catalog.0.8.0.md
0.9.0Vocabulary ownership: the compiler declares the seven content-vocabulary slots (emotion, action, anchor, mood, volume, musicAction, vfxType) and ships no members, so every member is the project’s, declared inline, in a schema reached through uses:, or by a plugin’s enums export; the enums: long form’s exits:/default: carries the member semantics two hardcoded name heuristics used to guess; using a slot nobody declared is E-DOMAIN-UNKNOWN instead of silently unchecked; and five root-only check stages now run over imported component bodies. Breaking for documents that use a slot. The IR re-aligned to 0.9.0 and was shape-identical to 0.8.0.0.9.0.md
0.10.0the toolchain says what it knows: thirteen changes, each closing a place where the checker held the answer and did not say it. ::set types its right-hand side against the path it writes (E-SET-TYPE); the six logic tags close their attribute sets (E-UNKNOWN-ATTR, E-AS-REMOVED); a quest gate that can never open is an error and a contradictory objective pair is E-OBJECTIVE-CONTRADICTION; lute.project.yaml gains defaults:; nested manifests are validated by every command that walks them; mocks/*.yaml is checked and requires file:; timeline time is integer milliseconds; four new warnings land and W-INJECT-CONFLICT is removed because its only trigger was agreement. Breaking for documents (§§3, 4, 5) and for mocks. The IR shape changes for the first time since 0.8.0 — provenance.reason becomes provenance.explanation, so an engine gated on IR 0.9 must widen to 0.10.0.10.0.md
0.10.1alignment only: no grammar production, no static-semantic rule, no IR shape change; 0.10.0 semantics byte for byte. The release’s two fixes are toolchain — lute test gains --project so a document that reaches its schema or its directives through a manifest can be tested at all, and an assetKind segment declared with a Type outside the closed segment production is rejected at plugin load instead of enforcing nothing. Written down because a language version absent from this stack cannot be told apart from one nobody recorded.0.10.1.md
0.10.2Alignment only on the language axis; the IR axis is not: no grammar production, no static-semantic rule (0.10.1/0.10.0 semantics byte for byte). The release is one IR/toolchain change — a plugin-owned, checker-validated frontmatter key now reaches the compiled artifact (meta.plugin) instead of being discarded at compile time (plugin-system 0.0.4). Additive only: no engine gate widens, since 0.10.2 shares major.minor 0.10 with 0.10.0/0.10.1.0.10.2.md
0.11.0Alignment only, on BOTH axes: no grammar production, no static-semantic rule, no IR shape or content change (0.10.2/0.10.1/0.10.0 semantics byte for byte). The release is entirely toolchain — a schedule.yaml project-file layer and lute play, a whole-project chained-playthrough command with --coverage review-gap reporting, plus two shared-reference-runner fixes. Under the then-current major.minor gate this one was not free for a consuming engine: 0.10 → 0.11 crossed the gated line, so the schema file was renamed under the 0.7.0 precedent (published today as lute-ir-0.29.schema.json).0.11.0.md
0.11.1<branch> gains two optional attributes: prompt="…" (the situation sentence a host UI shows above the option labels, E-BRANCH-PROMPT) and timeout="N" (a positive-integer seconds budget, E-BRANCH-TIMEOUT). IR additive-only: prompt/timeoutSec on the choice record, omitted when unauthored. Backfilled 2026-08-26 — shipped 2026-08-19 with its record only in the changelog.0.11.1.md
0.12.0Forward jumps: ::mark{id=…} / content-line id=… mark a position; ::next{to=… when=…} jumps to it, forward-only, so the walk stays a DAG. New diagnostics E-MARK-DUP, E-NEXT-UNDEFINED, E-NEXT-BACKWARD, W-CODE-AFTER-NEXT. IR is a pure restamp (::next lowers to the existing jump/match records); lute.core gains mark/next, so capabilityVersion moves.0.12.0.md
0.13.0Code lock: one new universal frontmatter key, codesLocked:, marking a document’s line codes as published identity; lute tag --force renumbers drafts in clean document order and refuses locked documents (fail-closed). Ships alongside the toolchain’s lint layer (lute lint, lute.lint.yaml, plugin lints exports) and relaxes the runtime version gate to MAJOR-only (minor/patch compatible-by-default). IR is a pure restamp.0.13.0.md
0.14.0Subquests: <objective quest="childId"/> makes a child quest’s completion a parent objective. Completion and upward failure are compiler-synthesized (done = quest.<child>.state == 'complete'; parent fail gains one failed test per required child); downward failure (terminal parent fails still-active children) and referenced-child activation (no start → activates with the parent; a start is evaluated only while the parent is active) are engine rules. Project shape is a tree, not a DAG — E-OBJECTIVE-QUEST-DONE, E-QUEST-REF-UNKNOWN, E-QUEST-MULTI-PARENT, E-QUEST-TREE-CYCLE, plus E-OBJECTIVE-UNSATISFIABLE propagation from an unreachable child. IR additive-only: ObjectiveEntry.quest, omitted when unauthored.0.14.0.md
0.15.0Authored scene identity: one new scene frontmatter key, id:, becomes the canonical scene key everywhere the derived {character}.{episodeId} join is consumed today (lineId prefix, visited()/connectivity, prereqEdges, project.index.json, the runtime visited-set), and demotes the four legacy identity keys (character/season/episode/episodeId) to optional when present; a second new frontmatter key, extra:, lands on scene and quest roots as a free descriptive block (open mapping of scalars or flat scalar-lists, carried verbatim into meta.extra, read by no language rule). Three new diagnostics E-META-ID/E-META-VALUE/W-META-LEGACY, E-META-MISSING narrowed to fire only when merged frontmatter lacks id:, and E-CONN-EPISODE-ID-DUP’s message generalised to name the canonical scene id. IR additive-only under 0.13.0’s MAJOR-only gate: SceneMeta.id required, the four legacy identity fields optional, extra on both SceneMeta and QuestMeta.0.15.0.md
0.15.1Alignment only, on BOTH axes: no grammar production, no static-semantic rule, no IR shape or content change (0.15.0 semantics byte for byte). The release is entirely toolchain — the npm distribution ships lute-lsp alongside lute (a second bin launcher entry, both binaries carried in each per-platform core package), so downstream editors can spawn the language server through the same @lute-lang/lute install the CLI already uses. schemas/lute-ir-0.15.schema.json keeps its name and $id; no engine gate widens under 0.13.0’s MAJOR-only runtime contract.0.15.1.md
0.16.0Declarative rewards: one new self-closing element, <reward kind= target= amount= when= on=/>, legal only as a direct child of <quest> or <objective>, with amount admitting an integer scalar (negatives real) or the new range literal N..M; lowers to pure data on the owning records (QuestCmd.rewards / ObjectiveEntry.rewards) — the engine grants, the reference runtime never rolls a range. Two new diagnostics E-REWARD-ATTR (shape) and E-REWARD-KIND (vocabulary, only when a plugin declares rewardKinds:); reward.when joins the CEL-slot registry. lute run / play / trace emit deterministic grant transcript events at each fresh transition (spec §3 D-D — objective grants precede quest grants, all grants precede the corresponding lifecycle handler body). A plugin manifest MAY export a rewardKinds: vocabulary; folded into the capability snapshot as a guarded, sorted section so capabilityVersion moves only for projects that declare kinds. IR additive-only under 0.13.0’s MAJOR-only gate: both rewards arrays are skip-when-empty, so a rewardless artifact stays byte-identical to 0.15.1 output. The tree-sitter grammar gains the reward production.0.16.0.md
0.16.1 candidateProspective tooling only; not released. A checked continuation compiler appends ordinary final-shot body source to a frozen scene template and emits full ordinary artifact snapshots per complete accepted unit; lute compile-stream exposes the same contract as flushed NDJSON. No new grammar, permission, runtime effect, or IR patch shape. Each unit recompiles cumulative source through the existing compiler, and snapshot consumers retain their numeric cursor/state/facts while rebuilding address lookup.0.16.1.md
0.17.0Checked continuations and least-authority compilation: the toolchain adds the append-only checked continuation compiler and lute compile-stream, plus generic capability-permission ceilings for directives, state and fact writes, bridges, rewards, and quests. Both reuse the existing language and ordinary artifact contract, so language and IR are alignment restamps with no grammar, static-semantic, or IR-shape change.0.17.0.md
0.17.1Alignment-only release: no grammar production, static-semantic rule, or IR shape/content change (0.17.0 semantics byte for byte). The release is repository hygiene — example cast/project identifiers renamed to neutral same-length names, a non-normative adoption assessment dropped. schemas/lute-ir-0.17.schema.json keeps its name and $id.0.17.1.md
0.17.2Additive plugin owner metadata: passthrough kind: "plugin" records may carry the resolved owning plugin id in optional plugin metadata, so hosts can dispatch by (plugin, tag); declarative presentation directives with lower: { record, fields } continue to emit stable core records. No grammar or static-semantic change; schemas/lute-ir-0.17.schema.json keeps its name and $id.0.17.2.md
0.18.0Range patterns in <when is>: inclusive numeric range literals N..M / N.. / ..M (signed decimal bounds) join the is= literal set, and a subject declared number becomes a coverage-checkable domain — interval union over the reals, so E-NONEXHAUSTIVE names the first uncovered gap and E-ARM-DEAD / W-OVERLAP-ARMS / W-OTHERWISE-DEAD are interval-aware. New E-WHEN-RANGE (a malformed or empty range) and W-WHEN-TEST-LITERAL (a literal-comparison test guard that is= expresses; its migrate fixit is applied by lute fix). IR is an alignment restamp — range arms lower to the existing >= / <= / && operators — and the schema renames to schemas/lute-ir-0.18.schema.json.0.18.0.md
0.19.0Lore entries (queried content): a third document kind, kind: lore, whose top level is a set of <entry id target category title series order when> declarations — content the engine looks up (item descriptions, found notes, inscriptions, codex pages, barks) instead of plays. Entry bodies admit content lines, <match>, ::set, ::assert, ::retract; knowledge is revealed with plain ::assert; the reserved engine-written entry.<id>.read path gates series and scenes; effects apply on the first read only. Quest and lore documents may declare a document id: (the bundle name), and a lore document a series: ordered by file position. New E-ENTRY-ATTR, E-ENTRY-ID-DUP, E-ENTRY-SERIES-ORDER, W-ENTRY-REF-UNKNOWN; lute trace/run --entry, lute lore. IR is additive — artifact kind: "lore" with LoreMeta, the entry record, QuestMeta.id, ProjectIndex.entries — and the schema renames to schemas/lute-ir-0.19.schema.json.0.19.0.md
0.20.0Fact envelopes (knowledge analysis): static semantics only, no grammar change. check-project decides every relational query (holds(…), count(…)) in every guard slot as impossible, guaranteed, or possible from a project-wide, argument-level may set (every ground fact any seed, live assert, rule, or reserved relation can produce) and a path-sensitive must set (the monotone facts true on every route to the slot, within a document and across the after: graph; guards are assumptions inside their regions; count is decided over an interval). Dead relational guards reuse each slot’s existing code (E-ARM-DEAD, E-OBJECTIVE-UNSATISFIABLE, E-QUEST-UNREACHABLE, W-OBJECTIVE-HIDDEN); new E-ENTRY-UNREACHABLE (a lore entry when that never holds — also scalar-decided in single-file check) and W-FACT-GUARANTEED (a redundant relational guard); W-UNPROVEN-RELATIONAL is removed. lute scenario … envelope lists the guaranteed facts at a node. IR is an alignment restamp and the schema renames to schemas/lute-ir-0.20.schema.json.0.20.0.md
0.21.0Beats and occasions (story selection): a project names occasions — engine moments at which a story beat may be chosen (a hub visit, entering a room, talking to an NPC) — and a scene or lore entry becomes a beat by answering one: scene frontmatter on: / target: / when: / priority: / once: run | user | false, entry attributes on= / priority=. A beat is eligible when its after: and when hold and its once is unspent; eligible beats order by priority, then project order. A plugin may export occasions: (select: first | all, target: true); occasion names are shape-only until one does. New E-BEAT-ATTR, E-OCCASION-UNKNOWN, E-BEAT-UNREACHABLE, W-BEAT-SHADOWED. schedule.yaml and every E-SCHED-* / W-SCHED-* code are removed; lute play is rebuilt on raised occasions and drives quest lifecycles. IR additive — SceneMeta.beat, EntryCmd.on / priority, ProjectIndex.beats — and the schema renames to schemas/lute-ir-0.21.schema.json.0.21.0.md
0.21.1Patch: no silent wrong answers. No new syntax; static semantics tighten so checks that accepted a defect now report it — quest.<id>.state reads as an always-assigned lifecycle enum (W-QUEST-STATE-ISSET), def bodies pass the CEL profile gate, a def in a directive attribute folds to its literal or is E-ATTR-DEF-DYNAMIC, an un-inlinable {{@def}} is E-INTERP-DEF, <quest> / <objective> / <on> close their attributes, a single-quoted attribute value is E-ATTR-QUOTE, and check-project runs the compile, the single-snapshot gate (E-CAPABILITY-MISMATCH), and the new E-DUP-VOICEKEY (a multi-scene project on the default voice-key template pins identity.voiceKey: "{prefix}.{speaker}-{code}"). The IR change is additive — a ref placeholder carries optional expr, the def body inlined — and schemas/lute-ir-0.21.schema.json keeps its name and $id.CHANGELOG; versioning.md
0.22.0A reference player that can stand in for the engine: lute play scripts write what the engine owns with engine: steps (declared state as a literal or { add: n }, facts and retractions of any declared relation, reserved ones included; newRun: { state, facts }), start from a save (visited:, presented: { run, user }, quests:, entriesRead: { run, user }), take a per-step choose:, fire world events (event:), pass on a select: all list (pick: none), and assert with step expect: { winner, offered, notOffered } and a top-level expect:; lute test runs every play carrying an expect:, and lute trace / test / play apply the project’s seed facts and Datalog rules by default (derive: false / --no-derive restores the 0.21 model; lute play --explain <atom>). Run boundaries: <quest tier="run"> resets at a new run, entry beats take once="run" | "user", and the reserved user-tier entry.<id>.everRead survives every run. A state: declaration may be owner: engine (E-ENGINE-OWNED-WRITE); an occasion’s target: may name a domain { prefix, entity } (E-BEAT-ATTR outside it); W-STAGE-ABSENT follows paths; new warnings W-QUEST-HANDLER-DEAD, W-BEAT-PRIORITY-TIE, W-BEAT-ONCE-RUN-USER. Breaking for compiled ids: the default voiceKey becomes {prefix}.{speaker}-{code} (pin identity.voiceKey: "{speaker}-{code}" to keep the old keys) and a component line is addressed {prefix}.{component}#{n}.{speaker}_{code}. IR additive — QuestCmd.tier, EntryCmd.once — and the schema renames to schemas/lute-ir-0.22.schema.json (since renamed).0.22.0.md
0.23.0Author overviews and time: lute beats prints each occasion’s beat ladder with the check-project verdicts, lute calendar evaluates play’s own eligibility over a grid of state values (--axis run.day=1..7 --axis run.slot=…) from a save, and lute scenario knowledge traces every fact-guarded condition to its producers; <objective by="…"> fails an objective the first time its deadline holds and <objective on target> judges it only for that target; occasion select: sequence presents every eligible beat and a beat’s also: true rides along after a select: first winner; a lore document may bundle scene-like <beat> blocks (canonical id <document id>.<beat id>); <hub prompt>; component string params interpolate as {{@p}}; the reserved read-only prev.run.<path>; a declared cast makes an unknown speaker E-CAST-UNKNOWN; a reward kind may credits: a state path (W-REWARD-DOUBLE-CREDIT); the condition decider reasons per path across && / `
0.23.1Patch: trace, test and play agree. No new syntax and no IR shape change; static semantics tighten — a subquest whose tier differs from its parent’s is E-QUEST-TIER-MIX, a match arm narrows its subject so reads after an arm that takes every unset value are no longer E-MAYBE-UNSET, a negation over a stably derived fact is decided, and W-BEAT-ONCE-RUN-USER fires only on a defaulted once. An occasion target domain may narrow to members:. Runtime answers move: a raised occasion first fires a same-named declared world event (every active quest’s <on event> handlers) and then judges its on= objectives, whose by deadline is judged only there, after done; an untagged component line takes the code lute tag would write, so its lineId may change once. Tooling: a ::end in lute play ends only its presentation (a step end: true ends the play), trace and test apply reward credits: and objective bodies, and lute calendar --script replays the script. schemas/lute-ir-0.23.schema.json keeps its name and $id.CHANGELOG; versioning.md
0.24.0Clocks, quest structure, parties: a schema may declare one clock: over two owner: engine paths (clock.index / clock.weekday / clock.weekdayLabel, once: day / once: slot, lute play advance: and include: steps, a clock calendar axis); enum member display labels, integer %, and a guarded ::set{… when="…"}; <quest activate="accept"> subquests, <quest complete="any"> alternatives that fail the rest as superseded, the read-only quest.<id>.failedBy / objectives.<o>.failed, occasion judge: before, ::accept{at="nextRun"}, <on event target>; by= judged at every settle with the place-bound until= beside it; entity sub-kinds (subsetOf:) and entity-indexed state (per:); cast present: / emotions: with W-CAST-ABSENT, ::clear, components with effects: true and speaker params, the {{x:ordinal}} format hint; bridge answers in play, test and trace (bridges:). IR additive.0.24.0.md
0.25.0Exclusion, shared spends, graph edges: a relation may declare excludes: (symmetric; a guard needing both is dead, a negation follows, E-FACT-EXCLUSIVE / E-RULE-EXCLUSIVE, and lute play / lute trace halt on a violation); beats with one share key are spent together; a bundle <beat after=…> is a scenario edge, and lute scenario draws [subquest] and [start] anchors; <quest accept="external"> is the only non-content acceptance (a test mock no longer counts); a reserved relation’s changedOn: narrows cast assume: true; an unread bridge result field may be omitted; {{x:ordinalWord}}. IR additive.0.25.0.md
0.25.1Patch: faster project commands. No language change and no IR shape change: project commands load each project’s shared inputs once per run, parse each document once and check documents in parallel, and span offsets are computed per line; output, diagnostics and exit codes are byte-identical. schemas/lute-ir-0.25.schema.json keeps its name and $id.CHANGELOG; versioning.md
0.26.0Scale and many authors (a lead and four area writers on one monster-collecting RPG): the fact analysis and lute test load a project once (Monster League check-project 6.1 s → ≈1.2 s, lute test in parallel); check-project compares every frontmatter state: declaration of a path (E-STATE-DECL-CONFLICT); a member listed twice is E-ENTITY-KIND-SHAPE, a schema may add: members to a kind another import declares, and a subsetOf: child’s members are its parent’s; defaults.uses takes globs and defaults.questTier sets quest tiers; directive attributes typed { entity: K }, reward target: contracts (E-REWARD-TARGET) and lute refs; components speak as a speaker param (@@who:), take param default:s, read their own directive results and reach parity with direct text; when= on ::use, ::accept, ::assert, ::retract, ::give and plugin directives; a beat or entry may target a whole kind (target="kind:X", occasion.target); count / countDistinct in rule bodies (E-RULE-AGGREGATE-CYCLE); trace and test follow ::next, apply handler accepts and fail on ineligible walks; advance: { to }, expect.clock and engine: { accept } in plays; W-DISPLAY-NAME-DUP, W-ENTRY-WRITE-REREAD. IR additive only. See Many authors, one project.0.26.0.md
0.27.0One runtime, seasons, templates (the fifth dogfood round): lute play, lute run, lute trace and lute test execute the compiled IR on one walker, held to one transcript by a differential test; occasion.target is a ground term in fact queries and per: indexes, for="kind:X" presents a beat once per member, and an occasion may declare a typed payload:; the engine seam — occasion raisedWhen: gates, a schema terminal: condition (E-OCCASION-GATE), a finite clock (last: / days:, E-CLOCK-END) and directive effects: { asserts, retracts }; cadence — once: week, quest rearm=, seasons: (E-SEASON-DECL) and spentBy:; beat templates (<beat use=…>, E-TEMPLATE) and the manifest’s sequence: (E-SEQUENCE); kind labels: and {{n:plural(one|many)}}; the checker says no more often (E-SET-SHAPE, member-checked { domain: K } paths, E-ATTR-QUOTE, W-QUEST-TIER-IMPLICIT), and messages drop their spec citations for --json spec and lute --explain <CODE>. IR additive only.0.27.0.md
0.28.0One rule per slot, one name per idea (the sixth dogfood round: eleven games and four new personas): every condition slot (when, after, rearm, spentBy, season live, raisedWhen, terminal, rule cel(), def bodies) runs the same typed checks (E-CEL-TYPE); every YAML surface refuses unknown keys with a located did-you-mean (E-MANIFEST-KEY, E-PLUGIN-KEY); reserved words are refused where they are declared (E-RESERVED-NAME) and flag attributes are written bare (E-FLAG-VALUE); renames land as clean cutovers whose old spelling is an error naming the new one — sequence: → chapters: [{ on, scenes }] (E-CHAPTERS, W-CHAPTER-STALL), <quest after=> / <reward on=> / <objective when=> → follows= / outcome= / visibleWhen=, expect.offered → expect.options, expect.exit → expect.end, ::camera{move-x} → moveX, camelCase plugin exports, lute new --occasion; spentBy latches and once beside it sets how long; occasion.target can be written; select: sequence re-judges each beat at its turn and a for beat spends once per member; clock.day / clock.slot / clock.ended and a clock’s raiseAtStart:; season-tier relations; outsideRun: occasions; a hub <return> block; kind label forms with :start / :indefinite / :capitalize / :cardinalWord, and kind labels win over cast names in text; other languages’ markup warns (W-TEXT-SINGLE-BRACE, W-TEXT-COMMENT-LIKE, W-TEXT-BRACKET-LABEL, W-TEXT-GLUE). IR: three quest-layer fields renamed (visibleWhen, outcome, follows), the rest additive.0.28.0.md
0.28.1Patch: leftovers from the sixth dogfood round. No language change and no IR shape change: a mistake the checker reported as several diagnostics is one naming its cause, and lute play, lute lore, lute beats and lute trace answer as the language says. schemas/lute-ir-0.28.schema.json keeps its name and $id.CHANGELOG; versioning.md
0.29.0One identifier rule, intent you can state, games as examples: every name an author writes — scene, beat, entry, quest, objective, branch, hub, choice and mark ids, document id segments, share keys, seasons, relations, defs, kinds and members, component params, and what a plugin declares — is a letter, then letters, digits or _, and a - is an error at the name under the slot’s code naming the camelCase spelling (0.28 refused it only where CEL reads the name); lute check-project prints causes first (lute.project.yaml, plugins, schemas, documents in path order, then project-wide rows); W-CHAPTER-STALL stays specific to chapters:; lute new scene --occasion writes on:; a warning that can describe an intended design has a spelling that states the intent, and terminal: gains the long form { when, persists: true } for an ending that outlives runs on purpose (W-TERMINAL-PERSISTENT silent, lute play says the game is over for good); the fourteen dogfood games ship as checked examples under docs/examples/games/. IR additive (terminalPersists); schemas/lute-ir-0.28.schema.json is renamed lute-ir-0.29.schema.json.0.29.0.md
0.29.1Patch: engine-owned ids keep their spelling. No new syntax and no IR shape change: the 0.29 identifier rule stays for every name Lute declares and stops reaching ids the engine owns — a target whose members no entities: kind lists and an entry’s category accept - again, as in 0.28.1. schemas/lute-ir-0.29.schema.json kept its name and $id.CHANGELOG; versioning.md
0.30.0Names as written, paths like JavaScript: every name a condition reaches by path, index or fact argument — scene, beat, entry, quest, objective, branch, hub, choice and mark ids, document id segments, share keys, seasons, relations, enum and entity kinds and members, plugin occasions and events, targets and categories — is letters, digits, _ or -, not starting with - (lamp-out, zero-coke-001, 001), ending 0.29.1’s split between Lute-declared names and engine-owned ids; a name read bare as @name (defs, def params, component and template params) stays an identifier. A name that is not an identifier is written in a condition as a quoted index or a quoted fact argument, as in JavaScript and CEL — quest["zero-coke-001"].state, run.visits['lab-b2'], holds('at', ["lab-b2"]) — in guards, ::set / ::assert / ::retract targets, {{…}}, Datalog rules and play / test / trace inputs, and both spellings are one name; a non-identifier name after a . is E-PATH-IDENT naming the bracket spelling. The IR adds no field: path fields carry the canonical dotted path, whose segments may contain - or start with a digit; schemas/lute-ir-0.29.schema.json is renamed schemas/lute-ir-0.30.schema.json.0.30.0.md
0.32.0Standard CEL and execution IR: the closed standard-CEL profile uses int / double and list-form fact queries in conditions while Datalog rule syntax stays unchanged; scalar state removes number; quest saves expose numbered questInstances, grants carry instance / index; execution IR carries celEnv, owner, and {cel, expr, authored?} slots; W-QUEST-STATE-HAS replaces W-QUEST-STATE-ISSET, with E-FACT-QUERY and E-RUN-OWNED-WRITE.0.32.0.md
0.33.0Domain modules and semantic negotiation: the execution IR and project index carry compiler-derived sorted requiredSemantics; the registry assigns engine-visible semantic ids, while lute.engine.yaml declares supportedIds. Pre-1.0 engines gate exact major.minor first, then refuse unsupported ids before playback (E-ENGINE-SEMANTICS); check --engine and check-project --engine report missing ids at author time (E-CHECK-ENGINE-SEMANTICS). New diagnostics cover malformed matrices, unsupported IR lines, and unknown ids. The reference matrix supports every current id; plugin meaning remains pinned by the exact capability snapshot.0.33.0.md
0.34.0Project semantic model and impact analysis: one shared project model powers project commands; lute impact reports deterministic reverse dependency closures; lute constraints checks reachable, completable, speaker-guard, and single-slot-progress policies; diagnostics and analysis results carry explicit evidence levels and bounded scopes.0.34.0.md
0.36.6AI edit loop: lossless syntax and canonical formatting, source/project revisions, task-scoped context and position queries, semantic diff, and atomic revision-checked source patches.0.36.0.md
0.37.0Surface cleanup — breaking, clean cutover: ## sections with an optional stable {#id}; ::actor, ::cg{assetId display layout}, ::sequence{name}, ::music{playback}, ::video{display}; ::camera takes project-declared framing / move / transition members; ::jump{to} / ::label{name}; choice text=; <match subject=>; <reward id>; {mono} only for the point of view or monoSpeakers:; a voiceKey on every line; inline text modifiers :pause{s}, :speed[…]{rate} and textStyle spans; frontmatter title: is the only title. The IR gives every command family and position, nests timing in timing, renames kinds to bg / actor / cg and roles to mono / os / vo, and carries capabilitySnapshot; lute fix rewrites the lossless renames.0.37.0.md
0.50.0Authoring extensibility, with no new keyword or element: plugin stampAttrs scoped to named surfaces with on: (line, branch, hub, choice, a directive tag; a scoped stamp may be required); host interpolation tokens declared by a plugin’s hostTokens export; reward-kind effects (asserts, retracts, accepts) applied at the grant and read by every analysis; project diagnostics: levels for checker W-* codes in lute.project.yaml; target: node lints with string members. New codes E-PLUGIN-STAMP-SURFACE and E-DIAGNOSTICS-CONFIG. IR additive (option stamps, RewardEntry effects, semantic id lute.quest.rewardEffects/1).0.50.0.md
0.51.0Flow and text, with no new keyword or element: ::jump{to max}, a bounded backward jump (max 1 to 99) that every flow analysis joins at labels and iterates to a fixed point, which supersedes the 0.1.0 non-goal of backward diverts; plugin effects.raises on directives and reward kinds with occasion raisedBy: declared, drawn as [raises] edges in the scenario graph; attribute type text, localizable directive text with code=, lute tag and lute loc; target: pair lints. Structured attribute values (#176) stay deferred. New codes E-JUMP-MAX, E-JUMP-LOOP-SCOPE, E-JUMP-INTO-LOOP, E-JUMP-LOOP-CROSS, E-PLUGIN-RAISES, E-RAISES-TARGET, E-LINE-CODE-RESERVED, E-LINE-ID-DUP, E-COMPONENT-TEXT-DIRECTIVE, W-OCCASION-NEVER-RAISED. IR additive (jump.max, semantic id lute.core.boundedJump/1, raises, localized).0.51.0.md
0.52.0Clean slate, with no new keyword or element: a literal { before a marker is E-INTERP-BRACE and \{{ is a literal {{ in labels, prompts and plugin text attributes; a quest’s top level admits only <on>, <objective> and <reward>; a forward jump out of hub bodies leaves the hub and a jump into a separately run body is E-JUMP-INTO-BODY; the hub stage head; prompts take the label grammar and the runtime renders labels and prompts onto the menu; objective titles are plain text; reward-kind attributes on <reward>, read by effects as @<attr>; lute impact follows [accept]/[raises], envelope provenance stops at [accept]. New codes E-INTERP-BRACE, E-JUMP-INTO-BODY, E-OBJECTIVE-TITLE-INTERP. IR additive (jump.leaves, semantic id lute.core.hubLeave/1, promptPlaceholders, rewardEntry.attrs); the awaitChoice menu carries rendered text/texts/prompt/promptTexts.0.52.0.md
0.53.0Dogfood round 3, with no new keyword or element and no IR change: a bounded loop may span the top-level ## sections; a reference inside a structured plugin frontmatter value (record, list, map) is checked with its field path; an in-script accept activates a quest whatever its follows, accept="external" follows="…" is host policy, and an accept deadlock is E-QUEST-START-CYCLE; a marker that opens on a literal brace run is E-INTERP-BRACE (not E-CEL-PROFILE) and a translation must keep its source’s markers (E-L10N-MARKERS); pair lints take scope: document; lute fix keeps plugin stamps, names its rules and reports kept sites; every multi-document command builds the project input layer once and the LSP keeps one cache per root. New code E-L10N-MARKERS.0.53.0.md
—State-model design rationale & audit record — why the four-tier (scene/run/user/app) state model is shaped this way; non-normative companion to 0.0.1 §9.state-model-design.md

For the authoring-side reading of the vocabulary slots — the three declaration routes and their precedence, and the exits:/default: long form — see Content vocabulary.

ProposalScopeSource
Plugin system 0.0.1Normative formats and semantics of the capability/plugin system — plugin packages, YAML manifest schemas, resolution, the capability snapshot, and the data↔code boundary.plugin-system/0.0.1.md
Plugin system 0.0.2Delta on 0.0.1 — plugin option and frontmatter value validation, reserved stamp-attribute rejection, cross-cutting stampAttrs admissible on every directive and content line, and the declarative lower: { record, fields } form implemented.plugin-system/0.0.2.md
Plugin system 0.0.3Delta on 0.0.1/0.0.2, landing with dsl 0.9.0 — lute.core exports an empty enums, an enums entry may use the long form carrying member semantics (members/default/exits), and the closed semantics flag vocabulary drops the two flags no consumer read (isStateful, cancelsPrevious), twelve → ten.plugin-system/0.0.3.md
Plugin system 0.0.4Delta on 0.0.1/0.0.2/0.0.3, landing with dsl 0.10.2 — a plugin-owned, checker-validated top-level frontmatter key (E-FRONTMATTER-SCHEMA) now reaches the compiled artifact’s meta.plugin instead of being discarded at compile time; deterministic (key-sorted), defense-in-depth re-validated against the snapshot regardless of which CheckResult gate produced the artifact.plugin-system/0.0.4.md

| Plugin system 0.0.5 | Delta on 0.0.1–0.0.4, landing with dsl 0.13.0 — the advisory lints export kind: declarative CEL lint rules (lints/*.yaml, namespaced <plugin-id>/<rule-id>), loaded for the lint engine and excluded from the capability snapshot and capabilityVersion, so editorial policy never moves artifact identity. | plugin-system/0.0.5.md | | Plugin system 0.0.6 candidate | Prospective; not released. Delta on 0.0.1–0.0.5 adding generic project/host capability-permission ceilings for directives, state/fact writes, bridges, rewards, and quests; conjunctive project/profile/host layering; --permission-profile; shared checker/compiler/LSP/context enforcement; and restrictive-policy capability hashing without changing policy-free hashes. Compile-time admission only—no runtime sandbox or reward implementation. | plugin-system/0.0.6.md | | Plugin system 0.0.7 | Delta on 0.0.1–0.0.6 — passthrough plugin IR records may carry the resolved owner id in optional plugin metadata; hosts dispatch by (plugin, tag), while declarative presentation directives still lower to stable core records. Older artifacts without the field remain valid. | 0.0.7.md | | Character & cast 0.0.1 | The language contract for character identity, display label, costume, name-reveal, and voice-join — as a capability plugin (registry, cast: frontmatter, scene.cast.* state, resolution, seal/reveal/wear). | character-cast/0.0.1.md |

The plugin system also carries a human-facing overview at docs/plugin-system.md, and the character/cast capability its design rationale at character-cast/design.md.