Every morning, thinkidiot.com/digest/daily publishes a short AI news digest without a single human touching it. Three models write it, check it, and illustrate it, all running on one machine in my home, a DGX Spark that sits on my desk and hums through the night. I gave myself one rule when I started: everything happens on that box, with free and open tools, and I should be able to forget the whole thing exists.

If you can't read the article further because of paywall then please click here

For a long time I couldn't forget it, because one part kept dragging me back in. The writing held up, the images held up, the publishing held up, and one thing kept failing anyway, the most basic step you can imagine: going to a website and reading what it says.

If that sounds like it should be the easy part, stay with me, because it turned out to be the hardest problem in the whole project. The thing that finally solved it is a browser so small it ships as one file, and it changed my mornings more than any model I've added.

Why reading a website was the hard part

The digest has one promise, and everything else hangs off it: every story gets checked against the original source. Not a summary of the announcement, not a tweet about the paper, but the actual page where the news first appeared. Think of a journalist who refuses to print a rumor until she's read the document herself, and you have the rule my pipeline lives by.

There's a very practical reason for being this strict about it. Hand a language model a two-line snippet from an aggregator and ask for a summary, and it will cheerfully invent the details the snippet left out, in a confident voice, with no hint that it's guessing. A news digest that sounds sure and gets facts wrong is worse than no digest at all. So the agent that gathers the news has to open the real page and read the real words.

That's where the wall is, and it's higher than it looks. Big websites don't like robots, so they run checks that try to spot an automated browser before showing it anything. My pipeline used Chrome in "headless" mode, which just means Chrome running without a window, and headless Chrome smells like a robot from a mile away. Instead of the article, the agent would get a puzzle page, a cookie wall, or a blank response, and the more important the source, the more likely it was to slam the door.

So my mornings developed a pattern I came to dread. A source would quietly start turning the agent away, a fetch would come back empty or half-empty, and that day's issue would lean on secondhand coverage more than I ever wanted. The whole point of the pipeline was finding and reading the original news, and finding and reading the original news was the one thing it couldn't reliably do. I'd spot the gap over coffee, log into the Spark, fiddle with settings, and get things limping again until the next site raised its drawbridge.

The workaround I'm not proud of

My fix, for days, was to have the research agent visit sources one at a time. It would open a site, wait for Chrome to load it or fail, try again if it failed, and move to the next one, like a patient intern with a bad browser. Every source I added made the nightly run a little longer, and every new site was one more place to get blocked.

There was a reason it had to be one at a time. Chrome is heavy, and each copy wants around 200MB of memory before it has loaded a single page. On my Spark, that memory already belongs to three models: Nemotron 3.5 Lightning, which does the research, Qwen 3.8, which writes the issue, and SenseNova U1.5, which draws the infographic. Running a crowd of Chrome copies next to them was never an option, so the agent shuffled through sites in a line.

Chrome also never travels alone, which is its own kind of problem. To drive it from code you need Node, a library called Puppeteer, a special build of Chrome for automation, and all three pinned to versions that agree with each other. That's a lot of moving parts for a job that's supposed to run quietly at 6 AM on a machine whose real purpose is running models.

The obvious escapes were off the table before I even considered them. Paid scraping services and rented browser farms would have solved the blocking overnight, but they'd also break the only rule I had: it runs on my desk, on open tools, with nothing charged per request. So I lived with the duct tape, and, if I'm honest, I kept finding reasons to work on any part of the pipeline except this one.

Good news that made things worse

A few weeks ago the creative half of the pipeline finally came together. I added SenseNova U1.5 to draw an infographic for every issue, after an earlier image model kept mangling every word it tried to write. For the first time, the whole digest, the words, the headlines, and a picture you could actually read, came out of models running on one box in my house. Until now, I'd have called that the biggest upgrade the project ever got.

It also shone a bright light on the weak spot. Once the writing and the images were solid, every flaw left in an issue traced back to the same place: a fetch step that kept getting turned away at the door. A beautifully written digest built on pages the agent never managed to read is still just a well-dressed guess.

The post I was sure was fake

Obscura found me the way most tools find people now, through a LinkedIn post that went viral with numbers that looked invented. A full browser in a single 70MB file, using about 30MB of memory where Chrome uses 200 and more, loading pages in 85 milliseconds instead of 500, and sneaking past bot checks out of the box. I've been burned by enough "miracle" scraping tools that my first instinct was to close the tab.

It's a browser with no window, built for programs rather than people, written in Rust and released as free open source.

It runs a website's JavaScript with the same engine Chrome uses, and it speaks the same control language Chrome uses (the Chrome DevTools Protocol, if you want the name), so tools like Puppeteer that were built to drive Chrome can drive Obscura instead without noticing the swap. There is no Chrome hidden inside it and no Node needed around it, because it draws pages itself and arrives as one downloadable file.

What actually happened

I started the most cautious way I could, by running Obscura next to my existing setup and pointing my old scripts at it, changing nothing else.

# one file, no dependencies
curl -LO https://github.com/h4ckf0r0day/obscura/releases/latest/download/obscura-x86_64-linux.tar.gz
tar xzf obscura-x86_64-linux.tar.gz
./obscura fetch https://example.com --eval "document.title"
# or run it in Docker as a drop-in for Chrome (~57MB image)
docker run -d --name obscura -p 127.0.0.1:9222:9222 h4ckf0r0day/obscura
const browser = await puppeteer.connect({
  browserURL: 'http://127.0.0.1:9222',
});
// everything below this line stayed exactly as it was

That was the whole migration, because my scripts genuinely couldn't tell they were talking to something new. Chrome stayed installed, so I could run both side by side and compare what each one brought back, source by source, before trusting anything. Three things changed almost as soon as real traffic went through it.

  • First, the doors that had been shut for days swung open. Sites that used to greet the agent with a puzzle page now hand over the article, and I've moved the worst offenders onto Obscura's extra-stealthy builds just to be safe. For the digest this means one concrete thing: every summary is now written from the original page, because the agent can finally reach it, instead of from someone else's retelling. When I say the news is verified now, that's exactly what I mean, and the problem this project kept tripping over is simply gone.
  • Second, the text got cleaner without me changing a single prompt. I expected the ad and tracker blocking to matter for speed, and instead it mattered for what came out the other end. Fewer cookie banners and "subscribe now" pop-ups end up in the scraped text, so fewer of them end up in front of the model that writes the issue, and I've since deleted a pile of prompt instructions whose only job was keeping newsletter pop-ups out of the summaries.
  • Third, the era of visiting sites one at a time is finally over. At around 30MB a copy, Obscura is light enough to fetch every source at once, on the same machine that's holding three models in memory. The research loop that used to trudge down the list now grabs everything in parallel, the nightly run got shorter, and adding a new source no longer starts with me doing memory math.

Put it all together and the morning finally looks like the sketch I drew when I started. The scheduler fires, Obscura fans out across the sources and brings back clean pages, Nemotron reads the originals and picks the stories, Qwen writes them up, SenseNova draws the graphic, and the site publishes, all on one box, with nothing leaving it except the finished digest.

What's next, and whether you should try it

The current setup is deliberately cautious: Obscura standing in for Chrome behind my old scripts, with the most hostile sources on the stealth builds. The next step is to drop Node from the fetch stage completely, because Obscura has its own command for scraping many pages in parallel, and at that point the whole digest becomes one small binary, three local models, and a scheduler. Updating one file also beats keeping Node, Puppeteer, and a Chrome build in agreement.

So, should you point this at your own scraper? If you scrape websites, build agents that need to read the web, or run anything unattended that depends on Chrome behaving itself, I'd say yes without much hesitation. It's free, the license is Apache-2.0, installing is one command, and you can run it beside your current setup and compare results before you commit to anything.

I'm documenting the whole switch as I go at thinkidiot.com/digest/daily, and the project lives at github.com/h4ckf0r0day/obscura if the 6 AM job in your life needs the same rescue.

If you found this useful, follow me on Medium or check out Towards Deep Learning for more breakdowns like this. No hype. Just the numbers and what they mean.