Joint-consistency resolution — reading an address through the hermeneutic circle
The target this document sets: an address should resolve to wherever its spansspanA 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. are jointly consistent in the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture.'s containment graph, not to wherever a whole-string countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. guess points. "Portland, ME" is Portland, Maine because a Portland sits under Maine and none sits under Messina or Medway. Geography decides; the countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. prior only fills the silences. This is the design the #832 / #833 / VT failures asked for; it is not yet built.
What the failures told us
Three bugs in late June 2026 share one root. "New York, NY" resolved to New York Mills (#832). "Portland, ME" resolved to Messina, Italy; "Portland, OR" to Ourense, Spain (#833). "Portland, VT" resolves to Viterbo and refuses to move even when you pin the countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. prior to the US at near-certainty.
The shape underneath all three: 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. works greedily. It resolves the regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. tokentokenOne word or subword in the tokenized input. For the neural classifier, tokens come from SentencePiece (subword units); for the rule classifiers, tokens are whitespace- and punctuation-separated words. "ME" on its own — by name match, population, and the soft countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. prior — and Messina wins on population before anything checks whether a "Portland" actually sits beneath it. Then it scopes the localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. into the regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. it already picked. The one question that would have saved it — which "ME" has a "Portland" under it? — never gets asked.
We did once ask it. 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). 5 (the cross-component reconcile, see plan/reference/STAGES.mdx) scored
whole assignments and vetoed an impossible parent chainparent chainThe sequence of administrative parents above a place — Springfield → Sangamon County → Illinois → United States. Used to check the geographic coherence of a parse. (Houston → New York earns -Infinity
because Who's On FirstWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. says Houston descends from Texas). That reconcile was retired to argmax in #566
because, bundled as it was, 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 the majority of US
addresses. The admin-coherencecoherenceThe property of a parse whose resolved places form a consistent geographic hierarchy — the resolved locality really does sit inside the resolved region. check went out with the bathwater. The greedy fallback that replaced it
is what produces #833.
The frame: a hermeneutic circle
You cannot read the parts of an address without a guess at the whole, and you cannot fix the whole without the parts. "ME" is only Maine once you suspect the address is American; you only suspect it is American once you have read "ME", "Portland", and the shape of the string. Interpretation spirals: a provisional whole conditions the parts, the parts revise the whole, until the two cohere. This is the hermeneutic circle, and an address is a small, tractable instance of it.
The system already runs a faint version of this loop inside the parser — neural/anchor-inference.ts
infers a soft localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. posterior and conditions its own labeling on it, with tokentokenOne word or subword in the tokenized input. For the neural classifier, tokens come from SentencePiece (subword units); for the rule classifiers, tokens are whitespace- and punctuation-separated words.-dropoutdropoutA regularization trick that randomly zeroes a fraction of activations during training, forcing the model not to rely on any single feature. Disabled at inference. so it never
becomes dependent. What is missing is the outer loop, between the 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. and the atlas, run over
geography rather than over tokentokenOne word or subword in the tokenized input. For the neural classifier, tokens come from SentencePiece (subword units); for the rule classifiers, tokens are whitespace- and punctuation-separated words. featuresfeatureAn input signal a model conditions on. Beyond the raw tokens, Mailwoman feeds soft features — gazetteer-membership channels and the postcode anchor — that inform predictions without overriding them..
The circle has two halves, and they are complementary:
- Forward — format and script propose a system. A CJK script implies CJK address systemsexpectation-maximizationAn iterative algorithm that estimates model parameters when some variables are unobserved. In Mailwoman's matcher, EM learns the Fellegi-Sunter m and u parameters from unlabeled data — no training labels needed.; a UK
alphanumeric 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. implies GB; a bare five-digit implies one of a small set. Today this is
rule-based (
query-shape,locale-gate's script-class scorer), and deliberately so — it reads character classescharacter classA token's character category — digit, alpha, CJK, Cyrillic, Arabic, mixed — used by the query-shape stage as a structural signal for locale and kind inference. and known 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. formats, never place-name dictionaries. - Backward — the resolved place confirms the system. Once "Portland, ME" lands in Maine, "US" falls out of the result. You did not guess the countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.; you derived it.
The coarse placer (#244) is a forward signal that skips the format and guesses the whole string at once. It is useful, but it is a guess, and on "Portland, ME" it guesses GB at 0.79 and routes the address to Italy. The backward half — geographic consistency — is the half we under-built.
What we build now: scoped admin descendant-consistency
The minimum viable form of the backward half, using machinery that already exists. When resolving a
regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. and a localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. together, prefer the assignment in which the localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. is a descendant of the
regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. in the ancestors table (the same table #832 repaired):
- "Portland, ME" → candidate regionsregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.
{Maine/US, Messina/IT, Medway/GB}; only Maine has a "Portland" descendant; resolve there. - "Portland, VT" → Vermont, because a Portland/Burlington descends from Vermont and not from Viterbo.
This generalizes to every countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. with no countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. list and no placer in the loop. It needs no new
data — Portland (43.647, -70.168) already records Maine as an ancestor in the swapped gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture.. The
hard constraint is scope: the check applies to the admin assignment only, so it cannot re-break 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 path that retired the #566 reconcile. We are reintroducing the admin-coherencecoherenceThe property of a parse whose resolved places form a consistent geographic hierarchy — the resolved locality really does sit inside the resolved region.
veto in isolation, not the whole beam.
Tracking issue: #263. The deterministic countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.-prior patch we prototyped first (a β-weighted damp
of the foreign posterior when a US stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. is recognized) is shelved on spike/833-beta-fusion-shelved
— it fixed four of six targets but it is a US-only rule that never reaches the VT cases, and it leans on
exactly the countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.-pin reflex this document is trying to retire.
The placer demotes to gap-filler
The coarse placer does not disappear; it changes job. Where the
gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. is rich, geography disambiguates and the placer is silent. Where the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. is thin — a
localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. missing under any candidate parent — joint consistency returns nothing, and the placer's prior
is what keeps a sparsely-covered countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. from resolving to "no answer." The graph is the primary
signal; the prior is the fallback. The HARD_PLACE_COUNTRY_SAFELIST is the piece this most directly undercuts; its
job shrinks as the graph's grows, and the docs already half-disown it (the 2026-06-26 postmortem
measured that widening it changes nothing, because the placer abstains and it never gates).
The quick cache reuses the gazetteer, not a list
There is room for a "well-known place" fast path, and the piecesECE (Expected Calibration Error). A metric that measures how well a model's confidence scores align with its actual accuracy. Lower is better. Mailwoman's held-out ECE drops from 0.067 (raw) to 0.0035 (calibrated). already exist: the candidate-table
byte-range gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. (WofCandidateTableLookup, candidate-schema.ts) — a WITHOUT ROWID B-tree keyed
by name_key, denormalized so a resolve is a single probe, population-ranked. The constraint graph does
not replace it; it re-ranks within it, choosing the joint-consistent candidate over the merely
most-populous one. Anything that looks like a curated list of famous places must instead be derived
from the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture.'s own structure and provenance-tracked — the moment a hand-list can override where
the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. says an address actually is, we have rebuilt PeliasPeliasAn open-source geocoder, Mailwoman's spiritual predecessor. in a nicer font. (#264.)
The MVP shipped on this branch (opts.adminCoherence, default-on for the geocode path). It fixes the
clean class — a regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. whose child localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. fell through — and the build surfaced exactly the residual
this document predicted. "Augusta, ME" stays at Augusta, Sicily: "Augusta" resolves to a real town
under Messina, so the localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. is not unresolved, and both Messina and Maine genuinely contain an
Augusta. Two consistent pairs, and geography alone cannot break the tie. That is the forward
address-system prior's job (US-format ⇒ prefer the US stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.), and it is the next section.
Future work, gated on a measured residual
Build to a measured gap, not to a good idea. Once descendant-consistency lands (#263), measure where it still fails, bucketed by why — romanized or transliterated input, mixed script, ambiguous field ordering, sparse-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. countries where geography stays silent. That residual, and only that residual, scopes two things we do not have today (#265):
- A supplementary address-system 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.'. The "don't memorize" rule is task-scoped: the open-world job (which Portland, of thousands) must generalize and retrieve; address-system identification is a closed, small taxonomy where densely encoding the format-to-system structure is learning the real distribution, not overfittingoverfittingWhen a model memorizes quirks of its training set instead of learning general patterns, so it scores well in training but poorly on new data. Guarded against with held-out evals and regularization.. Such 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.' earns its keep on the fuzzy 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 rules and the graph both miss. It stays advisory — it proposes a system, it never vetoes where geography lands an address. Its larger payoff may be routing (which normalizer, which abbreviations, which field order, which gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. slice), not disambiguation.
- An address-system → countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.-set linkage. "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. like a US address" makes US and Canada candidate postal systemsexpectation-maximizationAn iterative algorithm that estimates model parameters when some variables are unobserved. In Mailwoman's matcher, EM learns the Fellegi-Sunter m and u parameters from unlabeled data — no training labels needed., and floats their regionsregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. up — a derived prior, softer and better grounded than a whole-string guess. Not built.
Non-goals
- No hand-curated countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. list, safe-list, or famous-place list as a source of truth. Derived and provenance-tracked, or it does not ship.
- No re-enabling of the full #566 beam reconcile. The admin-coherencecoherenceThe property of a parse whose resolved places form a consistent geographic hierarchy — the resolved locality really does sit inside the resolved region. check returns scoped and alone.
- No supplementary 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.' built ahead of the residual that justifies it.
Status
MVP landed (#263, opts.adminCoherence). Remaining: #264 (quick-cache role) → measure the residual →
#265 (residual-scoped forward 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.' + the address-system→countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. linkage; "Augusta, ME" is the first
case in that bucket). Companion reading: concepts/synonymy-and-homonymy.mdx (the
tag-vs-referent split this extends), concepts/joint-decoding-walkthrough.mdx (the retired 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). 5
veto), plan/2026-06-14-coarse-placer-soft-signal-spec.md (the prior that becomes the gap-filler).