Selected work
Projects
ai-news
Keeping up with AI had become a several-hours-a-day job. I built a pipeline that cuts that reading down to ten minutes: it filters a dozen sources with classic rules and brings in the language model only for the final stretch. People read it, and other agents query it.
- fastapi
- react
- pgvector
- mcp
bizkaia-now
What's on today or this weekend in Bizkaia? A map that answers it. I was going to write scrapers for nine venues, and it turned out the Basque Government's open events feed already covered them. The hard part wasn't getting the events, but knowing when two are the same.
- python
- fastapi
- postgis
- maplibre
a11y-crawler
Auditing a whole site's accessibility without checking it page by page. I group the pages that share a template, audit one, and propagate. The heavy lifting is deterministic; the model only steps in at the end to settle doubts. Later I found I'd reinvented a Princeton paper.
- typescript
- playwright
- axe-core
- postgres
recon
Most «deep research» tools produce convincing prose without telling you what to trust. Recon splits researching from verifying: several agents search, a fact-checker tags every claim —verified, contradicted, unsourced— and only then writes the report.
- python
- crewai
- sqlite
- embeddings
viz-components
A chart library that works the same in Next.js, Angular or plain HTML, with no adapters. The key was not marrying any framework: standard Web Components. Published on npm; the same <viz-chart> anywhere.
- lit
- web-components
- highcharts
- npm
vlm-dni-ocr
Pulling the fields off an ID card when the photo comes in crooked or smudged, where classic OCR gives up. Here the vision model earns its place: it reads the image. But with a safety net: if a value fails validation, it goes null. A made-up field on an ID is worse than none.
- python
- doctr
- qwen-vl
- fastapi
workflow-lint
Claude Code's agent workflows orchestrate, but won't warn you if the script has a silly bug: you find out mid-run, with the cost already spent. This catches it before launch —in milliseconds, spending no tokens—. It needed a linter, not a model.
- javascript
- static-analysis
- claude-code
lighthouses-bot
Eight generations of a Rust bot that won almost every game… against itself. Against real bots, fourth out of five. I threw it away, rewrote it in Python and reached first place measuring a tournament that wasn't the real one. Measuring a lot isn't measuring well.
- rust
- python
- heuristics
- game-bot
euskal34
Thirteen hacking and cryptography challenges from Euskal Encounter 34: we won with twelve out of thirteen and wrote them up one by one. How each level was attacked, what we tried that failed, and the scripts to reproduce it.
- ctf
- reversing
- cryptography
- python