Mova0.6 pre-release⌕
☰
home / docs / tools / Hard cases: from reading failures to learning

Hard cases: from reading failures to learning

Mova's animals and the Octopus, the large language model that develops Mova, are described in the sea animals of Mova.

mova-hard-cases closes the loop between what Mova reads badly and what it learns. The corpus readers keep the sentences they read badly on one shared list; this tool sends them to the 🐟 Fish in a priority lane, harvests the validated annotations, prepares them for the ⚡ Eel, re-checks the list with every new gparse model and measures the before and after on a fixed sample. No step calls a teacher or trains by itself: the Fish pagers and the Eel do, under their own controls and gates.

The Wikipedia corpus tool wikicorpus named below is internal and not in the public tree: in a public checkout the Wikipedia source of the list stays empty, and the topic corpus (tools/topic-corpus) is the public source.

cargo build --release --manifest-path tools/hard-cases/Cargo.toml --offline
cargo test --manifest-path tools/hard-cases/Cargo.toml --offline
 wikicorpus check (hourly) ──┐                          ┌── recheck (gparse view) ◄── new gparse weights
 topic corpora (Whales) ─────┴─► hard-cases list ───────┤
                                       │                └── measure (fixed sample, before/after)
                                       ▼
                      wave: Fish tasks in context (priority lane)
                                       │  Fish pagers (review-only)
                                       ▼
                      harvest: learn/<track>/<split>.conllu
                                       │  witness: Stanza, Trankit, spaCy trf
                                       ▼
                      eel: one Eel round per track (tree agreement, dev gates, guarded install)

Commands

Every command takes one configuration (mova-hard-cases-v1, absolute paths, unknown fields refused).

commanddoes
list CONFIGthe list by source, licence track, en and gparse view, lane kinds
wave CONFIG [--dry]the next lane wave: tasks, pages, ready manifests, pager configurations, ledger
lane CONFIGthe waves and their pagers' progress (status snapshots, not liveness)
guard CONFIGthe lane's priority (generic pagers yield while lane pagers work) and its share of the limit guard
harvest CONFIGvalidated Fish replies → learn/<track>/<split>.conllu and -prag.tsv
witness CONFIG TRACKStanza, Trankit, spaCy trf on the track's training trees, under the GPU lease
eel CONFIG TRACKone Eel round on the track's data, when ready
sample CONFIG [NAME]the fixed measurement sample (built once, never changed)
measure CONFIG MODEL_DIR [LABEL] [NAME]pass rate, silver LAS, EWT LAS of a gparse model on the sample
recheck CONFIG [--force]the list re-judged with the deployed gparse
watch CONFIG [SECONDS] [NAME]measure and recheck whenever the deployed gparse weights change; STOP in the lane root stops it

A deployment keeps its configuration outside this repository.

What a case may become (policy)

Track, by the licence of the text (Data and model licences):

licence classtrackmeaning
CC BY, CC0, public domain, oursshippedmay train the model that ships
CC BY-SA (Wikipedia)internaltrains only a separate internal model, installed in its own directory, never shipped
NC, ND, research-only, no-reuse, unknownmeasurenever trained and, by default, not sent to the teacher

Why CC BY-SA is internal: the shipped en.bin and gparse are already CC BY-SA 4.0, because they learn from UD English EWT, so a model trained on Wikipedia would not change their licence. But the data rules say CC BY-SA «as little as possible», never carried or modified into a release, so Wikipedia's hard cases train a separate internal track by default; the track is one line of the configuration (policy.tracks."cc-by-sa"). The internal track's annotations and model stay internal, like everything derived from the Wikipedia corpus.

Split, by document (no document is in two splits): train 70 %, dev 15 % (the Eel's development gate for the track), measure 15 % (the fixed before/after sample's first choice, never used to select a checkpoint).

Kinds. The lane takes cases flagged by syntax judges (parse, tree, frames, grammar, nonprojective, witness, roots, head_outside, no_words, no_frames). no_skeleton (what the Whales' reader could not restate: mostly table cells and headings) stays on the list for the readers but is not annotated. Text that is markup the reader let through (wiki tables, templates, links, tags) is left out too: that is a reader bug to fix, not a sentence to teach.

The lane (wave)

A wave selects the cases open for gparse that the policy admits, not already in the ledger, not held out (exact or whitespace-normalized text against the pinned held-out sets), one per normalized text, hardest first (most judges, then lowest witness agreement), up to fish.max_cases. Each document is fetched once as a bundle (mova-hard-bundle-v1, below) and pinned in the wave. Each target becomes a single-target Fish task:

The public packer (mova-fish-batch) packs them page by page under the 100 000-byte request bound, up to fish.max_targets targets per task, never across documents. Every target of a page is queued, deferred or rejected, with its reason (a registry violation in a candidate tree is a rejection). The wave writes intents, task queues and ready manifests exactly as mova-fish-pages consumes them, deals the pages round-robin to fish.pagers plans, writes one pager configuration each, selection.jsonl, wave.json last, then appends every selected case to the ledger (queued.jsonl): a case is sent once. An incomplete wave (no wave.json) stops the next one until inspected.

Priority. The lane's pagers are ordinary mova-fish-pages processes with their configured profile. guard gives them priority: while a lane pager works (fresh heartbeat, non-terminal state), the generic pagers named in control.yield_roots hold an owned PAUSE (they stop at their next job boundary), so the hard cases take those places first and the pool keeps its size. When no lane pager works, only the exact owned bytes are removed; an operator's PAUSE is never touched. A lane pager that turns uncertain stops every lane pager and every control.stop_roots root; a generic root stopped by its own limit guard stops the lane.

Bundles

A source's command prints one document as mova-hard-bundle-v1: schema, source, document, title, version, licence, model and sentences in order, each {id, n, text, conllu, prag}. prag is the pragmatics SLM's row (header ending in conf) or empty; empty rows are labelled by the lane with the SLM trained on the configured silver run, the whole document in order. The Wikipedia corpus: wikicorpus bundle <title>; the topic corpora: mova-topic-corpus bundle CONFIG CORPUS DOC. The case's where fields named by sources.<name>.pointer are the command's arguments.

Harvest, witnesses, the Eel

harvest reads every lane pager's jobs; a reply counts when the pager's receipt and the Fish executor's receipt both say validated_review_only, the reply binds the job's task and each target tree keeps the target's id and text (first validated reply per target; held-out texts dropped again). It writes learn/annotations.jsonl and, per track and split, learn/<track>/<split>.conllu (a # hard_case = <key> <source> <wave> line after the text) and <split>-prag.tsv. These are review-only silver: the teacher's agreement is not gold.

witness runs the existing neural runner (neural-offline.py) for Stanza, Trankit and spaCy trf on the track's training trees under the shared GPU lease, attaches # text only where id and every token form agree, and writes agreed-input.conllu (the fully witnessed records) and parsers/out/*.conllu; the others are listed as missing. It is bound to the training file's hash.

eel runs one round of the public Eel engine (mova_eel::trainer::run, once) on the track's data: the engine's own tree agreement with the three parsers, held-out exclusions, the snapshot development set (the track's dev split: a candidate may not lose LAS on hard cases it never trained on), the EWT LAS gate and absolute floor, the reload check and the guarded installation. The track's engine configuration (eel.<track>) sets fresh_train_prefixes: ["hard:"] (the engine admits only school: ids otherwise) and its own run root; the internal track must name a model directory other than the deployed one. A round is ready only when train, dev and current witnesses exist; otherwise it reports why and does nothing.

Recheck and measurement (judge)

The judge of one tree, without gold: roots, head_outside, grammar (world::induce::violations), nonprojective (never for gparse, whose decoder is projective) and witness (Stanza on the same tokens attaches fewer than 80 % of the non-punctuation words to the same heads). It measures disagreement and broken constraints, not correctness.

recheck judges every case open for gparse (and every case it learned, so regressions show) that has a draft tree, with the deployed gparse: read well → learned for gparse, a learned case failing again → open. Cases without tokens (the topic corpora's) stay as they are. It runs once per model (recheck.json). Run it before a wave: cases gparse already reads well are not sent to the teacher.

sample builds the fixed sample once (measure/<name>.jsonl): cases with a draft tree, the measure split first, then by key; Stanza's heads stored. measure runs gparse (CPU) on it and reports the pass rate, the drafts' own rate (0 by construction: a red control), UAS/LAS against the harvested Fish trees of sample cases (the never-trained measure split apart) and LAS on the pinned EWT dev; one file per run in measure/runs/, one line in measure/log.tsv. watch does both whenever the deployed weights change, so every Eel round gets an after.

Measured (2026-11-04)

On a copy of the list after wikicorpus hard-sync (2 068 cases: 1 937 topic-corpus cases, all no_skeleton and without licence, and 131 Wikipedia cases from 8 articles), branch hard-loop:

modelwhathard-case passEWT dev LAS
gparse:4924991fef908b4dfrozen copy of 2026-11-0168 / 131 (51.9 %)85.46
gparse:bdcbb8637c347e62before Eel round 268366 / 131 (50.4 %)85.41
gparse:6e9564693e9307b6after round 2683 and one more66 / 131 (50.4 %)85.40
gparse:458cffe10655ec46after round 2699 (deployed)66 / 131 (50.4 %)85.40

The three ordinary rounds since round 2683 left every per-case verdict identical: ordinary Eel rounds on school data do not reach these sentences. The drafts (en.bin) pass 0 of 131 (red by construction). The silver LAS column fills once harvested annotations exist. These are first numbers on 131 sentences from 8 articles; the split by document is coarse at this size.

Limits

This page in Markdown → https://md.mova.elamur.ai/free/en/docs/tools/hard-cases/