InboxQL Documentation

Using it

Labels, extractors and review

Annotators: labels and extractors, the engines, and teaching them with rulings. · 8 min read

An annotator is a question InboxQL asks of your mail, by name, and remembers the answers to. There are two kinds:

  • a label answers yes or no — is this a purchase? — or picks a level — how important is this conversation?
  • an extractor pulls values out — what was the amount, the merchant, the order number?

The answers become query terms: label:purchased, label:importance, extract:receipts.amount>100. Nothing else about them is special, which is the point — once a question has been answered, it is as cheap to ask as from:.

Open Intelligence → Labels & extractors to see them.

These are the labels a model or a rule decides. The labels you put on mail — Inbox, Receipts, the ones you made in Gmail — are the other kind, covered in the Desk. label: answers for both, and the rail lists both under Labels.

Somewhere to start

A new mailbox offers the elements: the things worth knowing about mail later, answered by your AI harness reading each conversation once. Each says how much it has answered.

Name Kind Asks
receipts extractor, per message what was bought: merchant, items with their brands, total
travel extractor, per message flights, trains, hotels and cars, with confirmations
events extractor, per message events to put in the calendar — proposed
tasks extractor, per conversation what someone has to do — proposed as tickets
waiting label, three levels is someone waiting? on me, on them, no one
importance label, four levels how much does this need me? important, normal, low, ignorable
sender label, per sender person, organization, system
topics tags, per conversation what it is about

They replaced an earlier pack built on Laya and GLiNER, which read a few hundred words of one message — too little to know that a receipt line is a DeWalt battery, or that “I’ll get back to you Monday” leaves the next move with you. Installing replaces those starters only where they never answered anything.

Installing runs nothing. Reading with your AI harness is how they get answered.

The engines

An annotator’s engine is what answers the question. They are good at different things, and choosing well matters more than anything else here.

Your AI harness — reading whole conversations

The elements’ engine. Nothing in InboxQL runs it: the AI you already use reads conversations through iql work next and answers through iql work submit, and InboxQL checks every answer — levels and fields as declared, a quote that is really in the conversation — before keeping it. It reads everything: the whole conversation and the text of its attachments. See Reading with your AI harness.

Laya — judgement, on this machine

Answers a yes-or-no question, or picks a level, by scoring each possible answer. It cannot invent an answer that was not one of the options, and it runs on this computer: about two to three seconds a message, nothing sent anywhere. The first annotator that needs it asks to download it (about 600 MB, becoming 1.2 GB once prepared), or run iql laya install.

Write its instruction as a statement about the message — “this message is a receipt or an invoice” — not a question or a prompt.

It reads the subject, the sender and the first few hundred words. For a per-conversation label it reads a one-line summary of the conversation, then the newest message.

GLiNER — values, found in the text

Pulls values out by finding them in the message: every value it returns is a piece of the message’s own text, with its position. That makes it the right engine for extraction — it cannot make up an order number that is not there. It reads the body and any attached files whose text has been read, which for receipts is usually where the numbers are.

Its labels are the field names, so name fields plainly: amount, merchant, due date. Up to twelve at once. About 15–20 seconds a message on a laptop, and also entirely local; iql gliner install downloads it once.

LLM — understanding

A prompt sent to a language model, through the AI provider configured in Settings → AI Configuration. Use it when the question needs understanding rather than locating — what is the sender actually asking for? — and expect it to be slower and, for extraction, less trustworthy: a generative model can return a value that is nowhere in the message.

If the provider is on another machine, running an LLM annotator sends every message in its scope there. InboxQL refuses to do that until you have agreed to it for that annotator, and Plan says how much text would leave.

Rules — a query

A rule annotator is just a query, evaluated by the database in one pass. It is free and exact, and if a query can say what you mean, a saved query is usually the simpler way to say it. Rules are available from the command line (iql annotate create … --engine rule).

Reading the answers

label:purchased              said yes
extract:importance.level=important
                             picked that level
label:purchased@0.9          said yes with a score of 0.9 or more
-label:purchased             looked, and said no
unlabeled:purchased          has not looked yet
extract:receipts             found at least one record
extract:receipts.amount>100  found one with an amount over 100

A levelled label’s level is a value like an extractor’s, so it is filtered and grouped the same way: extract:importance.level=important, and label:importance | count by importance.level for how your mail divides up. A per-conversation label is recorded on each conversation’s newest message; add | thread to see the whole conversation.

A label has three states. -label:x is evaluated, no — it does not include mail the annotator has never looked at. Before concluding “there are no more of these”, check how far it has got: the card shows how many messages it has evaluated out of how many are in scope.

Its scores order; they do not measure

Laya’s scores rank messages well — a 0.95 is more likely to be right than a 0.6 — but out of the box they are not percentages. Its published model is not calibrated, so label:purchased@0.9 is “the more confident ones”, not “ninety per cent of these are right”. Calibration, below, fixes that from your own rulings.

Teaching it: rulings

When a label is wrong, say so. Every label appears as a chip under the sender of a message; right-click it (or focus it and press the menu key):

  • Right — confirm it.
  • Wrong — it should have said the opposite.
  • Should be … — for a levelled label, the level it should have picked.
  • Clear my ruling — go back to what the engine said.

A ruling outranks the engine. It survives every re-run and every change to the annotator’s instruction, and the message never goes back in its queue. On a per-conversation label, the ruling lands on the conversation’s newest message, which is the one the label is about.

Review

Rulings made while reading are biased: you notice the mistakes. To measure an annotator honestly, use Review on its card. It draws ten messages it has answered — spread across its whole range of scores, not just the doubtful ones — and hides its answer until you have given yours. Press y or n, or 1–4 for a level.

The card then shows a score line: how often it agreed with your reviewed rulings, and for a yes-or-no label the score cut-off that would have been right most often. Ten rulings is too few to trust, and it says so; around fifty starts to mean something. iql annotate score <name> gives the same answer in a terminal.

Two annotators asking the same question with different engines, each reviewed, is how you find out which engine to keep.

Calibration

Once an annotator has at least twenty reviewed rulings,

iql laya calibrate purchased

fits its scores to them, so that afterwards 0.9 means roughly nine in ten. Calibration never changes which way an answer goes — it cannot make the model more accurate, only more honest about how sure it is. It uses reviewed rulings only, because a set made of corrections would drag every score toward a coin toss.

Making your own

New annotator on the Annotators tab, or:

iql annotate create waiting-on-legal --engine laya \
  --instructions "this message is waiting on a contract or legal review"
iql annotate run waiting-on-legal --scope "after:30d" --dry-run
iql annotate run waiting-on-legal --scope "after:30d"

Changing the instruction makes it a new version. Earlier answers are kept but marked stale, and the next run revisits only what the new version has not answered. Your rulings carry over.

Try a new annotator on a narrow --scope first. A whole mailbox at three seconds a message is a long time to find out an instruction was badly worded.

When it runs

Trigger Runs
manual when you press Run — the default
after sync on whatever is waiting, once new mail has arrived
daily on whatever is waiting, once a day

A pass handles at most 200 messages per annotator and leaves the rest for the next. On a laptop running on battery, the automatic pass after a sync waits for mains power unless you allow it in Settings → System → Heavy work on battery.

Switching off, and deleting

Switching an annotator off stops it running — triggers skip it, Run is disabled — and keeps everything it has already said. label:purchased keeps matching. That is almost always what you want.

Deleting removes the annotator and every answer it ever gave, including your rulings, which nothing can rebuild. The confirmation says how many answers that is. An annotator another one is scoped on — purchased, behind receipts — cannot be deleted without switching the dependent one off.