<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: Project</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/series/project.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-01-23T21:26:10+00:00</updated><author><name>Simon Willison</name></author><entry><title>Wilson Lin on FastRender: a browser built by thousands of parallel agents</title><link href="https://simonwillison.net/2026/Jan/23/fastrender/#atom-series" rel="alternate"/><published>2026-01-23T21:26:10+00:00</published><updated>2026-01-23T21:26:10+00:00</updated><id>https://simonwillison.net/2026/Jan/23/fastrender/#atom-series</id><summary type="html">
    &lt;p&gt;Last week Cursor published &lt;a href="https://cursor.com/blog/scaling-agents"&gt;Scaling long-running autonomous coding&lt;/a&gt;, an article describing their research efforts into coordinating large numbers of autonomous coding agents. One of the projects mentioned in the article was &lt;a href="https://github.com/wilsonzlin/fastrender"&gt;FastRender&lt;/a&gt;, a web browser they built from scratch using their agent swarms. I wanted to learn more so I asked Wilson Lin, the engineer behind FastRender, if we could record a conversation about the project. That 47 minute video is &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4"&gt;now available on YouTube&lt;/a&gt;. I've included some of the highlights below.&lt;/p&gt;

&lt;iframe style="margin-top: 1.5em; margin-bottom: 1.5em;" width="560" height="315" src="https://www.youtube-nocookie.com/embed/bKrAcTf2pL4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="allowfullscreen"&gt; &lt;/iframe&gt;

&lt;p&gt;See my &lt;a href="https://simonwillison.net/2026/Jan/19/scaling-long-running-autonomous-coding/"&gt;previous post&lt;/a&gt; for my notes and screenshots from trying out FastRender myself.&lt;/p&gt;


&lt;h4 id="what-fastrender-can-do-right-now"&gt;What FastRender can do right now&lt;/h4&gt;
&lt;p&gt;We started the conversation with a demo of FastRender loading different pages (&lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=195s"&gt;03:15&lt;/a&gt;). The JavaScript engine isn't working yet so we instead loaded &lt;a href="https://github.com/wilsonzlin/fastrender"&gt;github.com/wilsonzlin/fastrender&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/"&gt;Wikipedia&lt;/a&gt; and &lt;a href="https://cnn.com"&gt;CNN&lt;/a&gt; - all of which were usable, if a little slow to display.&lt;/p&gt;
&lt;p&gt;JavaScript had been disabled by one of the agents, which decided to add a feature flag! &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=242s"&gt;04:02&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;JavaScript is disabled right now. The agents made a decision as they were currently still implementing the engine and making progress towards other parts... they decided to turn it off or put it behind a feature flag, technically.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="from-side-project-to-core-research"&gt;From side-project to core research&lt;/h4&gt;
&lt;p&gt;Wilson started what become FastRender as a personal side-project to explore the capabilities of the latest generation of frontier models - Claude Opus 4.5, GPT-5.1, and GPT-5.2. &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=56s"&gt;00:56&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;FastRender was a personal project of mine from, I'd say, November. It was an experiment to see how well frontier models like Opus 4.5 and back then GPT-5.1 could do with much more complex, difficult tasks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A browser rendering engine was the ideal choice for this, because it's both &lt;em&gt;extremely&lt;/em&gt; ambitious and complex but also well specified. And you can visually see how well it's working! &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=117s"&gt;01:57&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As that experiment progressed, I was seeing better and better results from single agents that were able to actually make good progress on this project. And at that point, I wanted to see, well, what's the next level? How do I push this even further?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once it became clear that this was an opportunity to try multiple agents working together it graduated to an official Cursor research project, and available resources were amplified.&lt;/p&gt;
&lt;p&gt;The goal of FastRender was never to build a browser to compete with the likes of Chrome. &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=2512s"&gt;41:52&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We never intended for it to be a production software or usable, but we wanted to observe behaviors of this harness of multiple agents, to see how they could work at scale.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The great thing about a browser is that it has such a large scope that it can keep serving experiments in this space for many years to come. JavaScript, then WebAssembly, then WebGPU... it could take many years to run out of new challenges for the agents to tackle.&lt;/p&gt;
&lt;h4 id="running-thousands-of-agents-at-once"&gt;Running thousands of agents at once&lt;/h4&gt;
&lt;p&gt;The most interesting thing about FastRender is the way the project used multiple agents working in parallel to build different parts of the browser. I asked how many agents were running at once: &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=324s"&gt;05:24&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At the peak, when we had the stable system running for one week continuously, there were approximately 2,000 agents running concurrently at one time. And they were making, I believe, thousands of commits per hour.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The project has &lt;a href="https://github.com/wilsonzlin/fastrender/commits/main/"&gt;nearly 30,000 commits&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;How do you run 2,000 agents at once? They used &lt;em&gt;really big machines&lt;/em&gt;. &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=356s"&gt;05:56&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The simple approach we took with the infrastructure was to have a large machine run one of these multi-agent harnesses. Each machine had ample resources, and it would run about 300 agents concurrently on each. This was able to scale and run reasonably well, as agents spend a lot of time thinking, and not just running tools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point we switched to a live demo of the harness running on one of those big machines (&lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=392s"&gt;06:32&lt;/a&gt;). The agents are arranged in a tree structure, with planning agents firing up tasks and worker agents then carrying them out. &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=434s"&gt;07:14&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/wilson-lin-agents.jpg" alt="Terminal window showing a tmux session running &amp;quot;grind-swarm&amp;quot; task manager with RUNNING status. Header shows &amp;quot;grind-swarm – 45:54:15&amp;quot; with stats &amp;quot;planners: 9 (0 done) | tasks: 111 working, 0 pending, 232 done | 12900.9M↑ 514.1M↓&amp;quot;. Task list includes: p1 Root (main), p2 CSS selector matching performance + bloom filter integration, p3 CSS stylesheet parsing semantics &amp;amp; at-rule handling, p4 Custom properties (@property) + var() resolution + incremental recompute/invalidation, p37 CSS at-rule artifact integration, p50 Selector engine correctness &amp;amp; spec coverage, p51 Computed-value + property coverage across css-cascade, p105 Style sharing / computed style caching in fastrender-style, p289 CSS cascade layers (@layer) global ordering, w5 Fix workspace lockfile drift, w7 Implement computed-style snapshot sharing, w15 Fix css-properties namespace handling, w17 (Stretch) Enable bloom fast-reject in HTML quirks mode, w18 Refactor css-properties stylesheet parsing. Activity log shows shell commands including cargo check, git status, git push origin main, and various test runs. Bottom status bar shows &amp;quot;grind-css0:target/release/grind-swarm*&amp;quot; and &amp;quot;streamyard.com is sharing your screen&amp;quot; notification with timestamp &amp;quot;12:02 22-Jan-26&amp;quot;." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This cluster of agents is working towards building out the CSS aspects of the browser, whether that's parsing, selector engine, those features. We managed to push this even further by splitting out the browser project into multiple instructions or work streams and have each one run one of these harnesses on their own machine, so that was able to further parallelize and increase throughput.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But don't all of these agents working on the same codebase result in a huge amount of merge conflicts? Apparently not: &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=501s"&gt;08:21&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We've noticed that most commits do not have merge conflicts. The reason is the harness itself is able to quite effectively split out and divide the scope and tasks such that it tries to minimize the amount of overlap of work. That's also reflected in the code structure—commits will be made at various times and they don't tend to touch each other at the same time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This appears to be the key trick for unlocking benefits from parallel agents: if planning agents do a good enough job of breaking up the work into non-overlapping chunks you can bring hundreds or even thousands of agents to bear on a problem at once.&lt;/p&gt;
&lt;p&gt;Surprisingly, Wilson found that GPT-5.1 and GPT-5.2 were a better fit for this work than the coding specialist GPT-5.1-Codex: &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=1048s"&gt;17:28&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some initial findings were that the instructions here were more expansive than merely coding. For example, how to operate and interact within a harness, or how to operate autonomously without interacting with the user or having a lot of user feedback. These kinds of instructions we found worked better with the general models.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I asked what the longest they've seen this system run without human intervention: &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=1108s"&gt;18:28&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;So this system, once you give an instruction, there's actually no way to steer it, you can't prompt it, you're going to adjust how it goes. The only thing you can do is stop it. So our longest run, all the runs are basically autonomous. We don't alter the trajectory while executing. [...]&lt;/p&gt;
&lt;p&gt;And so the longest at the time of the post was about a week and that's pretty close to the longest. Of course the research project itself was only about three weeks so you know we probably can go longer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="specifications-and-feedback-loops"&gt;Specifications and feedback loops&lt;/h4&gt;
&lt;p&gt;An interesting aspect of this project design is feedback loops. For agents to work autonomously for long periods of time they need as much useful context about the problem they are solving as possible, combined with effective feedback loops to help them make decisions.&lt;/p&gt;
&lt;p&gt;The FastRender repo &lt;a href="https://github.com/wilsonzlin/fastrender/tree/19bf1036105d4eeb8bf3330678b7cb11c1490bdc/specs"&gt;uses git submodules to include relevant specifications&lt;/a&gt;, including csswg-drafts, tc39-ecma262 for JavaScript, whatwg-dom, whatwg-html and more. &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=846s"&gt;14:06&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Feedback loops to the system are very important. Agents are working for very long periods continuously, and without guardrails and feedback to know whether what they're doing is right or wrong it can have a big impact over a long rollout. Specs are definitely an important part—you can see lots of comments in the code base that AI wrote referring specifically to specs that they found in the specs submodules.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GPT-5.2 is a vision-capable model, and part of the feedback loop for FastRender included taking screenshots of the rendering results and feeding those back into the model:
&lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=983s"&gt;16:23&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the earlier evolution of this project, when it was just doing the static renderings of screenshots, this was definitely a very explicit thing we taught it to do. And these models are visual models, so they do have that ability. We have progress indicators to tell it to compare the diff against a golden sample.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The strictness of the Rust compiler helped provide a feedback loop as well: &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=952s"&gt;15:52&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The nice thing about Rust is you can get a lot of verification just from compilation, and that is not as available in other languages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="the-agents-chose-the-dependencies"&gt;The agents chose the dependencies&lt;/h4&gt;
&lt;p&gt;We talked about the &lt;a href="https://github.com/wilsonzlin/fastrender/blob/19bf1036105d4eeb8bf3330678b7cb11c1490bdc/Cargo.toml"&gt;Cargo.toml dependencies&lt;/a&gt; that the project had accumulated, almost all of which had been selected by the agents themselves.&lt;/p&gt;
&lt;p&gt;Some of these, like &lt;a href="https://skia.org/"&gt;Skia&lt;/a&gt; for 2D graphics rendering or &lt;a href="https://github.com/harfbuzz/harfbuzz"&gt;HarfBuzz&lt;/a&gt; for text shaping, were obvious choices. Others such as &lt;a href="https://github.com/DioxusLabs/taffy"&gt;Taffy&lt;/a&gt; felt like they might go against the from-scratch goals of the project, since that library implements CSS flexbox and grid layout algorithms directly. This was not an intended outcome. &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=1673s"&gt;27:53&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Similarly these are dependencies that the agent picked to use for small parts of the engine and perhaps should have actually implemented itself. I think this reflects on the importance of the instructions, because I actually never encoded specifically the level of dependencies we should be implementing ourselves.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The agents vendored in Taffy and &lt;a href="https://github.com/wilsonzlin/fastrender/commits/main/vendor/taffy"&gt;applied a stream of changes&lt;/a&gt; to that vendored copy.
&lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=1878s"&gt;31:18&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It's currently vendored. And as the agents work on it, they do make changes to it. This was actually an artifact from the very early days of the project before it was a fully fledged browser... it's implementing things like the flex and grid layers, but there are other layout methods like inline, block, and table, and in our new experiment, we're removing that completely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The inclusion of QuickJS despite the presence of a home-grown ecma-rs implementation has a fun origin story:
&lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=2115s"&gt;35:15&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I believe it mentioned that it pulled in the QuickJS because it knew that other agents were working on the JavaScript engine, and it needed to unblock itself quickly. [...]&lt;/p&gt;
&lt;p&gt;It was like, eventually, once that's finished, let's remove it and replace with the proper engine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I love how similar this is to the dynamics of a large-scale human engineering team, where you could absolutely see one engineer getting frustrated at another team not having delivered yet and unblocking themselves by pulling in a third-party library.&lt;/p&gt;
&lt;h4 id="intermittent-errors-are-ok-actually"&gt;Intermittent errors are OK, actually&lt;/h4&gt;
&lt;p&gt;Here's something I found really surprising: the agents were allowed to introduce small errors into the codebase as they worked! &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=2382s"&gt;39:42&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One of the trade-offs was: if you wanted every single commit to be a hundred percent perfect, make sure it can always compile every time, that might be a synchronization bottleneck. [...]&lt;/p&gt;
&lt;p&gt;Especially as you break up the system into more modularized aspects, you can see that errors get introduced, but small errors, right? An API change or some syntax error, but then they get fixed really quickly after a few commits. So there's a little bit of slack in the system to allow these temporary errors so that the overall system can continue to make progress at a really high throughput. [...]&lt;/p&gt;
&lt;p&gt;People may say, well, that's not correct code. But it's not that the errors are accumulating. It's a stable rate of errors. [...] That seems like a worthwhile trade-off.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you're going to have thousands of agents working in parallel optimizing for throughput over correctness turns out to be a strategy worth exploring.&lt;/p&gt;
&lt;h4 id="a-single-engineer-plus-a-swarm-of-agents-in-january-2026"&gt;A single engineer plus a swarm of agents in January 2026&lt;/h4&gt;
&lt;p&gt;The thing I find most interesting about FastRender is how it demonstrates the extreme edge of what a single engineer can achieve in early 2026 with the assistance of a swarm of agents.&lt;/p&gt;
&lt;p&gt;FastRender may not be a production-ready browser, but it represents over a million lines of Rust code, written in a few weeks, that can already render real web pages to a usable degree.&lt;/p&gt;
&lt;p&gt;A browser really is the ideal research project to experiment with this new, weirdly shaped form of software engineering.&lt;/p&gt;
&lt;p&gt;I asked Wilson how much mental effort he had invested in browser rendering compared to agent co-ordination. &lt;a href="https://www.youtube.com/watch?v=bKrAcTf2pL4&amp;amp;t=694s"&gt;11:34&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The browser and this project were co-developed and very symbiotic, only because the browser was a very useful objective for us to measure and iterate the progress of the harness. The goal was to iterate on and research the multi-agent harness—the browser was just the research example or objective.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;FastRender is effectively using a full browser rendering engine as a "hello world" exercise for multi-agent coordination!&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/browsers"&gt;browsers&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/youtube"&gt;youtube&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/coding-agents"&gt;coding-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cursor"&gt;cursor&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/parallel-agents"&gt;parallel-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/browser-challenge"&gt;browser-challenge&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="browsers"/><category term="youtube"/><category term="ai"/><category term="rust"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="coding-agents"/><category term="cursor"/><category term="parallel-agents"/><category term="browser-challenge"/></entry><entry><title>Under the hood of Canada Spends with Brendan Samek</title><link href="https://simonwillison.net/2025/Dec/9/canada-spends/#atom-series" rel="alternate"/><published>2025-12-09T23:52:05+00:00</published><updated>2025-12-09T23:52:05+00:00</updated><id>https://simonwillison.net/2025/Dec/9/canada-spends/#atom-series</id><summary type="html">
    &lt;p&gt;I talked to Brendan Samek about &lt;a href="https://canadaspends.com/"&gt;Canada Spends&lt;/a&gt;, a project from &lt;a href="https://www.buildcanada.com/"&gt;Build Canada&lt;/a&gt; that makes Canadian government financial data accessible and explorable using a combination of Datasette, a neat custom frontend, Ruby ingestion scripts, &lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils&lt;/a&gt; and pieces of LLM-powered PDF extraction.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://www.youtube.com/watch?v=T8xiMgmb8po"&gt;the video on YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;iframe style="margin-bottom: 1.5em;" width="560" height="315" src="https://www.youtube-nocookie.com/embed/T8xiMgmb8po" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="allowfullscreen"&gt; &lt;/iframe&gt;

&lt;p&gt;Sections within that video:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=T8xiMgmb8po&amp;amp;t=177s"&gt;02:57&lt;/a&gt; Data sources and the PDF problem&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=T8xiMgmb8po&amp;amp;t=351s"&gt;05:51&lt;/a&gt; Crowdsourcing financial data across Canada&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=T8xiMgmb8po&amp;amp;t=447s"&gt;07:27&lt;/a&gt; Datasette demo: Search and facets&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=T8xiMgmb8po&amp;amp;t=753s"&gt;12:33&lt;/a&gt; Behind the scenes: Ingestion code&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=T8xiMgmb8po&amp;amp;t=1044s"&gt;17:24&lt;/a&gt; Data quality horror stories&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=T8xiMgmb8po&amp;amp;t=1246s"&gt;20:46&lt;/a&gt; Using Gemini to extract PDF data&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=T8xiMgmb8po&amp;amp;t=1524s"&gt;25:24&lt;/a&gt; Why SQLite is perfect for data distribution&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="build-canada-and-canada-spends"&gt;Build Canada and Canada Spends&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://www.buildcanada.com/"&gt;Build Canada&lt;/a&gt; is a volunteer-driven non-profit that launched in February 2025 - here's &lt;a href="https://www.canadianaffairs.news/2025/09/26/builders-at-the-gate-inside-the-civic-movement-to-jolt-canada-out-of-stagnation/"&gt;some background information&lt;/a&gt; on the organization, which has a strong pro-entrepreneurship and pro-technology angle.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://canadaspends.com/"&gt;Canada Spends&lt;/a&gt; is their project to make Canadian government financial data more accessible and explorable. It includes a tax sources and sinks visualizer and a searchable database of government contracts, plus a collection of tools covering financial data from different levels of government.&lt;/p&gt;
&lt;h4 id="datasette-for-data-exploration"&gt;Datasette for data exploration&lt;/h4&gt;
&lt;p&gt;The project maintains a Datasette instance at &lt;a href="https://api.canadasbuilding.com/"&gt;api.canadasbilding.com&lt;/a&gt; containing the data they have gathered and processed from multiple data sources - currently more than 2 million rows plus a combined search index across a denormalized copy of that data.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/api-canadasbuilding-com-canada-spends.jpg" alt="  Datasette UI for a canada-spends database.  aggregated-contracts-under-10k:  year, contract_goods_number_of, contracts_goods_original_value, contracts_goods_amendment_value, contract_service_number_of, contracts_service_original_value, contracts_service_amendment_value, contract_construction_number_of, contracts_construction_original_value, contracts_construction_amendment_value, acquisition_card_transactions_number_of, acquisition_card_transactions_total_value, owner_org, owner_org_title  487 rows cihr_grants  external_id, title, project_lead_name, co_researchers, institution, province, country, competition_year, award_amount, program, program_type, theme, research_subject, keywords, abstract, duration, source_url  53,420 rows contracts-over-10k:   reference_number, procurement_id, vendor_name, vendor_postal_code, buyer_name, contract_date, economic_object_code, description_en, description_fr, contract_period_start, delivery_date, contract_value, original_value, amendment_value, comments_en, comments_fr, additional_comments_en, additional_comments_fr, agreement_type_code, trade_agreement, land_claims, commodity_type, commodity_code, country_of_vendor, solicitation_procedure, limited_tendering_reason, trade_agreement_exceptions, indigenous_business, indigenous_business_excluding_psib, intellectual_property, potential_commercial_exploitation, former_public_servant, contracting_entity, standing_offer_number, instrument_type, ministers_office, number_of_bids, article_6_exceptions, award_criteria, socioeconomic_indicator, reporting_period, owner_org, owner_org_title  1,172,575 rows global_affairs_grants:   id, projectNumber, dateModified, title, description, status, start, end, countries, executingAgencyPartner, DACSectors, maximumContribution, ContributingOrganization, expectedResults, resultsAchieved, aidType, collaborationType, financeType, flowType, reportingOrganisation, programName, selectionMechanism, policyMarkers, regions, alternameImPositions, budgets, Locations, otherIdentifiers, participatingOrgs, programDataStructure, relatedActivities, transactions  2,378 rows nserc_grants:   title, award_summary, application_id, competition_year, fiscal_year, project_lead_name, institution, department, province, award_amount, installment, program, selection_committee, research_subject, area_of_application, co-researchers, partners, external_id, source_url  701,310 rows sshrc_grants:   id, title, program, fiscal_year, competition_year, applicant, organization, amount, discipline, area_of_research, co_applicant, keywords, source_url  213,085 rows transfers:   FSCL_YR, MINC, MINE, MINF, DepartmentNumber-Numéro-de-Ministère, DEPT_EN_DESC, DEPT_FR_DESC, RCPNT_CLS_EN_DESC, RCPNT_CLS_FR_DESC, RCPNT_NML_EN_DESC, RCPNT_NML_FR_DESC, CTY_EN_NM, CTY_FR_NM, PROVTER_EN, PROVTER_FR, CNTRY_EN_NM, CNTRY_FR_NM, TOT_CY_XPND_AMT, AGRG_PYMT_AMT  357,797 rows  Download SQLite DB: canada-spends.db 2.4 GB Powered by Datasette · Queries took 24.733ms " style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="processing-pdfs"&gt;Processing PDFs&lt;/h4&gt;
&lt;p&gt;The highest quality government financial data comes from the audited financial statements that every Canadian government department is required to publish. As is so often the case with government data, these are usually published as PDFs.&lt;/p&gt;
&lt;p&gt;Brendan has been using Gemini to help extract data from those PDFs. Since this is accounting data the numbers can be summed and cross-checked to help validate the LLM didn't make any obvious mistakes.&lt;/p&gt;
&lt;h4 id="further-reading"&gt;Further reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datasette.io/"&gt;datasette.io&lt;/a&gt;, the official website for Datasette&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils.datasette.io&lt;/a&gt; for more on &lt;code&gt;sqlite-utils&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://canadaspends.com/"&gt;Canada Spends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/BuildCanada/CanadaSpends"&gt;BuildCanada/CanadaSpends&lt;/a&gt; on GitHub&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/data-journalism"&gt;data-journalism&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/politics"&gt;politics&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/youtube"&gt;youtube&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="data-journalism"/><category term="politics"/><category term="sqlite"/><category term="youtube"/><category term="datasette"/><category term="sqlite-utils"/></entry><entry><title>Six short video demos of LLM and Datasette projects</title><link href="https://simonwillison.net/2025/Jan/22/office-hours-demos/#atom-series" rel="alternate"/><published>2025-01-22T02:09:54+00:00</published><updated>2025-01-22T02:09:54+00:00</updated><id>https://simonwillison.net/2025/Jan/22/office-hours-demos/#atom-series</id><summary type="html">
    &lt;p&gt;Last Friday Alex Garcia and I hosted a new kind of Datasette Public Office Hours session, inviting members of the Datasette community to share short demos of projects that they had built. The session lasted just over an hour and featured demos from six different people.&lt;/p&gt;
&lt;p&gt;We broadcast live on YouTube, but I've now edited the session into separate videos. These are listed below, along with project summaries and show notes for each presentation.&lt;/p&gt;
&lt;p&gt;You can also watch all six videos in &lt;a href="https://www.youtube.com/playlist?list=PLSocEbMlNGotyeonEbgFP1_uf9gk1z7zm"&gt;this YouTube playlist&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Jan/22/office-hours-demos/#llm-logs-feedback-by-matthias-l-bken"&gt;llm-logs-feedback by Matthias Lübken&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Jan/22/office-hours-demos/#llm-model-gateway-and-llm-consortium-by-thomas-hughes"&gt;llm-model-gateway and llm-consortium by Thomas Hughes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Jan/22/office-hours-demos/#congressional-travel-explorer-with-derek-willis"&gt;Congressional Travel Explorer with Derek Willis&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Jan/22/office-hours-demos/#llm-questioncache-with-nat-knight"&gt;llm-questioncache with Nat Knight&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Jan/22/office-hours-demos/#improvements-to-datasette-enrichments-with-simon-willison"&gt;Improvements to Datasette Enrichments with Simon Willison&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Jan/22/office-hours-demos/#datasette-comments-pins-and-write-ui-with-alex-garcia"&gt;Datasette comments, pins and write UI with Alex Garcia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="llm-logs-feedback-by-matthias-l-bken"&gt;llm-logs-feedback by Matthias Lübken&lt;/h4&gt;
&lt;p&gt;&lt;lite-youtube videoid="9pEP6auZmvg"
  title="llm-logs-feedback by Matthias Lübken"
  playlabel="Play: llm-logs-feedback by Matthias Lübken"
&gt; &lt;/lite-youtube&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/luebken/llm-logs-feedback"&gt;llm-logs-feedback&lt;/a&gt; is a plugin by Matthias Lübken for &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; which adds the ability to store feedback on prompt responses, using new &lt;code&gt;llm feedback+1&lt;/code&gt; and &lt;code&gt;llm feedback-1&lt;/code&gt; commands. These also accept an optional comment, and the feedback is stored in a &lt;code&gt;feedback&lt;/code&gt; table in SQLite.&lt;/p&gt;
&lt;p&gt;You can install the plugin from PyPI like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm install llm-logs-feedback&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The full plugin implementation is in the &lt;a href="https://github.com/luebken/llm-logs-feedback/blob/main/llm_logs_feedback.py"&gt;llm_logs_feedback.py file&lt;/a&gt; in Matthias' GitHub repository.&lt;/p&gt;
&lt;h4 id="llm-model-gateway-and-llm-consortium-by-thomas-hughes"&gt;llm-model-gateway and llm-consortium by Thomas Hughes&lt;/h4&gt;
&lt;p&gt;&lt;lite-youtube videoid="Th5WOyjuRdk"
  title="llm-model-gateway and llm-consortium by Thomas Hughes"
  playlabel="Play: llm-model-gateway and llm-consortium by Thomas Hughes"
&gt; &lt;/lite-youtube&gt;&lt;/p&gt;
&lt;p&gt;Tommy Hughes has been developing a whole array of LLM plugins, including his &lt;a href="https://github.com/irthomasthomas/llm-plugin-generator"&gt;llm-plugin-generator&lt;/a&gt; which is a plugin that can help write new plugins!&lt;/p&gt;
&lt;p&gt;He started by demonstrating &lt;a href="https://github.com/irthomasthomas/llm-model-gateway"&gt;llm-model-gateway&lt;/a&gt;, a plugin that adds a &lt;code&gt;llm serve&lt;/code&gt; command which starts a localhost server running an imitation of the OpenAI API against LLM models:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm install llm-model-gateway
llm serve&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Tommy's main demo was of &lt;a href="https://github.com/irthomasthomas/llm-consortium"&gt;llm-consortium&lt;/a&gt;, a plugin which can use a consortium of collaborating LLM models working together to solve problems.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm install llm-consortium
llm consortium &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Best way to prepare avocado toast&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; \
  --models claude-3.5-sonnet \
  --models gpt-4o \
  --arbiter gegemini-1.5-pro-latest \
  --confidence-threshold 0.8 \
  --max-iterations 3 \
  --output results.json&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I ran this and got back:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The best way to prepare avocado toast involves selecting quality ingredients and careful preparation. Start with a thick slice of good quality bread, such as sourdough or whole grain. Toast it to your preferred level of crispness.  While the bread is toasting, prepare a ripe avocado. Cut it in half, remove the pit, and scoop the flesh into a bowl. Mash the avocado with a fork, leaving some chunks for texture. Season with salt, black pepper, and a squeeze of fresh lemon juice to prevent browning.  Optional additions include a touch of red pepper flakes.&lt;/p&gt;
&lt;p&gt;Once the toast is ready, let it cool slightly before spreading the seasoned avocado evenly over it. Consider lightly rubbing a cut garlic clove on the warm toast for an extra layer of flavor (optional).&lt;/p&gt;
&lt;p&gt;Enhance your avocado toast with your favorite toppings. Popular choices include: everything bagel seasoning, sliced tomatoes, radishes, a poached or fried egg (for added protein), microgreens, smoked salmon (for a more savory option), feta cheese crumbles, or a drizzle of hot sauce.  For a finishing touch, drizzle with high-quality olive oil and sprinkle with sesame or chia seeds for added texture.&lt;/p&gt;
&lt;p&gt;Consider dietary needs when choosing toppings. For example, those following a low-carb diet might skip the tomatoes and opt for more protein and healthy fats.&lt;/p&gt;
&lt;p&gt;Finally, pay attention to presentation. Arrange the toppings neatly for a visually appealing toast. Serve immediately to enjoy the fresh flavors and crispy toast.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But the really interesting thing is the full log of the prompts and responses sent to Claude 3.5 Sonnet and GPT-4o, followed by a combined prompt to Gemini 1.5 Pro to have it arbitrate between the two responses. You can see &lt;a href="https://gist.github.com/simonw/425f42f8ec1a963ae13c5b57ba580f56"&gt;the full logged prompts and responses here&lt;/a&gt;. Here's that &lt;a href="https://gist.github.com/simonw/e82370f0e5986a15823c82200c1b77f8"&gt;results.json&lt;/a&gt; output file.&lt;/p&gt;
&lt;h4 id="congressional-travel-explorer-with-derek-willis"&gt;Congressional Travel Explorer with Derek Willis&lt;/h4&gt;
&lt;p&gt;&lt;lite-youtube videoid="CDilLbFP1DY"
  title="Congressional Travel Explorer with Derek Willis"
  playlabel="Play: Congressional Travel Explorer with Derek Willis"
&gt; &lt;/lite-youtube&gt;&lt;/p&gt;
&lt;p&gt;Derek Willis teaches data journalism at the Philip Merrill College of Journalism at the University of Maryland. For a recent project his students built a &lt;a href="https://cnsmaryland.org/interactives/fall-2024/congressional_travel_explorer/index.html"&gt;Congressional Travel Explorer&lt;/a&gt; interactive using Datasette, AWS Extract and Claude 3.5 Sonnet to analyze travel disclosures from members of Congress.&lt;/p&gt;
&lt;p&gt;One of the outcomes from the project was this story in Politico: &lt;a href="https://www.politico.com/news/2024/10/30/israel-aipac-funded-congress-travel-00185167"&gt;Members of Congress have taken hundreds of AIPAC-funded trips to Israel in the past decade&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="llm-questioncache-with-nat-knight"&gt;llm-questioncache with Nat Knight&lt;/h4&gt;
&lt;p&gt;&lt;lite-youtube videoid="lXwfEYXjsak"
  title="llm-questioncache with Nat Knight"
  playlabel="Play: llm-questioncache with Nat Knight"
&gt; &lt;/lite-youtube&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/nathanielknight/llm-questioncache"&gt;llm-questioncache&lt;/a&gt; builds on top of &lt;a href="https://llm.datasette.io/"&gt;https://llm.datasette.io/&lt;/a&gt; to cache answers to questions, using embeddings to return similar answers if they have already been stored.&lt;/p&gt;
&lt;p&gt;Using embeddings for de-duplication of similar questions is an interesting way to apply LLM's &lt;a href="https://llm.datasette.io/en/stable/embeddings/python-api.html"&gt;embeddings feature&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="improvements-to-datasette-enrichments-with-simon-willison"&gt;Improvements to Datasette Enrichments with Simon Willison&lt;/h4&gt;
&lt;p&gt;&lt;lite-youtube videoid="GumAgaYpda0"
  title="Improvements to Datasette Enrichments with Simon Willison"
  playlabel="Play: Improvements to Datasette Enrichments with Simon Willison"
&gt; &lt;/lite-youtube&gt;&lt;/p&gt;
&lt;p&gt;I've demonstrated improvements I've been making to Datasette's &lt;a href="https://enrichments.datasette.io/"&gt;Enrichments&lt;/a&gt; system over the past few weeks.&lt;/p&gt;
&lt;p&gt;Enrichments allow you to apply an operation - such as geocoding, a QuickJS JavaScript transformation or an LLM prompt - against selected rows within a table.&lt;/p&gt;
&lt;p&gt;The latest release of &lt;a href="https://github.com/datasette/datasette-enrichments/releases/tag/0.5"&gt;datasette-enrichments&lt;/a&gt; adds visible progress bars and the ability to pause, resume and cancel an enrichment job that is running against a table.&lt;/p&gt;
&lt;h4 id="datasette-comments-pins-and-write-ui-with-alex-garcia"&gt;Datasette comments, pins and write UI with Alex Garcia&lt;/h4&gt;
&lt;p&gt;&lt;lite-youtube videoid="i0u4N6g15Zg"
  title="Datasette comments, pins and write UI with Alex Garcia"
  playlabel="Play: Datasette comments, pins and write UI with Alex Garcia"
&gt; &lt;/lite-youtube&gt;&lt;/p&gt;
&lt;p&gt;We finished with three plugin demos from Alex, showcasing collaborative features we have been developing for &lt;a href="https://www.datasette.cloud/"&gt;Datasette Cloud&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/datasette/datasette-write-ui"&gt;datasette-write-ui&lt;/a&gt; provides tools for editing and adding data to Datasette tables. A new feature here is the ability to shift-click a row to open the editing interface for that row.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/datasette/datasette-pins"&gt;datasette-pins&lt;/a&gt; allows users to pin tables and databases to their Datasette home page, making them easier to find.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/datasette/datasette-comments"&gt;datasette-comments&lt;/a&gt; adds a commenting interface to Datasette, allowing users to leave comments on individual rows in a table.&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/community"&gt;community&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/derek-willis"&gt;derek-willis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/demos"&gt;demos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/youtube"&gt;youtube&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/data-journalism"&gt;data-journalism&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/enrichments"&gt;enrichments&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/alex-garcia"&gt;alex-garcia&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette-public-office-hours"&gt;datasette-public-office-hours&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="community"/><category term="derek-willis"/><category term="demos"/><category term="youtube"/><category term="data-journalism"/><category term="enrichments"/><category term="llm"/><category term="datasette"/><category term="llms"/><category term="alex-garcia"/><category term="datasette-public-office-hours"/><category term="ai"/><category term="generative-ai"/></entry><entry><title>Project: Civic Band - scraping and searching PDF meeting minutes from hundreds of municipalities</title><link href="https://simonwillison.net/2024/Nov/16/civic-band/#atom-series" rel="alternate"/><published>2024-11-16T22:14:01+00:00</published><updated>2024-11-16T22:14:01+00:00</updated><id>https://simonwillison.net/2024/Nov/16/civic-band/#atom-series</id><summary type="html">
    &lt;p&gt;I interviewed &lt;a href="https://phildini.dev/"&gt;Philip James&lt;/a&gt; about &lt;a href="https://civic.band/"&gt;Civic Band&lt;/a&gt;, his "slowly growing collection of databases of the minutes from civic governments". Philip demonstrated the site and talked through his pipeline for scraping and indexing meeting minutes from many different local government authorities around the USA.&lt;/p&gt;

&lt;iframe style="margin-top: 1.5em; margin-bottom: 1.5em;" width="560" height="315" src="https://www.youtube-nocookie.com/embed/OziYd7xcGzc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="allowfullscreen"&gt; &lt;/iframe&gt;

&lt;p&gt;We recorded this conversation as part of yesterday's Datasette Public Office Hours session.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/16/civic-band/#civic-band"&gt;Civic Band&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/16/civic-band/#the-technical-stack"&gt;The technical stack&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/16/civic-band/#scale-and-storage"&gt;Scale and storage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/16/civic-band/#future-plans"&gt;Future plans&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="civic-band"&gt;Civic Band&lt;/h4&gt;
&lt;p&gt;Philip was inspired to start thinking more about local government after the 2016 US election. He realised that there was a huge amount of information about decisions made by local authorities tucked away in their meeting minutes,but that information was hidden away in thousands of PDF files across many different websites.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There was this massive backlog of basically every decision that had ever been made by one of these bodies. But it was almost impossible to discover because it lives in these systems where the method of exchange is a PDF.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Philip lives in Alameda, which makes its minutes available &lt;a href="https://alameda.legistar.com/Calendar.aspx"&gt;via this portal&lt;/a&gt; powered by &lt;a href="https://granicus.com/product/legistar-agenda-management/"&gt;Legistar&lt;/a&gt;. It turns out there are a small number of vendors that provide this kind of software tool, so once you've written a scraper for one it's likely to work for many others as well.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://alameda.ca.civic.band/"&gt;the Civic Band portal for Alameda&lt;/a&gt;, powered by &lt;a href="https://datasette.io/"&gt;Datasette&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/civic-band-1.jpg" alt="Datasette instance titled Alameda Civic Data, has search box, a note that says  A fully-searchable database of Alameda, CA civic meeting minutes. Last updated: 2024-11-15T20:27:36. See the full list at Civic Band and a meetings database with tables minutes and agendas." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;It's running the &lt;a href="https://github.com/simonw/datasette-search-all"&gt;datasette-search-all&lt;/a&gt; plugin and has both tables configured for full-text search. Here's a &lt;a href="https://alameda.ca.civic.band/-/search?q=housing"&gt;search for housing&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/civic-band-2.jpg" alt="Search all tables - for housing. 43 results in meetings: agendas. Each result shows a meeting, date, page, text and a rendered page image" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="the-technical-stack"&gt;The technical stack&lt;/h4&gt;
&lt;p&gt;The public Civic Band sites all run using Datasette in Docker Containers - one container per municipality. They're hosted on a single &lt;a href="https://www.hetzner.com/"&gt;Hetzner&lt;/a&gt; machine.&lt;/p&gt;
&lt;p&gt;The ingestion pipeline runs separately from the main hosting environment, using a Mac Mini on Philp's desk at home.&lt;/p&gt;
&lt;p&gt;OCR works by breaking each PDF up into images and then running &lt;a href="https://github.com/tesseract-ocr/tesseract"&gt;Tesseract OCR&lt;/a&gt; against them directly on the Mac Mini. This processes in the order of 10,000 or less new pages of documents a day.&lt;/p&gt;
&lt;p&gt;Philip treats PDF as a normalization target, because the pipeline is designed around documents with pages of text. In the rare event that a municipality publishes documents in another format such as &lt;code&gt;.docx&lt;/code&gt; he converts them to PDF before processing.&lt;/p&gt;
&lt;p&gt;PNG images of the PDF pages are served via a CDN, and the OCRd text is written to SQLite database files - one per municipality. &lt;a href="https://sqlite.org/fts5.html"&gt;SQLite FTS&lt;/a&gt; provides full-text search.&lt;/p&gt;
&lt;h4 id="scale-and-storage"&gt;Scale and storage&lt;/h4&gt;
&lt;p&gt;The entire project currently comes to about 265GB on disk.  The PNGs of the pages use about 350GB of CDN storage.&lt;/p&gt;
&lt;p&gt;Most of the individual SQLite databases are very small. The largest is for &lt;a href="https://maui-county.hi.civic.band/"&gt;Maui County&lt;/a&gt; which is around 535MB because that county has professional stenographers taking detailed notes for every one of their meetings.&lt;/p&gt;
&lt;p&gt;Each city adds only a few documents a week so growth is manageable even as the number of cities grows.&lt;/p&gt;
&lt;h4 id="future-plans"&gt;Future plans&lt;/h4&gt;
&lt;p&gt;We talked quite a bit about a goal to allow users to subscribe to updates that match specific search terms.&lt;/p&gt;
&lt;p&gt;Philip has been building out a separate site called Civic Observer to address this need, which will store searches and then execute the periodically using the Datasette JSON API, with a Django app to record state to avoid sending the same alert more than once.&lt;/p&gt;

&lt;p&gt;I've had a long term ambition to build some kind of saved search alerts plugin for Datasette generally, to allow users to subscribe to new results for arbitrary SQL queries. My &lt;a href="https://github.com/simonw/sqlite-chronicle"&gt;sqlite-chronicle&lt;/a&gt; library is part or that effort - it uses SQLite triggers to maintain version numbers for individual rows in a table, allowing you to query just the rows that have been inserted or modified since the version number last time you ran the query.&lt;/p&gt;

&lt;p&gt;Philip is keen to talk to anyone who is interested in using Civic Band or helping expand it to even more cities. You can find him on the &lt;a href="https://datasette.io/discord"&gt;Datasette Discord&lt;/a&gt;.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/data-journalism"&gt;data-journalism&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/political-hacking"&gt;political-hacking&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/politics"&gt;politics&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette-public-office-hours"&gt;datasette-public-office-hours&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="data-journalism"/><category term="political-hacking"/><category term="politics"/><category term="sqlite"/><category term="datasette"/><category term="datasette-public-office-hours"/></entry><entry><title>Project: VERDAD - tracking misinformation in radio broadcasts using Gemini 1.5</title><link href="https://simonwillison.net/2024/Nov/7/project-verdad/#atom-series" rel="alternate"/><published>2024-11-07T18:41:51+00:00</published><updated>2024-11-07T18:41:51+00:00</updated><id>https://simonwillison.net/2024/Nov/7/project-verdad/#atom-series</id><summary type="html">
    &lt;p&gt;I'm starting a new interview series called &lt;strong&gt;Project&lt;/strong&gt;. The idea is to interview people who are building interesting data projects and talk about what they've built, how they built it, and what they learned along the way.&lt;/p&gt;
&lt;p&gt;The first episode is a conversation with Rajiv Sinclair from &lt;a href="https://publicdata.works/"&gt;Public Data Works&lt;/a&gt; about &lt;a href="https://verdad.app/"&gt;VERDAD&lt;/a&gt;, a brand new project in collaboration with journalist &lt;a href="https://twitter.com/mguzman_detroit"&gt;Martina Guzmán&lt;/a&gt; that aims to track misinformation in radio broadcasts around the USA.&lt;/p&gt;
&lt;p&gt;VERDAD hits a whole bunch of my interests at once. It's a beautiful example of scrappy data journalism in action, and it attempts something that simply would not have been possible just a year ago by taking advantage of new LLM tools.&lt;/p&gt;
&lt;p&gt;You can watch &lt;a href="https://www.youtube.com/watch?v=t_S-loWDGE0"&gt;the half hour interview&lt;/a&gt; on YouTube. Read on for the shownotes and some highlights from our conversation.&lt;/p&gt;

&lt;iframe style="margin-top: 1.5em; margin-bottom: 1.5em;" width="560" height="315" src="https://www.youtube-nocookie.com/embed/t_S-loWDGE0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="allowfullscreen"&gt; &lt;/iframe&gt;

&lt;h4 id="the-verdad-project"&gt;The VERDAD project&lt;/h4&gt;
&lt;p&gt;VERDAD tracks radio broadcasts from 48 different talk radio radio stations across the USA, primarily in Spanish. Audio from these stations is archived as MP3s, transcribed and then analyzed to identify potential examples of political misinformation.&lt;/p&gt;
&lt;p&gt;The result is "snippets" of audio accompanied by the trancript, an English translation, categories indicating the type of misinformation that may be present and an LLM-generated explanation of why that snippet was selected.&lt;/p&gt;
&lt;p&gt;These are then presented in an interface for human reviewers, who can listen directly to the audio in question, update the categories and add their own comments as well.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/verdad-1.jpg" alt="Screenshot of a content moderation interface titled VERDAD showing three posts with ratings and tags. Main view shows filters on left including Source Language, State, Source, Label, and Political Spectrum slider. Two users visible in left sidebar: Simon Willison and Rajiv Sinclair. Posts discuss claims about Harris, Walz, and election results, with timestamps and political leaning indicators." /&gt;&lt;/p&gt;
&lt;p&gt;VERDAD processes around a thousand hours of audio content a day - &lt;em&gt;way&lt;/em&gt; more than any team of journalists or researchers could attempt to listen to manually.&lt;/p&gt;
&lt;h4 id="the-technology-stack"&gt;The technology stack&lt;/h4&gt;
&lt;p&gt;VERDAD uses &lt;a href="https://github.com/PrefectHQ/prefect"&gt;Prefect&lt;/a&gt; as a workflow orchestration system to run the different parts of their pipeline.&lt;/p&gt;
&lt;p&gt;There are multiple stages, roughly as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MP3 audio is recorded from radio station websites and stored in Cloudflare R2&lt;/li&gt;
&lt;li&gt;An initial transcription is performed using the extremely inexpensive Gemini 1.5 Flash&lt;/li&gt;
&lt;li&gt;That transcript is fed to the more powerful Gemini 1.5 Pro with a complex prompt to help identify potential misinformation snippets&lt;/li&gt;
&lt;li&gt;Once identified, audio containing snippets is run through the more expensive Whisper model to generate timestamps for the snippets&lt;/li&gt;
&lt;li&gt;Further prompts then generate things like English translations and summaries of the snippets&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/verdad-2.jpg" alt="Screenshot of a Prefect workflow dashboard showing the apricot-silkworm run execution timeline. Interface displays task runs including audio file transcription and processing tasks with timestamps from 11:05 PM to 11:09 PM. Bottom panel shows detailed logs of task creation and completion." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="developing-the-prompts"&gt;Developing the prompts&lt;/h4&gt;
&lt;p&gt;The prompts used by VERDAD are &lt;a href="https://github.com/PublicDataWorks/verdad/tree/main/prompts"&gt;available in their GitHub repository&lt;/a&gt; and they are &lt;em&gt;fascinating&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Rajiv initially tried to get Gemini 1.5 Flash to do both the transcription and the misinformation detection, but found that asking that model to do two things at once frequently confused it.&lt;/p&gt;
&lt;p&gt;Instead, he switched to a separate prompt running that transcript against Gemini 1.5 Pro. Here's &lt;a href="https://github.com/PublicDataWorks/verdad/blob/main/prompts/Stage_3_analysis_prompt.md"&gt;that more complex prompt&lt;/a&gt; - it's 50KB is size and includes a whole bunch of interesting sections, including plenty of examples and a detailed JSON schema.&lt;/p&gt;
&lt;p&gt;Here's just one of the sections aimed at identifying content about climate change:&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3 id="4-climate-change-and-environmental-policies"&gt;&lt;strong&gt;4. Climate Change and Environmental Policies&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Disinformation that denies or minimizes human impact on climate change, often to oppose environmental regulations. It may discredit scientific consensus and promote fossil fuel interests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Common Narratives&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Labeling climate change as a &lt;strong&gt;"hoax"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Arguing that climate variations are natural cycles.&lt;/li&gt;
&lt;li&gt;Claiming environmental policies harm the economy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cultural/Regional Variations&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spanish-Speaking Communities&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Impact of climate policies on agricultural jobs.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arabic-Speaking Communities&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Reliance on oil economies influencing perceptions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Potential Legitimate Discussions&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debates on balancing environmental protection with economic growth.&lt;/li&gt;
&lt;li&gt;Discussions about energy independence.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Spanish&lt;/em&gt;: "El 'cambio climático' es una mentira para controlarnos."&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Arabic&lt;/em&gt;: "'تغير المناخ' كذبة للسيطرة علينا."&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rajiv iterated on these prompts over multiple months - they are the core of the VERDAD project. Here's &lt;a href="https://github.com/PublicDataWorks/verdad/commit/3eac808e77b6d1aadf0de055a1d5287166dbb6d3"&gt;an update from yesterday&lt;/a&gt; informing the model of the US presidental election results so that it wouldn't flag claims of a candidate winning as false!&lt;/p&gt;

&lt;p&gt;Rajiv used both Claude 3.5 Sonnet and OpenAI o1-preview to help develop the prompt itself. Here's &lt;a href="https://gist.github.com/rajivsinclair/8fb0371f6eda25f9e5cc515cd77abd62"&gt;his transcript&lt;/a&gt; of a conversation with Claude used to iterate further on an existing prompt.&lt;/p&gt;

&lt;h4 id="the-human-review-process"&gt;The human review process&lt;/h4&gt;
&lt;p&gt;The final component of VERDAD is the web application itself. Everyone knows that AI makes mistakes, &lt;em&gt;a lot&lt;/em&gt;. Providing as much context as possible for human review is essential.&lt;/p&gt;
&lt;p&gt;The Whisper transcripts provide accurate timestamps (Gemini is sadly unable to provide those on its own), which means the tool can provide the Spanish transcript, the English translation and a play button to listen to the audio at the moment of the captured snippet.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/verdad-3.jpg" alt="Screenshot of VERDAD content moderation interface showing detailed view of a post titled False Claim of Trump Victory from WAXY radio station in Florida. Shows audio player with Spanish/English transcript toggle, green highlighted fact-check box. Post metadata indicates &amp;quot;Right&amp;quot; political leaning and timestamp Nov 6, 2024 23:06 GMT+7." style="max-width: 100%;" /&gt;&lt;/p&gt;

&lt;h4 id="want-to-learn-more-"&gt;Want to learn more?&lt;/h4&gt;
&lt;p&gt;VERDAD is under active development right now. Rajiv and his team are keen to collaborate, and are actively looking forward to conversations with other people working in this space. You can reach him at &lt;code&gt;help@verdad.app&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The technology stack itself is &lt;em&gt;incredibly&lt;/em&gt; promising. Pulling together a project like this even a year ago would have been prohibitively expensive, but new multi-modal LLM tools like Gemini (and Gemini 1.5 Flash in particular) are opening up all sorts of new possibilities.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/data-journalism"&gt;data-journalism&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/youtube"&gt;youtube&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/prompt-engineering"&gt;prompt-engineering&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gemini"&gt;gemini&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/digital-literacy"&gt;digital-literacy&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="data-journalism"/><category term="youtube"/><category term="ai"/><category term="prompt-engineering"/><category term="generative-ai"/><category term="llms"/><category term="gemini"/><category term="digital-literacy"/></entry></feed>