AoAH Day 24: Tuatara, an evolving Atom aggregator that mutates / Dec 2025
My original purpose for starting this
I'm not sure if taking the longest way around was wise here but I ended up building tuatara, an aggregator to pull together all my colleagues' writing into one place. They're a quirky bunch with many diverse homegrown feeds in various states of brokenness, so it's difficult to build a one-size-fits-all tool.
So given it's the end of the year and I'm sozzled on Christmas eve on
mulled wine, I decided to make Tuatara mutate its own code by linking with my
Evolving code like it's 2026
The initial generation of the code was pretty straightforward, using Sqlite to
store a database with all the posts and importing metadata from my previously
created
> tuatara import-sortal
Sortal Import Results:
Total contacts scanned: 420
Contacts with feeds: 15
Feeds imported: 16
Feeds skipped (already exist): 0
Run 'tuatara fetch' to download posts from the imported feeds.
But when we actually get the feeds, I rapidly realised that there are lots of parsing quirks needed:
> tuatara fetch
Fetching Anil Madhavapeddy...
340 posts (0 new)
Fetching David Allsopp...
Not modified
Fetching Jessica Man...
Not modified
Fetching Jon Ludlam...
28 posts (0 new)
Fetching Jon Sterling...
Not modified
Fetching Mark Elvers...
Not modified
Fetching Martin Kleppmann...
Error: Feed parse error: document MUST contains exactly one <feed> element at l.0 c.0
URL: http://feeds.feedburner.com/martinkl
Fetching Onkar Gulati...
Error: Not_found
URL: https://onkargulati.com/feed.xml
Fetching Patrick Ferris...
Error: Feed parse error: <entry> elements MUST contains at least an <author> element or <feed> element MUST contains one or more <author> elements at l.1460 c.7
URL: http://patrick.sirref.org/weeklies/atom.xml
Fetching Richard Mortier...
79 posts (79 new)
Fetching Ryan Gibb...
38 posts (38 new)
Fetching Sadiq Jaffer...
10 posts (10 new)
Total: 127 new posts (3 errors)
Either we skip content, or talk to the people involved to fix their feeds, but it's Christmas eve so that's unlikely. And anyway, we want to be liberal in what we accept so why can't I fix my own software first?!

Medice, cura te ipsum
The non-obvious and probably-terrible answer here is to use our fancy coding
models to force the Tuatara source code to heal itself. I added an --evolve
flag to allow tuatara to invoke
Fetching Martin Kleppmann...
Error: Feed parse error: document MUST contains exactly one <feed> element at l.0 c.0
URL: http://feeds.feedburner.com/martinkl
Invoking Claude Code to fix parse error...
The feed from `http://feeds.feedburner.com/martinkl` is an **RSS 2.0 feed** (it
starts with `<rss version="2.0">`), but tuatara was incorrectly detecting it as
an **Atom feed**. This caused the Syndic Atom parser to fail with "document
MUST contains exactly one \<feed\> element".
The root cause was that the `detect_feed_type` function prioritized the HTTP
`Content-Type` header over the actual content. FeedBurner (and other feed
aggregators) often serve RSS feeds with an incorrect `application/atom+xml`
content-type header.
This is a generic fix that will work for any feed aggregator or CDN that
mis-labels RSS feeds as Atom (or vice versa), so no domain-specific quirk was
needed.
But the parsing drama continued, as
The quirk module converts ISO 8601 dates (2025-10-22T12:24:00-00:00) to RFC 822 format (Wed, 22 Oct 2025 12:24:00 GMT) which is what Syndic's RSS2 parser expects.
And Not_found exception:
Fetching Patrick Ferris...Error: Feed parse error:
elements MUST contains at least an element or element MUST contains one or more elements at l.1460 c.7 URL: http://patrick.sirref.org/weeklies/atom.xml
But never fear, the inexorable --evolve flag figures it out and patches its own code!
There were some non-trivial quirks as well;
Using the Claude frontend design
Then I needed a quick way to do a clean frontend output so I can visualise the
JSONfeed. Claude has a /plugin frontend-design skill that is built in, and
prompting it to give me a few designs let me integrate a --html output.
And because it's Christmas, I added some snowflakes as well. Yay!

Reflections
The paper I enjoyed writing the most this year was
Back in March, I had the honour of being invited to a Bellairs meeting to discuss a heady combination of semantics and computational science.

As I noted in my