Sulaiman Shariff

personal

People Research Platform

A research agent on the Cloudflare edge that returns people, each with a quote you can check.

Role
Engineer
When
2026
Built with
Cloudflare Workers, Workflows, Durable Objects, D1, Python
Result
0 → 11people found on the benchmark query
The query form, with a natural-language research prompt, seed URLs and country filters on the left and the run's stage list on the right.
You describe who you want in a sentence. The column on the right is every stage the run moves through, streamed as it happens, so a slow run is legible instead of just slow.

You describe the people you want in a sentence. The system searches, reads what it finds, and hands back a list where every person carries the sentence that justified including them.

The quote is the whole point. A research tool that returns a name and a confidence score is asking you to trust it, and there is no way to check. A tool that returns the sentence it read is asking you to check, which takes about two seconds per row.

Why it is spread across four runtimes

A Worker takes the request and owns the API. A Workflow runs the research, so a run survives a restart and resumes at the last completed step rather than starting again. A Durable Object coordinates the run and is the single place event ordering is decided. A Container runs the Python extraction, which needs libraries that do not exist on the Workers runtime.

That is four runtimes for one feature, which is more moving parts than I would choose if it were free. It buys resumability and a single ordering authority. A long research run that dies twenty minutes in and starts over is not a tool anyone uses twice.

Everything between them is one versioned NDJSON event stream. Version it from the first line and a schema change is a migration; leave it unversioned and it is an outage.

The run that returned nothing

The benchmark query returned zero people in 266 seconds. Not an error, not a crash. It ran to completion and found nobody, which is a worse failure than crashing because nothing tells you it happened. Across 31 benchmark runs, 13 came back empty.

The cause was not one thing. The planner was writing queries too narrow to match any real page. The extraction step was discarding candidates on a schema mismatch. Nothing reported either, because both were behaving as written.

0 → 11
people found8 of them qualified
266s → 149s
wall clock on the same query
33-107s → 20-27s
planning latencyand far less variance

The planner now writes broader queries and widens them when a pass comes back thin. Extraction keeps partial records instead of discarding them. And an empty result is now reported as an empty result, with the queries that produced it.

What I have not measured

125 of 125 Worker tests pass, which tells you the plumbing holds and nothing about whether the answers are right. I have not measured precision or recall against a labelled set, so I cannot tell you how many of those 8 qualified people actually qualify. The quotes make that checkable by hand, which is not the same as having checked it.