The agents

There is one agent per CoNLL-U element, except that by default one syntax agent sets HEAD and DEPREL together, plus the scorer. Each is a pydantic-ai agent whose only tools are the MCP server’s tools, connected in-process.

Role

Writes

Typical evidence

segmentation

FORM, multiword tokens (split_word, merge_words, …)

find_multiword, analyse_form

upos

UPOS

analyse_form, find_tokens, allowed_values

xpos

XPOS

allowed_values(column="xpos", upos=...), analyse_form

lemma

LEMMA

analyse_form, find_tokens(suffix=...) analogies, find_tokens(root=...)

feats

FEATS

feature_inventory(upos=...), analyse_form

syntax

HEAD and DEPREL (default)

similar treebank sentences, fixed expressions, where similar words attach and with which relation; in review rounds, relation_stats for each word’s actual head

head

HEAD (with --separate-syntax)

similar treebank sentences, fixed expressions, find_arcs, relation_stats

deprel

DEPREL (with --separate-syntax)

relation_stats for the word’s actual head, find_arcs, allowed_values

deps

DEPS (off by default)

find_arcs, the trusted data’s enhanced graphs

scorer

confidences only

all of the above, plus relation counts for each current arc

What an agent sees

Every agent can read everything: the whole sentence, its neighbours (get_context), any other sentence in the prediction file, and the trusted data. Its task prompt lists the cells it should work on and the open flags for its column:

Sentence "14.15" in source "pred".
These UPOS cells need annotation or review (low confidence):
- word 1 (NIM.LÀL): UPOS = '_', confidence 0.02
...
Open flags for UPOS:
- flag 3 (whole sentence, from scorer): Empty UPOS everywhere. Suggested: 'NOUN NUM NOUN NUM NOUN NOUN VERB'.
You may also correct any other UPOS value in this sentence.

The instructions (see mcparser.agents.prompts) describe the team, the evidence tools for that role, the confidence scale (0.95 = clear and well attested, 0.8 = probable, 0.6 = plausible, below 0.5 = a guess), and the rule that problems in other columns are flagged, not fixed.

What an agent may do

RoleToolset wraps the MCP connection for each role:

  • only the role’s tools are listed (the search tools for every role; the editing tools only for segmentation), and results come back as compact text lines instead of JSON;

  • writes are allowed only to the prediction source and only to the role’s own column; the scorer may only send confidences;

  • every write is signed with the role’s name, so the history and flags show who did what (the agent argument is hidden from the model);

  • a refused call goes back to the model as a retry message explaining what to do instead (for example “call flag_issue with column=’upos’”).

Answers

Every role except segmentation answers in one request with compact lines, SENTENCE TOKEN_ID VALUE CONFIDENCE, e.g. s1 3 NOUN 0.95. The sentences of a task are labelled s1, s2, … in the prompt; a full sent_id is accepted too. The scorer answers SENTENCE TOKEN_ID COLUMN CONFIDENCE, where * stands for all the listed cells of a word (s1 3 * 0.9) or of the sentence (s1 * * 0.95), and later lines override earlier ones. Answers also include any flags for other columns and the flags it closed. Code checks the whole answer first: an unknown sentence or word, an invalid tag or, for HEAD, a cycle or wrong number of roots is sent back to the model to fix; only a valid answer is applied. For HEAD and DEPREL, code also asks once when an answer gives a word two dependents of a relation the treebank almost never repeats (in Coptic Scriptorium, 4 of 3,491 words have two nsubj; a noun subject next to a pronoun subject is dislocated); sending the same answer again confirms it. The same goes for chains: a word attached with a relation to a word that has the same relation, when the treebank almost never does that (conj attaches to the first conjunct, not the previous one: only 12 of 3,269 Georgian conj words attach to another conj). The reasons for every retry are kept in the run report. The segmentation agent still edits through the tools, since splitting and merging renumber the words.

Joint syntax

Choosing a head and choosing a relation are one decision (obl or nmod depends on whether the head is a verb or a noun; a fronted noun is nsubj or dislocated depending on the verb’s other dependents), and in the Coptic runs about half of the wrong labels were on words with a wrong head. So by default one syntax agent answers s1 3 4 nsubj 0.95 0.7 (head, relation, and their confidences). It works in two passes:

  1. Round 1 builds the tree from the attachment evidence, examples and fixed expressions. The arc-specific relation counts cannot be shown yet, since there are no heads.

  2. Later rounds review only sentences with something to look at: cells the scorer doubted, flags from other agents, and flags from code that compared each label with the treebank’s counts for that exact arc (a label that contradicts an overwhelming majority, see mcparser.rules). Now the prompt includes those counts for each word’s current arc.

When an answer gives a word two dependents of a relation the treebank almost never repeats, the agent is asked once to check it. --separate-syntax (setting joint_syntax: false) restores the separate head and deprel agents.

Models and reasoning

Roles use one of three tiers. The strong model (default Opus 5.5) handles segmentation, lemmas, syntax (HEAD and DEPREL) and DEPS. The efficient model (default Sonnet 5.5) handles UPOS, FEATS and the scorer. The light model (default Haiku 5.5) handles XPOS, which is close to a lookup once UPOS and FEATS are known. Change the split with --efficient-roles / --light-roles, the models with --strong-model / --efficient-model / --light-model, or give one role its own model with --model-for or in the settings file. Any pydantic-ai model name works (anthropic:..., openai:..., google-gla:..., ollama:...).

Reasoning (“thinking”) is set per tier with --strong-thinking / --efficient-thinking / --light-thinking (off, on, minimal, low, medium, high, xhigh) or per role in the settings. More reasoning costs output tokens and has not been measured here yet; HEAD and DEPREL are the likeliest to benefit. Left unset, each model uses its own default.

Using the agents from Python

import anyio
from mcparser.agents.team import AgentTeam
from mcparser.corpus import CorpusStore
from mcparser.pipeline import Pipeline
from mcparser.settings import Settings

store = CorpusStore()
store.add_source("train", "train.conllu")
store.add_source("pred", "pred.conllu", mode="rw")
settings = Settings(rounds=2)
team = AgentTeam(store, "pred", settings)
report = anyio.run(Pipeline(store, "pred", team, settings).run)

AgentTeam(..., models={"*": some_model}) substitutes models. The test suite uses this to run the whole pipeline with scripted FunctionModel agents.