Build1 publisher3 min readPublished
Verivello drops a retrieved record before the model can cite the wrong company
The grounding layer in Verivello re-verifies every register record against the entity the user actually asked about, preferring an exact company number and falling back to a normalised name plus a matching incorporation year.
The Engineer · Build desk

What happened
- Verivello routes a user question through an intent-and-entity classifier and an engine router into a function-calling loop over MCP servers for Companies House, Land Registry, FCA and the Gazette.
- Tool output is placed in context as verbatim JSON, and the system prompt limits the model to explaining and citing those records, with no fact allowed that the records do not contain.
- Before a record enters that context, verifyMatch compares company numbers when both sides have one, and otherwise requires normalised name equality or 0.92 similarity plus the same incorporation year.
- Records that fail the check are dropped instead of shown, and when nothing verifies the agent answers that it does not know.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision Copying this design means shipping a refusal path, so someone with product authority has to sign off on an agent that answers fewer questions than the register could support.
- constraint The strong gate depends on an authoritative identifier appearing on both sides of the comparison, so domains whose records are only names never get past the fuzzy test.
- exposure One rule is code and the other is text in a prompt, so the verbatim-only discipline stays subject to model compliance in a way the entity filter is not.
- cost Adoption is priced per source: a typed server, a schema and tests for each register, plus a matcher written against the fields that particular entity type actually carries.
The filter runs late. `const grounded = toolResults.filter(r => verifyMatch(query, r))` sits after the tool calls, so the register request is already paid for by the time a record is rejected [5][15]. A name search that returns dozens of candidates costs the same round trip whether one candidate survives or none [12].
Order matters inside `verifyMatch`. The identifier comparison is guarded and returns: if both the query and the record carry a `companyNumber`, the function returns the equality of those two numbers [4]. When the numbers differ, the record is dropped and the name comparison never runs [13]. Companies House issues that number [1].
The other branch admits a record when normalised names are equal, or when similarity is at least 0.92 and the incorporation years agree [4]. That is weaker than it looks. Because those two tests sit either side of an OR, exact normalised-name equality passes with no corroborating signal, so where several records collapse to the query string after normalisation, the filter drops none of them [14]. The post's own worked example is a search for "Smith Consulting" that might return any of dozens [12]. Whether those dozens survive normalisation is not something the snippet can answer: the author calls it illustrative shape, not production code, and does not include the implementations of `normalise()` or `similarity()` [18].
Of the two grounding rules, only one is enforced by the interpreter. Tool output goes into context as verbatim JSON and the system prompt instructs the model to answer only from the provided records and to cite them [3]. That is an instruction. The filter is a boolean [5]. Writing on dev.to, Verivello's builder gives the reason the ordering is worth the code: feed an unverified name-search hit into context and "Now it's confidently wrong with a citation, which is worse," the author wrote [7].
Refusal follows from the same ordering. With no verified record, the agent says it does not know instead of offering a plausible guess [6]. The author treats a wrong director or ownership figure in due diligence as worse than no answer at all [16]. It is also the only branch with a fixed expected output.
For this pattern to transfer, two fields have to exist in your domain: an identifier issued by whoever is authoritative, present on both the query and the record, and a second attribute like an incorporation year that can corroborate a fuzzy name match [4]. UK company registers supply both [1], while a customer table keyed on names typed in by hand supplies neither and inherits only the 0.92 branch.
Each register sits behind its own typed Model Context Protocol server, independently unit-testable, and the same servers are reused across Claude Code, Claude Desktop and the product [9]. That prices the transport layer per source. The author open-sourced a small keyless example exposing UK public-data tools, with unit tests and CI [10].
What to watch
- Whether the open-source example publishes testable normalise() and similarity() implementations. That is where the fuzzy branch either holds or leaks.
- Whether the fallback gains a second corroborating field, such as registered address or officer name, for candidates sharing a normalised name and incorporation year.
- A published refusal rate would show what failing closed costs in answered questions.