Back to projects
Mapper chart: the bar before launch was 87% of companies right; in a small sample of 15, it reached 93% (14 of 15). In the one that failed, a domain-for-sale page passed as its website.
A hand review of 15 directory companies, against a bar set before its first production run.

Mapper

An agent that waits for a person before it researches

Three sales tools for a data and AI firm. Models research and draft; none of them scores leads or writes to anyone.


The firm and my role

I work at a firm that sells data analysis, automation and AI to small and mid-size companies in Colombia, and I own its engineering. The slowest parts of its sales process were finding companies worth talking to, preparing for a first meeting and writing a proposal after it.

The prospect-research agent I built checks every company against the national company registry and stops until a person confirms it is the right one. It is one of three pieces I built for the sales process of a data and AI firm, alongside a company directory and a pipeline that turns an approved offer into a proposal. One rule runs through all three: models research and draft, people decide, and nothing reaches a prospect without a person sending it.

The prospect brief

The brief the agent leaves behind reports facts only: no verdict on whether the prospect is worth pursuing and no prices. The person walking into the meeting forms their own judgment from facts they can trace back to a source.

How it works

Once confirmed, the company record acts as a filter: any later fact that doesn’t match its tax ID, city, website or profiles is discarded. Three research helpers run in parallel on a smaller model, one each for the business, the decision maker and the competition, with web search and fetch as their only tools. The main agent checks ad libraries itself in a browser, re-verifies the key facts and writes a brief to a shared drive plus one task per area in the team’s tracker, through MCP.

The directory is a set of n8n workflows over a PostgreSQL queue in Supabase. The proposal pipeline only starts after the owner approves the offer. It builds a client PDF and an internal one and runs automatic checks on both. An advisory panel of model reviewers can flag issues but can’t block or approve; the owner decides.

The directory, on purpose without a model

The second piece is a directory of Colombian companies with their location and contacts. It starts from the national registry, finds each company’s website and reads it for contact details. Reading the websites raised the share of companies with a contact email other than the accounting mailbox from 74% to 77.5%.

The most important decision in the directory was what not to build. A score from a model would look precise without any data on which companies actually become clients, and an automated message would speak for the firm in a way nobody had reviewed.

A queue that survives a crash

The directory claims work from its queue with FOR UPDATE SKIP LOCKED, so two runs never hold the same company at once. Each claim is a 20-minute lease and a company gets 3 attempts: if a worker dies, its companies return to the queue when the lease expires, and running them again doesn’t duplicate rows because the writes are idempotent. Each company has a single row that gets updated, and contacts are written with ON CONFLICT on company, type and value.

Production tested the queue when DNS failed for three minutes. The workers couldn’t reach the database, so one batch did its work and couldn’t save it. Because every claim expires, most of that batch went back to the queue on its own. Nine companies that were already on their third attempt were marked failed, which is the behavior I want: a visible failure instead of an endless retry.

The logic lives in plain JavaScript modules with their own tests, and a script embeds them into the workflow definitions, so it’s tested outside n8n. The schema isn’t exposed through the public API, it has Row Level Security, and n8n connects with its own limited role. It respects robots.txt and costs zero dollars a month, using only free sources and free quotas. The agents run as Claude Code skills on purpose: a person approves each step, and with this few runs a hosted service would add surface without adding control.

What broke in production

One online store had 687 forms on its home page. The parser cleaned the page again on every loop, and because each match kept a reference to its copy of the page, one batch pushed the worker to 2 GB of memory and killed it. I changed the parser to clean each page once and redeployed; that page is now a test case. The same store now takes 97 ms with 256 MB of heap.

The proposal checks

Automatic checks run on every draft: no banned terms, no leftovers from the previous client, short sentences, every font embedded and a file small enough to open on a phone. The internal version can’t contain a single price.

Design decisions

  • Identity before anything expensive. Researching the wrong company is the worst failure the agent can have, and having a person confirm it is the cheapest step, so it goes first.
  • No model in the lead directory. It finds companies and contacts, and nothing else: it doesn’t score leads, write messages or send anything. A person makes first contact with a company, not a model.
  • Every fact carries its source, the date it was seen and whether it was seen or inferred. The agent never guesses a count, date or figure; when a page can’t be read, the brief says so instead of filling the gap.
  • Collect the minimum, by law. The brief lives in a shared drive, so under Colombia’s data protection law the agent keeps only professional information: never family, home address, personal phone or email, ID numbers, health, politics, religion or photos. It never contacts the prospect or fills in a form.
  • The model drafts proposals and never sends them. The owner approves the offer before anything is built, and a person sends the final file.

How I checked it

The directory had a pass bar written before its first production run, and it was checked by hand. The agents don’t have a formal eval set yet: their quality comes from structural checks on every output and from a person approving each step that leaves the company.

Measure Result
Directory pass bar at least 13 of 15 companies correct, set before the run
Manual review 14 of 15 correct, across 5 departments
Failure found a domain-for-sale page taken as the company’s site
After the fix re-check of 91 of the 1,395 guessed websites: 3 rejected (3.3%)
Companies with a website found 10,380 (33.6%)
Formal eval set for the agents designed, not yet run (see “Next”)

Limits

The directory’s quality was checked on a small sample, which can catch a systematic error but not measure a rate precisely. And until the agents have an eval set, I can’t put a number on how often the brief gets things right.

Next

The first thing I’ll add is an eval set for the prospect brief, built from the briefs the team has already used: for each fact, whether it was right and whether it mattered in the meeting. That turns “a person reviews it” into a number I can watch when the prompt or the model changes.