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

# Judge a ticket with its queue

> Show each open ticket the newest open tickets in its queue, to spot one problem reported many times: what it reads, what it leaves out, and what a busy queue costs.

Some questions about a ticket depend on what else is open. Is this the fifth report of the same outage? Is the customer describing an error three other customers reported this morning? Write the queue's name on every ticket as an attribute, such as `attributes.queue` set to `support`, and the judgment reads the other open tickets with the same value. Each queue is one block of a [blocking relation](/guides/entity-matching), one of the [relations](/concepts/relations), with the queue's name as its key.

```mermaid theme={"theme":{"light":"css-variables","dark":"css-variables"}}
flowchart LR
  t["ticket"] -- "queue: support" --- k{{"support"}}
  k --- o1["open ticket"]
  k --- o2["open ticket"]
  k -.-x c["closed ticket"]
  k -.-x b["open ticket in billing"]
  t -.-> q(["repeats an open problem?"])
  t:::judged
  classDef judged stroke-width:3px
```

The ticket reads the newest other open tickets in `support`. A closed ticket is not shown, and neither is any ticket in another queue, such as `billing`.

## Start from the queue starter

The `triage.against_queue` [starter](/guides/starter-judgments#triage-does-this-ticket-repeat-a-problem-already-open-in-its-queue) is this judgment, ready-made. Fill it with your own paths:

```json POST /v1/namespaces/acme%2Fhelpdesk/judgments theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "from_starter": "triage.against_queue",
  "paths": {
    "kind": "attributes.kind",
    "ticket_kind": "ticket",
    "status": "attributes.status",
    "open": "open",
    "queue": "attributes.queue",
    "subject": "state.subject",
    "body": "state.body"
  },
  "engine": {"name": "jev", "version": "current"},
  "freshness": {"policy": "on_change"},
  "confirm": true
}
```

* **It judges only open tickets**, the documents whose `kind` is `ticket` and whose `status` is `open`. A ticket that is closed leaves the judgment: it is no longer judged or answered, and no longer shown to the other tickets in its queue.
* **It reads** the ticket's subject and the first 1,000 characters of its body, and from its queue the newest 10 other open tickets created within 14 days of the queue's newest open ticket: when each was created, its subject, and the first 200 characters of its body. An edit past either cut changes nothing the judgment reads, so it re-judges nothing. See [cut a long field](/guides/context-recipes#cut-a-long-field).
* **Each queue is its own.** Tickets in `billing` never see the tickets in `support`. Write the queue's name as a short string of letters, digits, `-`, `_` and `.`. A ticket with no queue, or a value that is not such a string, is in no queue: it is judged with no other tickets shown, and the criteria say such a ticket repeats nothing.
* **The answer is yes or no.** It does not name the tickets the problem repeats. Its evaluation lists every ticket it read in `related_documents`, so you can see what it was compared with.
* **Thresholds.** `repeat` at 0.8 and `review` at 0.5: group a ticket above `repeat` with the problem it repeats, and send the band between them to a person. These are starting points; post outcomes to [measure and tune them](/guides/measure-improve-tune).
* **Your own wording.** Send `dry_run: true` to get the filled definition, change its question or criteria, and create from that. The same relation serves any question that depends on what else is open.

In the dashboard, the relation editor's "Add: Queue as context" fills the same relation; set the judgment's Applies to to your open tickets.

Unless the namespace already has one, the first version also builds a reference index on `attributes.queue`, listed as a `reference_index` job in the create response's `job_ids`; the judgment's answers are `unavailable` until it is done.

The context is capped at 1,500 tokens with `max_tokens`, which keeps each judgment standard in [size](/pricing#judgments). Over the cap, the oldest of the queue's tickets are left out first, before any of the ticket's own text is cut, and the evaluation records `context_truncated: true`.

## What it is not

* **Not everything open.** It shows the newest 10 open tickets created within 14 days of the queue's newest one. An open ticket older than that, or past the newest 10, is not shown, however much it matters. The 14 days count back from the queue's newest open ticket, not from now, so a quiet queue still shows its last tickets. Raise `last_n` (up to 254) or `window` in the definition to show more; every ticket shown is in every judgment's context, so its size class grows with it.
* **Creation order, not priority.** Newest means most recently created. Editing a ticket, raising its priority or reassigning it does not move it up, and the tickets shown are not the ones your team will work next. The judgment cannot tell what is ahead of this ticket in your process. Another order is not available on this relation: the tickets shown are always the newest.
* **Not their answers.** It reads the other tickets' text, never their answers to this or any other judgment.

## What it costs

A ticket's own writes re-judge it, as for any judgment: a new ticket is judged within seconds, against its queue as it stands then. The cost to plan for is the other direction. Every open ticket reads the same newest few, so a change to them re-judges the whole queue:

> **re-judgments a month = queue changes a month × open tickets per queue inside the re-judge scope**

```mermaid theme={"theme":{"light":"css-variables","dark":"css-variables"}}
flowchart LR
  n["new ticket"] -- "queue: support" --- k{{"support"}}
  k -. "re-judges" .-> o1["open ticket"]
  k -. "re-judges" .-> o2["open ticket"]
  k -. "re-judges" .-> o3["open ticket"]
  o1:::judged
  o2:::judged
  o3:::judged
  classDef judged stroke-width:3px
```

A new ticket changes what every open ticket in `support` reads, so once the queue is quiet, every open ticket in it inside the re-judge scope is judged again, not only the new one.

### What counts as a queue change

* **A write to a ticket that is open before or after it**, such as a new ticket, an edit to an open one, or closing one, makes every open ticket in its queue `pending` from the moment the write acks.
* **After a quiet period**, Brussle reads the queue once and compares what it shows with what it showed before. Each queue waits on its own until it has had no such write for `freshness.fanout.debounce_ms`, 10 minutes by default, or until `fanout.max_wait_ms`, 1 hour by default, has passed since the first write it has not yet read. So a burst of writes costs one read of the queue, and a queue that is never quiet for 10 minutes is read once an hour.
* **Nothing shown changed**, such as a new assignee, a tag, an edit past a cut, or a closed ticket that was not among the newest shown: the open tickets are `fresh` again, with no engine call and nothing counted.
* **Something shown changed**, such as a new ticket, or one of the shown tickets closed or its subject edited: every open ticket in the queue inside the re-judge scope is re-judged. Nearly all of their contexts change, so nearly every one is billed. One whose context did not change keeps its answer and is not billed, but counts toward the rolling limit.

Until the queue is read, its open tickets read `pending` with their last answers, so in a queue written to all day they read `pending` much of the time. `answers: "fresh_only"` leaves `pending` answers out: list a busy queue's answers without it.

### Worked numbers

Each ticket's context and question come to about 1,300 tokens: a standard [judgment](/pricing#judgments), at \$0.25 per 1,000.

| Case | Open tickets in scope | Queue changes a month | Re-judgments a month | Cost a month |
| - | -: | -: | -: | -: |
| **A.** One queue, written to all through a 10-hour working day, every day, and quiet overnight | 300 | 300 | 90,000 | \$22.50 |
| **B.** One queue, written to around the clock | 800 | 720 | 576,000 | \$144 |
| **C.** B, with `fanout.debounce_ms` 30 minutes and `fanout.max_wait_ms` 4 hours | 800 | 180 | 144,000 | \$36 |
| **D.** B, with `fanout.scope.created_within` 7 days, a quarter of the open tickets | 200 | 720 | 144,000 | \$36 |

A is well inside the default rolling limit of 300,000 re-judgments in any 30 days. B is nearly twice it: the first 300,000 run, \$75 of re-judging, and the rest are deferred until the window has room. C trades freshness for cost: the other open tickets see a new ticket up to 4 hours later, and read `pending` until then, while the new ticket itself is still judged within seconds. D re-judges only the newest open tickets; the older ones keep their answers and read `stale`. Splitting a queue in four does not help a queue written to around the clock, since each part is still read once an hour: it helps when each part is quiet for longer.

### The limits it runs under

Each is a setting in `freshness.fanout`, sent at create or changed later with a `PATCH`:

* **`rolling_limit`**, 300,000 by default: the most open tickets the judgment's queue changes may re-judge in any 30 days. Within it, a change runs on its own, at most 1,000 tickets at a time. Past it, the tickets that fit are re-judged and the rest wait: their answers read `stale` with `stale_reason` `limit_reached`, and the [events feed](/guides/events-feed) records one `judgment.limit_reached` with `limit` `rolling_limit`. They catch up on their own when older re-judgments leave the 30-day window, or at once when a `PATCH` raises the limit. With several queues, the queues that have spent the most of the limit wait first, so one busy queue cannot leave every other queue's tickets stale.
* **`block_cap`**, 1,000 by default: the most open tickets one queue may hold. A create is refused with `invalid_request` when a queue already holds more, naming it in `details.key`; closed tickets do not count, since they are outside Applies to. A queue that grows past the cap later waits, whatever the rolling limit: its open tickets read `stale` (`limit_reached`), and the events feed records `judgment.limit_reached` with `limit` `block_cap` and the queue in `key`. It runs again once tickets are closed and the queue is back within the cap, or when a `PATCH` raises it.
* **`scope.created_within`**, 30 days by default: an open ticket created longer before the change is not re-judged. It keeps its answer and reads `stale` with `stale_reason` `referenced_changed` until its own next write. If tickets stay open for weeks and their answers matter, send `"scope": {"created_within": null}`: the Applies to on open tickets already keeps closed ones out.

See [the rolling limit](/concepts/relations#no-routine-action-waits-for-a-person) for how deferral catches up, and [match one record to another](/guides/entity-matching#cost-and-limits) for the same limits on another blocking relation.

### What you confirm

Without `confirm: true`, the create returns its cost estimate and creates nothing:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "replay": {
    "replayed_days": 30,
    "entities": 300,
    "judgments_per_month": 41000,
    "judgment_units_per_month": 41000,
    "cost_usd_per_month": 10.25,
    "bulk_pool_share": 0.001,
    "lower_bound": true,
    "excludes": ["candidate_edits", "candidate_deletes"]
  },
  "fanout": {"rolling_limit": 300000, "cost_usd_at_limit": 75.0}
}
```

`replay` runs the last 30 days of writes through the judgment: the open tickets' own writes, and each queue's tickets created in that time that are still open, re-judging the open tickets then in their queue after the quiet period. It cannot see a ticket that was closed, edited or deleted in those 30 days, so a ticket opened and closed within the month leaves no trace of the changes it made. It says so with `"excludes": ["candidate_edits", "candidate_deletes"]` and `lower_bound: true`, and its figures are "at least". In a queue where most tickets close within days, the real figure can be several times the estimate. What bounds it is the rolling limit: `fanout.cost_usd_at_limit` is the most the queue changes can cost in any 30 days. Sending `confirm: true` agrees to both, and no queue change waits for a person after that.


## Related topics

- [Relations](/concepts/relations.md)
- [Starter judgments](/guides/starter-judgments.md)
- [Preview the labelling queue by probability band](/api-reference/outcomes-and-calibration/preview-the-labelling-queue-by-probability-band.md)
- [Judge what changed since the last verdict](/guides/change-detection.md)
- [Migrating from an LLM classification pipeline](/guides/migrating-from-llm-classification.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.