Start with the simplest context
Before you add related documents, decide what the question depends on. Try these in order:- A few recent records, as raw text. Use this when the answer is in what was said: an angry reply, a mention of a competitor, a question nobody answered. Keep
last_nsmall, around 5 to 10, and show only the fields that carry meaning. - Aggregates. Use these when the answer depends on how much or how often: how many tickets, the total of overdue invoices, the status of the latest one. An aggregate costs a few tokens where raw text costs hundreds.
- Both, as two relations on the same documents. Show the last few as text and count the last 90 days.
- Which works depends on the question. Text, counts or both can do best, so try both against your outcomes.
- A few recent records usually carry most of the signal. Newest first with a small
last_nis the right default. - Adding counts beside text rarely hurts. A composite judgment can take a relation’s aggregates as features beside a judgment’s answer.
Define one
Related documents point at the judged one through an ordinary attribute. In this example the judged documents are accounts, and each ticket and invoice carriesattributes.account_id. The judgment is an ordinary judgment with related in its context recipe, usually with applies_to so it judges only accounts:
match on posts, theirs: "attributes.user_id") or a conversation and its messages (theirs: "attributes.conversation_id").
applies_tolimits which documents the judgment judges, answers and bills. Other documents have no answer for it:answersleaves it out, and filters treat it as missing.matchpicks which documents a relation reads, andjoinsays how they point at the judged one: a document belongs to the judged document whoseidequals itstheirsattribute.last_n,windowor both bound each relation, so a context cannot grow without limit. A relation reads at most the newest 1,000 documents either way.fieldsare the paths shown from each document, andaggregateadds numbers over the same documents. Each relation needs one or both.
reference_index job, and the create response lists it in job_ids, one job per new attribute. Each job’s attribute names the attribute it indexes. A version that joins on an attribute that already has an index, but over documents the index does not cover yet (another match, or the same attribute used the other way round), gets a job for it too: the index is rebuilt to cover them, and still counts as one. A version that joins the same way as an existing one gets no job. The judgment answers once the job is done. Until then its answers are unavailable, except that under on_change a document written since the judgment was created reads pending: its evaluation waits for the index. A namespace has at most 8 reference indexes, one per attribute its relations join on, in either direction, blocking keys included. Reuse an attribute when you can: a create that would need a ninth is refused, and the error names the eight you have.
Newest created first, and what an edit costs
A relation orders its documents bycreated_at, newest first. window keeps those created within the window, and last_n then keeps the newest n. So “the last 8 tickets” means the 8 most recently created. The window counts back from the later of the judged document’s own newest write and its newest related document’s creation, not from the clock. Editing a related document moves neither the list nor the window.
created_at is when a document was first written to Brussle, unless the write that created it gave its own created_at. When you import existing data, send each record’s original creation time as created_at, so relations read your history in the order it happened, and a window counts it from when it happened.
A write to a related document always marks the judged document it points at as touched, but it only costs an evaluation when it changes what the engine would see:
- A new related document changes the list, so the judged document is judged again, once its writes settle.
- An edit to one of the last 8, in a field the relation shows, changes the context, so it is judged again.
- An edit to an older one, or to a field no relation shows, leaves the compiled context exactly as it was. The answer is kept, and nothing is billed.
match: one with a small last_n and fields, one with a window and aggregate.
Debounce and its ceiling
A judged document with busy related documents, such as an account whose tickets keep arriving, would be judged on every write without a debounce.debounce_ms makes Brussle wait until its writes have been quiet that long, so a burst of 100 tickets in five minutes costs one evaluation.
A debounce alone never ends for a judged document that gets a new related document every few minutes. The ceiling, max_wait_ms, judges it anyway once that long has passed since its oldest unjudged write. With a 10-minute debounce and a 1-hour ceiling, a ticket every minute for 3 hours costs 3 evaluations, not 180 and not 0.
max_wait_msdefaults to 12 ×debounce_msfor a judgment with related documents and adebounce_msabove 0. With no debounce, and for a judgment of a single document, it defaults to no ceiling.- It must be at least
debounce_ms, andnullturns it off. - Like the debounce, it is a setting: change it with a
PATCHand no new version.GETreturns the value in effect.
The replay estimate
The cost of a judgment with related documents depends on how often those documents change, not on how many judged documents you have. So before one runson_change, Brussle replays your namespace’s last 30 days of writes through its relations, debounce and ceiling, and tells you what it would have cost. Two requests return this estimate and create nothing until you send confirm: true: creating a version with related on a judgment that runs on_change, and switching such a judgment to on_change.
entitiesis how many documents the judgment applies to now.judgments_per_monthis how many evaluations the replay counted, andjudgment_units_per_monththe judgments they would bill, each counted by its size class, from the compiled context and question of up to 1,000 of your judged documents. Here each is standard, so the two are the same.cost_usd_per_monthprices those judgments at the tiers your organization would be in, counting what it has already used this billing period.bulk_pool_shareis the share of the background judging rate available to the judgment that those evaluations would use. Above 1, the judgment cannot keep up with your writes: raise the debounce or the ceiling, or narrow the relations.replayed_daysis below 30 in a younger namespace, and the monthly figures are scaled up from it. A document counts as created at itscreated_at, so records imported with their original creation time are left out when they are older than the days replayed. Imported without it, they count as created during the import, and the replay describes the import rather than your ongoing writes.
lower_bound is true, and the dashboard shows them as “at least”. The replay also does not count the evaluations that unchanged contexts save, which only lower the bill.
The namespace budget still caps real spend. A confirmed request whose cost_usd_per_month is more than the namespace’s budget is refused with budget_exceeded, and the dashboard says so before you confirm.
What each answer and evaluation shows
An answer of a judgment with related documents carries awatermark: the position in the namespace’s log that its context was read at. Every write at or below it, to the judged document or to any document that points at it, is reflected in the answer.
- A write to a related document makes the judged document it points at
pendingat the next read. It isfreshagain once an answer lands with a watermark at or above that write. revisionstill names the judged document’s own revision.wait_forandwait_mswait for the written or read document’s own answer. Writing a ticket withwait_for: ["churn_risk"]returns at once, becausechurn_riskdoes not apply to tickets.
related_documents: every related document its context read, rendered or aggregated, with the relation and the revision it was read at. It comes back with include=history,context and from GET /namespaces/{ns}/evaluations/{id}. That list is what makes an audit exact after the documents have changed, and what an outcome with a horizon joins to: “this account churned” labels the evaluation whose 60-day prediction window it falls in, and that evaluation names the tickets it read. The dashboard’s evaluation page shows them grouped by relation.
When the context is too long
A judgment’s compiled context, the judged document’s fields plus its related documents, is capped at the recipe’smax_tokens or the engine’s limit, whichever is lower. For Jev current that is 32,000 tokens for the context plus the longest question; see limits. It rarely comes to that, because each relation is bounded by its last_n or window, and reads at most the newest 1,000 documents.
When a context is still over the cap, it is cut in this order:
- The oldest records of the last relation, then those of the relation before it. Relations are cut in the order of their names.
- Then any
previousentries, the last first. - Then the judged document’s own fields, from the end of the last field.
- Aggregates are never cut. They are computed over the full selection before anything is cut, so a count or a sum still covers every record in the window even when only the newest few fit as text.
context_truncated: true and context_tokens, and its context shows the exact text the engine saw. You are billed for the context after cutting. If a judgment is often truncated, lower last_n, show fewer or shorter fields, or move volume into aggregates. The full rules are in context recipes.
Keep it affordable
A few habits keep the bill small, and most of them also make the answers better:- Keep
last_nsmall. The last 5 to 10 records usually suffice and keep the judgment standard; a year of history can make it large or extra-large, which count as 4 and 16. - Prefer aggregates when volume is the signal. A count is a few tokens.
- Show only the fields that matter. Leave long bodies out of a relation unless the question depends on them.
- Share recipes. Judgments with the same context recipe, such as
churn_riskandexpansionon the same relations, are answered together. Each is still billed, but together they are lighter on rate limits, sobulk_pool_sharestays low. - Pick the debounce for how fresh the answer must be, not shorter. A 10-minute debounce with a 1-hour ceiling judges a busy account at most a few times an hour.
- Read the replay estimate. It is your own traffic, so it is the best guide to what a change will cost.
The document each judged document points at
A relation can also run the other way, and read the one document the judged document points at through its own attribute, such as an order line reading its product. A change to that document re-judges the documents that point at it, which has its own costs and controls. See judge a document with the document it points at.More kinds of relation
A relation can also show another judgment’s answers for the documents it reads (roll-ups), or read the documents that share a key with the judged one and choose among them (matching). Relations lists every kind.Not available yet
- Oldest-first ordering, such as the opening messages of a thread.
- Named collections. Use
applies_toon an attribute such askind. - Judgments that read the answers of a judgment with related documents: a relation reads only plain judgments.