Skip to main content

Arbitration layer spec (#478) — pipeline ≥ v0 by construction

2026-06-14. The arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. capstone, re-specced after the reconcile retirement (#566), the grouper fix (#565), and the coarse-placercoarse-placerA lightweight int8 country classifier (~0.79 MB) that predicts which of a set of target countries an address belongs to, feeding a soft prior into resolver disambiguation. (#244) reshaped the landscape. The original #478 issue is substantially stale — its "untested / unwired / mock" framing predates this work. This is the current stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality., the design, and — the critical correction — the gate the reconcile retirement proved we need.

Why this matters (unchanged)

Parity is a pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. property, not a modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' property. The arenaarenaA standardized test set probing one capability: libpostal (clean canonical), perturb (noisy and degraded), postal (edge formats). Each arena answers a different question about where rule vs neural wins. scorecard's v0-only column (whole parsesaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. the old rules system gets that the neural pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. drops) is the gap. Calibrated per-component arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. closes it by construction — the pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. keeps whichever source is right per component, so it can't score below v0 on any component v0 wins.

Status audit — what the stale issue gets wrong

#478 pieceissue saysactual stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. (2026-06-14)
1. Policy registrypolicy registryThe per-component table that decides which classifier (rule or neural) has authority for each address component. The Ship-of-Theseus dial. + config"applyPreferenceFilters has zero tests; no config surface"DONE. core/policy/registry.test.ts covers the dedup core (preferred-mode drop/fallback, merged survival, below-threshold no-trigger, both modes); core/policy/from-config.ts + from-config.test.ts are the loadable per-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. per-tag config surface. Wired into core/parser/AddressParser.ts via proposal-pipeline.ts.
2. Reconcile top-k"ClassifierCandidate is a mock; no production path emits it"DONE. core/pipeline/runtime-pipeline.ts joint path emits real top-k via parseWithLogits + aggregateSpanLogits.
3. Input-shape routing"in docs since #15, unwired"OPEN. The routing prior (clean→rules, noisy→neural, both-weak→abstain) is not wired as an arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. prior. The signals exist: kind-classifier, query-shape, and now the #244 coarse-placercoarse-placerA lightweight int8 country classifier (~0.79 MB) that predicts which of a set of target countries an address belongs to, feeding a soft prior into resolver disambiguation. (the abstention tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous., M1+M2 shipped).
4. Calibrated arbiter"use the calibrated posterior"PARTIAL. Isotonic calibrationisotonic calibrationA post-hoc calibration that fits a monotonic map from raw model scores to true probabilities without retraining (via PAVA). Mailwoman's confidence calibrator. shipped (#59/#367) + the conformal interp radius (#569); the policy registrypolicy registryThe per-component table that decides which classifier (rule or neural) has authority for each address component. The Ship-of-Theseus dial. compares on confidence, but a unified calibrated-posterior currency across rule/neural sources is not yet the arbiter.

The two latent dependencies the issue names — and their current stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.:

  • parse() vs parseWithLogits() divergencedivergenceA training failure where loss descends through warmup, plateaus low for a while, then climbs catastrophically back to its starting magnitude — the model unlearns everything despite no obvious component failure. (deep-dive §2.1): parseWithLogits (what reconcile consumes) skips the postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one./unitunitA subdivision of a building — apartment, suite, floor — that refines a street address. Mailwoman's unit component; a designator plus identifier forms a subpremise. repair that parse() runs, so reconcile sees unrepaired labelscomponent tagOne of the 33 labels in Mailwoman's address schema — street, locality, region, postcode, house_number, unit, po_box, country, venue, intersection, and others. Each parsed span carries exactly one component tag.. This must land first — a shared buildTokens() so both paths repair identically. Still open.
  • The grouper bundled the house numberhouse numberThe numeric or alphanumeric identifier of a building on a street. Mailwoman's house_number component; its position relative to the street name flips between locales. (#565): FIXED this session. The grouper now proposes the bare streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. phrase, so reconcile separates streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. + house_number (US precondition 20% → 91.7%). Reconcile is viable again — but it is OFF by default (#566) until the gate below clears.

The landscape shift this session — and why it rewrites the gate

#566 retired joint-reconcile as the default after an audit found it broke the streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.+house_number precondition on 77–84% of US addresses and fixed 0% — worse-or-flat on every tag, venuevenueA named, non-address place — a business, building, park, or stadium. Mailwoman's free-text point-of-interest component, added as a Tier 2 fine label. included. The catastrophe was invisible for weeks because every evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. grades raw neural (classifier.parse), never the assembled pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package.. That is the single most important input to this spec:

An arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. that "can't score below v0 by construction" is only true if you GRADE THE ASSEMBLED PIPELINE against truth. Grading raw-neural per-tag F1 will hide an arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. regression exactly as it hid the reconcile one.

So #478's pre-registered gate, as originally written (arenaarenaA standardized test set probing one capability: libpostal (clean canonical), perturb (noisy and degraded), postal (edge formats). Each arena answers a different question about where rule vs neural wins. re-run), is necessary but not sufficient — it must run the assembled pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package., and it must include the non-circular precondition/coordinate metricscoord metricThe primary evaluation metric: distance from the resolved coordinate to the true address point. Measured at percentiles (p50, p90) and as 'within X meters.' Prevents the label-F1 trap where a model scores higher on token labels but geocodes worse. the geocoder campaign added, not just per-tag F1.

The design

ArbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. is a per-component decision over candidate proposals from N sources (rule, neural, merged), priored by input shape, decided on calibrated confidence, with abstention as a first-class outcome.

  1. Input-shape routerinput-shape routerAn arbitration prior that reads kind-classifier, query-shape, and coarse-placer signals to set per-component defaults (rule_preferred, neural_preferred, or abstain) according to how clean or out-of-distribution the query is. (the prior). kind-classifier + query-shape classify the input; the #244 coarse-placercoarse-placerA lightweight int8 country classifier (~0.79 MB) that predicts which of a set of target countries an address belongs to, feeding a soft prior into resolver disambiguation. adds (script, coarse-regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality., OOD-abstain). The prior sets per-component default modes:
    • clean structured addressrule_preferred (v0's home turf).
    • noisy / OOD-script / low rule-confidenceneural_preferred.
    • both sources weak OR coarse-placercoarse-placerA lightweight int8 country classifier (~0.79 MB) that predicts which of a set of target countries an address belongs to, feeding a soft prior into resolver disambiguation. abstainsabstain to the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon./admin tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. (the geocoder's honest-radius downgrade, #244 + the interp calibration #569) rather than emit a confident wrong parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates..
  2. Policy registrypolicy registryThe per-component table that decides which classifier (rule or neural) has authority for each address component. The Ship-of-Theseus dial. (the per-component decision). The router's prior is overlaid by the loadable from-config policy (per-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. per-tag mode + confidence_threshold), so a tag can be A/B'd without code edits. applyPreferenceFilters is the dedup core — keep the preferred source when it has a survivor, fall back otherwise.
  3. Reconcile (the spanspanA contiguous range of characters or tokens in the input string, tagged with an address component type (street, locality, postcode, etc.). Parsed addresses are represented as collections of spans, possibly nested in a tree.-level joint decode). With #565 landed and the repair-divergencedivergenceA training failure where loss descends through warmup, plateaus low for a while, then climbs catastrophically back to its starting magnitude — the model unlearns everything despite no obvious component failure. fix, reconcile is a candidate-producing stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). feeding the registry, not a silent default. Its value is OOD localeslocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. (it keeps a mangled streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. string intact); its danger is the US fusion (now fixed).
  4. Calibrated confidence (the currency). All cross-source comparison uses the calibrated posterior (#59 isotonic), never raw logitslogitA raw, unnormalized per-label score the model outputs before softmax. Priors and biases are added in logit space, then softmax turns logits into probabilities. — the only way rule-confidence and neural-confidence are comparable.

The two arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. sites — the AddressParser proposal-pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. (rule/neural via the policy registrypolicy registryThe per-component table that decides which classifier (rule or neural) has authority for each address component. The Ship-of-Theseus dial.) and the runtime-pipeline (neural reconcile/argmax) — should converge on one registry-driven arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. applied to the union of candidates. Unifying them is the bulk of the remaining wiring.

The gate (pre-registered, corrected)

Re-run with arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. on, grading the assembled pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. (not raw neural):

  • ArenaarenaA standardized test set probing one capability: libpostal (clean canonical), perturb (noisy and degraded), postal (edge formats). Each arena answers a different question about where rule vs neural wins. capability (the original #478 gate): v0-only → ~0 in every arenaarenaA standardized test set probing one capability: libpostal (clean canonical), perturb (noisy and degraded), postal (edge formats). Each arena answers a different question about where rule vs neural wins. (clean/noisy/edge); every v0 win captured. neural-only retained. both-fail unchanged-or-better.
  • Assembled-pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. precondition + coordinate (the #566 lesson, the non-negotiable addition): on the non-circular holdouts (Travis E-911 + OA), streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.+house_number+postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. precondition does not regress vs argmax; geocode within-100m and the calibrated-radius coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present. hold.
  • Per-locale F1per-locale F1Per-tag accuracy computed separately for each locale (US, FR, DE…) rather than aggregated, surfacing locale-specific regressions that macro F1 would mask under the dominant locale. floors hold. Demo presets stable.

Re-promoting reconcile to default is gated on this — specifically the precondition row, the one the original re-gate (#427) omitted.

Sequencing

  1. Land the repair-divergencedivergenceA training failure where loss descends through warmup, plateaus low for a while, then climbs catastrophically back to its starting magnitude — the model unlearns everything despite no obvious component failure. fix (shared buildTokens()) so parseWithLogits repairs like parse(). Prerequisite for any reconcile re-promotion.
  2. Wire the input-shape routerinput-shape routerAn arbitration prior that reads kind-classifier, query-shape, and coarse-placer signals to set per-component defaults (rule_preferred, neural_preferred, or abstain) according to how clean or out-of-distribution the query is. as the per-component prior (kind-classifier + query-shape + #244).
  3. Unify the two arbitrationarbitrationA pipeline stage that compares rule-based (v0) and neural classifier output, resolving disagreements via a policy registry. Built and merged but not promoted — the coordinate gate showed label-F1 gains came at the cost of worse geocoding. sites on the policy registrypolicy registryThe per-component table that decides which classifier (rule or neural) has authority for each address component. The Ship-of-Theseus dial. over the candidate union.
  4. Run the corrected gate. Promote per-component modes only where the assembled-pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. gate clears.

Scope guard (unchanged)

No retrain. No new classifiers. Wiring + tests + config over machinery that exists. The coarse-placercoarse-placerA lightweight int8 country classifier (~0.79 MB) that predicts which of a set of target countries an address belongs to, feeding a soft prior into resolver disambiguation. (#244) is the one new modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' and it is already built (M1+M2); here it is consumed as a routing signal, not trained.

References: 2026-06-10-DEEP-DIVE-REVIEW.md §2; the reconcile retirement docs/articles/evals/2026-06-14-reconcile-retirement.md; the interp calibration docs/articles/evals/2026-06-14-interp-radius-calibration.md; the coarse-placercoarse-placerA lightweight int8 country classifier (~0.79 MB) that predicts which of a set of target countries an address belongs to, feeding a soft prior into resolver disambiguation. docs/articles/evals/2026-06-14-coarse-placer-milestone-1.md.