Skip to main content
A judgment usually reads one document. A relation lets it read more: the documents that point at it, the one it points at, what other judgments answered for them, the candidates it could match, or the document as the judgment last saw it. Each kind answers a question that is otherwise a pipeline you build and keep alive, and every kind follows the same rules: one hop, stable renderings, and no routine work that waits for a person. Documents point at each other through an ordinary attribute you write, such as attributes.account_id holding an account’s id. A relation is a key under related in the judgment’s context recipe, except previous, which is its own key.

Kinds of relation

In each diagram, the document with the heavy border is the one being judged.

Documents that point at the judged one

An account read with its tickets, a user with their posts, a conversation with its messages. Use it when the answer depends on a record’s recent children, as text, counts or both. See judge a document with its related documents. Each ticket carries its account’s id in attributes.account_id, so the account’s judgment reads its newest tickets, as text, counts or both. A new or edited ticket makes the account pending, and it is judged again.

The document it points at

An order line read with its product, a message with its conversation. Use it when many records share one parent whose changes should reach them all; a change re-judges them, a fan-out. See judge a document with the document it points at. Each order line carries its product’s id in attributes.product_id, so every line is judged with the product it points at. When the product changes in a way the lines read, every line in scope is re-judged, within the judgment’s rolling limit.

Other judgments’ answers

An account read with what its conversations were judged to be, such as each one’s frustrated answer, banded or cut. Use it when you already run a judgment on the children and the parent should follow it without a copy pipeline. See roll up answers from related documents. Each conversation already has a frustrated answer. The account reads those answers, not the conversations’ text, for example as a count of frustrated conversations this month. When a conversation’s answer changes what the account reads, such as crossing the cut, the account is re-judged.

Candidates that share a key

A payment read with the open invoices in its block, and a choice that picks the one it settles. Use it to match one record to another, or to find a record’s duplicate, in one call per record rather than one per pair. See match one record to another. The payment and its open invoices carry the same block key, here the counterparty and currency, so they land in one block. One choice judgment reads the invoices in the block and picks the one the payment settles, or none of them.

The document as the judgment last saw it

A listing read beside the version its last verdict judged, or the version last approved. Use it when the question is about a change: a material edit, a changed clause, an edit that tries to get past moderation. See judge what changed since the last verdict. The judgment reads the listing as it is now beside the version its last verdict saw, or the version last approved, and answers whether it changed materially. An edit to a field the judgment doesn’t read changes nothing and costs nothing.

Groups, for reporting

Counts, sums and averages per key value, such as frustrated conversations by plan this month, over your documents and answers. Use it for dashboard numbers you would otherwise compute in a warehouse. A group reports to you only: no judgment reads it. See report on groups of documents. Each conversation counts toward its plan’s row: two frustrated conversations on pro, one on free. You read the rows from the API or chart them on the dashboard. No judgment reads a group.

Tenant documents

A template publishes each tenant’s groups, banded, as a document in the platform’s own namespace. Use it when a platform asks a question across its tenants, such as which are turning, and wants to judge, query and subscribe to tenants like any record. See summarize every tenant. Each tenant’s groups are published once an hour, as bands, into one tenant document per tenant in the platform’s own namespace. The platform judges, queries and subscribes to those documents like any other, without reading any tenant’s records. Before changing a document many others are judged against, such as a rulebook, you can simulate the change on a sample to see which answers it would flip, for free.

The rules every relation follows

One hop

A relation reads documents, and the answers of plain judgments, never further. A judgment is plain when its context reads no other document (no related), it does not choose among candidates (options.from), and it is not a composite. So a judgment that reads relations can’t itself be read, and nothing reads an answer that moves without being judged. A judgment whose answers a relation reads must also be active and on_change, in the same namespace or template, and answer every document the relation selects. While it has readers, a change that would break them is refused with conflict naming them. “Message, conversation, account” is therefore two relations from the message, not a chain: see two levels, not three. Related documents live in the judged document’s namespace.

Bands keep answers stable

A judgment is judged again only when its compiled context changes. So a number shown from another document can be a band, such as low, medium or high, and moves inside a band change nothing: no evaluation, no fan-out, no bill. Another judgment’s probability or score must be shown as a band or counted by a cut, never summed or averaged, because it moves a little every time that judgment runs. See bands.

No routine action waits for a person

You confirm once, when you create or change a definition: the replay estimate of what it costs a month, and for a judgment that can fan out, the most its fan-outs may re-judge. After that, everything runs on its own.
  • The rolling limit. freshness.fanout.rolling_limit is the most judged documents a judgment’s fan-outs re-judge in any 30 days, for changes to the documents it points at and to its blocks: 300,000 by default. The create response prices it in full as fanout.cost_usd_at_limit.
  • Deferral, not a queue. A fan-out past the limit, or a block past its block_cap, is deferred. Its answers read stale with stale_reason limit_reached, and the events feed records one judgment.limit_reached with the number of documents deferred. Nothing is declined, and there is no job to confirm.
  • A fan-out runs whole or not at all. A deferred fan-out never runs in part: it runs once the 30-day window has room for all of its documents, when older re-judgments leave the window (the judgment’s fanout_window.rolls_at says when the next ones do), or at once when a PATCH raises the limit. A single fan-out larger than the whole rolling_limit never fits, so it stays deferred until you raise the limit above it or set it to null. A block past its block_cap waits for a raised cap or a finer key, whatever the window.
  • Seeing it catch up. No event marks the catch-up. When a deferred fan-out runs, each of its answers keeps reading stale (limit_reached) until it is re-judged, then reads fresh; fanout_window.used on the judgment rises by the documents it ran.
  • No limit. Send "freshness": {"fanout": {"rolling_limit": null}} at create or in a PATCH. Every change then re-judges every document in scope with no deferral, the estimate’s cost_usd_at_limit is null, and only the namespace’s budget caps what fan-out spends. A volatile field shown without bands can then re-judge every document in scope on every change: prefer a higher limit you can reason about.
GET on the judgment shows how much of the window is used in fanout_window. What a fan-out costs works through the numbers.

What every kind shares

  • Limits. A judgment has at most 4 relations. Each attribute relations key on, in any direction and blocking keys included, needs a reference index, built by a reference_index job when a judgment first uses it: a namespace has at most 8. See limits.
  • Only what is shown counts. A write to another document that changes nothing a relation shows re-judges nothing and is not billed.
  • An exact audit. An answer carries its position: watermark, the log position its context was read at, and answers_generation, how far it had read other judgments’ answers and blocks. Its evaluation lists every document it read in related_documents, with the evaluation behind each answer it read. See answers and evaluations.
  • Freshness says why. A relation’s change makes an answer pending until it is judged again, or stale with a reason when nothing will judge it yet. See freshness.
Parts of the API reference call a judgment with relations an entity judgment, and its replay estimate counts the documents it applies to as entities.