> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tai42.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# The agenda state template

> The shipped state template for conversations that handle many intents per inbound, each living through phases across turns, with one composed reply per turn.

The **agenda** is a shipped [state template](/babelfish#state-templates) for a
conversation that handles MANY intents per inbound, each intent living through **phases**
across several turns, with dependencies between phases, and **one composed reply** per
turn. An *intent* is the flow's job for an inbound — not the user's wish — so a single
message can open several. The agenda is data end to end: a state-template document (its
schema, write regimes, declarations, and [template jq](/babelfish#state-templates)) plus
the bulky jq bodies beside it. A client attaches it to a state and drives it from a flow's
per-node [bindings](/babelfish#binding-a-node-to-a-state) — input injections read the
`input` template jq to decide what is due, and `update` template jq write the ledger after
each step. The pattern is client-agnostic: a client declares only its intents, phases,
follow-ons, and reply parts — the **manifest** — as the attachment's declarations.

<Note>
  The agenda ships with babelfish as resource files under
  `resources/state-template/agenda/` — the one `state-template` document and its template jq
  bodies — not as engine code and not as a flow. It is installed like any other state
  template (`tai state-templates put`, then attach it to a state). Babelfish is proprietary
  and licensed commercially; the agenda ships with it, not on PyPI.
</Note>

## What the template stores

The template's schema fragment holds four keyed lists and three single slots. The lists are
under the `composing` writer regime — many keyed writers per list — while `turn_talk`,
`last_outbound`, and `transcript` are each `single`, one whole-object writer:

* **`ledger`** — one row per **intent**: `id`, `kind`, an optional **`item`** (the intent's
  item identity when the kind declares one intent per item; `null` for an itemless kind),
  `source` (`inbound` | `event` | `tick` | `standing` | `follow_on` — a record derives
  `tick` when its event turn's inbound source is `event:tick`, the event kind a schedule
  adapter posts through the event door), the classifier's extracted `fields`, a `rank` (the
  order among open intents of a sequence group), a `minted_run` `{run, pass}`, and a
  `closed` `{resolution, sent_run}` mark that compose writes. A closed intent is **pruned**
  at the next mint once no open intent still names one of its phases (below).
* **`ledger_phases`** — one upserted row per phase key `<intent id>:<item>:<phase>`,
  carrying the **`outcome`** label the flow returned, its derived class
  (`resolution` — settled: `done`, or one of the manifest's declared not-done names) or wait
  (`wait_for` — `who` a party, or `waits_on` another phase
  key when `who` is `phase`), the outcome `fields`, the writing `run`/`pass`, and a `sent`
  run stamp compose sets.
* **`ledger_inbound`** — one row per binding of an inbound span to a phase; `relation`
  records HOW it bound (`answers`, `ambiguous`, `event`, `yes_no`, `form`, `option`,
  `reactivate`, `follow_on`), and an ambiguous binding carries an **`attempt`** counter (the
  clarify round, from 1).
* **`turn_talk`** — the classifier's free text for THIS turn (`{run, text}`), an input to
  the compose talk slot.
* **`last_outbound`** — a single `{run}` slot compose sets to the [turn id](#two-ids-named-run)
  whenever it emits any outbound content (a part or the turn's talk); a fully silent turn
  (no part and no talk) re-emits the prior pointer rather than erasing it. It names the last
  run whose reply emitted anything; a bare yes/no binds only to a wait whose own `sent`
  stamp equals that run.
* **`transcript`** — `{lines: [{role, text, run}]}`, the composed-talk window. Compose is
  the sole writer — the platform exposes no thread read to plugins — appending a `user`
  inbound line and, when it speaks, an `assistant` outbound line each turn, truncated to the
  talk slot's `transcript_window_lines`. The talk slot reads the PRIOR window as context.

### Two ids named `run`

A `ledger` and `ledger_phases` row each carry a field called `run`, and the trace stamp
carries another — they mean different things, and the page states the contrast so a dump
reads right:

* A row's own `run` (and `minted_run.run`) is the **turn id** — the unit of work, the
  turn the row was written for.
* `_trace.run` is the writing run's **session id** — the client flow run in which the
  update template jq wrote the row.

### The trace

`trace.enabled` is `true`, so every row carries a `_trace` object:
`{meta, run, turn, inbound, at}` — the writer-supplied `meta`, the run's session id, the
turn id, the turn's inbound id, and the write time. The engine supplies `meta: {node: <node
id>}`, so the writing node reads at `_trace.meta.node`. Because a `composing` list admits
several keyed writers per row kind and nothing statically pins one node per field, the trace
stamp is the evidence a dump needs for **which node wrote which row**.

Record ids are `<turn.inbound.id>/<kind>/<n>`, where `n` is the 0-based mint ordinal within
the run across every source (inbound spans, standing conditions, follow-ons) — one counter
takes the next ordinal for every row it mints, so `n` is unique per turn. Provenance lives in
`source` and `event`, never in the id. Both the mint and the record update derive `source`
through the template's shared `turn_source` input jq — `tick` when the inbound source is
`event:tick`, `event` for any other event turn, else `inbound`.

## The manifest a client declares

The attachment's `declarations` carry `intents`, `phases`, `outcomes`, and `parties` — the
wait parties beyond the engine's own — plus `clarify_kind`, `unmatched_kind`, `clarify_limit`
and `talk_slot`. The schema admits no other key. A placeholder manifest — kinds `status` and
`utilities`, the synthetic vocabulary this page uses throughout:

```jsonc theme={null}
{
  "intents": [
    {
      "kind": "status",                          // the classifier labels a span "status"
      "description": "the user asks after an open item",
      "sources": ["inbound", "event"],           // a message span OR an event-door turn
      "item": {"field": "ref"},                  // ONE INTENT PER ITEM: the item id is the
                                                 //   span's extracted "ref" (exclusive with "items")
      "fields": {                                // the per-kind classifier extraction schema,
        "ref": {"type": "string"}                //   merged into the forced span object beside "text"
      }
    },
    {
      "kind": "utilities",
      "description": "the user asks to run a utility",
      "sequence": "chores",                      // a SEQUENCE GROUP: one open utilities intent
                                                 //   fires per pass, in rank order (null = parallel)
      "sources": ["inbound"],
      "items": [],                               // a fixed item list; [] = the single implicit item "_"
      "standing": null                           // a jq predicate STRING; a standing kind is minted
                                                 //   when it holds and no intent of the kind is open
    }
  ],
  "phases": [
    {
      "key": "check", "kind": "status", "item": "_",
      "predecessor": null,                       // a phase key, "record", {phase, outcomes} (labels
                                                 //   that reach the successor), or null (a first phase)
      "expected_input": {                        // wake this wait on a correlated event
        "kind": "event",
        "event": "settle",                       //   the event kind that correlates here
        "key": "ref",                            //   the payload key whose value names the item
        "via": "$key"                            //   a jq over the flow root ($key = the payload's key value);
                                                 //     absent = the key value itself
      },
      "outcomes": [                              // the outcome LABELS this phase can record
        {"outcome": "done", "resolution": "done", "fields": ["state"],
         "follow_on": {"mint": "utilities", "item_field": "ref"}},   // open a utilities intent for this item
        {"outcome": "declined", "resolution": "declined", "fields": ["reason"]},
                                                 //   a NOT-DONE resolution, its name declared in
                                                 //   parameters.resolutions: the chain stops here
        {"outcome": "waiting", "wait_for": {"who": "partner"}, "fields": ["eta"]}
                                                 //   "partner" is a party declared below
      ]
    },
    {
      "key": "run", "kind": "utilities", "item": "_",
      "ordering_waits": [                        // {kind, phase} gates ("*" = every phase of the kind)
        {"kind": "status", "phase": "check"}     //   fires only when no status.check is still pending
      ],
      "expected_input": {
        "kind": "yes_no",
        "follow_ons": {                          // keyed by the answer value
          "no": {"reactivate": "run"}            //   "no" re-activates this phase to ask again
        }
      },
      "outcomes": [
        {"outcome": "confirm", "wait_for": {"who": "user"}, "fields": []},
        {"outcome": "done", "resolution": "done", "fields": ["result"]}
      ]
    }
  ],
  "outcomes": [
    {"kind": "status", "phase": "check", "outcome": "done",
     "part": {"kind": "text", "text": "status/check/done",   // one content-store template id
              "fields": ["state"], "order": 0}},
    {"kind": "status", "phase": "check", "outcome": "declined",
     "part": {"kind": "text", "text": "status/check/declined",
              "fields": ["reason"], "order": 0}},
    {"kind": "status", "phase": "check", "outcome": "waiting",
     "part": {"kind": "talk", "fields": ["eta"], "to": "user", "order": 0}},
    {"kind": "utilities", "phase": "run", "outcome": "confirm",
     "part": {"kind": "options",
              "text": "utilities/run/confirm",
              "options": {"fixed": [{"value": "yes", "label": "utilities/run/yes"},
                                    {"value": "no", "label": "utilities/run/no"}]},
                                    //   a fixed label, when present, is a template id too
              "order": 0}},
    {"kind": "utilities", "phase": "run", "outcome": "done",
     "part": {"kind": "text", "text": "utilities/run/done",
              "fields": ["result"], "order": 0}}
  ],
  "parties": ["partner"],                         // bare names: the wait parties beyond the
                                                  //   engine's own "user" and "phase"; a
                                                  //   label's wait_for.who names one
  "clarify_kind": "clarify",                      // the generic kind an ambiguous span mints
  "unmatched_kind": "unmatched",                  // the kind a no-hit / many-hit event or no-hit form mints
  "clarify_limit": 3,                             // clarify rounds before "unresolved" is recorded
  "talk_slot": {                                  // the composed-talk slot (null = off)
    "prompt_ref": "agenda/talk_prompt",           // one content-store template id
    "transcript_window_lines": 10
  }
}
```

### Intents

An intent declares its `kind`, classifier `description`, an optional `sequence` group, its
`sources`, a `standing` predicate, and EITHER `items` (a fixed list; empty means the single
implicit item `_`) OR `item {field}` (one intent per item, the item identity read from a
classifier-extracted field — **exclusive** with `items`). A kind may also declare
`fields {<name>: <JSON-Schema>}`, the per-kind extraction schema merged into the classifier's
forced per-span object beside the mandatory `text`; an `item.field`, and every `rank.match`
key targeting the kind, must name one of those `fields`. A kind with `item {field}` is
single-item in the phase sense (its `item` segment is `_`); the item identity lives on the
ledger row, not in the phase key.

An intent's `standing` is a jq predicate **string** that decides whether a standing intent
of the kind should be open. Because it reads a **sibling state** — a value on another state
the client also binds, such as an opt-in flag — it lives in the **client's flow binding over
that state**, not in the agenda template (a template-scoped input jq sees only its own
attached subtree). The client's mint step evaluates it each pass and opens one intent of the
kind when it holds and none of the kind is already open (a `null` predicate contributes no
intent).

An intent's `sources` lists the record sources the kind admits — `inbound` a classifier
span, `event`/`tick` an event-door turn. When non-empty the record update ENFORCES it: a span
minted for the kind from a source the list excludes raises
`agenda-record: kind <kind> does not admit source <source>` (a `standing` or `follow_on`
record is exempt — those are not inbound sources).

### Phases, labels, and follow-ons

A **phase** names its `key`, `kind`, `item`, a `predecessor`, its `ordering_waits`, an
optional `expected_input`, and `outcomes` — the outcome **labels**
the phase can record. A phase names no handler — the flow runs its handler for the phase and
returns the outcome. A `predecessor` is a phase key (satisfied when that phase resolves
`done`), `"record"` (the intent's item order — satisfied once the earlier items' same phase
is settled in any way), a `{phase, outcomes}` object (satisfied when the predecessor phase's
current outcome is one of the named labels), or `null` (a first phase). `ordering_waits` is a
list of `{kind, phase}` cross-intent gates, `phase` `"*"` meaning every phase of the kind. An
`expected_input` declares its `kind` (`yes_no` | `text` | `option` | `form` | `event`) and
`follow_ons` keyed by the answer value. An `event` input is `{kind: "event", event, key,
via?}`: `event` is the event kind that correlates here, `key` the payload key whose value
names the item, and an optional `via` a jq program over the flow root (with `$key` bound to
the payload's `key` value). For an **item kind** (one-per-item, or a static-item phase) `via`
must yield the wait's item — the ledger row's item (`item_of`) for a one-per-item kind, else
the phase key's item segment — and the event binds the wait naming it; a `via` yielding
nothing binds no item wait, and an absent `via` matches the `key` value itself. For an
**itemless kind** (item segment `_`) a `via` yielding `null` — which a wait that declares no
`via` also yields — means **this event is ours** and binds the kind's single open expectation,
while a `via` yielding the empty no-value (jq `empty`) means **not ours** and leaves the event
unmatched. An event whose key matches **zero or several** waits binds nothing and mints one
`unmatched_kind` record; a payload missing `key` raises `agenda: event <kind> carries no
'<key>'`.

Each **label** is `{outcome, resolution? | wait_for {who}, fields, follow_on?}`, declaring
exactly one of `resolution` (a class) or `wait_for` (a party). A `resolution` is one of two
classes: **`done`**, the class the engine names, whose successors are reached and whose chain
continues; or a **not-done** resolution, which stops the chain — the intent runs out of open
phases, closes, and compose speaks that resolution. The engine names no not-done class of its
own: every not-done name is declared by the deployment in
[`parameters.resolutions`](#the-parameters) and referenced by name from a label. A `wait_for`
names the party holding the wait: `user` and `phase` are the engine's own (`who: "phase"`
waits on another phase named at run time by the handler's `waits_on`), and every other party
is declared in the manifest's `parties` list and referenced there by name. `fields` are the
fields the label carries; an optional `follow_on` fires when the label is recorded. A
follow-on is exactly one action:

```jsonc theme={null}
{"mint": "utilities", "item_field": "ref"}     // open an intent of a kind; item_field = the
                                               //   source field whose value is the item to target
{"reactivate": "run"}                          // re-activate an earlier phase of the same intent
{"close": {"kind": "utilities",                // settle ANOTHER open intent with a declared resolution
           "item_field": "ref",                //   target the open (kind, item) intent by a source field…
           "outcome": "deferred"}}             //   …or "item": <static id> on an items-list kind,
                                               //   the outcome a declared not-done resolution
{"rank": {"kind": "utilities",                 // reprioritise an open intent of a SEQUENCED kind
          "ordinal": 3, "match": null,         //   exactly one of ordinal (n-th open, 1-based) / match (field equality)
          "first": true, "rank": null}}        //   first → below the group's current minimum; else the given number
```

An `item_field` on a `mint` **binds** the open `(kind, item)` intent's waiting phase (else
its next reached-unsettled phase, else mints a fresh record carrying that `item`); an
`item_field` on a `close` settles the open intent of that `(kind, item)`.

A `mint`'s `item_field` may carry a **list**: the mint then **fans out**, applying that same
match-else-mint rule once PER ELEMENT in the same pass — one record (or bind) per element, each
carrying its element as `item` and starting its own pass count. An empty list places nothing;
an absent value still raises. A fan-out targets a kind that declares `item {field}` (one intent
per item), so each record carries a single element — a list into any other kind raises at
attach, as does a list on a `close`, which names one item.

A follow-on rides an outcome label's `outcomes[].follow_on`, and a label's readable fields
ride `outcomes[].fields`.

### Parts rendered from fields

An **outcome** maps a `(kind, phase, outcome)` to a reply **`part`**. Every outcome row
carries its `kind`: a phase key can repeat across kinds, so `(kind, key)` is the phase
identity everywhere — in the outcome map, the `expectations` items, and the attach check. A
part is **rendered from the row's `fields`**, never a literal payload:

| `part.kind` | Speaks                            | Shape                                                                                                                                                                                                                                                                                                                                   |
| ----------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text`      | a message                         | `text` (one content-store template id) rendered with the row's `fields`; the platform's renderer resolves the locale from the subject                                                                                                                                                                                                   |
| `options`   | a message + tappable/link options | `text` (mandatory) plus `options: {field}` (replies from a list field of strings or `{text, description?, id?}` objects) or `{fixed: [{value, label?}], links?}` (each fixed value a reply, unless `links` names it — then a link button whose url is the named field; a fixed `label`, when present, is one content-store template id) |
| `form`      | a message + a form                | `text` (mandatory) plus `schema_field` (the field holding the answer schema) or a literal `schema`; an optional `values_field` names a field holding an object of top-level property → value that **pre-fills** the form                                                                                                                |
| `template`  | a rendered provider template      | `text` (mandatory) plus `{id, language, params: {<param>: <field>}}` — `language` is the provider template's registered language code (not a locale the template selects)                                                                                                                                                               |
| `media`     | media (no text required)          | `{url_field \| url, kind?}`                                                                                                                                                                                                                                                                                                             |
| `talk`      | nothing directly                  | its `fields` become a **fact** handed to the composed-talk slot                                                                                                                                                                                                                                                                         |
| `carried`   | nothing directly                  | `{phase, outcome}` — its `fields` merge into the named same-intent carrier's render context under `carried.<label>`                                                                                                                                                                                                                     |
| `none`      | nothing                           | the row is still stamped `sent`                                                                                                                                                                                                                                                                                                         |

A part may also declare `repeat_over` (a list field — compose emits ONE part per element, the
element bound as `item` in the render context) and `to` (`user`, the default, or a
party the manifest declares in `parties` — a part sent to a declared party leaves compose as
a **`deliveries`** entry, never in the reply to the user). `fields` lists the fields the part
reads; every field the part names —
`fields`, `field`, `schema_field`, `values_field`, `repeat_over`, a `links` target, a `params`
value, `url_field` — must be a member of the label's `fields`. A form's `values_field` prefill
is forwarded as the platform's per-send form `data.values`; compose refuses loudly (naming the
part and the key) a key that is not a top-level property of the form schema, or a value that
does not satisfy that property's schema — never a silent partial prefill. A client seeds each `part.text` path
through the platform's template-upload door — `POST /api/upload-template` with `{path,
content}` writes the content-store path the part names — the surface the
[stored-content guide](/guides/manage-stored-content) documents.

### The composed-talk slot

`talk_slot` (optional; `null` = off) turns on the composed-talk slot:
`{prompt_ref: <path>, transcript_window_lines}`, where `prompt_ref` is one content-store talk
prompt template id and `transcript_window_lines` (default 10) bounds
the transcript window. When declared, compose runs the talk step after the
fixed parts render, handing it the `talk` facts, the fixed parts, the prior transcript window,
the inbound text, the prompt, and the classifier's `turn_talk` text; it speaks ONE verified
reply appended last (see [the compose step](#the-compose-step)). A `talk`-disposition part with no
`talk_slot` declared is refused at attach.

## The rules the attach check enforces

The template's `declarations.check` is the agenda's structural rule; it runs at attach, names
the first offender, and the attach **refuses** on any message it returns. In an action message
`<via>` is `follow_on.<label>` (a label's follow-on) or `answer.<key>` (an
`expected_input.follow_ons` entry).

| The check refuses when…                                                                                                                                                 | Its refusal                                                                                                                                                        |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| the mint graph (phase → a label's `follow_on.mint` / `follow_ons.mint` → kind) has a cycle                                                                              | `mint graph has a cycle through kinds: <kinds>`                                                                                                                    |
| a sequenced kind declares more than one item                                                                                                                            | `kind '<kind>': a sequenced kind must be single-item`                                                                                                              |
| a `rank` gives both or neither of `ordinal`/`match`                                                                                                                     | `phase '<phase>' <via>: rank needs exactly one of ordinal/match`                                                                                                   |
| a `rank` targets a kind in no sequence group                                                                                                                            | `phase '<phase>' <via>: rank targets kind '<kind>' which is not in a sequence group`                                                                               |
| a `rank` `match` key is not a declared field of the target kind                                                                                                         | `phase '<phase>' <via>: rank match key '<key>' is not a declared field of kind '<kind>'`                                                                           |
| a `close` names a static `item` the kind does not declare                                                                                                               | `phase '<phase>' <via>: close item '<item>' not declared on kind '<kind>'`                                                                                         |
| a `close` names a static `item` on a kind declaring `item {field}`                                                                                                      | `phase '<phase>' <via>: close.item on item-kind '<kind>' — target by item_field`                                                                                   |
| a `mint` names an unknown kind                                                                                                                                          | `phase '<phase>' <via>: mint targets unknown kind '<kind>'`                                                                                                        |
| a `reactivate` names a phase key the acting phase's kind does not declare                                                                                               | `phase '<phase>' <via>: reactivate targets unknown phase '<phase>' in kind '<kind>'`                                                                               |
| a `close` names an unknown kind                                                                                                                                         | `phase '<phase>' <via>: close targets unknown kind '<kind>'`                                                                                                       |
| a `close` `outcome` is not a label of EVERY phase of the target kind                                                                                                    | `close from '<phase>' needs outcome '<outcome>' on phase '<key>'`                                                                                                  |
| a `close` `outcome` names a non-`resolution` label                                                                                                                      | `close from '<phase>' outcome '<outcome>' must be a resolution label on phase '<key>'`                                                                             |
| an `item_field` is not a field of its source label                                                                                                                      | `phase <kind>.<key>: label '<label>' item_field '<field>' is not in the label fields`                                                                              |
| a kind declares both `item` and `items`                                                                                                                                 | `kind '<kind>': declare one of item or items, not both`                                                                                                            |
| an `item.field` is not in the kind's `fields`                                                                                                                           | `kind '<kind>': item field '<field>' is not declared in fields`                                                                                                    |
| a phase kind or key is not id-safe (each heads a phase key)                                                                                                             | `phase kind '<kind>' is not id-safe (letters, digits, underscore only)` / `phase key '<key>' …`                                                                    |
| the same `(kind, key, item)` phase is declared twice                                                                                                                    | `phase <kind>.<key> item <item> is declared twice`                                                                                                                 |
| two distinct `(kind, key)` phases collide under the phase-key encoding                                                                                                  | `phases <k1>.<p1> and <k2>.<p2> collide under the phase-key encoding`                                                                                              |
| an `ordering_waits` entry is not a `{kind, phase}` object                                                                                                               | `ordering wait must be {kind, phase}`                                                                                                                              |
| an `ordering_waits` entry names an undeclared kind                                                                                                                      | `ordering wait of phase '<key>' names unknown kind '<kind>'`                                                                                                       |
| an `ordering_waits` entry names (non-wildcard) an undeclared phase                                                                                                      | `ordering wait of phase '<key>' names unknown phase '<kind>.<phase>'`                                                                                              |
| the `ordering_waits` graph over `(kind, phase)` nodes has a cycle (a self-wait is a length-1 cycle)                                                                     | `ordering waits form a cycle through <kind.phase …>`                                                                                                               |
| the `predecessor` graph within a kind has a cycle                                                                                                                       | `predecessor chain of kind '<kind>' forms a cycle through <keys>`                                                                                                  |
| an `event` input's `event` is blank                                                                                                                                     | `` phase <kind>.<key>: event input needs a non-blank `event` ``                                                                                                    |
| an `event` input's `key` is blank                                                                                                                                       | `` phase <kind>.<key>: event input needs a non-blank `key` ``                                                                                                      |
| a label declares neither or both of `resolution`/`wait_for`                                                                                                             | `phase <kind>.<key>: label '<label>' must declare exactly one of resolution or wait_for`                                                                           |
| a label's `wait_for` names a `who` that is neither `user` nor `phase` nor a party the manifest declares                                                                 | `phase <kind>.<key>: label '<label>' waits on '<who>', which is neither 'user' nor 'phase' nor a party declared in \`parties\`\`                                   |
| a `predecessor` object names a label the predecessor phase does not declare                                                                                             | `phase <kind>.<key>: predecessor label '<label>' is not declared on phase '<phase>'`                                                                               |
| two `outcomes` rows share a `(kind, phase, outcome)`                                                                                                                    | `duplicate outcome row for <kind>.<phase> <outcome>`                                                                                                               |
| an outcome row's `(kind, phase)` names no declared phase                                                                                                                | `outcome row names unknown phase <kind>.<phase>`                                                                                                                   |
| an outcome row names a label the phase does not declare                                                                                                                 | `outcome row <kind>.<phase> names label '<label>' the phase does not declare`                                                                                      |
| a declared label has other than exactly one map row (full coverage both ways)                                                                                           | `phase <kind>.<key>: label '<label>' has no outcome row` / `… has <n> outcome rows`                                                                                |
| a part reads a field the label's `fields` omits                                                                                                                         | `outcome <kind>.<phase> <outcome>: part reads field '<f>' the label does not declare in fields`                                                                    |
| a part names a field (`options.field`, `schema_field`, `values_field`, `repeat_over`, a `links` target, a `params` value, `media.url_field`) the label's `fields` omits | `outcome <kind>.<phase> <outcome>: part names field '<f>' the label does not declare in fields`                                                                    |
| an interactive part (`options`/`form`/`template`) carries no non-empty `text` template id                                                                               | `outcome <kind>.<phase> <outcome>: an interactive part (<kind>) requires a non-empty text template id`                                                             |
| a fixed option's `label`, when present, is not a non-empty template id                                                                                                  | `outcome <kind>.<phase> <outcome>: fixed option '<value>' label must be a non-empty template id`                                                                   |
| a part is sent (`to`) to a party that is neither `user` nor one of `parties`                                                                                            | `outcome <kind>.<phase> <outcome>: part is sent to '<to>', which is neither 'user' nor a party declared in \`parties\`\`                                           |
| a `mint` fans out over a source array field into a kind that declares no `item {field}`                                                                                 | `phase <kind>.<key>: label '<label>' mints '<kind>' per element of list field '<field>', so kind '<kind>' must declare item {field}`                               |
| a `close` names a source array field (a close names ONE item)                                                                                                           | `phase <kind>.<key>: label '<label>' closes by list field '<field>', but a close names ONE item`                                                                   |
| a `carried` part names a target row that does not exist                                                                                                                 | `outcome <kind>.<phase> <outcome>: carried target <kind>.<phase> <outcome> has no outcome row`                                                                     |
| a `carried` part names a target that is itself `carried`/`none`                                                                                                         | `outcome <kind>.<phase> <outcome>: carried target <phase> <outcome> is itself <kind> (carries no part)`                                                            |
| a `talk`-disposition part is declared but `talk_slot` is null                                                                                                           | ``label <label> is a talk row but `talk_slot` is null``                                                                                                            |
| the clarify kind lacks a `which` wait label bound to `user`                                                                                                             | `clarify kind '<ck>' must declare a 'which' wait label with who 'user'`                                                                                            |
| the clarify kind lacks an `unresolved` label carrying a declared not-done resolution                                                                                    | `clarify kind '<kind>' must declare an 'unresolved' label with a not-done resolution (a resolution other than 'done', from the attach parameter \`resolutions\`)\` |
| the `unmatched_kind` is not a declared intent kind                                                                                                                      | `the unmatched kind '<uk>' is not a declared intent kind`                                                                                                          |
| the `unmatched_kind` declares no phase                                                                                                                                  | `the unmatched kind '<uk>' must declare at least one phase`                                                                                                        |
| no phase of the unmatched kind carries a not-done label                                                                                                                 | `the unmatched kind '<kind>' must declare a phase with a not-done label (a resolution other than 'done', from the attach parameter \`resolutions\`)\`              |
| the `unmatched_kind`'s non-empty `sources` does not admit both `event` and `inbound`                                                                                    | `the unmatched kind '<uk>' must admit sources event and inbound`                                                                                                   |

The `close` rule spans every phase of the target kind because the target phase is chosen at
run time, so the outcome map must cover the resolution for all of them. The manifest declares
**no** data-fact preconditions, no discard rules, and no thresholds — dueness is computed
from the ledger, never gated on stored values.

The same attach seam also compiles each jq the manifest declares against the template's
schema — a phase's `via` correlation program — and refuses one that does not compile or whose
value type contradicts what the field expects. The cross-state `standing` predicate is NOT
checked here: it reads another state and lives in the client's binding, so the client's flow
validates it against that state.

### Re-attaching over open intents

An attach that replaces the manifest while intents are still open is checked against those open
records too. When an open intent names a kind, a phase, an outcome label, or a part the NEW
manifest no longer declares, the attach is **REFUSED** with `422` and the refusal lists every
orphaned record — the declarations never change under a conversation whose next turn could no
longer resolve:

```
re-attaching template '<template>' on state '<state>' would orphan <N> open record item(s) the
new declarations no longer cover: [<kind>:<key>] <label> (id <id>); … and <N> more. Re-attach
with options {"orphans": "close", "resolution": "<not-done resolution>"} to close them.
```

Open intents whose declarations survive are untouched: their ledger rows, waits, and bindings
cross the attach unchanged.

To retire those declarations anyway, the caller attaches with `options.orphans = "close"` and
names a declared not-done resolution in `options.resolution` (one of the new manifest's
resolution values other than `done`; a name the new declarations do not declare is itself
refused). The attach then **closes** every intent the new manifest no longer covers with that
resolution instead of refusing — set on the intent's open phase rows and marked `closed` on
its ledger row. The close carries no `sent_run`, so it is unspoken: the next mint's prune drops
the intent and compose says nothing for it.

## The parameters

Two attach parameters tune the template without editing its document:

* **`resolutions`** — the enum schema substituted into the `resolution` field at the
  `{"$parameter": "resolutions"}` marker, default `{"enum": ["done", null]}`. `done` is the
  only resolution the engine names; every OTHER member of the enum is a **not-done** name the
  deployment declares here, and a label referencing one closes its intent instead of reaching
  the phase's successors. The template jq read the allowed set as
  `$parameters.resolutions.enum`; a deployment names its own classes (`deferred`, `expired`,
  `declined`) and keeps the wording neutral, so none inherits a business vocabulary.
* **`yes_no_words`** — the bare yes/no word list the mint binds without the classifier,
  default `{"yes": ["yes", "y"], "no": ["no", "n"]}`, read as `$parameters.yes_no_words`.

## The template jq

The agenda's `template_jq` split into two purposes. The **`input`** programs are read-only
predicates over the record subtree — the client's flow reads them through the
[template-jq prelude](/babelfish#the-template-jq-prelude) (`tjq_<name>({…})`) or the record
`GET …/template-jq/{name}` route to decide what is due each pass. `.` is the attached record
subtree; `$declarations` is the manifest, `$parameters` the attach parameters, and a program's
declared `params` ride the single object `$params`. The core predicates read verbatim:

| Program              | Params             | What it answers                                                                                                                                                                                                                                                                                                                                                                                                                        |
| -------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reached`            | `(phase_key)`      | the predecessor is satisfied: a first phase always; a phase-predecessor phase only when that predecessor's current outcome is `done`; a `record`-predecessor phase once the earlier items' same phase is settled in any way; a `{phase, outcomes}` predecessor when the predecessor's current outcome is one of the named labels                                                                                                       |
| `pending`            | `(phase_key; run)` | `reached ∧ (no current outcome ∨ (reactivated this run ∧ ¬outcome_after_binding) ∨ stale)` — a phase that WANTS to fire this run, ordering aside                                                                                                                                                                                                                                                                                       |
| `stale`              | `(phase_key; run)` | the phase's predecessor holds a current outcome that is settled, reaches (the named label for a `{phase, outcomes}` predecessor, a `done` resolution for a bare phase name) and was recorded THIS run, while the phase's own current outcome is from another run or from an earlier pass of this run — a re-settled predecessor makes its successors due again. A `record` predecessor and a phase with no predecessor are never stale |
| `ordering_satisfied` | `(phase_key; run)` | for each `ordering_waits` `{kind, phase}`, no matching phase of ANY open intent of that kind is still `pending` — the acting intent included, the waiting phase itself excluded, `phase: "*"` matching every phase of the kind; a settled/waiting/never-reached target satisfies, only a reached-but-unfired one blocks                                                                                                                |
| `phase_due`          | `(phase_key; run)` | `pending ∧ ordering_satisfied` — the branch condition a pass fires on                                                                                                                                                                                                                                                                                                                                                                  |
| `active`             | `(id; run)`        | an intent minted this run ∨ with any pending phase ∨ with any phase bound this run — reads `pending`, never `due`, so ordering never masks an intent inactive                                                                                                                                                                                                                                                                          |
| `item_of`            | `(id)`             | the intent's `item` identity, or `null` for an itemless kind                                                                                                                                                                                                                                                                                                                                                                           |

The rest of the 56 input programs: `answer_target`, `answered`, `anything_due`, `bound`,
`clarify_rows`, `clarify_step`, `close_targets`, `closed`, `complete`, `compose`,
`current_outcome`, `deadlocked`, `done_outcome`, `due`, `due_set`, `expectations`,
`fill_target`, `follow_on_instances`, `group_of`, `has_open_phase`, `intent_has_inbound`,
`intent_row`, `intent_user_wait`, `intent_wordless`, `items_of`, `key_parts`,
`manifest_phase`, `open_clarify`, `open_intents`, `open_option_expectations`,
`open_yes_no_expectation`, `outcome_after_binding`, `pending_follow_ons`, `placed`,
`rank_targets`, `reactivated`, `reply_safe`, `resolve`, `settled`, `settled_this_run`,
`turn_source`, `unresolved_class`, `via`, `waiting`, `wordless_conflict`, and the graph
helpers the attach check reads to refuse cycles (`mint_adjacency`, `ow_adjacency`,
`seq_edges`, `reachable_cyclic`).

`due_set` orders the due items by intent `rank`, ties by the total mint order, one grouped
intent per pass; `expectations` returns the open waits `[{key, kind, phase, outcome, who,
expected_input, fields, question_ref, schema_field, schema, options}]`, the `expected_input`
joined from the manifest phase (the wait row carries only `wait_for` and `fields`), the
`schema_field`/`options` read from the outcome map's part — so the mint binds a form re-entry
by the row's own schema (`fields[schema_field].required`) and an option tap by the row's own
candidates, never by a static phase schema. `pending_follow_ons` reads the same `placed`
predicate the seed writes against, so the pair never disagrees — a branch never fires over an
empty slice, and the loop never over-runs a placed follow-on.

`complete` asserts every reached phase has settled or is waiting, and it holds wherever the
loop stops, because the attach check keeps each kind's `ordering_waits`, `predecessor`, and
mint graphs acyclic. At loop end nothing is pending, or the loop would not have stopped; a
reached phase that is neither settled nor waiting would be pending but ordering-blocked, so
its blocker is a pending same-kind phase, whose blocker is another pending phase, and so on —
a chain that, over an acyclic wait graph, must end at a pending, ordering-satisfied phase,
which is due. That contradicts the loop having stopped, so `complete` failing is a real bug —
a handler that fired without recording an outcome — never a benign ordering stall.

The `stale` disjunct re-opens settled phases, and it terminates for the same reason: a stale
phase stops being stale once it re-records at a pass at or after its predecessor's, each
re-record stamps a pass no earlier than the predecessor's, and the predecessor graph is a DAG
(the attach check refuses a cycle), so the re-reach cascade runs down finitely many edges. A
settled phase whose predecessor was re-settled this run is therefore due, not `complete`'s
business: `complete` stays true over it while the loop keeps going, exactly as it does over a
reactivated open wait.

### The update programs

The **`update`** template jq write the ledger. Each takes `{record, input}` — `.record` the
current subtree, `.input` the value the [binding adapter](/babelfish#binding-a-node-to-a-state)
shaped from the node's output — and returns a template-relative op batch under the writer
regimes; the record `POST …/template-jq/{name}` route (or a node binding's update) applies it.
The five are:

* **`mint`** — turns each inbound into records or binds it to an open expectation WITHOUT
  language judgment (`writes` `ledger`, `ledger_phases`, `ledger_inbound`). It mints standing
  intents whose predicate holds, binds an `event` turn by `expected_input.event` and its `via`
  to the waiting phase whose item matches, binds a `form` submission by its own row schema,
  binds a tapped `option` by its own row candidates, and binds a bare yes/no (via
  `yes_no_words`, no classifier call) when exactly one `open_yes_no_expectation` is open. An
  event with zero or several hits, or a form with no hit, mints an
  [`unmatched_kind`](#how-re-entry-binds) record; a form with several hits opens a clarify. It
  also **prunes** intents already closed and no longer referenced, and computes the open
  expectations the classifier reads.
* **`record`** — takes the classifier's structured `verdict` (or null) and writes per-kind
  spans as `ledger` records (`source` from `turn_source`, `item` from the kind's `item {field}` when declared), `answers` bindings as `ledger_inbound` rows, and each `ambiguous`
  span as a clarify — re-binding an already-open clarify and climbing its `attempt` up to
  `clarify_limit`, at which point it records the `unresolved` label (`writes` `ledger`,
  `ledger_phases`, `ledger_inbound`, `turn_talk`). With a `talk_slot` declared and a non-blank
  `verdict.talk`, it is the sole writer of `turn_talk`. Before it writes a row it refuses two
  bindings that resolve to one phase key in one pass
  (`agenda: two bindings resolve to phase <key> in one pass; a phase is answered once per pass,
  never twice`) and an ambiguous binding naming fewer than two candidates
  (`agenda: ambiguous binding on <span> names <n> candidate(s); ambiguous needs two or more` —
  the classifier contract, not the template, guarantees an ambiguity has options to choose
  between).
* **`seed`** — runs every pass; re-evaluates standing conditions and places pending follow-ons
  by one **match-else-mint** rule (bind to the open intent's waiting phase, else its next
  reached-unsettled phase, else mint a new record — never a closed intent) (`writes` `ledger`,
  `ledger_phases`, `ledger_inbound`). Follow-ons come from a wait's answer and from each label
  (resolution OR wait) recorded this run, read as `label.follow_on`. A `close` writes the
  target's open phase a full outcome row visible to `due` in the SAME pass; a `rank` is a
  `merge_by_key` on the target `ledger` row. Every action is bounded by the shared `placed`
  predicate — one placement per instance — and, before it writes a row, seed refuses two
  follow-ons that resolve to one phase key or to one intent id in a pass
  (`agenda: two follow-ons resolve to phase <key> in one pass; …` /
  `agenda: two follow-ons resolve to intent <id> in one pass; …`).
* **`outcome`** — validates each phase's returned `{key, outcome, fields, waits_on?}` (the
  `key` is due this pass; the `outcome` names a label the fired phase declares; every `fields`
  key is one the label lists; a `resolution` label's derived class is among the attached
  `resolutions`; a `wait_for` label's derived party is `user`, `phase`, or one of the
  manifest's declared `parties`; a `who: "phase"` label requires `waits_on`) and writes one
  `ledger_phases` row per outcome — the class and party read from the declaration, never the
  handler (`writes` `ledger_phases`). Two results carrying one key are refused before a row is
  written (`agenda: two results resolve to phase <key> in one pass; …`). A breach raises loudly.
* **`compose_mark`** — after the pass loop, writes the `closed` mark on every fully-settled
  intent, the `sent` run stamp on each rendered row, `last_outbound` `{run}` when the turn
  speaks, and the `transcript` window (`writes` `ledger`, `ledger_phases`, `transcript`,
  `last_outbound`). See [the compose step](#the-compose-step).

`reconcile` is the template's own three-program set (`orphans`, `close`, `resolutions`) the
platform runs when a [re-attach](#re-attaching-over-open-intents) retires declarations over
open records.

## The compose step

Compose runs after the pass loop, reading the rows the outcome update committed. The `compose`
input program first asserts `complete($run)`, else the flow raises
`agenda: incomplete run: <intents>` (the open intents' ids) — which becomes the route's error
reply. Then, for each row of `settled_this_run` and each open wait whose question has not yet
been `sent`, it
resolves the outcome map's part — keyed `(kind, phase, outcome)` — and renders it **from the
row's `fields`**:

* **`text`** renders through `render_text` over the part's content-store template id, the
  row's `fields` as the render context, and the platform's renderer resolves the locale from the
  subject — the template selects no language variant. A `text` that is not a template id string
  raises `agenda: part text for phase <key> is not a template id` rather than sending an empty
  message.
* **`options`**, **`form`**, **`template`**, **`media`** build their surfaces from the same
  fields — `options` from a list field or the fixed values, `form` from `fields[schema_field]`,
  `template`/`media` from their named fields. A `repeat_over` part fans out to one part per
  element of the named list field, the element bound as `item`.
* **`talk`** hands its fields to the talk slot as a fact and speaks no part; **`carried`**
  merges its fields into the named same-intent carrier; **`none`** speaks nothing but is still
  stamped `sent`. A carried row whose carrier is not spoken this turn raises
  `agenda: carried row <label> has no carrier this run` — unless the row is an open wait and
  the carrier's current outcome is the label the part names and already carries a `sent`
  stamp, in which case the question stands as already asked: the row sends nothing, renders no
  unit, and is stamped `sent` this run. (A reactivation re-records an open wait and drops its
  `sent` stamp, so compose meets the wait again while its carrier stays unsent.)
* a **`to`** naming a declared party routes the part into **`deliveries`** — a list of
  `{to, part}` the client's own `send` nodes deliver — instead of the reply to the user.

A part whose `fields` names a field the row does not carry raises
`agenda: part <kind>.<phase> <outcome> needs field '<f>' the row does not carry`, and an
outcome with no matching map row raises `agenda: no part for <kind>.<phase> <outcome>`
**before** any closed mark, so an unmapped outcome never silently closes its intent.

**The talk slot.** When the template declares a `talk_slot` and either a `talk` fact was handed
or the classifier supplied `turn_talk` text, the talk step runs after the fixed
parts render, and its one verified reply appends **last**. The classifier's talk is an INPUT
to the slot, never spoken as a part directly, and when the slot is declared but no fact and no
classifier talk exists, no talk part is emitted. The slot's `check` enforces two fatal
rules and one non-fatal one:

1. every handed fact leaf must appear in the text, else
   `agenda-talk: fact <label>.<field> not spoken` (fatal);
2. every id-shaped token (`[A-Z][A-Z0-9]*-[0-9]+`, e.g. `A-42`) must trace to a handed source
   — a fact, a fixed part, a transcript line, or the inbound — else
   `agenda-talk: text carries '<tok>', which was not handed` (fatal);
3. every bare numeric token not found in those sources is NON-FATAL — collected on
   `unverified_numbers` (visible in the run record's node output) so ordinary prose numbers
   ("two options", "in 5 minutes") never fail a turn.

`compose` returns `{parts, deliveries}` — `parts` the ordered `AnswerPart[]` the route returns
as its reply, `deliveries` the other-party parts. The `compose_mark` update then writes the
`closed` mark on every fully-settled intent, the `sent` run stamp on each rendered row (the
guard that sends a wait's question exactly once, never re-asking an open wait whose question
already went out), `last_outbound` `{run}` whenever this turn speaks (a silent turn re-emits
the prior pointer), and the `transcript` window (written after the talk, so the stored outbound
line includes the talk text).

## Using the agenda from a flow

A client attaches the agenda template to a state and drives it from one flow, wiring the pieces
the template does not itself run: a classifier, the pass loop, and the reply. There is no
shipped node, no lowering, and no chips — the template is data, and the flow orchestrates it
through [per-node bindings](/babelfish#binding-a-node-to-a-state) and the
[template-jq prelude](/babelfish#the-template-jq-prelude):

* **mint** — the flow applies the `mint` update at the start of the turn (input injections
  read the open `expectations` and `turn_source`), turning the inbound into records or binding
  it to an open expectation.
* **classify** — the flow's own classifier node (a `tools_agent`) runs once over the inbound
  when the mint left spans to classify; its forced response schema and prompt are built by jq
  from the manifest's intents. The flow applies the `record` update over the verdict.
* **the pass loop** — a [Router](/babelfish#routing-with-an-llm) in
  [Loop](/babelfish#dispatch-once-or-loop) mode runs the passes: each pass applies the `seed`
  update, reads `due_set`/`anything_due` (the loop's stop condition) through the prelude,
  dispatches each due item to the flow's handler for that intent — one run per due item, an
  unsequenced kind in parallel, a sequenced kind one at a time in rank order — and applies the
  `outcome` update over each handler's `{key, outcome, fields, waits_on?}`.
* **compose** — once the loop stops, the flow reads the `compose` input program for the reply
  and applies the `compose_mark` update; the route returns `.parts` and feeds `deliveries` to
  its own `send` nodes.

Because the agenda is a state template, it appears on the platform
[States screen](/studio/screens#states): it lists among the state-template documents, and a
client attaches it to a state and edits the attachment's `declarations` there — its manifest.

A handler for an intent reads the due item — its `{key, intent, item, kind, phase, rank,
fields}` — from the pass, does its work through the flow's own tools or a published flow, and
returns `{key, outcome, fields, waits_on?}` where `outcome` is a label the fired phase declares
and `waits_on` is supplied only for a `who: "phase"` wait. The handler reads state only through
the flow's own bindings and never writes the agenda ledger directly — the flow's `outcome`
update records the returned label, deriving the class and party from the declaration.

## The pass-loop rules

* **No asks inside the pass loop.** A handler never contains an `ask` or any interactive
  preset: a parking ask inside a fan-out re-runs its siblings on resume. An outbound question
  is a `wait_for` label the handler returns, spoken by compose — never a mid-loop prompt.
* **No preconditions on data facts.** Dueness comes only from the phase graph and the
  ledger's outcomes and bindings; the manifest declares no thresholds or stored-value gates.
* **A hit cap is a bug.** The loop's max-runs cap (default **20**) is the backstop, not a
  conversation state: reaching it raises, and the failure travels the flow's ordinary
  [`on_error`](/babelfish#handling-a-nodes-error) path. The template computes no static pass
  bound and the manifest declares none; what makes the loop finite is the attach check's
  refusal of a cycle in the `predecessor`, `ordering_waits` and mint graphs, plus the rule
  that a fired phase records an outcome and so stops being due. The cap on the client's loop
  must be generous enough for the manifest's longest predecessor/ordering-wait chain, which
  the client sizes from its own declarations.

## How re-entry binds

No outbound part carries a correlation key (`AnswerPart` forbids extras), so a later inbound
binds to an open wait by MATCH, and the `mint` update does it without the classifier:

* an **event** binds to the waiting phase whose `expected_input.event` names the event's kind
  and whose `via` (a jq program compiled over the flow root, `$key` bound to the payload's
  `key` field; absent means the `key` value itself) resolves to the wait's item — the ledger
  row's item for a one-per-item kind (`item_of`), else the phase key's item segment, else the
  intent id. The [event door](/reference/conversation-bridge#the-event-door) delivers it as a
  turn, and a payload missing that `key` raises `agenda: event <kind> carries no '<key>'`. An
  event with **zero or several** matching waits binds nothing and instead mints one
  `unmatched_kind` record; a malformed event (no `id`/`kind`) raises
  `agenda: malformed event turn — an event carries an id and a kind`.
* a **form** submission binds to the open form-wait whose ROW-carried schema's required
  properties are all present; **several** matches open a `clarify_kind` intent, **no** match
  mints an `unmatched_kind` record carrying the form payload;
* a tapped **option** arrives as the option's value in the message and binds to the single
  open option-wait whose ROW candidates hold that value;
* a bare **yes/no** binds when exactly one yes/no wait was asked in the **last outbound** line
  — the mint matches it against `open_yes_no_expectation`, so a yes/no never lands on a
  question two turns old.

**The unmatched kind.** An event that matches no wait (or several), and a form that matches
none, mints one record of the manifest's `unmatched_kind` (default `unmatched`) —
`{kind, source, event: <payload>, fields: {reason, candidates, inbound_kind}, rank,
minted_run}`, `reason` `no_match` (zero) or `ambiguous` (several), `candidates` the hit phase
keys. It reaches its own phase and is resolved by that kind's handler (a `done` or a declared
not-done resolution) like any other intent, and compose speaks its part. The record bypasses
the record update's `sources` gate, so the check enforces the unmatched kind's admission statically (its `sources`
must be absent/empty or cover both `event` and `inbound`); the mint never consults `sources`
at run time.

**Wait parties.** A wait's `who` is `user`, `phase`, or a party the manifest declares in its
`parties` list and the label names. `user` and `phase` are the engine's own. The classifier
binds a span only to a `user` wait — the platform-owned expectations instruction tells the
model so, and every expectation item carries its `who`. A declared party is held for someone
off this conversation: its wait wakes on an event of its declared kind delivered through the
[event door](/reference/conversation-bridge#the-event-door) (whatever posts that event is a
deployment-side adapter). A `phase` wait resolves internally when the phase its handler named
in `waits_on` records a resolution.

## Termination

The loop always terminates. `due` excludes a phase with an outcome after its latest binding
this run, and every fired handler records an outcome (the compose assertion catches one that
did not), so a phase never re-fires. A phase-predecessor chain ends the moment a phase
resolves to anything other than `done`: a not-done resolution leaves every phase successor
unreached, so the intent runs out of open phases and compose closes it, speaking that
terminal resolution. Item order under a `record` predecessor is the exception — it advances on
any resolution of the earlier items, so a not-done resolution on one item never drops the
items that follow.

Cross-intent waits are a **DAG** over `(kind, phase)`: the attach check refuses a cycle in the
`ordering_waits` graph, the `predecessor` graph within a kind, and the mint graph, so a chain
of waits always bottoms out in a due phase and a fresh intent can never be minted every pass.
Re-activations, closes, and ranks are each bounded by the `placed` predicate — one placement
per instance, an empty target set counting as placed — so a settled-first target never keeps
the loop alive. A **closed** intent is never re-opened, and it is pruned once no open intent
still names one of its phases. The pass cap is the backstop, and hitting it is a manifest bug.
