Assignment and Agreement¶
Who annotates what, and which agreement measure follows from it. It ships with the potato-tasks skill:
/plugin marketplace add davidjurgens/potato-skill
/plugin install potato-tasks@potato
Two questions that look separate and are not: the number of annotators per item decides whether an agreement number exists at all, and the per-annotator quota decides whether the items you care about ever reach three annotators.
The keys¶
| Key | What it does | Default |
|---|---|---|
num_annotators_per_item |
Target annotators per item. Set this one. | 3 |
min_annotators_per_instance |
Floor before an item counts as done | unset |
max_annotations_per_item |
Hard cap per item; -1 unlimited |
-1 |
max_annotations_per_user |
Cap on dataset items per annotator. The only key that sets it. | the item count |
automatic_assignment.instance_per_annotator |
Nothing. See below. | — |
assignment_strategy |
Order items are handed out in | fixed_order |
random_seed |
Makes the ordering reproducible | unset |
Setting num_annotators_per_item, min_annotators_per_instance and
max_annotations_per_item to the same number works and says you were unsure.
instance_per_annotator¶
Measured on 10 items, reading the assignment the server actually built:
| Config | Items served to one annotator |
|---|---|
| nothing set | 10 — all of them |
max_annotations_per_user: 5 |
5 |
automatic_assignment.instance_per_annotator: 5 |
10 — no effect |
num_annotators_per_item: 3 |
10 — no effect on the quota |
instance_per_annotator is not read anywhere in the server, and
automatic_assignment does not validate its sub-keys, so it produces no warning
and no behaviour. If you want a per-annotator workload cap,
max_annotations_per_user is the key.
Left unset, the cap is the number of items loaded, so every annotator is offered
the whole corpus. -1 is explicit unlimited, and is what a dynamic data source
needs for items added after boot to be assignable at all.
Quality-control items do not consume the quota¶
Attention checks and gold items are injected into the annotator's ordering as
they work, rather than drawn from the pool at assignment time. They do not count
against max_annotations_per_user, so enabling them does not shorten the corpus
each annotator sees. On a six-article study with checks every third item, both
annotators are served all six articles plus the checks:
ordering = [n001, n002, n003, check01, n004, n005, n006]
You do not need to pad the quota to make room for them. If you set the quota explicitly, set it to the number of dataset items an annotator should see.
Verify with a count across every annotator, not one. Per-annotator ordering looks plausible in isolation; what matters is whether each item reached the number of annotators you asked for:
import json, glob, collections
c = collections.Counter()
for f in glob.glob("annotation_output/*/user_state.json"):
c.update(json.load(open(f))["instance_id_to_label_to_value"])
print(sorted(c.items())) # every real item should hit num_annotators_per_item
More annotators than work¶
num_annotators_per_item times the item count is a hard ceiling on how much
work exists. Once it is used up, the next person to register lands on:
Thank You! You have completed the annotation task and your responses are saved.
They annotated nothing. No annotation_output/<user>/ directory is created and
nothing lands in the log, so from the outside this is indistinguishable from an
annotator who finished. Three items at num_annotators_per_item: 1 does it to
the fourth person through the door. On a pilot that person is usually you, with
a fresh login, and it reads as "the study is broken".
Before recruiting, multiply: items x num_annotators_per_item is the number of
annotator-items you have to hand out, and max_annotations_per_user divides it
into people.
Choosing the number of annotators¶
| Situation | Number |
|---|---|
| Near-mechanical labels, or prototyping, nobody reporting agreement | 1 |
| Any judgement involved and an agreement number will be reported | 3 |
| Genuinely subjective constructs, or per-annotator reliability is the object of study | 5+ |
Three is the smallest number where a majority can break a tie. More annotators on fewer items beats fewer annotators on more items whenever you do not yet know the labels are learnable — find that out on 100 items before spending the budget on 10,000.
This is the researcher's decision, not yours. Propose a number with the reason and say what it costs.
Order¶
assignment_strategy takes: random, fixed_order, active_learning,
llm_confidence, max_diversity, least_annotated, category_based,
diversity_clustering, batch, priority, psychometric.
random with a random_seed is right unless there is a reason:
fixed_orderwhen items are a narrative and order carries meaningleast_annotatedwhen finishing every item matters more than annotator experience — the one to reach for when the quota trap above is biting and you cannot raise the capactive_learning/llm_confidencewhen a model is in the loop and the point is to spend annotator time where it changes something
The response format decides the metric¶
Potato classifies each scheme and computes what fits. You do not choose the metric separately, which makes this a measurement decision rather than a cosmetic one.
| Kind | Schemes | What it computes |
|---|---|---|
| nominal | radio, multiselect capped at 1 |
percent agreement, Cohen's κ, Fleiss' κ, α |
| ordinal | likert, ordered scales |
linear and quadratic weighted κ, Spearman ρ, ordinal α |
| continuous | slider, number, vas |
Pearson r, MAE, RMSE, interval α, ICC(2,k) |
| multilabel | multiselect |
mean Jaccard, MASI α |
| ranking | ranking, bws |
Kendall's τ, Spearman footrule |
| span | span, error_span |
token-level κ, exact and partial span F1, Krippendorff's αU, γ |
| geometry | boxes, polygons, points | matched IoU, detection F1, chance-corrected σ, ks, detection α |
(the only way to score a geometry task against a known answer — gold_label cannot hold a box) |
||
| free text | text, text_edit |
nothing |
Consequences worth stating to a researcher up front:
- A 1–5 rating stored as a
radiois scored as unordered, so "1 vs 2" counts the same as "1 vs 5" and the agreement number is badly understated. Store ordered judgements aslikert. - Span agreement tolerates disagreement about boundaries. Annotators asked to mark sentences will not agree on where a sentence starts; token-level and partial-overlap measures handle that, whereas splitting the text into sentence rows and computing per-sentence κ requires the units to match exactly.
- Free text has no metric. If a judgement is going to be counted, make it a scale. If it is genuinely prose, plan to read it and say so.
Enable the admin-side computation with:
num_annotators_per_item: 3
agreement_metrics:
enabled: true
agreement_metrics recognizes enabled and nothing else — metrics, measures,
types, min_annotators and by_scheme are all rejected by --strict. The
measures follow from the schema kinds; there is nothing to select.
Reading the agreement report¶
Nothing is computed at boot, and nothing is linked from the annotation page. Agreement appears once items have more than one annotator.
cat admin_api_key.txt # generated into task_dir at first boot
curl -H "X-API-Key: $(cat admin_api_key.txt)" localhost:8000/admin/iaa
/admin/iaa returns, per scheme, the kind it inferred and the metrics that
follow from it, plus n_overlap_items. Confirming the kind matters more than
the number: it is where you find out a rating you meant as ordinal is being
scored as nominal.
Without the header, every admin JSON route answers
403 {"error":"Admin access required"} — which reads like a broken config if you
do not know the key exists. /admin itself serves HTML with no key.
/admin/api/agreement covers the same ground and has been seen to fail on a
geometry task with calculate_krippendorffs_alpha() got an unexpected keyword
argument 'experiment_col' for every scheme, while /admin/iaa returned proper
numbers for the same data. If it errors, that is the build, not your config —
use /admin/iaa.
An empty report on a fresh task means "no overlapping annotations yet".
Related¶
quality-control.md— the checks that consume the quotadesigning-a-task.md— the seven decisions, including how many annotatorsrunning-a-task.md— readinginstance_id_orderingto see what was assigned