<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: go</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/go.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-03-27T00:35:01+00:00</updated><author><name>Simon Willison</name></author><entry><title>We Rewrote JSONata with AI in a Day, Saved $500K/Year</title><link href="https://simonwillison.net/2026/Mar/27/vine-porting-jsonata/#atom-tag" rel="alternate"/><published>2026-03-27T00:35:01+00:00</published><updated>2026-03-27T00:35:01+00:00</updated><id>https://simonwillison.net/2026/Mar/27/vine-porting-jsonata/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.reco.ai/blog/we-rewrote-jsonata-with-ai"&gt;We Rewrote JSONata with AI in a Day, Saved $500K/Year&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Bit of a hyperbolic framing but this looks like another case study of &lt;strong&gt;vibe porting&lt;/strong&gt;, this time spinning up a new custom Go implementation of the &lt;a href="https://jsonata.org"&gt;JSONata&lt;/a&gt; JSON expression language - similar in focus to jq, and heavily associated with the &lt;a href="https://nodered.org"&gt;Node-RED&lt;/a&gt; platform.&lt;/p&gt;
&lt;p&gt;As with other vibe-porting projects the key enabling factor was JSONata's existing test suite, which helped build the first working Go version in 7 hours and $400 of token spend.&lt;/p&gt;
&lt;p&gt;The Reco team then used a shadow deployment for a week to run the new and old versions in parallel to confirm the new implementation exactly matched the behavior of the old one.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/json"&gt;json&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;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/agentic-engineering"&gt;agentic-engineering&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/vibe-porting"&gt;vibe-porting&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="json"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="agentic-engineering"/><category term="vibe-porting"/></entry><entry><title>go-size-analyzer</title><link href="https://simonwillison.net/2026/Feb/24/go-size-analyzer/#atom-tag" rel="alternate"/><published>2026-02-24T16:10:06+00:00</published><updated>2026-02-24T16:10:06+00:00</updated><id>https://simonwillison.net/2026/Feb/24/go-size-analyzer/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Zxilly/go-size-analyzer"&gt;go-size-analyzer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The Go ecosystem is &lt;em&gt;really&lt;/em&gt; good at tooling. I just learned about this tool for analyzing the size of Go binaries using a pleasing treemap view of their bundled dependencies.&lt;/p&gt;
&lt;p&gt;You can install and run the tool locally, but it's also compiled to WebAssembly and hosted at &lt;a href="https://gsa.zxilly.dev/"&gt;gsa.zxilly.dev&lt;/a&gt; - which means you can open compiled Go binaries and analyze them directly in your browser.&lt;/p&gt;
&lt;p&gt;I tried it with a 8.1MB macOS compiled copy of my Go &lt;a href="https://github.com/simonw/showboat"&gt;Showboat&lt;/a&gt; tool and got this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Treemap visualization of a Go binary named &amp;quot;showboat&amp;quot; showing size breakdown across four major categories: &amp;quot;Unknown Sections Size&amp;quot; (containing __rodata __TEXT, __rodata __DATA_CONST, __data __DATA, and Debug Sections Size with __zdebug_line __DWARF, __zdebug_loc __DWARF, __zdebug_info __DWARF), &amp;quot;Std Packages Size&amp;quot; (showing standard library packages like runtime, net, crypto, reflect, math, os, fmt, strings, syscall, context, and many subpackages such as crypto/tls, crypto/x509, net/http, with individual .go files visible at deeper levels), &amp;quot;Main Packages Size&amp;quot; (showing main, showboat, cmd), and &amp;quot;Generated Packages Size&amp;quot; (showing &amp;lt;autogenerated&amp;gt;). A tooltip is visible over __zdebug_line __DWARF showing: Section: __zdebug_line __DWARF, Size: 404.44 KB, File Size: 404.44 KB, Known size: 0 B, Unknown size: 404.44 KB, Offset: 0x52814a – 0x58d310, Address: 0x1005c014a – 0x1005c5310, Memory: false, Debug: true. The treemap uses green for main/generated packages, blue-gray for unknown sections, and shades of purple/pink for standard library packages." src="https://static.simonwillison.net/static/2026/showboat-treemap.jpg" /&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://www.datadoghq.com/blog/engineering/agent-go-binaries/"&gt;Datadog: How we reduced the size of our Agent Go binaries by up to 77%&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/showboat"&gt;showboat&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="webassembly"/><category term="showboat"/></entry><entry><title>Introducing Showboat and Rodney, so agents can demo what they’ve built</title><link href="https://simonwillison.net/2026/Feb/10/showboat-and-rodney/#atom-tag" rel="alternate"/><published>2026-02-10T17:45:29+00:00</published><updated>2026-02-10T17:45:29+00:00</updated><id>https://simonwillison.net/2026/Feb/10/showboat-and-rodney/#atom-tag</id><summary type="html">
    &lt;p&gt;A key challenge working with coding agents is having them both test what they’ve built and demonstrate that software to you, their supervisor. This goes beyond automated tests - we need artifacts that show their progress and help us see exactly what the agent-produced software is able to do. I’ve just released two new tools aimed at this problem: &lt;a href="https://github.com/simonw/showboat"&gt;Showboat&lt;/a&gt; and &lt;a href="https://github.com/simonw/rodney"&gt;Rodney&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/10/showboat-and-rodney/#proving-code-actually-works"&gt;Proving code actually works&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/10/showboat-and-rodney/#showboat-agents-build-documents-to-demo-their-work"&gt;Showboat: Agents build documents to demo their work&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/10/showboat-and-rodney/#rodney-cli-browser-automation-designed-to-work-with-showboat"&gt;Rodney: CLI browser automation designed to work with Showboat&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/10/showboat-and-rodney/#test-driven-development-helps-but-we-still-need-manual-testing"&gt;Test-driven development helps, but we still need manual testing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/10/showboat-and-rodney/#i-built-both-of-these-tools-on-my-phone"&gt;I built both of these tools on my phone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="proving-code-actually-works"&gt;Proving code actually works&lt;/h4&gt;
&lt;p&gt;I recently wrote about how the job of a software engineer isn't to write code, it's to &lt;em&gt;&lt;a href="https://simonwillison.net/2025/Dec/18/code-proven-to-work/"&gt;deliver code that works&lt;/a&gt;&lt;/em&gt;. A big part of that is proving to ourselves and to other people that the code we are responsible for behaves as expected.&lt;/p&gt;
&lt;p&gt;This becomes even more important - and challenging - as we embrace coding agents as a core part of our software development process.&lt;/p&gt;
&lt;p&gt;The more code we churn out with agents, the more valuable tools are that reduce the amount of manual QA time we need to spend.&lt;/p&gt;
&lt;p&gt;One of the most interesting things about &lt;a href="https://simonwillison.net/2026/Feb/7/software-factory/"&gt;the StrongDM software factory model&lt;/a&gt; is how they ensure that their software is well tested and delivers value despite their policy that "code must not be reviewed by humans". Part of their solution involves expensive swarms of QA agents running through "scenarios" to exercise their software. It's fascinating, but I don't want to spend thousands of dollars on QA robots if I can avoid it!&lt;/p&gt;
&lt;p&gt;I need tools that allow agents to clearly demonstrate their work to me, while minimizing the opportunities for them to cheat about what they've done.&lt;/p&gt;

&lt;h4 id="showboat-agents-build-documents-to-demo-their-work"&gt;Showboat: Agents build documents to demo their work&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonw/showboat"&gt;Showboat&lt;/a&gt;&lt;/strong&gt; is the tool I built to help agents demonstrate their work to me.&lt;/p&gt;
&lt;p&gt;It's a CLI tool (a Go binary, optionally &lt;a href="https://simonwillison.net/2026/Feb/4/distributing-go-binaries/"&gt;wrapped in Python&lt;/a&gt; to make it easier to install) that helps an agent construct a Markdown document demonstrating exactly what their newly developed code can do.&lt;/p&gt;
&lt;p&gt;It's not designed for humans to run, but here's how you would run it anyway:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;showboat init demo.md &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;How to use curl and jq&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
showboat note demo.md &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Here's how to use curl and jq together.&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
showboat &lt;span class="pl-c1"&gt;exec&lt;/span&gt; demo.md bash &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;curl -s https://api.github.com/repos/simonw/rodney | jq .description&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
showboat note demo.md &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;And the curl logo, to demonstrate the image command:&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
showboat image demo.md &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;curl -o curl-logo.png https://curl.se/logo/curl-logo.png &amp;amp;&amp;amp; echo curl-logo.png&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here's what the result looks like if you open it up in VS Code and preview the Markdown:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/curl-demo.jpg" alt="Screenshot showing a Markdown file &amp;quot;demo.md&amp;quot; side-by-side with its rendered preview. The Markdown source (left) shows: &amp;quot;# How to use curl and jq&amp;quot;, italic timestamp &amp;quot;2026-02-10T01:12:30Z&amp;quot;, prose &amp;quot;Here's how to use curl and jq together.&amp;quot;, a bash code block with &amp;quot;curl -s https://api.github.com/repos/simonw/rodney | jq .description&amp;quot;, output block showing '&amp;quot;CLI tool for interacting with the web&amp;quot;', text &amp;quot;And the curl logo, to demonstrate the image command:&amp;quot;, a bash {image} code block with &amp;quot;curl -o curl-logo.png https://curl.se/logo/curl-logo.png &amp;amp;&amp;amp; echo curl-logo.png&amp;quot;, and a Markdown image reference &amp;quot;2056e48f-2026-02-10&amp;quot;. The rendered preview (right) displays the formatted heading, timestamp, prose, styled code blocks, and the curl logo image in dark teal showing &amp;quot;curl://&amp;quot; with circuit-style design elements." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Here's that &lt;a href="https://gist.github.com/simonw/fb0b24696ed8dd91314fe41f4c453563#file-demo-md"&gt;demo.md file in a Gist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So a sequence of &lt;code&gt;showboat init&lt;/code&gt;, &lt;code&gt;showboat note&lt;/code&gt;, &lt;code&gt;showboat exec&lt;/code&gt; and &lt;code&gt;showboat image&lt;/code&gt; commands constructs a Markdown document one section at a time, with the output of those &lt;code&gt;exec&lt;/code&gt; commands automatically added to the document directly following the commands that were run.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;image&lt;/code&gt; command is a little special - it looks for a file path to an image in the output of the command and copies that image to the current folder and references it in the file.&lt;/p&gt;
&lt;p&gt;That's basically the whole thing! There's a &lt;code&gt;pop&lt;/code&gt; command to remove the most recently added section if something goes wrong, a &lt;code&gt;verify&lt;/code&gt; command to re-run the document and check nothing has changed (I'm not entirely convinced by the design of that one) and a &lt;code&gt;extract&lt;/code&gt; command that reverse-engineers the CLI commands that were used to create the document.&lt;/p&gt;
&lt;p&gt;It's pretty simple - just 172 lines of Go.&lt;/p&gt;
&lt;p&gt;I packaged it up with my &lt;a href="https://github.com/simonw/go-to-wheel"&gt;go-to-wheel&lt;/a&gt; tool which means you can run it without even installing it first like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx showboat --help&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That &lt;code&gt;--help&lt;/code&gt; command is really important: it's designed to provide a coding agent with &lt;em&gt;everything it needs to know&lt;/em&gt; in order to use the tool. Here's &lt;a href="https://github.com/simonw/showboat/blob/main/help.txt"&gt;that help text in full&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This means you can pop open Claude Code and tell it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Run "uvx showboat --help" and then use showboat to create a demo.md document describing the feature you just built&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And that's it! The &lt;code&gt;--help&lt;/code&gt; text acts &lt;a href="https://simonwillison.net/2025/Oct/16/claude-skills/"&gt;a bit like a Skill&lt;/a&gt;. Your agent can read the help text and use every feature of Showboat to create a document that demonstrates whatever it is you need demonstrated.&lt;/p&gt;
&lt;p&gt;Here's a fun trick: if you set Claude off to build a Showboat document you can pop that open in VS Code and watch the preview pane update in real time as the agent runs through the demo. It's a bit like having your coworker talk you through their latest work in a screensharing session.&lt;/p&gt;
&lt;p&gt;And finally, some examples. Here are documents I had Claude create using Showboat to help demonstrate features I was working on in other projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/showboat-demos/blob/main/shot-scraper/README.md"&gt;shot-scraper: A Comprehensive Demo&lt;/a&gt; runs through the full suite of features of my &lt;a href="https://shot-scraper.datasette.io/"&gt;shot-scraper&lt;/a&gt; browser automation tool, mainly to exercise the &lt;code&gt;showboat image&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/sqlite-history-json/blob/main/demos/cli.md"&gt;sqlite-history-json CLI demo&lt;/a&gt; demonstrates the CLI feature I added to my new &lt;a href="https://github.com/simonw/sqlite-history-json"&gt;sqlite-history-json&lt;/a&gt; Python library.
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/sqlite-history-json/blob/main/demos/row-state-sql.md"&gt;row-state-sql CLI Demo&lt;/a&gt; shows a new &lt;code&gt;row-state-sql&lt;/code&gt; command I added to that same project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/sqlite-history-json/blob/main/demos/change-grouping.md"&gt;Change grouping with Notes&lt;/a&gt; demonstrates another feature where groups of changes within the same transaction can have a note attached to them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/research/blob/main/libkrun-go-cli-tool/demo.md"&gt;krunsh: Pipe Shell Commands to an Ephemeral libkrun MicroVM&lt;/a&gt; is a particularly convoluted example where I managed to get Claude Code for web to run a libkrun microVM inside a QEMU emulated Linux environment inside the Claude gVisor sandbox.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've now used Showboat often enough that I've convinced myself of its utility.&lt;/p&gt;
&lt;p&gt;(I've also seen agents cheat! Since the demo file is Markdown the agent will sometimes edit that file directly rather than using Showboat, which could result in command outputs that don't reflect what actually happened. Here's &lt;a href="https://github.com/simonw/showboat/issues/12"&gt;an issue about that&lt;/a&gt;.)&lt;/p&gt;
&lt;h4 id="rodney-cli-browser-automation-designed-to-work-with-showboat"&gt;Rodney: CLI browser automation designed to work with Showboat&lt;/h4&gt;
&lt;p&gt;Many of the projects I work on involve web interfaces. Agents often build entirely new pages for these, and I want to see those represented in the demos.&lt;/p&gt;
&lt;p&gt;Showboat's image feature was designed to allow agents to capture screenshots as part of their demos, originally using my &lt;a href="https://shot-scraper.datasette.io/"&gt;shot-scraper tool&lt;/a&gt; or &lt;a href="https://www.playwright.dev"&gt;Playwright&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Showboat format benefits from CLI utilities. I went looking for good options for managing a multi-turn browser session from a CLI and came up short, so I decided to try building something new.&lt;/p&gt;
&lt;p&gt;Claude Opus 4.6 pointed me to the &lt;a href="https://github.com/go-rod/rod"&gt;Rod&lt;/a&gt; Go library for interacting with the Chrome DevTools protocol. It's fantastic - it provides a comprehensive wrapper across basically everything you can do with automated Chrome, all in a self-contained library that compiles to a few MBs.&lt;/p&gt;
&lt;p&gt;All Rod was missing was a CLI.&lt;/p&gt;
&lt;p&gt;I built the first version &lt;a href="https://github.com/simonw/research/blob/main/go-rod-cli/README.md"&gt;as an asynchronous report prototype&lt;/a&gt;, which convinced me it was worth spinning out into its own project.&lt;/p&gt;
&lt;p&gt;I called it Rodney as a nod to the Rod library it builds on and a reference to &lt;a href="https://en.wikipedia.org/wiki/Only_Fools_and_Horses"&gt;Only Fools and Horses&lt;/a&gt; - and because the package name was available on PyPI.&lt;/p&gt;
&lt;p&gt;You can run Rodney using &lt;code&gt;uvx rodney&lt;/code&gt; or install it like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv tool install rodney&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(Or grab a Go binary &lt;a href="https://github.com/simonw/rodney/releases/"&gt;from the releases page&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Here's a simple example session:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;rodney start &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; starts Chrome in the background&lt;/span&gt;
rodney open https://datasette.io/
rodney js &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Array.from(document.links).map(el =&amp;gt; el.href).slice(0, 5)&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
rodney click &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;a[href="/for"]&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
rodney js location.href
rodney js document.title
rodney screenshot datasette-for-page.png
rodney stop&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here's what that looks like in the terminal:&lt;/p&gt;
&lt;p&gt;&lt;img alt=";~ % rodney start
Chrome started (PID 91462)
Debug URL: ws://127.0.0.1:64623/devtools/browser/cac6988e-8153-483b-80b9-1b75c611868d
~ % rodney open https://datasette.io/
Datasette: An open source multi-tool for exploring and publishing data
~ % rodney js 'Array.from(document.links).map(el =&amp;gt; el.href).slice(0, 5)'
[
&amp;quot;https://datasette.io/for&amp;quot;,
&amp;quot;https://docs.datasette.io/en/stable/&amp;quot;,
&amp;quot;https://datasette.io/tutorials&amp;quot;,
&amp;quot;https://datasette.io/examples&amp;quot;,
&amp;quot;https://datasette.io/plugins&amp;quot;
]
~ % rodney click 'a[href=&amp;quot;/for&amp;quot;]'
Clicked
~ % rodney js location.href
https://datasette.io/for
~ % rodney js document.title
Use cases for Datasette
~ % rodney screenshot datasette-for-page.png
datasette-for-page.png
~ % rodney stop
Chrome stopped" src="https://static.simonwillison.net/static/2026/rodney-demo.jpg" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;As with Showboat, this tool is not designed to be used by humans! The goal is for coding agents to be able to run &lt;code&gt;rodney --help&lt;/code&gt; and see everything they need to know to start using the tool. You can see &lt;a href="https://github.com/simonw/rodney/blob/main/help.txt"&gt;that help output&lt;/a&gt; in the GitHub repo.&lt;/p&gt;
&lt;p&gt;Here are three demonstrations of Rodney that I created using Showboat:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/showboat-demos/blob/main/rodney/README.md"&gt;Rodney's original feature set&lt;/a&gt;, including screenshots of pages and executing JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/rodney/blob/main/notes/accessibility-features/README.md"&gt;Rodney's new accessibility testing features&lt;/a&gt;, built during development of those features to show what they could do.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/showboat-demos/blob/main/datasette-database-page-accessibility-audit/README.md"&gt;Using those features to run a basic accessibility audit of a page&lt;/a&gt;. I was impressed at how well Claude Opus 4.6 responded to the prompt "Use showboat and rodney to perform an accessibility audit of &lt;a href="https://latest.datasette.io/fixtures"&gt;https://latest.datasette.io/fixtures&lt;/a&gt;" - &lt;a href="https://gisthost.github.io/?dce6b2680db4b05c04469ed8f251eb34/index.html"&gt;transcript here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="test-driven-development-helps-but-we-still-need-manual-testing"&gt;Test-driven development helps, but we still need manual testing&lt;/h4&gt;
&lt;p&gt;After being a career-long skeptic of the test-first, maximum test coverage school of software development (I like &lt;a href="https://simonwillison.net/2022/Oct/29/the-perfect-commit/#tests"&gt;tests included&lt;/a&gt; development instead) I've recently come around to test-first processes as a way to force agents to write only the code that's necessary to solve the problem at hand.&lt;/p&gt;
&lt;p&gt;Many of my Python coding agent sessions start the same way:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Run the existing tests with "uv run pytest". Build using red/green TDD.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Telling the agents how to run the tests doubles as an indicator that tests on this project exist and matter. Agents will read existing tests before writing their own so having a clean test suite with good patterns makes it more likely they'll write good tests of their own.&lt;/p&gt;
&lt;p&gt;The frontier models all understand that "red/green TDD" means they should write the test first, run it and watch it fail and then write the code to make it pass - it's a convenient shortcut.&lt;/p&gt;
&lt;p&gt;I find this greatly increases the quality of the code and the likelihood that the agent will produce the right thing with the smallest amount of prompts to guide it.&lt;/p&gt;
&lt;p&gt;But anyone who's worked with tests will know that just because the automated tests pass doesn't mean the software actually works! That’s the motivation behind Showboat and Rodney - I never trust any feature until I’ve seen it running with my own eye.&lt;/p&gt;
&lt;p&gt;Before building Showboat I'd often add a “manual” testing step to my agent sessions, something like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Once the tests pass, start a development server and exercise the new feature using curl&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="i-built-both-of-these-tools-on-my-phone"&gt;I built both of these tools on my phone&lt;/h4&gt;
&lt;p&gt;Both Showboat and Rodney started life as Claude Code for web projects created via the Claude iPhone app. Most of the ongoing feature work for them happened in the same way.&lt;/p&gt;
&lt;p&gt;I'm still a little startled at how much of my coding work I get done on my phone now, but I'd estimate that the majority of code I ship to GitHub these days was written for me by coding agents driven via that iPhone app.&lt;/p&gt;
&lt;p&gt;I initially designed these two tools for use in asynchronous coding agent environments like Claude Code for the web. So far that's working out really well.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/testing"&gt;testing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/markdown"&gt;markdown&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;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/async-coding-agents"&gt;async-coding-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/showboat"&gt;showboat&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rodney"&gt;rodney&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="go"/><category term="projects"/><category term="testing"/><category term="markdown"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="coding-agents"/><category term="async-coding-agents"/><category term="showboat"/><category term="rodney"/></entry><entry><title>Distributing Go binaries like sqlite-scanner through PyPI using go-to-wheel</title><link href="https://simonwillison.net/2026/Feb/4/distributing-go-binaries/#atom-tag" rel="alternate"/><published>2026-02-04T14:59:47+00:00</published><updated>2026-02-04T14:59:47+00:00</updated><id>https://simonwillison.net/2026/Feb/4/distributing-go-binaries/#atom-tag</id><summary type="html">
    &lt;p&gt;I've been exploring Go for building small, fast and self-contained binary applications recently. I'm enjoying how there's generally one obvious way to do things and the resulting code is boring and readable - and something that LLMs are very competent at writing. The one catch is distribution, but it turns out publishing Go binaries to PyPI means any Go binary can be just a &lt;code&gt;uvx package-name&lt;/code&gt; call away.&lt;/p&gt;
&lt;h4 id="sqlite-scanner"&gt;sqlite-scanner&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/sqlite-scanner"&gt;sqlite-scanner&lt;/a&gt; is my new Go CLI tool for scanning a filesystem for SQLite database files.&lt;/p&gt;
&lt;p&gt;It works by checking if the first 16 bytes of the file exactly match the SQLite magic number sequence &lt;code&gt;SQLite format 3\x00&lt;/code&gt;. It can search one or more folders recursively, spinning up concurrent goroutines to accelerate the scan. It streams out results as it finds them in plain text, JSON or newline-delimited JSON. It can optionally display the file sizes as well.&lt;/p&gt;
&lt;p&gt;To try it out you can download a release from the &lt;a href="https://github.com/simonw/sqlite-scanner/releases"&gt;GitHub releases&lt;/a&gt; - and then &lt;a href="https://support.apple.com/en-us/102445"&gt;jump through macOS hoops&lt;/a&gt; to execute an "unsafe" binary. Or you can clone the repo and compile it with Go. Or... you can run the binary like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx sqlite-scanner
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default this will search your current directory for SQLite databases. You can pass one or more directories as arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx sqlite-scanner ~ /tmp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;--json&lt;/code&gt; for JSON output, &lt;code&gt;--size&lt;/code&gt; to include file sizes or &lt;code&gt;--jsonl&lt;/code&gt; for newline-delimited JSON. Here's a demo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx sqlite-scanner ~ --jsonl --size
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/sqlite-scanner-demo.gif" alt="running that command produces a sequence of JSON objects, each with a path and a size key" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;If you haven't been uv-pilled yet you can instead install &lt;code&gt;sqlite-scanner&lt;/code&gt; using &lt;code&gt;pip install sqlite-scanner&lt;/code&gt; and then run &lt;code&gt;sqlite-scanner&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To get a permanent copy with &lt;code&gt;uv&lt;/code&gt; use &lt;code&gt;uv tool install sqlite-scanner&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="how-the-python-package-works"&gt;How the Python package works&lt;/h4&gt;
&lt;p&gt;The reason this is worth doing is that &lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;uv&lt;/code&gt; and &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt; will work together to identify the correct compiled binary for your operating system and architecture.&lt;/p&gt;
&lt;p&gt;This is driven by file names. If you visit &lt;a href="https://pypi.org/project/sqlite-scanner/#files"&gt;the PyPI downloads for sqlite-scanner&lt;/a&gt; you'll see the following files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-win_arm64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-win_amd64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-musllinux_1_2_x86_64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-musllinux_1_2_aarch64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-manylinux_2_17_x86_64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-manylinux_2_17_aarch64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-macosx_10_9_x86_64.whl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I run &lt;code&gt;pip install sqlite-scanner&lt;/code&gt; or &lt;code&gt;uvx sqlite-scanner&lt;/code&gt; on my Apple Silicon Mac laptop Python's packaging magic ensures I get that &lt;code&gt;macosx_11_0_arm64.whl&lt;/code&gt; variant.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://tools.simonwillison.net/zip-wheel-explorer?url=https%3A%2F%2Ffiles.pythonhosted.org%2Fpackages%2F88%2Fb1%2F17a716635d2733fec53ba0a8267f85bd6b6cf882c6b29301bc711fba212c%2Fsqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl#sqlite_scanner/__init__.py"&gt;what's in the wheel&lt;/a&gt;, which is a zip file with a &lt;code&gt;.whl&lt;/code&gt; extension.&lt;/p&gt;
&lt;p&gt;In addition to the &lt;code&gt;bin/sqlite-scanner&lt;/code&gt; the most important file is &lt;code&gt;sqlite_scanner/__init__.py&lt;/code&gt; which includes the following:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;get_binary_path&lt;/span&gt;():
    &lt;span class="pl-s"&gt;"""Return the path to the bundled binary."""&lt;/span&gt;
    &lt;span class="pl-s1"&gt;binary&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;path&lt;/span&gt;.&lt;span class="pl-c1"&gt;join&lt;/span&gt;(&lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;path&lt;/span&gt;.&lt;span class="pl-c1"&gt;dirname&lt;/span&gt;(&lt;span class="pl-s1"&gt;__file__&lt;/span&gt;), &lt;span class="pl-s"&gt;"bin"&lt;/span&gt;, &lt;span class="pl-s"&gt;"sqlite-scanner"&lt;/span&gt;)
 
    &lt;span class="pl-c"&gt;# Ensure binary is executable on Unix&lt;/span&gt;
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;platform&lt;/span&gt; &lt;span class="pl-c1"&gt;!=&lt;/span&gt; &lt;span class="pl-s"&gt;"win32"&lt;/span&gt;:
        &lt;span class="pl-s1"&gt;current_mode&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;stat&lt;/span&gt;(&lt;span class="pl-s1"&gt;binary&lt;/span&gt;).&lt;span class="pl-c1"&gt;st_mode&lt;/span&gt;
        &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-c1"&gt;not&lt;/span&gt; (&lt;span class="pl-s1"&gt;current_mode&lt;/span&gt; &lt;span class="pl-c1"&gt;&amp;amp;&lt;/span&gt; &lt;span class="pl-s1"&gt;stat&lt;/span&gt;.&lt;span class="pl-c1"&gt;S_IXUSR&lt;/span&gt;):
            &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;chmod&lt;/span&gt;(&lt;span class="pl-s1"&gt;binary&lt;/span&gt;, &lt;span class="pl-s1"&gt;current_mode&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-s1"&gt;stat&lt;/span&gt;.&lt;span class="pl-c1"&gt;S_IXUSR&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-s1"&gt;stat&lt;/span&gt;.&lt;span class="pl-c1"&gt;S_IXGRP&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-s1"&gt;stat&lt;/span&gt;.&lt;span class="pl-c1"&gt;S_IXOTH&lt;/span&gt;)
 
    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s1"&gt;binary&lt;/span&gt;
 
 
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;():
    &lt;span class="pl-s"&gt;"""Execute the bundled binary."""&lt;/span&gt;
    &lt;span class="pl-s1"&gt;binary&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;get_binary_path&lt;/span&gt;()
 
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;platform&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"win32"&lt;/span&gt;:
        &lt;span class="pl-c"&gt;# On Windows, use subprocess to properly handle signals&lt;/span&gt;
        &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;exit&lt;/span&gt;(&lt;span class="pl-s1"&gt;subprocess&lt;/span&gt;.&lt;span class="pl-c1"&gt;call&lt;/span&gt;([&lt;span class="pl-s1"&gt;binary&lt;/span&gt;] &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;argv&lt;/span&gt;[&lt;span class="pl-c1"&gt;1&lt;/span&gt;:]))
    &lt;span class="pl-k"&gt;else&lt;/span&gt;:
        &lt;span class="pl-c"&gt;# On Unix, exec replaces the process&lt;/span&gt;
        &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;execvp&lt;/span&gt;(&lt;span class="pl-s1"&gt;binary&lt;/span&gt;, [&lt;span class="pl-s1"&gt;binary&lt;/span&gt;] &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;argv&lt;/span&gt;[&lt;span class="pl-c1"&gt;1&lt;/span&gt;:])&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;main()&lt;/code&gt; method - also called from &lt;code&gt;sqlite_scanner/__main__.py&lt;/code&gt; - locates the binary and executes it when the Python package itself is executed, using the &lt;code&gt;sqlite-scanner = sqlite_scanner:main&lt;/code&gt; entry point defined in the wheel.&lt;/p&gt;
&lt;h4 id="which-means-we-can-use-it-as-a-dependency"&gt;Which means we can use it as a dependency&lt;/h4&gt;
&lt;p&gt;Using PyPI as a distribution platform for Go binaries feels a tiny bit abusive, albeit &lt;a href="https://simonwillison.net/2022/May/23/bundling-binary-tools-in-python-wheels/"&gt;there is plenty of precedent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ll justify it by pointing out that this means &lt;strong&gt;we can use Go binaries as dependencies&lt;/strong&gt; for other Python packages now.&lt;/p&gt;
&lt;p&gt;That's genuinely useful! It means that any functionality which is available in a cross-platform Go binary can now be subsumed into a Python package. Python is really good at running subprocesses so this opens up a whole world of useful tricks that we can bake into our Python tools.&lt;/p&gt;
&lt;p&gt;To demonstrate this, I built &lt;a href="https://github.com/simonw/datasette-scan"&gt;datasette-scan&lt;/a&gt; - a new Datasette plugin which depends on &lt;code&gt;sqlite-scanner&lt;/code&gt; and then uses that Go binary to scan a folder for SQLite databases and attach them to a Datasette instance.&lt;/p&gt;
&lt;p&gt;Here's how to use that (without even installing anything first, thanks &lt;code&gt;uv&lt;/code&gt;) to explore any SQLite databases in your Downloads folder:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run --with datasette-scan datasette scan &lt;span class="pl-k"&gt;~&lt;/span&gt;/Downloads&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you peek at the code you'll see it &lt;a href="https://github.com/simonw/datasette-scan/blob/1a2b6d1e6b04c8cd05f5676ff7daa877efd99f08/pyproject.toml#L14"&gt;depends on sqlite-scanner&lt;/a&gt; in &lt;code&gt;pyproject.toml&lt;/code&gt; and calls it using &lt;code&gt;subprocess.run()&lt;/code&gt; against &lt;code&gt;sqlite_scanner.get_binary_path()&lt;/code&gt; in its own &lt;a href="https://github.com/simonw/datasette-scan/blob/1a2b6d1e6b04c8cd05f5676ff7daa877efd99f08/datasette_scan/__init__.py#L38-L58"&gt;scan_directories() function&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've been exploring this pattern for other, non-Go binaries recently - here's &lt;a href="https://github.com/simonw/tools/blob/main/python/livestream-gif.py"&gt;a recent script&lt;/a&gt; that depends on &lt;a href="https://pypi.org/project/static-ffmpeg/"&gt;static-ffmpeg&lt;/a&gt; to ensure that &lt;code&gt;ffmpeg&lt;/code&gt; is available for the script to use.&lt;/p&gt;
&lt;h4 id="building-python-wheels-from-go-packages-with-go-to-wheel"&gt;Building Python wheels from Go packages with go-to-wheel&lt;/h4&gt;
&lt;p&gt;After trying this pattern myself a couple of times I realized it would be useful to have a tool to automate the process.&lt;/p&gt;
&lt;p&gt;I first &lt;a href="https://claude.ai/share/2d9ced56-b3e8-4651-83cc-860b9b419187"&gt;brainstormed with Claude&lt;/a&gt; to check that there was no existing tool to do this. It pointed me to &lt;a href="https://www.maturin.rs/bindings.html#bin"&gt;maturin bin&lt;/a&gt; which helps distribute Rust projects using Python wheels, and &lt;a href="https://github.com/Bing-su/pip-binary-factory"&gt;pip-binary-factory&lt;/a&gt; which bundles all sorts of other projects, but did not identify anything that addressed the exact problem I was looking to solve.&lt;/p&gt;
&lt;p&gt;So I &lt;a href="https://gisthost.github.io/?41f04e4eb823b1ceb888d9a28c2280dd/index.html"&gt;had Claude Code for web build the first version&lt;/a&gt;, then refined the code locally on my laptop with the help of more Claude Code and a little bit of OpenAI Codex too, just to mix things up.&lt;/p&gt;
&lt;p&gt;The full documentation is in the &lt;a href="https://github.com/simonw/go-to-wheel"&gt;simonw/go-to-wheel&lt;/a&gt; repository. I've published that tool to PyPI so now you can run it using:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx go-to-wheel --help&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;sqlite-scanner&lt;/code&gt; package you can &lt;a href="https://pypi.org/project/sqlite-scanner/"&gt;see on PyPI&lt;/a&gt; was built using &lt;code&gt;go-to-wheel&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx go-to-wheel &lt;span class="pl-k"&gt;~&lt;/span&gt;/dev/sqlite-scanner \
  --set-version-var main.version \
  --version 0.1.1 \
  --readme README.md \
  --author &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Simon Willison&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; \
  --url https://github.com/simonw/sqlite-scanner \
  --description &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Scan directories for SQLite databases&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This created a set of wheels in the &lt;code&gt;dist/&lt;/code&gt; folder. I tested one of them like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run --with dist/sqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl \
  sqlite-scanner --version&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When that spat out the correct version number I was confident everything had worked as planned, so I pushed the whole set of wheels to PyPI using &lt;code&gt;twine upload&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx twine upload dist/&lt;span class="pl-k"&gt;*&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I had to paste in a PyPI API token I had saved previously.&lt;/p&gt;
&lt;h4 id="i-expect-to-use-this-pattern-a-lot"&gt;I expect to use this pattern a lot&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;sqlite-scanner&lt;/code&gt; is very clearly meant as a proof-of-concept for this wider pattern - Python is very much capable of recursively crawling a directory structure looking for files that start with a specific byte prefix on its own!&lt;/p&gt;
&lt;p&gt;That said, I think there's a &lt;em&gt;lot&lt;/em&gt; to be said for this pattern. Go is a great complement to Python - it's fast, compiles to small self-contained binaries, has excellent concurrency support and a rich ecosystem of libraries.&lt;/p&gt;
&lt;p&gt;Go is similar to Python in that it has a strong standard library. Go is particularly good for HTTP tooling - I've built several HTTP proxies in the past using Go's excellent &lt;code&gt;net/http/httputil.ReverseProxy&lt;/code&gt; handler.&lt;/p&gt;
&lt;p&gt;I've also been experimenting with &lt;a href="https://github.com/wazero/wazero"&gt;wazero&lt;/a&gt;, Go's robust and mature zero dependency WebAssembly runtime as part of my ongoing quest for the ideal sandbox for running untrusted code. &lt;a href="https://github.com/simonw/research/tree/main/wasm-repl-cli"&gt;Here's my latest experiment&lt;/a&gt; with that library.&lt;/p&gt;
&lt;p&gt;Being able to seamlessly integrate Go binaries into Python projects without the end user having to think about Go at all - they &lt;code&gt;pip install&lt;/code&gt; and everything Just Works - feels like a valuable addition to my toolbox.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&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/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="go"/><category term="packaging"/><category term="projects"/><category term="pypi"/><category term="python"/><category term="sqlite"/><category term="datasette"/><category term="ai-assisted-programming"/><category term="uv"/></entry><entry><title>Thoughts on Go vs. Rust vs. Zig</title><link href="https://simonwillison.net/2025/Dec/5/go-vs-rust-vs-zig/#atom-tag" rel="alternate"/><published>2025-12-05T04:28:05+00:00</published><updated>2025-12-05T04:28:05+00:00</updated><id>https://simonwillison.net/2025/Dec/5/go-vs-rust-vs-zig/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://sinclairtarget.com/blog/2025/08/thoughts-on-go-vs.-rust-vs.-zig/"&gt;Thoughts on Go vs. Rust vs. Zig&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Thoughtful commentary on Go, Rust, and Zig by Sinclair Target. I haven't seen a single comparison that covers all three before and I learned a lot from reading this.&lt;/p&gt;
&lt;p&gt;One thing that I hadn't noticed before is that none of these three languages implement class-based OOP.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=46153466"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/object-oriented-programming"&gt;object-oriented-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/programming-languages"&gt;programming-languages&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/zig"&gt;zig&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="object-oriented-programming"/><category term="programming-languages"/><category term="rust"/><category term="zig"/></entry><entry><title>Claude Code Can Debug Low-level Cryptography</title><link href="https://simonwillison.net/2025/Nov/1/claude-code-cryptography/#atom-tag" rel="alternate"/><published>2025-11-01T22:26:43+00:00</published><updated>2025-11-01T22:26:43+00:00</updated><id>https://simonwillison.net/2025/Nov/1/claude-code-cryptography/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://words.filippo.io/claude-debugging/"&gt;Claude Code Can Debug Low-level Cryptography&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Go cryptography author Filippo Valsorda reports on some very positive results applying Claude Code to the challenge of implementing novel cryptography algorithms. After Claude was able to resolve a "fairly complex low-level bug" in fresh code he tried it against two other examples and got positive results both time.&lt;/p&gt;
&lt;p&gt;Filippo isn't directly using Claude's solutions to the bugs, but is finding it useful for tracking down the cause and saving him a solid amount of debugging work:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Three out of three one-shot debugging hits with no help is &lt;em&gt;extremely impressive&lt;/em&gt;. Importantly, there is no need to trust the LLM or review its output when its job is just saving me an hour or two by telling me where the bug is, for me to reason about it and fix it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using coding agents in this way may represent a useful entrypoint for LLM-skeptics who wouldn't &lt;em&gt;dream&lt;/em&gt; of letting an autocomplete-machine writing code on their behalf.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=45784179"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cryptography"&gt;cryptography&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&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;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/filippo-valsorda"&gt;filippo-valsorda&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/claude-code"&gt;claude-code&lt;/a&gt;&lt;/p&gt;



</summary><category term="cryptography"/><category term="go"/><category term="security"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="filippo-valsorda"/><category term="coding-agents"/><category term="claude-code"/></entry><entry><title>A modern approach to preventing CSRF in Go</title><link href="https://simonwillison.net/2025/Oct/15/csrf-in-go/#atom-tag" rel="alternate"/><published>2025-10-15T05:03:46+00:00</published><updated>2025-10-15T05:03:46+00:00</updated><id>https://simonwillison.net/2025/Oct/15/csrf-in-go/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.alexedwards.net/blog/preventing-csrf-in-go"&gt;A modern approach to preventing CSRF in Go&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Alex Edwards writes about the new &lt;code&gt;http.CrossOriginProtection&lt;/code&gt; middleware that was added to the Go standard library in &lt;a href="https://tip.golang.org/doc/go1.25"&gt;version 1.25&lt;/a&gt; in August and asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have we finally reached the point where CSRF attacks can be prevented without relying on a token-based check (like double-submit cookies)?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It looks like the answer might be &lt;em&gt;yes&lt;/em&gt;, which is extremely exciting. I've been &lt;a href="https://simonwillison.net/tags/csrf/"&gt;tracking CSRF&lt;/a&gt; since I first learned about it &lt;a href="https://simonwillison.net/2005/May/6/bad/"&gt;20 years ago in May 2005&lt;/a&gt; and a cleaner solution than those janky hidden form fields would be very welcome.&lt;/p&gt;
&lt;p&gt;The code for the new Go middleware lives in &lt;a href="https://github.com/golang/go/blob/go1.25.0/src/net/http/csrf.go"&gt;src/net/http/csrf.go&lt;/a&gt;. It works using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site"&gt;Sec-Fetch-Site&lt;/a&gt; HTTP header, which Can I Use shows as having &lt;a href="https://caniuse.com/mdn-http_headers_sec-fetch-site"&gt;94.18%&lt;/a&gt; global availability - the holdouts are mainly IE11, iOS versions prior to iOS 17 (which came out in 2023 but can be installed on any phone released since 2017) and some other ancient browser versions.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;Sec-Fetch-Site&lt;/code&gt; is &lt;code&gt;same-origin&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt; then the page submitting the form was either on the same origin or was navigated to directly by the user - in both cases safe from CSRF. If it's &lt;code&gt;cross-site&lt;/code&gt; or &lt;code&gt;same-site&lt;/code&gt; (&lt;code&gt;tools.simonwillison.net&lt;/code&gt; and &lt;code&gt;til.simonwillison.net&lt;/code&gt; are considered &lt;code&gt;same-site&lt;/code&gt; but not &lt;code&gt;same-origin&lt;/code&gt;) the submission is denied.&lt;/p&gt;
&lt;p&gt;If that header isn't available the middleware falls back on comparing other headers: &lt;code&gt;Origin&lt;/code&gt; - a value like &lt;code&gt;https://simonwillison.net&lt;/code&gt; - with &lt;code&gt;Host&lt;/code&gt;, a value like &lt;code&gt;simonwillison.net&lt;/code&gt;. This should cover the tiny fraction of browsers that don't have the new header, though it's not clear to me if there are any weird edge-cases beyond that.&lt;/p&gt;
&lt;p&gt;Note that this fallback comparison can't take the scheme into account since &lt;code&gt;Host&lt;/code&gt; doesn't list that, so administrators are encouraged to use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security"&gt;HSTS&lt;/a&gt; to protect against HTTP to HTTPS cross-origin requests.&lt;/p&gt;
&lt;p&gt;On Lobste.rs I questioned if this would work for &lt;code&gt;localhost&lt;/code&gt;, since that normally isn't served using HTTPS. Firefox security engineer Frederik Braun &lt;a href="https://lobste.rs/s/fzw9g7/modern_approach_preventing_csrf_go#c_e24o9q"&gt;reassured me&lt;/a&gt; that &lt;code&gt;*.localhost&lt;/code&gt; is treated as a Secure Context, so gets the &lt;code&gt;Sec-Fetch-Site&lt;/code&gt; header despite not being served via HTTPS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Also relevant is &lt;a href="https://words.filippo.io/csrf/"&gt;Filippo Valsorda's article in CSRF&lt;/a&gt; which includes detailed research conducted as part of building the new Go middleware, plus this related &lt;a href="https://bsky.app/profile/filippo.abyssdomain.expert/post/3lmyu7c25zq2o"&gt;Bluesky conversation&lt;/a&gt; about that research from six months ago.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/fzw9g7/modern_approach_preventing_csrf_go"&gt;lobste.rs&lt;/a&gt;&lt;/small&gt;&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/csrf"&gt;csrf&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/filippo-valsorda"&gt;filippo-valsorda&lt;/a&gt;&lt;/p&gt;



</summary><category term="browsers"/><category term="csrf"/><category term="go"/><category term="security"/><category term="filippo-valsorda"/></entry><entry><title>CompileBench: Can AI Compile 22-year-old Code?</title><link href="https://simonwillison.net/2025/Sep/22/compilebench/#atom-tag" rel="alternate"/><published>2025-09-22T19:44:52+00:00</published><updated>2025-09-22T19:44:52+00:00</updated><id>https://simonwillison.net/2025/Sep/22/compilebench/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://quesma.com/blog/introducing-compilebench/"&gt;CompileBench: Can AI Compile 22-year-old Code?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Interesting new LLM benchmark from Piotr Grabowski and Piotr Migdał: how well can different models handle compilation challenges such as cross-compiling &lt;code&gt;gucr&lt;/code&gt; for ARM64 architecture?&lt;/p&gt;
&lt;p&gt;This is one of my favorite applications of coding agent tools like Claude Code or Codex CLI: I no longer fear working through convoluted build processes for software I'm unfamiliar with because I'm confident an LLM will be able to brute-force figure out how to do it.&lt;/p&gt;
&lt;p&gt;The benchmark on &lt;a href="https://www.compilebench.com/"&gt;compilebench.com&lt;/a&gt; currently show Claude Opus 4.1 Thinking in the lead, as the only model to solve 100% of problems (allowing three attempts). Claude Sonnet 4 Thinking and GPT-5 high both score 93%. The highest open weight model scores are DeepSeek 3.1 and Kimi K2 0905, both at 80%.&lt;/p&gt;
&lt;p&gt;This chart showing performance against cost helps demonstrate the excellent value for money provided by GPT-5-mini:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A scatter plot showing AI model performance on tasks completed (%) versus total cost across tasks (USD, log scale). GPT-5-mini-high is highlighted, cost 27 cents and 80% score, making it the cheapest model to score at least 80%. The vertical axis ranges from 45% to 100% tasks completed, and the horizontal axis ranges from $0.02 to $20. A blue line marks the Pareto frontier. Low-cost models (left side): GPT-4.1-mini (~67%), Grok code-fast-1 (~72%), Gemini 2.5-flash (~58%), GPT-OSS 120b-high (~59%), and Gemini-2.5 flash-thinking (~50%). Mid-range models (~$0.1–$2): GPT-5 minimal (~79%), GPT-5 high (~86%), Qwen3 max (~62%), GPT-4.1 (~60%), DeepSeek-v3.1 (~82%), GLM 4.5 (~70%), and Kimi k2-0905 (~82%). High-cost models (&amp;gt;$5): Claude-Sonnet 4-thinking-16k (~87%) and Claude-Opus 4.1-thinking-16k (~99%). Overall, GPT-5 high and Claude models dominate the top-right, while budget models like GPT-4.1-mini and Grok code-fast-1 balance lower cost with moderate performance." src="https://static.simonwillison.net/static/2025/compilebench-pareto.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The Gemini 2.5 family does surprisingly badly solving just 60% of the problems. The benchmark authors note that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When designing the benchmark we kept our benchmark harness and prompts minimal, avoiding model-specific tweaks. It is possible that Google models could perform better with a harness or prompt specifically hand-tuned for them, but this is against our principles in this benchmark.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The harness itself is &lt;a href="https://github.com/QuesmaOrg/CompileBench"&gt;available on GitHub&lt;/a&gt;. It's written in Go - I had a poke around and found their core agentic loop in &lt;a href="https://github.com/QuesmaOrg/CompileBench/blob/main/bench/agent.go"&gt;bench/agent.go&lt;/a&gt; - it builds on top of the OpenAI Go library and defines &lt;a href="https://github.com/QuesmaOrg/CompileBench/blob/aa0f29a58651a6dc9e42928699bd04912aa90ac0/bench/agent.go#L232-L252"&gt;a single tool&lt;/a&gt; called &lt;code&gt;run_terminal_cmd&lt;/code&gt;, described as "Execute a terminal command inside a bash shell".&lt;/p&gt;
&lt;p&gt;The system prompts live in &lt;a href="https://github.com/QuesmaOrg/CompileBench/blob/main/bench/container/environment.go"&gt;bench/container/environment.go&lt;/a&gt; and differ based on the operating system of the container. Here's &lt;a href="https://github.com/QuesmaOrg/CompileBench/blob/aa0f29a58651a6dc9e42928699bd04912aa90ac0/bench/container/environment.go#L20-L33"&gt;the system prompt&lt;/a&gt; for &lt;code&gt;ubuntu-22.04-amd64&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You are a package-building specialist operating a Ubuntu 22.04 bash shell via one tool: run_terminal_cmd.
The current working directory of every run_terminal_cmd is /home/peter.&lt;/p&gt;
&lt;p&gt;Execution rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Always pass non-interactive flags for any command that could prompt (e.g., &lt;code&gt;-y&lt;/code&gt;, &lt;code&gt;--yes&lt;/code&gt;, &lt;code&gt;DEBIAN_FRONTEND=noninteractive&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Don't include any newlines in the command.&lt;/li&gt;
&lt;li&gt;You can use sudo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you encounter any errors or issues while doing the user's request, you must fix them and continue the task.
At the end verify you did the user request correctly.&lt;/p&gt;
&lt;/blockquote&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=45332814"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&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/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/evals"&gt;evals&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/coding-agents"&gt;coding-agents&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="ai"/><category term="prompt-engineering"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="evals"/><category term="coding-agents"/></entry><entry><title>Maintainers of Last Resort</title><link href="https://simonwillison.net/2025/Aug/16/maintainers-of-last-resort/#atom-tag" rel="alternate"/><published>2025-08-16T16:52:45+00:00</published><updated>2025-08-16T16:52:45+00:00</updated><id>https://simonwillison.net/2025/Aug/16/maintainers-of-last-resort/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://words.filippo.io/last-resort/"&gt;Maintainers of Last Resort&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Filippo Valsorda founded Geomys &lt;a href="https://simonwillison.net/2024/Jul/8/geomys/"&gt;last year&lt;/a&gt; as an "organization of professional open source maintainers", providing maintenance and support for critical packages in the Go language ecosystem backed by clients in retainer relationships. &lt;/p&gt;
&lt;p&gt;This is an inspiring and optimistic shape for financially sustaining key open source projects, and it appears be working really well.&lt;/p&gt;
&lt;p&gt;Most recently, Geomys have started acting as a "maintainer of last resort" for security-related Go projects in need of new maintainers. In this piece Filippo describes their work on the &lt;a href="https://github.com/microcosm-cc/bluemonday"&gt;bluemonday&lt;/a&gt; HTML sanitization library - similar to Python’s bleach which was &lt;a href="https://github.com/mozilla/bleach/issues/698"&gt;deprecated in 2023&lt;/a&gt;. He also talks at length about their work on CSRF for Go after &lt;a href="https://github.com/gorilla/csrf"&gt;gorilla/csrf&lt;/a&gt; lost active maintenance - I’m still working my way through his earlier post on &lt;a href="https://words.filippo.io/csrf/"&gt;Cross-Site Request Forgery&lt;/a&gt; trying to absorb the research shared their about the best modern approaches to this vulnerability.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/4hc7o5/maintainers_last_resort"&gt;lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/csrf"&gt;csrf&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/open-source"&gt;open-source&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/filippo-valsorda"&gt;filippo-valsorda&lt;/a&gt;&lt;/p&gt;



</summary><category term="csrf"/><category term="go"/><category term="open-source"/><category term="security"/><category term="filippo-valsorda"/></entry><entry><title>From Async/Await to Virtual Threads</title><link href="https://simonwillison.net/2025/Aug/3/virtual-threads/#atom-tag" rel="alternate"/><published>2025-08-03T18:57:56+00:00</published><updated>2025-08-03T18:57:56+00:00</updated><id>https://simonwillison.net/2025/Aug/3/virtual-threads/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://lucumr.pocoo.org/2025/7/26/virtual-threads/"&gt;From Async/Await to Virtual Threads&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Armin Ronacher has long been critical of async/await in Python, both for necessitating &lt;a href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/"&gt;colored functions&lt;/a&gt; and because of the more subtle challenges they introduce like &lt;a href="https://lucumr.pocoo.org/2020/1/1/async-pressure/"&gt;managing back pressure&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Armin &lt;a href="https://lucumr.pocoo.org/2024/11/18/threads-beat-async-await/"&gt;argued convincingly&lt;/a&gt; for the threaded programming model back in December. Now he's expanded upon that with a description of how virtual threads might make sense in Python.&lt;/p&gt;
&lt;p&gt;Virtual threads behave like real system threads but can vastly outnumber them, since they can be paused and scheduled to run on a real thread when needed. Go uses this trick to implement goroutines which can then support millions of virtual threads on a single system.&lt;/p&gt;
&lt;p&gt;Python core developer Mark Shannon &lt;a href="https://discuss.python.org/t/add-virtual-threads-to-python/91403"&gt;started a conversation&lt;/a&gt; about the potential for seeing virtual threads to Python back in May.&lt;/p&gt;
&lt;p&gt;Assuming this proposal turns into something concrete I don't expect we will see it in a production Python release for a few more years. In the meantime there are some exciting improvements to the Python concurrency story - most notably &lt;a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep734"&gt;around sub-interpreters&lt;/a&gt; - coming up this year in Python 3.14.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/armin-ronacher"&gt;armin-ronacher&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/concurrency"&gt;concurrency&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gil"&gt;gil&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/threads"&gt;threads&lt;/a&gt;&lt;/p&gt;



</summary><category term="armin-ronacher"/><category term="concurrency"/><category term="gil"/><category term="go"/><category term="python"/><category term="threads"/></entry><entry><title>Serving 200 million requests per day with a cgi-bin</title><link href="https://simonwillison.net/2025/Jul/5/cgi-bin-performance/#atom-tag" rel="alternate"/><published>2025-07-05T23:28:31+00:00</published><updated>2025-07-05T23:28:31+00:00</updated><id>https://simonwillison.net/2025/Jul/5/cgi-bin-performance/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://jacob.gold/posts/serving-200-million-requests-with-cgi-bin/"&gt;Serving 200 million requests per day with a cgi-bin&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Jake Gold tests how well 90s-era CGI works today, using a Go + SQLite CGI program running on a 16-thread AMD 3700X.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Using CGI on modest hardware, it’s possible to serve 2400+ requests per second or 200M+ requests per day.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I got my start in web development with CGI back in the late 1990s - I was a huge fan of &lt;a href="https://web.archive.org/web/20010509081826/http://www.amphibianweb.com/newspro/"&gt;NewsPro&lt;/a&gt;, which was effectively a weblog system before anyone knew what a weblog was.&lt;/p&gt;
&lt;p&gt;CGI works by starting, executing and terminating a process for every incoming request. The nascent web community quickly learned that this was a bad idea, and invented technologies like PHP and &lt;a href="https://en.wikipedia.org/wiki/FastCGI"&gt;FastCGI&lt;/a&gt; to help avoid that extra overhead and keep code resident in-memory instead.&lt;/p&gt;
&lt;p&gt;This lesson ended up baked into my brain, and I spent the next twenty years convinced that you should &lt;em&gt;never&lt;/em&gt; execute a full process as part of serving a web page.&lt;/p&gt;
&lt;p&gt;Of course, computers in those two decades got a &lt;em&gt;lot&lt;/em&gt; faster. I finally overcame that twenty-year core belief in 2020, when &lt;a href="https://simonwillison.net/2020/Nov/28/datasette-ripgrep/"&gt;I built datasette-ripgrep&lt;/a&gt;, a Datasette plugin that shells out to the lightning fast &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt; CLI tool (written in Rust) to execute searches. It worked great!&lt;/p&gt;
&lt;p&gt;As was &lt;a href="https://news.ycombinator.com/item?id=44464272#44465143"&gt;pointed out on Hacker News&lt;/a&gt;, part of CGI's problem back then was that we were writing web scripts in languages like Perl, Python and Java which had not been designed for lightning fast startup speeds. Using Go and Rust today helps make CGI-style requests a whole lot more effective.&lt;/p&gt;
&lt;p&gt;Jake notes that CGI-style request handling is actually a great way to take advantage of multiple CPU cores:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These days, we have servers with 384 CPU threads. Even a small VM can have 16 CPUs. The CPUs and memory are much faster as well.&lt;/p&gt;
&lt;p&gt;Most importantly, CGI programs, because they run as separate processes, are excellent at taking advantage of many CPUs!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Maybe we should start coding web applications like it's 1998, albeit with Go and Rust!&lt;/p&gt;
&lt;p&gt;&lt;small&gt;To clarify, I don't think most people should do this. I just think it's interesting that it's not as bad an idea as it was ~25 years ago.&lt;/small&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=44464272"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cgi"&gt;cgi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/performance"&gt;performance&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;&lt;/p&gt;



</summary><category term="cgi"/><category term="go"/><category term="performance"/><category term="sqlite"/></entry><entry><title>Agentic Coding Recommendations</title><link href="https://simonwillison.net/2025/Jun/12/agentic-coding-recommendations/#atom-tag" rel="alternate"/><published>2025-06-12T16:20:51+00:00</published><updated>2025-06-12T16:20:51+00:00</updated><id>https://simonwillison.net/2025/Jun/12/agentic-coding-recommendations/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://lucumr.pocoo.org/2025/6/12/agentic-coding/"&gt;Agentic Coding Recommendations&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
There's a ton of actionable advice on using Claude Code in this new piece from Armin Ronacher. He's getting excellent results from Go, especially having invested a bunch of work in making the various tools (linters, tests, logs, development servers etc) as accessible as possible through documenting them in a Makefile.&lt;/p&gt;
&lt;p&gt;I liked this tip on logging:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In general logging is super important. For instance my app currently has a sign in and register flow that sends an email to the user. In debug mode (which the agent runs in), the email is just logged to stdout. This is crucial! It allows the agent to complete a full sign-in with a remote controlled browser without extra assistance. It knows that emails are being logged thanks to a &lt;code&gt;CLAUDE.md&lt;/code&gt; instruction and it automatically consults the log for the necessary link to click.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Armin also recently shared a &lt;a href="https://www.youtube.com/watch?v=sQYXZCUvpIc"&gt;half hour YouTube video&lt;/a&gt; in which he worked with Claude Code to resolve two medium complexity issues in his &lt;code&gt;minijinja&lt;/code&gt; Rust templating library, resulting in &lt;a href="https://github.com/mitsuhiko/minijinja/pull/805"&gt;PR #805&lt;/a&gt; and &lt;a href="https://github.com/mitsuhiko/minijinja/pull/804"&gt;PR #804&lt;/a&gt;.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://bsky.app/profile/mitsuhiko.at/post/3lrfld3r74k2e"&gt;@mitsuhiko.at&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/armin-ronacher"&gt;armin-ronacher&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&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/anthropic"&gt;anthropic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude"&gt;claude&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/claude-code"&gt;claude-code&lt;/a&gt;&lt;/p&gt;



</summary><category term="armin-ronacher"/><category term="go"/><category term="ai"/><category term="rust"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="anthropic"/><category term="claude"/><category term="coding-agents"/><category term="claude-code"/></entry><entry><title>f2</title><link href="https://simonwillison.net/2025/May/24/f2/#atom-tag" rel="alternate"/><published>2025-05-24T19:20:48+00:00</published><updated>2025-05-24T19:20:48+00:00</updated><id>https://simonwillison.net/2025/May/24/f2/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/ayoisaiah/f2"&gt;f2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Really neat CLI tool for bulk renaming of files and directories by Ayooluwa Isaiah, written in Go and designed to work cross-platform.&lt;/p&gt;
&lt;p&gt;There's a &lt;em&gt;lot&lt;/em&gt; of great design in this. &lt;a href="https://f2.freshman.tech/guide/tutorial"&gt;Basic usage&lt;/a&gt; is intuitive - here's how to rename all &lt;code&gt;.svg&lt;/code&gt; files to &lt;code&gt;.tmp.svg&lt;/code&gt; in the current directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;f2 -f '.svg' -r '.tmp.svg' path/to/dir
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;f2 defaults to a dry run which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*————————————————————*————————————————————————*————————*
|      ORIGINAL      |        RENAMED         | STATUS |
*————————————————————*————————————————————————*————————*
| claude-pelican.svg | claude-pelican.tmp.svg | ok     |
| gemini-pelican.svg | gemini-pelican.tmp.svg | ok     |
*————————————————————*————————————————————————*————————*
dry run: commit the above changes with the -x/--exec flag
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;-x&lt;/code&gt; executes the rename.&lt;/p&gt;
&lt;p&gt;The really cool stuff is the advanced features - Ayooluwa has thought of &lt;em&gt;everything&lt;/em&gt;. The EXIF integration is particularly clevel - here's an example &lt;a href="https://f2.freshman.tech/guide/organizing-image-library"&gt;from the advanced tutorial&lt;/a&gt; which renames a library of photos to use their EXIF creation date as part of the file path:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;f2 -r '{x.cdt.YYYY}/{x.cdt.MM}-{x.cdt.MMM}/{x.cdt.YYYY}-{x.cdt.MM}-{x.cdt.DD}/{f}{ext}' -R
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-R&lt;/code&gt; flag means "recursive". The small &lt;code&gt;-r&lt;/code&gt; uses variable syntax &lt;a href="https://f2.freshman.tech/guide/exif-variables"&gt;for EXIF data&lt;/a&gt;. There are plenty of others too, including &lt;a href="https://f2.freshman.tech/guide/file-hash-variables"&gt;hash variables&lt;/a&gt; that use the hash of the file contents.&lt;/p&gt;
&lt;h4 id="f2-installation"&gt;Installation notes&lt;/h4&gt;

&lt;p&gt;I had Go 1.23.2 installed on my Mac via Homebrew. I ran this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;go install github.com/ayoisaiah/f2/v2/cmd/f2@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And got an error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;requires go &amp;gt;= 1.24.2 (running go 1.23.2; GOTOOLCHAIN=local)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I upgraded Go using Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew upgrade go
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which took me to 1.24.3 - then the &lt;code&gt;go install&lt;/code&gt; command worked. It put the binary in &lt;code&gt;~/go/bin/f2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There's also &lt;a href="https://www.npmjs.com/package/@ayoisaiah/f2"&gt;an npm package&lt;/a&gt;, similar to the pattern I wrote about a while ago of people &lt;a href="https://simonwillison.net/2022/May/23/bundling-binary-tools-in-python-wheels/"&gt;Bundling binary tools in Python wheels&lt;/a&gt;.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=44081850"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="go"/></entry><entry><title>llm-fragments-go</title><link href="https://simonwillison.net/2025/Apr/10/llm-fragments-go/#atom-tag" rel="alternate"/><published>2025-04-10T15:19:33+00:00</published><updated>2025-04-10T15:19:33+00:00</updated><id>https://simonwillison.net/2025/Apr/10/llm-fragments-go/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/FiloSottile/mostly-harmless/tree/main/llm-fragments-go"&gt;llm-fragments-go&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Filippo Valsorda released the first plugin by someone other than me that uses LLM's new &lt;a href="https://llm.datasette.io/en/stable/plugins/plugin-hooks.html#register-fragment-loaders-register"&gt;register_fragment_loaders()&lt;/a&gt; plugin hook I announced &lt;a href="https://simonwillison.net/2025/Apr/7/long-context-llm/"&gt;the other day&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Install with &lt;code&gt;llm install llm-fragments-go&lt;/code&gt; and then:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can feed the docs of a Go package into LLM using the &lt;code&gt;go:&lt;/code&gt; &lt;a href="https://llm.datasette.io/en/stable/fragments.html"&gt;fragment&lt;/a&gt; with the package name, optionally followed by a version suffix.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;llm -f go:golang.org/x/mod/sumdb/note@v0.23.0 "Write a single file command that generates a key, prints the verifier key, signs an example message, and prints the signed note."&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The implementation is &lt;a href="https://github.com/FiloSottile/mostly-harmless/blob/44fb3e6e0b56decd72e893409e8085d88ad43e3d/llm-fragments-go/llm_fragments_go.py"&gt;just 33 lines of Python&lt;/a&gt; and works by running these commands in a temporary directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;go mod init llm_fragments_go
go get golang.org/x/mod/sumdb/note@v0.23.0
go doc -all golang.org/x/mod/sumdb/note
&lt;/code&gt;&lt;/pre&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://bsky.app/profile/filippo.abyssdomain.expert/post/3lmhhqccp2c2i"&gt;@filippo.abyssdomain.expert&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/plugins"&gt;plugins&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;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/filippo-valsorda"&gt;filippo-valsorda&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="plugins"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="filippo-valsorda"/></entry><entry><title>Quoting Ryan Cavanaugh</title><link href="https://simonwillison.net/2025/Mar/11/ryan-cavanaugh/#atom-tag" rel="alternate"/><published>2025-03-11T19:32:31+00:00</published><updated>2025-03-11T19:32:31+00:00</updated><id>https://simonwillison.net/2025/Mar/11/ryan-cavanaugh/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://github.com/microsoft/typescript-go/discussions/411"&gt;&lt;p&gt;Languages that allow for a structurally similar codebase offer a significant boon for anyone making code changes because we can easily port changes between the two codebases. In contrast, languages that require fundamental rethinking of memory management, mutation, data structuring, polymorphism, laziness, etc., might be a better fit for a ground-up rewrite, but we're undertaking this more as a &lt;em&gt;port&lt;/em&gt; that maintains the existing behavior and critical optimizations we've built into the language. Idiomatic Go strongly resembles the existing coding patterns of the TypeScript codebase, which makes this porting effort much more tractable.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://github.com/microsoft/typescript-go/discussions/411"&gt;Ryan Cavanaugh&lt;/a&gt;, on why TypeScript chose to rewrite in Go, not Rust&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/typescript"&gt;typescript&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="rust"/><category term="typescript"/></entry><entry><title>Everything I've learned so far about running local LLMs</title><link href="https://simonwillison.net/2024/Nov/10/running-llms/#atom-tag" rel="alternate"/><published>2024-11-10T18:01:58+00:00</published><updated>2024-11-10T18:01:58+00:00</updated><id>https://simonwillison.net/2024/Nov/10/running-llms/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://nullprogram.com/blog/2024/11/10/"&gt;Everything I&amp;#x27;ve learned so far about running local LLMs&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Chris Wellons shares detailed notes on his experience running local LLMs on Windows - though most of these tips apply to other operating systems as well.&lt;/p&gt;
&lt;p&gt;This is great, there's a ton of detail here and the root recommendations are very solid: Use &lt;code&gt;llama-server&lt;/code&gt; from &lt;a href="https://github.com/ggerganov/llama.cpp"&gt;llama.cpp&lt;/a&gt; and try ~8B models first (Chris likes Llama 3.1 8B Instruct at Q4_K_M as a first model), anything over 10B probably won't run well on a CPU so you'll need to consider your available GPU VRAM.&lt;/p&gt;
&lt;p&gt;This is neat:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Just for fun, I ported llama.cpp to Windows XP and ran &lt;a href="https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct"&gt;a 360M model&lt;/a&gt; on a 2008-era laptop. It was magical to load that old laptop with technology that, at the time it was new, would have been worth billions of dollars.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I need to spend more time with Chris's favourite models, Mistral-Nemo-2407 (12B) and Qwen2.5-14B/72B.&lt;/p&gt;
&lt;p&gt;Chris also built &lt;a href="https://github.com/skeeto/illume"&gt;illume&lt;/a&gt;, a Go CLI tool for interacting with models that looks similar to my own &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; project.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/u7hgw0/everything_i_ve_learned_so_far_about"&gt;lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/windows"&gt;windows&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;a href="https://simonwillison.net/tags/local-llms"&gt;local-llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llama-cpp"&gt;llama-cpp&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="windows"/><category term="ai"/><category term="generative-ai"/><category term="local-llms"/><category term="llms"/><category term="llama-cpp"/></entry><entry><title>Some Go web dev notes</title><link href="https://simonwillison.net/2024/Sep/27/some-go-web-dev-notes/#atom-tag" rel="alternate"/><published>2024-09-27T23:43:31+00:00</published><updated>2024-09-27T23:43:31+00:00</updated><id>https://simonwillison.net/2024/Sep/27/some-go-web-dev-notes/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://jvns.ca/blog/2024/09/27/some-go-web-dev-notes/"&gt;Some Go web dev notes&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Julia Evans on writing small, self-contained web applications in Go:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In general everything about it feels like it makes projects easy to work on for 5 days, abandon for 2 years, and then get back into writing code without a lot of problems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Go 1.22 &lt;a href="https://go.dev/blog/routing-enhancements"&gt;introduced HTTP routing&lt;/a&gt; in February of this year, making it even more practical to build a web application using just the Go standard library.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/http"&gt;http&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/web-development"&gt;web-development&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/julia-evans"&gt;julia-evans&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="http"/><category term="web-development"/><category term="julia-evans"/></entry><entry><title>Notes on running Go in the browser with WebAssembly</title><link href="https://simonwillison.net/2024/Sep/14/go-in-the-browser-with-webassembly/#atom-tag" rel="alternate"/><published>2024-09-14T17:10:51+00:00</published><updated>2024-09-14T17:10:51+00:00</updated><id>https://simonwillison.net/2024/Sep/14/go-in-the-browser-with-webassembly/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://eli.thegreenplace.net/2024/notes-on-running-go-in-the-browser-with-webassembly/"&gt;Notes on running Go in the browser with WebAssembly&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Neat, concise tutorial by Eli Bendersky on compiling Go applications that can then be loaded into a browser using WebAssembly and integrated with JavaScript. Go functions can be exported to JavaScript like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;js.Global().Set("calcHarmonic", jsCalcHarmonic)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And Go code can even access the DOM using a pattern like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;doc := js.Global().Get("document")
inputElement := doc.Call("getElementById", "timeInput")
input := inputElement.Get("value")
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bundling the WASM Go runtime involves a 2.5MB file load, but there’s also a TinyGo alternative which reduces that size to a fourth.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/i5pkoh/notes_on_running_go_browser_with"&gt;Lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="javascript"/><category term="webassembly"/></entry><entry><title>Migrating Mess With DNS to use PowerDNS</title><link href="https://simonwillison.net/2024/Aug/19/migrating-mess-with-dns/#atom-tag" rel="alternate"/><published>2024-08-19T22:12:07+00:00</published><updated>2024-08-19T22:12:07+00:00</updated><id>https://simonwillison.net/2024/Aug/19/migrating-mess-with-dns/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://jvns.ca/blog/2024/08/19/migrating-mess-with-dns-to-use-powerdns/"&gt;Migrating Mess With DNS to use PowerDNS&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Fascinating in-depth write-up from Julia Evans about how she upgraded her "mess with dns" playground application to use &lt;a href="https://github.com/PowerDNS/pdns"&gt;PowerDNS&lt;/a&gt;, an open source DNS server with a &lt;a href="https://doc.powerdns.com/authoritative/http-api/index.html#working-with-the-api"&gt;comprehensive JSON API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you haven't explored &lt;a href="https://messwithdns.net/"&gt;mess with dns&lt;/a&gt; it's absolutely worth checking out. No login required: when you visit the site it assigns you a random subdomain (I got &lt;code&gt;garlic299.messwithdns.com&lt;/code&gt; just now) and then lets you start adding additional sub-subdomains with their own DNS records - A records, CNAME records and more.&lt;/p&gt;
&lt;p&gt;The interface then shows a live (WebSocket-powered) log of incoming DNS requests and responses, providing instant feedback on how your configuration affects DNS resolution.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=41292784"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/dns"&gt;dns&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/julia-evans"&gt;julia-evans&lt;/a&gt;&lt;/p&gt;



</summary><category term="dns"/><category term="go"/><category term="julia-evans"/></entry><entry><title>High-precision date/time in SQLite</title><link href="https://simonwillison.net/2024/Aug/9/high-precision-datetime-in-sqlite/#atom-tag" rel="alternate"/><published>2024-08-09T15:31:40+00:00</published><updated>2024-08-09T15:31:40+00:00</updated><id>https://simonwillison.net/2024/Aug/9/high-precision-datetime-in-sqlite/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://antonz.org/sqlean-time/"&gt;High-precision date/time in SQLite&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Another neat SQLite extension from Anton Zhiyanov. &lt;code&gt;sqlean-time&lt;/code&gt; (&lt;a href="https://github.com/nalgeon/sqlean/tree/main/src/time"&gt;C source code here&lt;/a&gt;) implements high-precision time and date functions for SQLite, modeled after the design used by Go.&lt;/p&gt;
&lt;p&gt;A time is stored as a 64 bit signed integer seconds &lt;code&gt;0001-01-01 00:00:00 UTC&lt;/code&gt; - signed so you can represent dates in the past using a negative number - plus a 32 bit integer of nanoseconds - combined into a a 13 byte internal representation that can be stored in a BLOB column.&lt;/p&gt;
&lt;p&gt;A duration uses a 64-bit number of nanoseconds, representing values up to roughly 290 years.&lt;/p&gt;
&lt;p&gt;Anton includes dozens of functions for parsing, displaying, truncating, extracting fields and converting to and from Unix timestamps.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/6mzz3c/high_precision_date_time_sqlite"&gt;lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datetime"&gt;datetime&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/anton-zhiyanov"&gt;anton-zhiyanov&lt;/a&gt;&lt;/p&gt;



</summary><category term="datetime"/><category term="go"/><category term="sqlite"/><category term="anton-zhiyanov"/></entry><entry><title>Quoting Nikita Melkozerov</title><link href="https://simonwillison.net/2024/Jul/13/nikita-melkozerov/#atom-tag" rel="alternate"/><published>2024-07-13T23:44:04+00:00</published><updated>2024-07-13T23:44:04+00:00</updated><id>https://simonwillison.net/2024/Jul/13/nikita-melkozerov/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://twitter.com/meln1k/status/1812116658300817477"&gt;&lt;p&gt;My architecture is a monolith written in  Go (this is intentional, I sacrificed scalability to improve my shipping  speed), and this is where SQLite shines. With a DB located on the local NVMe disk, a 5$ VPS can deliver a whopping 60K reads and 20K writes per second.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://twitter.com/meln1k/status/1812116658300817477"&gt;Nikita Melkozerov&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/performance"&gt;performance&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="performance"/><category term="sqlite"/></entry><entry><title>Geomys, a blueprint for a sustainable open source maintenance firm</title><link href="https://simonwillison.net/2024/Jul/8/geomys/#atom-tag" rel="alternate"/><published>2024-07-08T15:40:28+00:00</published><updated>2024-07-08T15:40:28+00:00</updated><id>https://simonwillison.net/2024/Jul/8/geomys/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://words.filippo.io/dispatches/geomys/"&gt;Geomys, a blueprint for a sustainable open source maintenance firm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Filippo Valsorda has been &lt;a href="https://words.filippo.io/full-time-maintainer/"&gt;working as a full-time professional open source maintainer&lt;/a&gt; for nearly two years now, accepting payments on retainer from companies that depend on his cryptography Go packages.&lt;/p&gt;
&lt;p&gt;This has worked well enough that he's now expanding: Geomys (a &lt;a href="https://en.m.wikipedia.org/wiki/Geomys"&gt;genus of gophers&lt;/a&gt;) is a new company which adds two new "associate maintainers" and an administrative director, covering more projects and providing clients with access to more expertise.&lt;/p&gt;
&lt;p&gt;Filipino describes the model like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you’re betting your business on a critical open source technology, you&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;want it to be sustainably and predictably maintained; and&lt;/li&gt;
&lt;li&gt;need occasional access to expertise that would be blisteringly expensive to acquire and retain.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Getting maintainers on retainer solves both problems for a fraction of the cost of a fully-loaded full-time engineer. From the maintainers’ point of view, it’s steady income to keep doing what they do best, and to join one more Slack Connect channel to answer high-leverage questions. It’s a great deal for both sides.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For more on this model, watch Filippo's &lt;a href="https://fosdem.org/2024/schedule/event/fosdem-2024-2000-maintaining-go-as-a-day-job-a-year-later/"&gt;FOSDEM talk from earlier this year&lt;/a&gt;.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://abyssdomain.expert/@filippo/112751477694100408"&gt;@filippo&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/open-source"&gt;open-source&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/filippo-valsorda"&gt;filippo-valsorda&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="open-source"/><category term="filippo-valsorda"/></entry><entry><title>Optimizing Large-Scale OpenStreetMap Data with SQLite</title><link href="https://simonwillison.net/2024/Jul/2/openstreetmap-data-sqlite/#atom-tag" rel="alternate"/><published>2024-07-02T14:33:09+00:00</published><updated>2024-07-02T14:33:09+00:00</updated><id>https://simonwillison.net/2024/Jul/2/openstreetmap-data-sqlite/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://jtarchie.com/posts/2024-07-02-optimizing-large-scale-openstreetmap-data-with-sqlite"&gt;Optimizing Large-Scale OpenStreetMap Data with SQLite&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
JT Archie describes his project to take 9GB of compressed OpenStreetMap protobufs data for the whole of the United States and load it into a queryable SQLite database.&lt;/p&gt;
&lt;p&gt;OSM tags are key/value pairs. The trick used here for FTS-accelerated tag queries is really neat: build a SQLite FTS table containing the key/value pairs as space concatenated text, then run queries that look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
    id
FROM
    entries e
    JOIN search s ON s.rowid = e.id
WHERE
    -- use FTS index to find subset of possible results
    search MATCH 'amenity cafe'
    -- use the subset to find exact matches
    AND tags-&amp;gt;&amp;gt;'amenity' = 'cafe';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JT ended up building a custom SQLite Go extension, &lt;a href="https://github.com/jtarchie/sqlitezstd"&gt;SQLiteZSTD&lt;/a&gt;, to further accelerate things by supporting queries against read-only zstd compresses SQLite files. Apparently zstd has &lt;a href="https://github.com/facebook/zstd/blob/3de0541aef8da51f144ef47fb86dcc38b21afb00/contrib/seekable_format/zstd_seekable_compression_format.md"&gt;a feature&lt;/a&gt; that allows "compressed data to be stored so that subranges of the data can be efficiently decompressed without requiring the entire document to be decompressed", which works well with SQLite's page format.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://www.reddit.com/r/sqlite/comments/1dtls62/optimizing_largescale_openstreetmap_data_with/"&gt;r/sqlite&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/zstd"&gt;zstd&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="openstreetmap"/><category term="sqlite"/><category term="zstd"/></entry><entry><title>Serving a billion web requests with boring code</title><link href="https://simonwillison.net/2024/Jun/28/boring-code/#atom-tag" rel="alternate"/><published>2024-06-28T16:22:45+00:00</published><updated>2024-06-28T16:22:45+00:00</updated><id>https://simonwillison.net/2024/Jun/28/boring-code/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://notes.billmill.org/blog/2024/06/Serving_a_billion_web_requests_with_boring_code.html"&gt;Serving a billion web requests with boring code&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Bill Mill provides a deep retrospective from his work helping build a relaunch of the &lt;a href="https://www.medicare.gov/plan-compare/"&gt;medicare.gov/plan-compare&lt;/a&gt; site.&lt;/p&gt;
&lt;p&gt;It's a fascinating case study of the &lt;a href="https://boringtechnology.club/"&gt;choose boring technology&lt;/a&gt; mantra put into action. The "boring" choices here were PostgreSQL, Go and React, all three of which are so widely used and understood at this point that you're very unlikely to stumble into surprises with them.&lt;/p&gt;
&lt;p&gt;Key goals for the site were accessibility, in terms of users, devices and performance. Despite best efforts:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The result fell prey after a few years to a common failure mode of react apps, and became quite heavy and loaded somewhat slowly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've seen this pattern myself many times over, and I'd love to understand why. React itself isn't a particularly large dependency but somehow it always seems to lead to architectural bloat over time. Maybe that's more of an SPA thing than something that's specific to React.&lt;/p&gt;
&lt;p&gt;Loads of other interesting details in here. The ETL details - where brand new read-only RDS databases were spun up every morning after a four hour build process - are particularly notable.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/icigm4/serving_billion_web_requests_with_boring"&gt;Lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/accessibility"&gt;accessibility&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/postgresql"&gt;postgresql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/scaling"&gt;scaling&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/react"&gt;react&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/boring-technology"&gt;boring-technology&lt;/a&gt;&lt;/p&gt;



</summary><category term="accessibility"/><category term="go"/><category term="postgresql"/><category term="scaling"/><category term="react"/><category term="boring-technology"/></entry><entry><title>Quoting Russ Cox</title><link href="https://simonwillison.net/2024/Jun/17/russ-cox/#atom-tag" rel="alternate"/><published>2024-06-17T10:46:56+00:00</published><updated>2024-06-17T10:46:56+00:00</updated><id>https://simonwillison.net/2024/Jun/17/russ-cox/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://research.swtch.com/vgo-eng"&gt;&lt;p&gt;Most people think that we format Go code with &lt;code&gt;gofmt&lt;/code&gt; to make code look nicer or to end debates among team members about program layout. But &lt;a href="https://groups.google.com/forum/#!msg/golang-nuts/HC2sDhrZW5Y/7iuKxdbLExkJ"&gt;the most important reason for gofmt&lt;/a&gt; is that if an algorithm defines how Go source code is formatted, then programs, like &lt;code&gt;goimports&lt;/code&gt; or &lt;code&gt;gorename&lt;/code&gt; or &lt;code&gt;go fix&lt;/code&gt;, can edit the source code more easily, without introducing spurious formatting changes when writing the code back. This helps you maintain code over time.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://research.swtch.com/vgo-eng"&gt;Russ Cox&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/russcox"&gt;russcox&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="russcox"/></entry><entry><title>redka</title><link href="https://simonwillison.net/2024/Apr/14/redka/#atom-tag" rel="alternate"/><published>2024-04-14T15:21:19+00:00</published><updated>2024-04-14T15:21:19+00:00</updated><id>https://simonwillison.net/2024/Apr/14/redka/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/nalgeon/redka"&gt;redka&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Anton Zhiyanov’s new project to build a subset of Redis (including protocol support) using Go and SQLite. Also works as a Go library.&lt;/p&gt;

&lt;p&gt;The guts of the SQL implementation are in the internal/sqlx folder.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/ohmypy/status/1779489065173467385"&gt;@ohmypy&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/redis"&gt;redis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/anton-zhiyanov"&gt;anton-zhiyanov&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="redis"/><category term="sqlite"/><category term="anton-zhiyanov"/></entry><entry><title>The One Billion Row Challenge in Go: from 1m45s to 4s in nine solutions</title><link href="https://simonwillison.net/2024/Mar/3/the-one-billion-row-challenge-in-go/#atom-tag" rel="alternate"/><published>2024-03-03T07:08:34+00:00</published><updated>2024-03-03T07:08:34+00:00</updated><id>https://simonwillison.net/2024/Mar/3/the-one-billion-row-challenge-in-go/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://benhoyt.com/writings/go-1brc/"&gt;The One Billion Row Challenge in Go: from 1m45s to 4s in nine solutions&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
How fast can you read a billion semicolon delimited (name;float) lines and output a min/max/mean summary for each distinct name—13GB total?&lt;/p&gt;

&lt;p&gt;Ben Hoyt describes his 9 incrementally improved versions written in Go in detail. The key optimizations involved custom hashmaps, optimized line parsing and splitting the work across multiple CPU cores.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=39578501"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/></entry><entry><title>How I write HTTP services in Go after 13 years</title><link href="https://simonwillison.net/2024/Feb/9/how-i-write-http-services-in-go-after-13-years/#atom-tag" rel="alternate"/><published>2024-02-09T20:40:23+00:00</published><updated>2024-02-09T20:40:23+00:00</updated><id>https://simonwillison.net/2024/Feb/9/how-i-write-http-services-in-go-after-13-years/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://grafana.com/blog/2024/02/09/how-i-write-http-services-in-go-after-13-years/"&gt;How I write HTTP services in Go after 13 years&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Useful set of current best practices for deploying HTTP servers written in Go. I guess Go counts as boring technology these days, which is high praise in my book.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=39318867"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/></entry><entry><title>How ima.ge.cx works</title><link href="https://simonwillison.net/2023/Dec/31/how-imagecx-works/#atom-tag" rel="alternate"/><published>2023-12-31T04:32:41+00:00</published><updated>2023-12-31T04:32:41+00:00</updated><id>https://simonwillison.net/2023/Dec/31/how-imagecx-works/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://awsteele.com/blog/2023/12/29/how-ima-ge-cx-works.html"&gt;How ima.ge.cx works&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
ima.ge.cx is Aidan Steele’s web tool for browsing the contents of Docker images hosted on Docker Hub. The architecture is really interesting: it’s a set of AWS Lambda functions, written in Go, that fetch metadata about the images using Step Functions and then cache it in DynamoDB and S3. It uses S3 Select to serve directory listings from newline-delimited JSON in S3 without retrieving the whole file.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/__steele/status/1740597225913458938"&gt;@__steele&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/aws"&gt;aws&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/s3"&gt;s3&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/docker"&gt;docker&lt;/a&gt;&lt;/p&gt;



</summary><category term="aws"/><category term="go"/><category term="s3"/><category term="docker"/></entry><entry><title>The WebAssembly Go Playground</title><link href="https://simonwillison.net/2023/Sep/19/webassembly-go-playground/#atom-tag" rel="alternate"/><published>2023-09-19T19:53:11+00:00</published><updated>2023-09-19T19:53:11+00:00</updated><id>https://simonwillison.net/2023/Sep/19/webassembly-go-playground/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://progrium.github.io/wasm-go-playground/"&gt;The WebAssembly Go Playground&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Jeff Lindsay has a full Go 1.21.1 compiler running entirely in the browser.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://github.com/progrium/wasm-go-playground"&gt;progrium/wasm-go-playground&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jeff-lindsay"&gt;jeff-lindsay&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="jeff-lindsay"/><category term="webassembly"/></entry></feed>