Skip to main content
The ML layer at Guild is small and deliberate. We didn’t build a model for everything. We built four, each at the point in the system where prediction beats a hand-coded rule. Walk through them in order — the order they fire in a typical user journey — and the shape of each will make sense.

The four models

Notice the loop. The ledger feeds the standing and matching models. The matching model’s interactions (applies, approvals, completions) flow back into the ledger. The standing model’s output (the tier) writes to the ledger as a derived signal. The negotiation engine learns inside Redis from every accept and decline. None of this is offline batch ETL. The signal flows continuously.

1. The matching model

Fires every time a job is posted. Ranks every plausible Job Seeker. Top candidates get pinged.

Shape

A lambdarank learning-to-rank model. Why lambdarank: the underlying problem is ordinal, not classification. We don’t care if the model is 92% confident a particular Job Seeker is a fit — we care that they rank above the people who aren’t a fit. Pairwise ranking objectives optimise for the order, not for the absolute scores. Trained with NDCG@5 as the primary metric. We measure on internal benchmarks against synthetic data calibrated to Nigerian informal-economy patterns. Production retraining cadence: nightly, on the new attendance and review data that arrived in the last 24 hours.

Features

Four factor families. Each one maps to one of the four production criteria in the OPay Scholars 2026 challenge. The model treats these factors as gradient-boosted weak learners — no manual coefficient tuning. The training process finds which combination of features in which ranges produces the best ranking. The same features matter differently for a bricklayer job in Bodija than for a tailor job in Surulere; the model learns this.

Where it fires

A typical inference is under 100ms for a candidate pool of a few thousand.

Why not a deep neural network

Three reasons.
  1. Tabular features. Gradient-boosted trees beat deep models on tabular data with our feature shape. This is well-established.
  2. Interpretability. When a candidate ranks where they do, the gradient-boosted model can explain it (feature contributions). A neural network cannot.
  3. Training cost. Nightly retraining on commodity hardware is feasible with trees; expensive with deep models.
We’d revisit if we had image, audio, or sequence inputs at scale. We don’t.

2. The standing model

Fires every time a dashboard renders, or when the ledger gains material new signal. Outputs the four-tier classification — Building, Emerging, Trusted, Established — plus a raw score 0-1000.

Shape

Multi-class softmax classifier on a gradient-boosted tree base. Four output classes match the four tiers. The score is the highest-probability class’s confidence, rescaled.

Features

The model trains separately for Job Seeker and trader sub-populations because the most predictive features differ.

Training data and recalibration

Trained on Guild ledger data. Retrained nightly on the new completed-jobs and ratings rows added in the last 24 hours. The model is calibrated continuously — not “trained once and frozen.” A Job Seeker who joined six months ago and has stayed active will have the model’s understanding of them sharpen as more activity arrives.

Where the output goes

The tier and score are written back to a derived column on the user row, scoped to the last computation time. Any read of the Job Seeker’s standing returns the cached tier unless a material event has invalidated it (a completed job, a new review, a repayment landed). Materially-invalidated tiers are recomputed lazily on the next dashboard read. The Partner API snapshot endpoint reads from the same cached tier. So lender reads and dashboard reads see identical values.

Why a classifier, not a regression

We tried regression first. The issue: a single 0-1000 score doesn’t degrade gracefully. A small ranking change moves a Job Seeker from 599 to 601 — which is the difference between Emerging and Trusted in product terms, even though almost nothing changed in their actual behaviour. A classifier with four discrete output classes is honest about the underlying decision being categorical. It also matches the user’s mental model: people understand “I moved from Emerging to Trusted” better than “my score went from 600 to 604.”

3. The negotiation engine

Fires when a Job Seeker is considering a counter-offer. Picks the recommended counter and learns from the outcome.

Shape

A Thompson sampling Beta(α, β) bandit. One arm per (segment × bucket).
  • Segment: a coarse-grained user-context bucket. Examples: (role=JOB_SEEKER, tier=Emerging, location_band=lagos_metro, skill=bricklayer).
  • Bucket: the candidate counter-offer. Examples: accept, counter_+5%, counter_+10%, counter_+15%, decline.
For each segment, we maintain a Beta(α, β) posterior per bucket. Drawing from each bucket’s posterior and picking the max θ gives Thompson sampling. The bucket we pick is the bucket the user sees as the recommended response.

Online learning

When the user takes an action (accepts the recommendation, picks a different bucket, declines entirely), we update the posterior:
  • Successful negotiation (the bucket led to a closed deal): α += 1.
  • Unsuccessful negotiation (the bucket led to a withdrawal or unaccepted counter): β += 1.
Posteriors live in Redis at ml:bandit:negotiate:{segment}:{bucket}. Persistence is durable enough — losing a recent posterior is recoverable because new outcomes will refill it. Cold-start posteriors are uniform Beta(1, 1).

Why Thompson sampling

We tried epsilon-greedy first. The issue: ε-greedy explores uniformly, even on buckets that are clearly underperforming. Thompson sampling explores in proportion to the posterior’s uncertainty — buckets we’re unsure about get tried often; buckets we’re confident are bad get rarely tried. Faster convergence on real-world signal. We also tried UCB1. It works but is harder to tune. Thompson sampling’s Beta posteriors are interpretable; we can read the (α, β) values and know exactly what the model has learned about each bucket.

Where it fires today

The negotiation engine is wired into the application service. When a Job Seeker is presented with a job offer that allows negotiation, the engine selects the recommended counter-offer based on the Job Seeker’s segment. The interaction outcome (accept, decline, counter) flows back as a posterior update. The engine’s posteriors update from every interaction. The longer the platform runs, the better its recommendations get for each segment. This is the data flywheel.

4. The transcription model

Fires whenever a voice note arrives in WhatsApp. Returns text.

Shape

A speech-to-text model fine-tuned for Nigerian English, Pidgin, Yoruba, Igbo, Hausa. Audio in, text out. Streaming-capable but typically used in batch mode (voice notes are short, completion latency matters more than first-token latency).

Where it fires

The user spoke. The system understood. The user didn’t have to type. Same path as a typed message from the intent engine onward.

Why a separate transcription model and not delegated to the channel platform

Two reasons.
  1. Quality on Nigerian voices. Generic speech-to-text models trained primarily on American or British English degrade on Nigerian accents and code-switching. A model with Nigerian voice data in its fine-tuning corpus gives materially better transcripts.
  2. Consistency across channels. When the voice agent (Tola) needs a transcription, when WhatsApp needs a transcription, when SMS-to-voice-note flows need one, they all hit the same model. One consistency surface.

How the four models reinforce each other

The system gets better the more it runs. Not in the marketing sense — in the literal sense. Every interaction either teaches a model (negotiation engine) or feeds a future retraining pass (matching, standing).

What we don’t model (and why)

A short list of things judges might ask about.
  • Fraud detection model. We don’t have one. The webhook reconcile guard + append-only audit + timing-safe HMAC is the defence layer. Fraud detection as a learned model would surface only after the platform has enough signal to be meaningful; we’d want at least 6 months of real production traffic before training one.
  • Default-prediction model. Lenders bring their own. Our job is to surface the underlying signals (standing tier, repayment streak, cashflow stability) — the lender’s model decides how to underwrite. This is a deliberate scope choice: we don’t want to be the bank.
  • Embedding-based search across job descriptions. Job postings are short and structured. A keyword match on skill tags + a structured filter on location/language outperforms a sentence-embedding search on the size of our corpus. We’d revisit at scale.
  • A foundation-scale Nigerian-language model. Tola’s conversational ability comes from her runtime, not from a Guild-trained LLM. We’re not training foundation models.

What’s deployed today

Data flow

The ledger as the data substrate. How signal flows in and out.

Resilience & scale

How the inference path is reliable enough to underwrite on.

Standing & the Ledger

The product surface — what users see when the standing model reads them.