How to Stop an AI Agent from Inventing Nigerian Postcodes
On 1 October 2026, Nigeria's postcode system changed from six digits to an 11-character code that identifies a single building, such as EK-01-A03-FK-01.
Language models trained before that date still describe the old format with full confidence. That affects two kinds of agent:
- A coding agent that writes your validation code
- A runtime agent that reads a postcode from a customer's message or fills in a delivery form
This article shows you how to contain both. For the coding agent, you'll measure what it says today and give it a rule that corrects it. For the runtime agent, you'll build a boundary: the model proposes a postcode, and deterministic code decides whether it's well formed, whether NIPOST has assigned it, and where the work goes next.
You'll also see the tools an agent can call directly and a table of every failure state. The examples use ng-postcode, an open-source library I maintain, and its MCP server.
Table of Contents
- Prerequisites
- What a Coding Agent Says Today
- How to Correct the Coding Agent at the Source
- Where to Draw the Boundary
- How to Build the Gate
- How to Confirm a Code with NIPOST
- How to Route Work by Prefix
- How to Put the Steps Together
- How to Give an Agent Tools with Enforced Limits
- How to Handle Every Failure State
Prerequisites
Before you begin, make sure you have:
- Node.js 20 or later (for the
ng-postcodelibrary and MCP server) - An active NIPOST API credential (for runtime code confirmation)
- Access to your agent framework of choice (OpenAI, Anthropic, or a local model)
What a Coding Agent Says Today
Ask a pre-2026 language model to validate a Nigerian postcode and it will almost certainly produce a regex like /^\d{6}$/. That pattern was correct for decades, but it now rejects every valid code issued since 1 October 2026.
Worse, the model rarely flags this as a guess. It presents the stale pattern as authoritative, which is exactly the behaviour that makes hallucinated postcodes dangerous in production systems.
How to Correct the Coding Agent at the Source
Rather than patching the model's output after the fact, give it a canonical reference. Point the coding agent at the ng-postcode library and its published format specification. When the agent has access to the current grammar, most modern models will generate correct validation logic on the first try.
Key corrections to embed in your agent's system prompt:
- The code is 11 characters, not 6 digits
- The format is
STATE-AREA-DISTRICT-BUILDING-SUFFIX - Example:
EK-01-A03-FK-01 - The old 6-digit format is deprecated and must not be accepted as valid
Where to Draw the Boundary
The core principle behind preventing hallucinated postcodes is simple: the model proposes, deterministic code decides. A runtime agent should never be trusted to determine whether a postcode is real. Instead, its role is limited to extracting a candidate string from unstructured input. Verification is a separate, non-LLM step.
This split matters because it isolates the failure mode. If the model invents a code, the gate catches it before anything downstream is affected.
How to Build the Gate
Using ng-postcode, a runtime gate looks like this:
- Extract โ the agent pulls a candidate postcode from the customer message.
- Well-formedness check โ apply the 11-character grammar.
- Existence check โ call NIPOST to confirm the code is assigned.
- Route โ on success, dispatch to the assigned delivery zone; on failure, escalate to a human or ask the customer to re-enter.
- Assigned โ the code exists and maps to a specific building
- Reserved โ the code is structurally valid but not yet in use
- Unknown โ the code is not recognised by NIPOST
validate_postcode(code)โ returns format and assignment statusroute_postcode(code)โ returns the target state and queue
The gate never asks the model whether a code is real. It only asks the model for a string.
How to Confirm a Code with NIPOST
Once a candidate passes the format check, send it to the NIPOST lookup endpoint. A well-formed but unassigned code is a common failure โ attackers and language models alike can generate plausible-looking strings that were never issued.
The confirmation step returns one of three results:
Treat anything other than assigned as a routing failure.
How to Route Work by Prefix
The first segment of a Nigerian postcode identifies the state. EK is Ekiti, LA is Lagos, and so on. Once a code is confirmed, you can route the request to the correct regional handler without a second lookup.
This is useful for agents that fan out work to regional queues, and it gives you a cheap sanity check: if the agent extracts EK-... but the customer's stated address is in Lagos, you have a mismatch worth surfacing.
How to Put the Steps Together
A minimal runtime flow:
customer message
โ agent extracts candidate string
โ format gate (ng-postcode)
โ NIPOST lookup
โ assigned: route to state queue
โ reserved/unknown: escalate to human
Every failure path returns a deterministic, auditable outcome rather than a fabricated success.
How to Give an Agent Tools with Enforced Limits
The ng-postcode MCP server exposes two tools to an agent:
Neither tool accepts free-form input beyond the candidate string, and neither delegates the correctness decision back to the model. This is the shape every postcode-related tool should take: narrow inputs, verifiable outputs, no room for the model to confabulate.
How to Handle Every Failure State
| State | Meaning | Action |
| --- | --- | --- |
| Malformed | Does not match the 11-character grammar | Ask customer to re-enter |
| Well-formed but unknown | Passes format, not assigned by NIPOST | Escalate to human |
| Reserved | Structurally valid, not yet issued | Escalate to human |
| Assigned | Confirmed by NIPOST | Route by state prefix |
| Mismatch | Code state conflicts with stated address | Escalate to human |
Log every state, including the successful ones. When a language model invents a postcode, the log is how you find out โ and how you prove the gate did its job.
via FreeCodeCamp
