Skip to main content

Runtime Pipeline Stages

The formal contract for the six-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). runtime 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.. Pairs with INTERFACES.md (classifier-level contracts) and SCHEMA.md (component vocabularyvocabularyThe fixed set of tokens a tokenizer can produce. Mailwoman's SentencePiece vocabulary is tens of thousands of subword pieces, with byte fallback for anything outside it.) — this document covers the 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). boundaries that compose them into a working 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..

For the narrative / why-it-exists framing, read concepts/the-staged-pipeline. For the bitter-lesson-safe sub-system used by stagesstageOne 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). 2 and 2.5, read QUERY_SHAPE.md.

Status

  • Source of truth: this file is the canonical interface contract for the runtime 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.. Any 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). implementation must conform.
  • Last edited: 2026-05-25.
  • Implementation stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. (frozen 2026-05-25):
    • Shipped and wired as default: 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). 1 (normalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input.), 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). 2.5 (kind classifierkind classifierStage 2.5 of the runtime pipeline: categorizes the input into one of seven query kinds (structured_address, postcode_only, locality_only, intersection, po_box, landmark, vague) so the coordinator can route to the right parsing strategy.), 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). 2.7 (phrase grouperphrase grouperStage 2.7 of the runtime pipeline: proposes coherent input units (street phrase, locality phrase, postcode, etc.) with structural kind hypotheses. Decouples boundary discovery from type classification so the classifier answers 'what type?' not 'where?', rule-based), 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). 3 (neural classifierneural 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.' + CRFCRF (Conditional Random Field). A statistical modeling method that predicts structured outputs by modeling dependencies between adjacent labels. Mailwoman uses a linear-chain CRF as the Viterbi decoder at inference time to enforce BIO label consistency — a B-street must be followed by I-street or O, never I-locality. ViterbiViterbi decodingA dynamic programming algorithm that finds the most likely sequence of hidden states (labels) given a sequence of observations (token emissions). Mailwoman uses Viterbi over a linear-chain CRF to produce globally coherent BIO label sequences from per-token model scores.), 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). 4a (CRFCRF (Conditional Random Field). A statistical modeling method that predicts structured outputs by modeling dependencies between adjacent labels. Mailwoman uses a linear-chain CRF as the Viterbi decoder at inference time to enforce BIO label consistency — a B-street must be followed by I-street or O, never I-locality. structural mask), 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). 6 (WOFWOF (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. SQLite 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.).
    • Shipped, soft-signal (#244): 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). 1.5 (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. 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. router). Off in the bare core coordinator (byte-stable); wired on by default in the mailwoman package. Runs after normalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input., feeds 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.'s anchorPosterior re-rank — never the classifier.
    • Shipped, opt-in (retired as default 2026-06-14, #566): 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 (joint reconcilejoint decodingA decode strategy where the neural model's proposals and rule-based solver's output are reconciled into a single parse tree. Formerly the default (Route A Phase II), now retired in favor of argmax after it was found to break the street+house_number geocode precondition.). Was default-on when the phrase grouperphrase grouperStage 2.7 of the runtime pipeline: proposes coherent input units (street phrase, locality phrase, postcode, etc.) with structural kind hypotheses. Decouples boundary discovery from type classification so the classifier answers 'what type?' not 'where?' + a parseWithLogits classifier were wired, but graded against 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. 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 geocode precondition on 77–84% of clean US addresses while fixing 0% — so argmax is now the default decode. Code + A/B harnesses remain; jointReconcile: true opts back in (forceJointReconcile is the deprecated alias).
    • Shipped but not wired as factory default: 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). 2 (locale gatelocale gateStage 2 of the runtime pipeline: rule-based locale detection from the query shape's script and known-format signals. Returns a LocaleHint with the top candidate and alternatives, surfacing disagreement with an explicit --locale flag. workspace exists, not in createRuntimePipeline factory).
    • Scaffold only: 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). 4b (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. re-reader — unbuilt).
    • In-flight: CE-only C-train producing new 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). 3 weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. (step 6800/50K, val_macro_f1=0.496).

The pipeline

The fast-path arrow is the optimization win: when the kind classifierkind classifierStage 2.5 of the runtime pipeline: categorizes the input into one of seven query kinds (structured_address, postcode_only, locality_only, intersection, po_box, landmark, vague) so the coordinator can route to the right parsing strategy. identifies a trivial input (postcode_only, locality_only) AND the QueryShape's known-format match agrees, the coordinator skips stagesstageOne 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). 3-5 and goes straight to resolve. See Fast-path routing below.

Shared types

These types appear across multiple stagesstageOne 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).; defined here, used everywhere.

import type { Span } from "@mailwoman/core"
import type { ComponentTag, BioLabel } from "@mailwoman/core"

/** Optional locale hint from the caller. Stage 2 may override with a more-confident detection. */
export type LocaleTag = "en-US" | "fr-FR" | "ja-JP" | (string & {})

/** Optional user-location signal. Stage 6 uses it as a soft prior. */
export type UserLocation = { lat: number; lon: number } | { country: string } | { region: string; country: string }

/** Common opts threaded through every stage. Each stage may add its own. */
export interface PipelineOpts {
locale?: LocaleTag
userLocation?: UserLocation
forceFullPipeline?: boolean // disable fast-path shortcuts
signal?: AbortSignal
}

ComponentTag and BioLabel are defined in SCHEMA.md. ClassificationProposal and Classifier are defined in INTERFACES.md.

Stage 1 — Normalize

Purpose. Deterministic preprocessing of the raw input string. Unicode NFC, case folding, abbreviation expansion, whitespace and punctuation normalization. Pure functions, no ML.

Interface.

// packages/normalize/src/types.ts

export interface NormalizedInput {
/** The input as the user sent it. */
raw: string

/** Canonical form, all transforms applied. */
normalized: string

/** Ordered record of what was done. */
transforms: NormalizationTransform[]

/**
* Character-offset map: `normalized[i]` came from `raw[offsetMap[i]]`.
*
* Essential: every downstream stage that emits spans uses this to translate normalized offsets
* back to raw offsets for the consumer.
*/
offsetMap: number[]

/** The locale used for case-folding + abbreviation rules, if any. */
appliedLocale?: LocaleTag
}

export type NormalizationTransform =
| { kind: "nfc" }
| { kind: "case_fold"; locale: LocaleTag }
| { kind: "expand_abbreviation"; from: string; to: string; at: Span }
| { kind: "collapse_whitespace"; at: Span }
| { kind: "normalize_punctuation"; at: Span }

export interface NormalizeOpts {
locale?: LocaleTag
/** Skip abbreviation expansion (for debugging / round-trip tests). */
skipAbbreviations?: boolean
}

export function normalize(raw: string, opts?: NormalizeOpts): NormalizedInput

Today. @mailwoman/normalize workspace shipped 2026-05-22 (Slice B of PHASE_7). Public normalize() function with NFC + punctuation + whitespace (always) + abbreviation expansion + case-fold (opt-in). Essential offsetMap composed across transforms. Small en-US + fr-FR abbreviation dictionaries.

Future. Share dictionaries with corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus. synthesis (synthesis is the inverse — canonical → variants; normalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input. is variants → canonical). Add per-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. case-folding edge cases (Turkish İ, Greek final sigma). Bidirectional offset lookup (raw[i] → normalized[i]).

Failure classes owned. Tokenization and whitespace traps (#3); Unicode/transliterationtransliterationConverting a name from one writing system to another while preserving pronunciation (Cyrillic → Latin, for instance). Needed for multilingual address handling and corpus synthesis. encoding half (#6). See addresses-that-break-geocoders.

Open questions.

  • How much localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for.-specific case-folding (Turkish İ, Greek final sigma)? Recommend: cover the localeslocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. we ship; byte-fallback the rest.
  • Should NormalizedInput carry the inverse map (raw[i]normalized[i]) too? Recommend: yes — bidirectional offset lookup is cheap and prevents a class of off-by-one bugs.
  • Where does language detection live — 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). 1 or 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). 2? Recommend: 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). 2 (it needs QueryShape.characterClass as input).

Boundary — compute QueryShape

Purpose. Pure-function structural analysis of the normalized input. Microseconds-cheap, shared by StagesstageOne 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). 2, 2.5, 3 (optional), and 6.

Interface. Defined in QUERY_SHAPE.md. The entry point:

// query-shape/compute.ts

export function computeQueryShape(input: string | NormalizedInputLite, opts?: ComputeQueryShapeOpts): QueryShape

Reuse. Once computed, QueryShape passes into every subsequent 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). as additional context. It is not its own 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)..

Today. @mailwoman/query-shape workspace shipped 2026-05-22 (Slice A of PHASE_7). Pure functions, no ML, zero workspace dependencies. Detects character classcharacter 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., punctuation-bounded segmentssegmentA punctuation-bounded chunk of the normalized input — the comma-separated parts of 'Portland, OR' — used to give downstream stages structural context., 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 (US ZIP/ZIP+4, UK, CA, JP + ambiguous 5-digit FR/DE/US), PO BoxPO boxA numbered mailbox at a post office used as a delivery address instead of a physical street location. Mailwoman tags it as the po_box component; structurally the same family as a subpremise. / BP. Hyphen / apostrophe / underscore are "connectors" so "10118-1234" and "Saint-Denis" stay one 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.. Accepts both string and NormalizedInputLite (structurally compatible with @mailwoman/normalize's NormalizedInput).

Stage 2 — Locale gate

Purpose. Decide which downstream weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. to load based on script, language, and the caller's optional localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. hint.

Interface.

// packages/locale-gate/src/types.ts

export interface LocaleHint {
/** The detected locale tag. */
locale: LocaleTag

/** Detector's confidence, 0..1. */
confidence: number

/** Alternative locales with confidence — possibilities, not constraints. */
alternatives: Array<{ locale: LocaleTag; confidence: number }>

/** Detection source: caller hint took precedence, vs. detected from input. */
source: "caller" | "detected" | "ensemble"
}

export interface LocaleGateOpts {
/** If provided, takes precedence; detector may still emit alternatives. */
hint?: LocaleTag

/**
* Below this confidence, the gate emits an ensemble of likely locales instead of committing to
* one.
*/
confidenceFloor?: number // default 0.7
}

export interface LocaleGate {
detect(input: NormalizedInput, shape: QueryShape, opts?: LocaleGateOpts): Promise<LocaleHint>
}

Today. @mailwoman/locale-gate workspace shipped 2026-05-23. Rule-based: caller-hint precedence at confidence 1.0; otherwise derived from QueryShape.characterClass (CJK → ja-JP, Cyrillic → ru-RU, Arabic → ar) + known-format hits (us_zip4 → en-US, uk_postcode → en-GB, ca_postcode → en-CA, jp_postcode → ja-JP). Ambiguous 5-digit → en-US at low confidence with FR/DE as alternatives. Always decisive (fallback en-US at 0.3). Not yet wired as default in createRuntimePipeline — callers must pass detectLocale explicitly. The coordinator falls back to caller-trust stub.

Future. Replace the rule scorers with a small character-level classifier (~100 KB) trained on the first 200 characters of input. Per-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus. rows already carry the locale field — supervised trainingtrainingThe process of adjusting a model's parameters so its predictions match labeled examples, by repeatedly measuring error and nudging the weights to reduce it. Distinct from inference, when the trained model is run on new input. data is free. Below the confidence floor, the gate signals an ensembleensembleCombining several models' predictions to cut variance and improve robustness. A general ML technique, noted in the docs as a lever Mailwoman has not needed to pull. run downstream.

Failure classes owned. Unicode/transliterationtransliterationConverting a name from one writing system to another while preserving pronunciation (Cyrillic → Latin, for instance). Needed for multilingual address handling and corpus synthesis. script-handling (#6); language-switch hybrids (#7).

Open questions.

  • Hand-rolled rule classifier vs trained 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.'? Recommend: trained 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.' — small, fast, and gets better with the corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus. we already have (each row carries locale).
  • Where do non-shipped localeslocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. fall back? Recommend: nearest-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. heuristic — a Polish input falls back to en-US weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. with a low-confidence flag, not an error.

Stage 2.5 — Kind classifier

Purpose. Categorize the input by query shapequery shapeStage 1.5 of the runtime pipeline: computes a structural fingerprint of the input — script class, segmentation, known-format hits (postcode regexes, state abbreviations) — in microseconds without ML. Used by downstream stages for locale detection and kind classification. so downstream stagesstageOne 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). can route appropriately. Trivial inputs short-circuit to 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). 6; structured inputs run the full 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.; specialist inputs route to dedicated headsattention headOne of several parallel attention computations in a layer, each free to focus on a different kind of relationship between tokens. Their outputs are concatenated — 'multi-head attention'. Mailwoman uses 4 heads. (future).

Interface.

// packages/kind-classifier/src/types.ts

export type QueryKind =
| "postcode_only" // "10118"
| "locality_only" // "Paris"
| "structured_address" // "350 5th Ave, New York, NY 10118"
| "intersection" // "5th and Main"
| "po_box" // "PO Box 1234"
| "landmark" // "Behind gas station" — out of scope
| "vague" // ambiguous; full pipeline + top-k

export interface QueryKindResult {
kind: QueryKind
confidence: number
alternatives: Array<{ kind: QueryKind; confidence: number }>
}

export interface KindClassifier {
classify(input: NormalizedInput, shape: QueryShape, locale: LocaleHint): Promise<QueryKindResult>
}

Today. @mailwoman/kind-classifier workspace shipped 2026-05-23. Rule-based, pure functions. Composes per-kind scorers (po_boxPO boxA numbered mailbox at a post office used as a delivery address instead of a physical street location. Mailwoman tags it as the po_box component; structurally the same family as a subpremise., intersectionintersectionAn address that names a location by two crossing streets ('5th & Main') rather than a number and street. Mailwoman tags the two streets as intersection_a and intersection_b — a negative-space format that starved the early model., landmark, postcode_only, locality_only, structured_address, vague) and returns the top kind plus alternatives sorted by confidence. Wired in as the default classifyKind by mailwoman/runtime-pipeline.ts::createRuntimePipeline.

Future. Hybrid rule + tiny 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.'. Rule path is shipped; 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.' path remains: when no format dominates, use a small classifier trained on (QueryShape, label) pairs from a labelled corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus. slice (PhasephaseA milestone in the implementation plan (Foundation, Corpus, Training, Integration, and forward-looking phases). Distinct from stage (runtime pipeline) and tier (model vocabulary). 5 Studio is the source of those 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.).

Failure classes owned. Routes around the encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). for the trivial cases that don't need it (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.-only, localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy.-only). Improves accuracy on intersectionintersectionAn address that names a location by two crossing streets ('5th & Main') rather than a number and street. Mailwoman tags the two streets as intersection_a and intersection_b — a negative-space format that starved the early model. / PO boxPO boxA numbered mailbox at a post office used as a delivery address instead of a physical street location. Mailwoman tags it as the po_box component; structurally the same family as a subpremise. by routing to specialist headsattention headOne of several parallel attention computations in a layer, each free to focus on a different kind of relationship between tokens. Their outputs are concatenated — 'multi-head attention'. Mailwoman uses 4 heads. when they exist (v0.6.0+).

Open questions.

  • Should kind be exclusive (one labelcomponent 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.) or distributional (probability over kinds)? Recommend: distributional via alternatives — graceful failure on ambiguous cases.
  • Specialist headsattention headOne of several parallel attention computations in a layer, each free to focus on a different kind of relationship between tokens. Their outputs are concatenated — 'multi-head attention'. Mailwoman uses 4 heads. vs single multi-purpose encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters).? Recommend: single encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). for v1; revisit when there's enough labelled intersection / po_box data to justify specialization.

Stage 2.7 — Phrase grouper

Purpose. Propose coherent input unitsunitA subdivision of a building — apartment, suite, floor — that refines a street address. Mailwoman's unit component; a designator plus identifier forms a subpremise. (boundary discovery) with a structural PhraseKind hypothesis + confidence — before 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). 3 runs. Decouples boundary discovery from type classification so the classifier's job becomes "what type is this proposed 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.?" instead of jointly discovering boundaries and types. The reconciler (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) also consumes the proposals as boundary candidates for joint decodingjoint decodingA decode strategy where the neural model's proposals and rule-based solver's output are reconciled into a single parse tree. Formerly the default (Route A Phase II), now retired in favor of argmax after it was found to break the street+house_number geocode precondition..

Interface.

// packages/phrase-grouper/src/types.ts

export type PhraseKind =
| "NUMERIC"
| "STREET_PHRASE"
| "LOCALITY_PHRASE"
| "REGION_ABBREVIATION"
| "POSTCODE"
| "VENUE_PHRASE"
| "HYPHENATED_COMPOUND"

export interface PhraseProposal {
span: Section // sub-Span of the tokenized input
kindHypothesis: PhraseKind
confidence: number // 0..1
}

export interface PhraseGrouper {
group(input: NormalizedInput, shape: QueryShape, locale: LocaleHint): Promise<PhraseProposal[]>
}

Per "possibilities not constraints", the grouper emits overlapping proposals freely (e.g. Saint Petersburg surfaces as a single LOCALITY_PHRASE AND as two single-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. LOCALITY_PHRASEs) — the reconciler picks the best non-overlapping subset.

Today. @mailwoman/phrase-grouper workspace shipped 2026-05-23 (v0.5.0 Thread E). Rule-based: per-kind scorers 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.-level structural cues (proximity, punctuation, capitalization, hyphenation, format-shape repetition from QueryShape). Bitter-lesson-safe — no place-name dictionaries. Wired as the default groupPhrases in mailwoman/runtime-pipeline.ts::createRuntimePipeline (hard dep in v0.5.0, not an opt-in shim — there are no v0.4.0 users to migrate). Result surfaced on PipelineResult.phraseProposals.

Future. Learned 1-2M-param 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. proposer trained on segmentsegmentA punctuation-bounded chunk of the normalized input — the comma-separated parts of 'Portland, OR' — used to give downstream stages structural context.-boundary 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. derived from corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus.-v0.4.0 + the kryptonite catalogue. Same PhraseProposal[] output contract; the consumers (StagesstageOne 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). 3, 5) don't need to know which implementation produced the proposals. See PHASE_8_E_learned_span_proposer.md for the scope.

Failure classes owned. Boundary discovery on mid-position postcodespostcodeThe 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. (Paris 75008), hyphenated compounds (NY-NY Steakhouse), multi-word localities (Saint Petersburg), repetition tokenstokenOne 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. (Buffalo Buffalo). Addresses v0.4.0's bio_slip slice at source rather than via decoderdecoderIn a transformer encoder-decoder model, the part that produces output sequences. Mailwoman's classifier is encoder-only (no decoder); the 'CRF decoder' is a different thing — a structured-prediction layer that picks the best label sequence from the encoder's outputs. post-trim.

Open questions.

  • Should the grouper consume the classifier's prior beliefs as additional input (joint decodingjoint decodingA decode strategy where the neural model's proposals and rule-based solver's output are reconciled into a single parse tree. Formerly the default (Route A Phase II), now retired in favor of argmax after it was found to break the street+house_number geocode precondition.)? Recommend: no — the grouper runs strictly before the classifier so it can be reused by 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 reconcile when re-evaluating. Joint decodingjoint decodingA decode strategy where the neural model's proposals and rule-based solver's output are reconciled into a single parse tree. Formerly the default (Route A Phase II), now retired in favor of argmax after it was found to break the street+house_number geocode precondition. lives in 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.
  • Per-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. rule packs vs localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for.-agnostic rules? Recommend: localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for.-agnostic core (proximity, punctuation, hyphenation) + small per-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. dictionaries for street suffixstreet affixA modifier on a street name indicating type or direction — Street, Avenue, rue, Calle, N, East. Mailwoman tags these as street_prefix / street_suffix, recognized via a morphology FST. + 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. marker. The learned version subsumes both.

Stage 3 — Token classify

Purpose. Per-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. BIO labelling. The current Mailwoman 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.' (@mailwoman/neural + @mailwoman/neural-weights-*).

Interface. The Classifier interface from INTERFACES.md, with QueryShape and LocaleHint added to ClassifierContext:

// extends the existing ClassifierContext in INTERFACES.md

export interface ClassifierContext {
locale?: LocaleTag // existing
prior?: ClassificationProposal[] // existing
signal?: AbortSignal // existing

/** Added by the staged pipeline. */
queryShape?: QueryShape
localeHint?: LocaleHint
normalized?: NormalizedInput
}

Today. Shipped at v3.0.0. Both rule classifiers and NeuralSequenceClassifier implement Classifier. The neural 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.' is the small encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters).-only transformerneural 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.' documented in ARCHITECTURE.md. The Tier 2 vocabularyvocabularyThe fixed set of tokens a tokenizer can produce. Mailwoman's SentencePiece vocabulary is tens of thousands of subword pieces, with byte fallback for anything outside it. (21 BIO labelsBIO tagging (Begin-Inside-Outside). A token-level labeling scheme where each token is tagged as B-X (beginning of an entity of type X), I-X (inside an entity of type X), or O (outside any entity). Mailwoman uses BIO over SentencePiece tokens to annotate address components.) is current.

Future. Larger context windowcontext windowThe span of input a model can consider at once, bounded by its max sequence length. Everything in the window can influence every token's label via attention. (128 → 256 tokenstokenOne 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.), more localeslocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for., continued corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus. expansion. Optional: use QueryShape.segments as an encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). input featurefeatureAn 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. (segmentsegmentA punctuation-bounded chunk of the normalized input — the comma-separated parts of 'Portland, OR' — used to give downstream stages structural context.-id per 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. concatenated to the input embeddingembeddingA vector of numbers representing a token (or other item) so that similar items sit near each other in vector space. The first thing the model does is turn each token into an embedding.) — cheap, no architecture change, gives the encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). a free structural prior.

Failure classes owned. StreetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels./localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. collisions (#4) via context-aware classification.

Open questions.

  • Should NeuralSequenceClassifier consume QueryShape.segments as input today, or wait for v0.6.0? Recommend: v0.5.0+ — requires a retrain anyway.
  • How do specialist headsattention headOne of several parallel attention computations in a layer, each free to focus on a different kind of relationship between tokens. Their outputs are concatenated — 'multi-head attention'. Mailwoman uses 4 heads. (intersectionintersectionAn address that names a location by two crossing streets ('5th & Main') rather than a number and street. Mailwoman tags the two streets as intersection_a and intersection_b — a negative-space format that starved the early model., PO boxPO boxA numbered mailbox at a post office used as a delivery address instead of a physical street location. Mailwoman tags it as the po_box component; structurally the same family as a subpremise.) compose with the main encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters).? Recommend: shared encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). + per-kind headattention headOne of several parallel attention computations in a layer, each free to focus on a different kind of relationship between tokens. Their outputs are concatenated — 'multi-head attention'. Mailwoman uses 4 heads.; see ARCHITECTURE.md's multi-headattention headOne of several parallel attention computations in a layer, each free to focus on a different kind of relationship between tokens. Their outputs are concatenated — 'multi-head attention'. Mailwoman uses 4 heads. extension section.

Stage 4 — Sequence correct

Purpose. Structural validity + ambiguity resolution at the labelcomponent 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.-sequence level. Two sub-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)..

Stage 4a — CRF

Today's implementation. Linear-chain CRF over a frozen BIO transition mask. Eliminates orphan-I sequences ("Saint Petersburg → Petersburg" bug).

Interface.

// packages/neural/src/sequence-correct.ts

export interface CrfDecoder {
/** Take per-token emissions, return the best label sequence under the CRF. */
decode(emissions: Float32Array, seqLen: number, numLabels: number): BioLabel[]
}

Today. Used at trainingtrainingThe process of adjusting a model's parameters so its predictions match labeled examples, by repeatedly measuring error and nudging the weights to reduce it. Distinct from inference, when the trained model is run on new input. time and in the Python evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error.. JavaScript runtime ships ViterbiViterbi decodingA dynamic programming algorithm that finds the most likely sequence of hidden states (labels) given a sequence of observations (token emissions). Mailwoman uses Viterbi over a linear-chain CRF to produce globally coherent BIO label sequences from per-token model scores. (2026-05-22) with the BIO structural mask — orphan-I-* sequences are structurally impossible at runtime, even without learned CRFCRF (Conditional Random Field). A statistical modeling method that predicts structured outputs by modeling dependencies between adjacent labels. Mailwoman uses a linear-chain CRF as the Viterbi decoder at inference time to enforce BIO label consistency — a B-street must be followed by I-street or O, never I-locality. transitions in the 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.' bundle. Learned transitions will compose on top once a future weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. release ships them via crf-transitions.json or the model cardmodel cardA JSON metadata file (model-card.json) shipped with each weights bundle. It declares the model version, lineage, label set, required inference channels (anchor, gazetteer), calibration data, and training provenance..

Stage 4b — Span re-reader (future)

When Stage 3 emits a 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. with low confidence, or two overlapping high-confidence proposals, re-run the encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). on that 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. alone with extra structural conditioning. Additive: only proposes new alternatives, never deletes existing ones.

Interface.

// packages/neural/src/span-rereader.ts (future)

export interface SpanRereader {
/**
* Re-classify an ambiguous span with extra structural context. Returns additional proposals; the
* orchestrator merges them into the candidate set.
*/
reread(
span: Span,
context: { neighborhood: BioLabel[]; queryShape: QueryShape },
input: NormalizedInput
): Promise<ClassificationProposal[]>
}

Today. Not built.

Future. v0.6.0+ at earliest. Needs a corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus. slice of ambiguity-flagged 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. (Studio can produce this).

Failure classes owned. Ambiguous localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. names (#1), repeated admin names (#2), consistency portion of numeric chaos (#5).

Open questions.

  • CRFCRF (Conditional Random Field). A statistical modeling method that predicts structured outputs by modeling dependencies between adjacent labels. Mailwoman uses a linear-chain CRF as the Viterbi decoder at inference time to enforce BIO label consistency — a B-street must be followed by I-street or O, never I-locality. should run on rule proposals too, or only neural? Recommend: only neural for now (rule proposals don't have a sequence structure to enforce); revisit if mixed-source ambiguity becomes a hot path.
  • 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. re-reader: same encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). + extra positional conditioning, or a separate specialist 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.'? Recommend: same encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). first (cheap, reuses trainingtrainingThe process of adjusting a model's parameters so its predictions match labeled examples, by repeatedly measuring error and nudging the weights to reduce it. Distinct from inference, when the trained model is run on new input. 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.); specialist 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.' only if the evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. justifies it.

Stage 5 — Cross-component reconcile

Purpose. Pick the best self-consistent parse treeparse treeThe hierarchical address structure produced by decoding BIO labels into spans and nesting them per the schema's containment rules (house_number → street → locality → region → country). from the union of phrase-grouper, classifier, and 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. candidates. Two paths coexist:

  • Fallback path (runtime-pipeline.ts's default): keep classifier-emitted 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. in source order. The same behavior pre-v0.5.0 shipped. Used when callers don't have top-k inputs from StagesstageOne 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). 2.7 / 3 / 6.
  • Joint-decoding path (reconcileSpans in core/pipeline/reconcile.ts): ViterbiViterbi decodingA dynamic programming algorithm that finds the most likely sequence of hidden states (labels) given a sequence of observations (token emissions). Mailwoman uses Viterbi over a linear-chain CRF to produce globally coherent BIO label sequences from per-token model scores.-style beam searchbeam searchA decoding search that keeps the top-k partial candidates at each position. Used in Stage-5 reconciliation to explore the (phrase, tag, resolver) space with controlled pruning. over (phrase_span × classifier_tag × resolver_place) with concordanceconcordanceA joint-decode signal that rewards a parse whose spans form a consistent Who's On First parent-child chain and vetoes contradictory ones — a hard veto for conflicts, a log-space bonus for full agreement. scoring on WOFWOF (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. parent_id chains. Opted into by callers wiring StagesstageOne 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). 2.7 + 3 + 6 with top-k outputs.

Interface (joint-decoding path).

// core/pipeline/reconcile.ts

export function reconcileSpans(inputs: ReconcileInputs): ParseTree

export interface ReconcileInputs {
raw: string
phraseProposals: ReadonlyArray<PhraseProposal> // from Stage 2.7
classifierTopK: ReadonlyArray<ClassifierCandidate> // from Stage 3 (Thread C-s contract)
resolverCandidates?: ResolverCandidatesLookup // from Stage 6, (span, tag) → ResolvedPlace[]
parentChain?: ParentChainLookup // from Stage 6, place → ancestor list
opts?: ReconcileOpts
}

export interface ClassifierCandidate {
span: { start: number; end: number }
tag: ComponentTag
score: number // 0..1
}

export interface ReconcileOpts {
kSpan?: number // default 3 — top-k overlapping phrase proposals per start position
kTag?: number // default 3 — top-k classifier tag interpretations per span
kResolver?: number // default 5 — top-k resolver candidates per (span, tag)
concordanceWeight?: number // default 1.0 — full chain match adds +concordanceWeight in log-space
beamWidth?: number // default 16
runnersUp?: number // default 3
}

export interface ParseTree {
tree: AddressTree
confidence: number // softmaxed over the finalized beam
runnersUp: AddressTree[]
scoreBreakdown: { phrase: number; classifier: number; resolver: number; concordance: number; total: number }
}

Score per beam = phrase_conf × classifier_score × resolver_score × concordance_bonus. Concordance bonusconcordanceA joint-decode signal that rewards a parse whose spans form a consistent Who's On First parent-child chain and vetoes contradictory ones — a hard veto for conflicts, a log-space bonus for full agreement. is +1 in log-space for a fully consistent WOFWOF (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. parent_id chain across all admin pairs in the assignment, -Infinity (hard veto) for an explicit chain contradiction, 0 when no admin signal is available. Per-axis pruning (kSpan / kTag / kResolver) keeps the search tight.

Today. Joint-decoding path shipped in v0.5.0 Thread D (core/pipeline/reconcile.ts, 2026-05-23), built to close the kryptonite catalogue — NY-NY Steakhouse, Houston, TX, Paris, Texas, Saint Petersburg, FL, Buffalo Buffalo — via the concordance bonusconcordanceA joint-decode signal that rewards a parse whose spans form a consistent Who's On First parent-child chain and vetoes contradictory ones — a hard veto for conflicts, a log-space bonus for full agreement. and hard-veto contradiction handling. Retired as the default on 2026-06-14 (#566): graded against 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. 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 geocode precondition on 77–84% of clean US addresses while fixing 0% of the catalogue, so argmax is the default decode. The code and A/B harnesses remain; jointReconcile: true opts back in (forceJointReconcile is the deprecated alias).

Future. Add learned-reranker comparison now that joint decodingjoint decodingA decode strategy where the neural model's proposals and rule-based solver's output are reconciled into a single parse tree. Formerly the default (Route A Phase II), now retired in favor of argmax after it was found to break the street+house_number geocode precondition. has a kryptonite-evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. baseline. Long-term: replace the concordanceconcordanceA joint-decode signal that rewards a parse whose spans form a consistent Who's On First parent-child chain and vetoes contradictory ones — a hard veto for conflicts, a log-space bonus for full agreement. scoring with a learned reranker trained on the kryptonite catalogue.

Failure classes owned. Consistency portion of numeric chaos (#5); the hybrid-policy decision in general; cross-component coherencecoherenceThe property of a parse whose resolved places form a consistent geographic hierarchy — the resolved locality really does sit inside the resolved region. for the kryptonite catalogue.

Open questions.

  • Should Components carry a "global confidence" (probability the whole 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. is correct)? Recommend: yes — ParseTree.confidence is the softmaxed equivalent; carry forward into 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..
  • Joint decodingjoint decodingA decode strategy where the neural model's proposals and rule-based solver's output are reconciled into a single parse tree. Formerly the default (Route A Phase II), now retired in favor of argmax after it was found to break the street+house_number geocode precondition. wired as default vs opt-in? Resolved twice: default-on at v4.4.0, then retired to opt-in on 2026-06-14 (#566) on 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. grade. Argmax is the default; jointReconcile: true opts back in.

Stage 6 — Resolve with candidates

Purpose. Take the final component bundle and query 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.. Return a ranked list of candidate places — not a single point.

Interface.

// packages/resolver-wof-sqlite/src/types.ts (extends today's Resolver)

export interface Resolver {
resolve(components: Components, opts?: ResolveOpts): Promise<Resolution[]>
}

export interface ResolveOpts {
locale?: LocaleHint
userLocation?: UserLocation
maxResults?: number // default 5
minConfidence?: number // default 0.1
}

export interface Resolution {
place: WOFPlace
confidence: number

/** Per-factor breakdown of the score for debugging. */
scoreBreakdown: {
match: number
populationPrior: number
regionalPriorFromLanguage?: number
regionalPriorFromUserLocation?: number
}
}

Today. Shipped through PhasephaseA milestone in the implementation plan (Foundation, Corpus, Training, Integration, and forward-looking phases). Distinct from stage (runtime pipeline) and tier (model vocabulary). 4.3.x. Each resolved AddressNode now carries alternatives: ResolvedPlace[] (added 2026-05-23) — the runner-up candidates the backend returned for that node. Surfaces failure mode #8 (Springfield-class ambiguity) without changing the resolveTree return type.

Future. Add regionalPriorFromLanguage and regionalPriorFromUserLocation factors (today's scoring uses only match + population).

Failure classes owned. Administrative nightmares (#8).

Open questions.

  • Default maxResults: 5 vs 10? Recommend: 5 — most consumers want top-1; callers wanting more can ask.
  • Should 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. expose alternatives for each component (e.g. multiple plausible locality matches)? Recommend: no — that's the reconciler's job; 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. returns place candidates only.
  • Multiple 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. sources (WOFWOF (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. + BANBAN (Base Adresse Nationale). France's authoritative open national address register — the highest-quality training source for French addresses, with full component structure. + others) — how to compose? Recommend: separate Resolver impls per source, composed by a CompositeResolver that merges candidates. Defer until BANBAN (Base Adresse Nationale). France's authoritative open national address register — the highest-quality training source for French addresses, with full component structure. integration lands.

The coordinator

The orchestration function that composes all stagesstageOne 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).. Lives in @mailwoman/core/pipeline (shipped 2026-05-23). Generic over its 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). implementations — each 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). is structurally typed, injected via the RuntimePipelineStages record. Keeps core free of dependencies on the concrete neural / normalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input. / query-shape / 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. packages. The user-facing convenience factory createRuntimePipeline() lives in the mailwoman workspace and pre-wires the defaults.

// packages/core/src/pipeline.ts

export interface PipelineResult {
input: string
normalized: NormalizedInput
queryShape: QueryShape
locale: LocaleHint
kind: QueryKindResult
components: Components
resolutions: Resolution[]
timing: PipelineTiming
}

export interface PipelineTiming {
[stage: string]: number // ms per stage
}

export async function runPipeline(raw: string, opts?: PipelineOpts): Promise<PipelineResult> {
const t = startTiming()

const normalized = await normalize(raw, opts)
t.mark("normalize")

const queryShape = computeQueryShape(normalized, opts?.locale)
t.mark("query-shape")

const locale = await localeGate.detect(normalized, queryShape, { hint: opts?.locale })
t.mark("locale-gate")

const kind = await kindClassifier.classify(normalized, queryShape, locale)
t.mark("kind-classifier")

// Fast path — see "Fast-path routing" below.
if (canShortCircuit(kind, queryShape, opts)) {
const components = fastPathComponents(kind, queryShape)
const resolutions = await resolver.resolve(components, { ...opts, locale })
t.mark("resolve")
return { input: raw, normalized, queryShape, locale, kind, components, resolutions, timing: t.snap() }
}

// Full pipeline.
const ruleProposals = await runRuleClassifiers(normalized, locale, queryShape)
const neuralProposals = await neuralClassifier.classify(normalized, { locale, queryShape, normalized })
t.mark("token-classify")

const correctedNeural = await sequenceCorrect(neuralProposals, queryShape)
t.mark("sequence-correct")

const components = await reconciler.reconcile([...ruleProposals, ...correctedNeural], policy, locale)
t.mark("reconcile")

const resolutions = await resolver.resolve(components, { ...opts, locale })
t.mark("resolve")

return { input: raw, normalized, queryShape, locale, kind, components, resolutions, timing: t.snap() }
}

Constraints.

  • Every 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). is async (matches existing Classifier.classify shape).
  • PipelineTiming is exposed for caller diagnostics — fast paths show up as missing token-classify / sequence-correct / reconcile entries.
  • The function never throws on a 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). failure; degraded stagesstageOne 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). emit empty results and 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. continues. Caller decides whether to retry / surface to user / fall back.

Fast-path routing

The coordinator skips stagesstageOne 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). 3-5 when all of the following hold:

  1. kind.kind is one of postcode_only or locality_only.
  2. kind.confidencekindClassifier.fastPathFloor (default 0.95, configurable).
  3. queryShape.knownFormats contains a matching high-confidence hit.
  4. opts.forceFullPipeline is not set.

The match between kind and knownFormats is the safety check — a postcode_only kind without a matching 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. regex hit is suspicious and falls through to the full 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..

function canShortCircuit(kind: QueryKindResult, shape: QueryShape, opts?: PipelineOpts): boolean {
if (opts?.forceFullPipeline) return false
if (kind.confidence < 0.95) return false
if (kind.kind === "postcode_only") {
return shape.knownFormats.some((f) => f.format.endsWith("_zip") || f.format.endsWith("_postcode"))
}
if (kind.kind === "locality_only") {
return shape.totalLength <= 30 && shape.characterClass === "alpha"
}
return false
}

Fast paths are advisory. A postcode_only shortcut returns a single ambiguous-when-no-location result (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. resolved against 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.). If the consumer wanted to disambiguate via additional input (mid-typed autocomplete), they pass forceFullPipeline: true. See QUERY_SHAPE.md for the broader rationale.

Packaging map

Each 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). lives in its own workspace where possible. Existing workspaces extend; new ones are netneural networkA model made of layers of simple numeric units whose connection strengths (weights) are learned from data. The transformer encoder at Mailwoman's core is a neural network.-new.

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).WorkspaceStatus
1 NormalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input.@mailwoman/normalizenew
Boundary QueryShape@mailwoman/query-shapenew
2 Locale gatelocale gateStage 2 of the runtime pipeline: rule-based locale detection from the query shape's script and known-format signals. Returns a LocaleHint with the top candidate and alternatives, surfacing disagreement with an explicit --locale flag.@mailwoman/locale-gate + @mailwoman/locale-gate-weightsnew
2.5 Kind classifierkind classifierStage 2.5 of the runtime pipeline: categorizes the input into one of seven query kinds (structured_address, postcode_only, locality_only, intersection, po_box, landmark, vague) so the coordinator can route to the right parsing strategy.@mailwoman/kind-classifiernew (small)
2.7 Phrase grouperphrase grouperStage 2.7 of the runtime pipeline: proposes coherent input units (street phrase, locality phrase, postcode, etc.) with structural kind hypotheses. Decouples boundary discovery from type classification so the classifier answers 'what type?' not 'where?'@mailwoman/phrase-groupershipped (rule-based v1)
3 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. classify@mailwoman/neural + @mailwoman/neural-weights-*shipped
4 Sequence correct (CRFCRF (Conditional Random Field). A statistical modeling method that predicts structured outputs by modeling dependencies between adjacent labels. Mailwoman uses a linear-chain CRF as the Viterbi decoder at inference time to enforce BIO label consistency — a B-street must be followed by I-street or O, never I-locality.)@mailwoman/neural (sequence-correct submodule)half-shipped
4 Sequence correct (re-reader)@mailwoman/neural (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.-rereader submodule)new
5 Reconcile@mailwoman/core (existing solver, formalize Components shape)shipped
6 Resolve@mailwoman/resolver-wof-sqlite, @mailwoman/resolver-wof-wasmshipped (candidate-list API new)
Coordinator@mailwoman/corenew (composes the above)

The packaging is intentional: a consumer can pull just @mailwoman/core + @mailwoman/normalize + @mailwoman/query-shape for cheap parsingaddress 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. without the neural runtime. Browser-side, the localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for.-gate weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. and the kind-classifier together should fit under 1 MB so a fast-path-only deployment is feasible.

Versioning rules

  • 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). interfaces (this file): semver-major bump on any breaking change. The Components shape change from flat dict to typed { value, confidence, span, … } will be a breaking change.
  • 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). implementations: independently versioned per workspace. WeightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. packages get major bumps on labelcomponent 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.-vocabularyvocabularyThe fixed set of tokens a tokenizer can produce. Mailwoman's SentencePiece vocabulary is tens of thousands of subword pieces, with byte fallback for anything outside it. changes (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. 1 → 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. 2 was the v2 → v3 bump).
  • 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. coordinator: lives in @mailwoman/core; semver-major on any change to PipelineResult shape.
  • Adding a new 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).: requires editing this file with new interface + Today/Future block + failure classes + open questions, then a contract test in @mailwoman/core proving the new 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). composes with the existing ones.
  • Removing a 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).: requires the operator's written sign-off in DECISIONS.md. Don't.

Open questions across the pipeline

These don't belong to one 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).; they affect composition.

  1. Cancellation. Every 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). accepts an AbortSignal. Should partial results be returned on abort, or just undefined? Recommend: partial results (a normalized + localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for.-detected + kind-classified input is useful even if classification was cancelled).
  2. Caching. QueryShape and LocaleHint are cheap but not free. Should they be cached keyed by (raw, locale)? Recommend: yes, in-memory LRU at the coordinator; configurable size.
  3. Telemetry. PipelineTiming is per-call. Should we also expose per-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). success rates and per-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). error counters? Recommend: yes, but as a plug-in observability hook, not baked into the core interface.
  4. Streaming. Today 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. is request/response. For autocomplete workflows, would a streaming variant ever ship? Recommend: not in v1. The fast path covers most of the autocomplete case via cheap repeated calls.
  5. Server vs browser parity. Every 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). must run in both. The locale gatelocale gateStage 2 of the runtime pipeline: rule-based locale detection from the query shape's script and known-format signals. Returns a LocaleHint with the top candidate and alternatives, surfacing disagreement with an explicit --locale flag. and kind classifierkind classifierStage 2.5 of the runtime pipeline: categorizes the input into one of seven query kinds (structured_address, postcode_only, locality_only, intersection, po_box, landmark, vague) so the coordinator can route to the right parsing strategy. should be small enough to bundle; the encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters). is already isomorphic. Document each 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).'s bundle-size budget when implementations land.

How this relates to existing files

  • INTERFACES.md defines the classifier-level contracts (Classifier, ClassificationProposal, PolicyRegistry, etc.). This file is the 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).-level contract that composes them.
  • SCHEMA.md defines the component vocabularyvocabularyThe fixed set of tokens a tokenizer can produce. Mailwoman's SentencePiece vocabulary is tens of thousands of subword pieces, with byte fallback for anything outside it. (ComponentTag, BioLabel). Both files key off it.
  • ARCHITECTURE.md describes the existing system shape narratively. Some of its diagrams predate this staging picture; reconcile gradually.
  • QUERY_SHAPE.md defines the QueryShape sub-system in detail. This file references it but does not duplicate.
  • concepts/the-staged-pipeline is the public-facing narrative version. This file is the implementer's contract.

What's next

The staging picture is now formalized. Implementation order, in suggested priority:

  1. 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). 1 + boundary QueryShape together. Cheap to build; every downstream improvement depends on it.
  2. 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). 6 candidate-list API. Small change, big win for failure mode #8.
  3. 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). 2 + 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). 2.5 together. Enables fast paths AND informs the encoderencoderThe part of a transformer that turns input tokens into contextualized vector representations. Mailwoman's classifier is a small encoder-only transformer (~30M parameters)..
  4. JS ViterbiViterbi decodingA dynamic programming algorithm that finds the most likely sequence of hidden states (labels) given a sequence of observations (token emissions). Mailwoman uses Viterbi over a linear-chain CRF to produce globally coherent BIO label sequences from per-token model scores. for 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). 4a. Already on the v0.4.0 list.
  5. 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). 4b 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. re-reader. Wait for v0.6.0 corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus. support.

Each item gets its own phases/PHASE_*.md doc when work opens.