Posts in Go (20 found)

Introducing Showboat and Rodney, so agents can demo what they’ve built

A key challenge working with coding agents is having them both test what they’ve built and demonstrate that software to you, their overseer. 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: Showboat and Rodney . I recently wrote about how the job of a software engineer isn't to write code, it's to deliver code that works . A big part of that is proving to ourselves and to other people that the code we are responsible for behaves as expected. This becomes even more important - and challenging - as we embrace coding agents as a core part of our software development process. 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. One of the most interesting things about the StrongDM software factory model 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! 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. Showboat is the tool I built to help agents demonstrate their work to me. It's a CLI tool (a Go binary, optionally wrapped in Python to make it easier to install) that helps an agent construct a Markdown document demonstrating exactly what their newly developed code can do. It's not designed for humans to run, but here's how you would run it anyway: Here's what the result looks like if you open it up in VS Code and preview the Markdown: Here's that demo.md file in a Gist . So a sequence of , , and commands constructs a Markdown document one section at a time, with the output of those commands automatically added to the document directly following the commands that were run. The 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. That's basically the whole thing! There's a command to remove the most recently added section if something goes wrong, a command to re-run the document and check nothing has changed (I'm not entirely convinced by the design of that one) and a command that reverse-engineers the CLI commands that were used to create the document. It's pretty simple - just 172 lines of Go. I packaged it up with my go-to-wheel tool which means you can run it without even installing it first like this: That command is really important: it's designed to provide a coding agent with everything it needs to know in order to use the tool. Here's that help text in full . This means you can pop open Claude Code and tell it: And that's it! The text acts a bit like a Skill . 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. 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. And finally, some examples. Here are documents I had Claude create using Showboat to help demonstrate features I was working on in other projects: row-state-sql CLI Demo shows a new command I added to that same project. Change grouping with Notes demonstrates another feature where groups of changes within the same transaction can have a note attached to them. I've now used Showboat often enough that I've convinced myself of its utility. (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 an issue about that .) 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. Showboat's image feature was designed to allow agents to capture screenshots as part of their demos, originally using my shot-scraper tool or Playwright . 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. Claude Opus 4.6 pointed me to the Rod 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. All Rod was missing was a CLI. I built the first version as an asynchronous report prototype , which convinced me it was worth spinning out into its own project. I called it Rodney as a nod to the Rod library it builds on and a reference to Only Fools and Horses - and because the package name was available on PyPI. You can run Rodney using or install it like this: (Or grab a Go binary from the releases page .) Here's a simple example session: Here's what that looks like in the terminal: As with Showboat, this tool is not designed to be used by humans! The goal is for coding agents to be able to run and see everything they need to know to start using the tool. You can see that help output in the GitHub repo. Here are three demonstrations of Rodney that I created using Showboat: After being a career-long skeptic of the test-first, maximum test coverage school of software development (I like tests included 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. Many of my Python coding agent sessions start the same way: 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. 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. 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. 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. Before building Showboat I'd often add a “manual” testing step to my agent sessions, something like: 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. 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. 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. You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options . Proving code actually works Showboat: Agents build documents to demo their work Rodney: CLI browser automation designed to work with Showboat Test-driven development helps, but we still need manual testing I built both of these tools on my phone shot-scraper: A Comprehensive Demo runs through the full suite of features of my shot-scraper browser automation tool, mainly to exercise the command. sqlite-history-json CLI demo demonstrates the CLI feature I added to my new sqlite-history-json Python library. row-state-sql CLI Demo shows a new command I added to that same project. Change grouping with Notes demonstrates another feature where groups of changes within the same transaction can have a note attached to them. krunsh: Pipe Shell Commands to an Ephemeral libkrun MicroVM 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. Rodney's original feature set , including screenshots of pages and executing JavaScript. Rodney's new accessibility testing features , built during development of those features to show what they could do. Using those features to run a basic accessibility audit of a page . I was impressed at how well Claude Opus 4.6 responded to the prompt "Use showboat and rodney to perform an accessibility audit of https://latest.datasette.io/fixtures " - transcript here .

0 views
Simon Willison 6 days ago

Distributing Go binaries like sqlite-scanner through PyPI using go-to-wheel

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 call away. sqlite-scanner is my new Go CLI tool for scanning a filesystem for SQLite database files. It works by checking if the first 16 bytes of the file exactly match the SQLite magic number sequence . 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. To try it out you can download a release from the GitHub releases - and then jump through macOS hoops to execute an "unsafe" binary. Or you can clone the repo and compile it with Go. Or... you can run the binary like this: By default this will search your current directory for SQLite databases. You can pass one or more directories as arguments: Add for JSON output, to include file sizes or for newline-delimited JSON. Here's a demo: If you haven't been uv-pilled yet you can instead install using and then run . To get a permanent copy with use . The reason this is worth doing is that , and PyPI will work together to identify the correct compiled binary for your operating system and architecture. This is driven by file names. If you visit the PyPI downloads for sqlite-scanner you'll see the following files: When I run or on my Apple Silicon Mac laptop Python's packaging magic ensures I get that variant. Here's what's in the wheel , which is a zip file with a extension. In addition to the the most important file is which includes the following: That method - also called from - locates the binary and executes it when the Python package itself is executed, using the entry point defined in the wheel. Using PyPI as a distribution platform for Go binaries feels a tiny bit abusive, albeit there is plenty of precedent . I’ll justify it by pointing out that this means we can use Go binaries as dependencies for other Python packages now. 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. To demonstrate this, I built datasette-scan - a new Datasette plugin which depends on and then uses that Go binary to scan a folder for SQLite databases and attach them to a Datasette instance. Here's how to use that (without even installing anything first, thanks ) to explore any SQLite databases in your Downloads folder: If you peek at the code you'll see it depends on sqlite-scanner in and calls it using against in its own scan_directories() function . I've been exploring this pattern for other, non-Go binaries recently - here's a recent script that depends on static-ffmpeg to ensure that is available for the script to use. After trying this pattern myself a couple of times I realized it would be useful to have a tool to automate the process. I first brainstormed with Claude to check that there was no existing tool to do this. It pointed me to maturin bin which helps distribute Rust projects using Python wheels, and pip-binary-factory which bundles all sorts of other projects, but did not identify anything that addressed the exact problem I was looking to solve. So I had Claude Code for web build the first version , 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. The full documentation is in the simonw/go-to-wheel repository. I've published that tool to PyPI so now you can run it using: The package you can see on PyPI was built using like this: This created a set of wheels in the folder. I tested one of them like this: 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 like this: I had to paste in a PyPI API token I had saved previously and that was all it took. 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! That said, I think there's a lot 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. 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 handler. I've also been experimenting with wazero , Go's robust and mature zero dependency WebAssembly runtime as part of my ongoing quest for the ideal sandbox for running untrusted code. Here's my latest experiment with that library. Being able to seamlessly integrate Go binaries into Python projects without the end user having to think about Go at all - they and everything Just Works - feels like a valuable addition to my toolbox. You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options .

0 views
Justin Duke 1 weeks ago

Brief notes on migrating to Postgres-backed jobs

It seems premature to talk about a migration that is only halfway done, even if it's the hard half that's done — but I think there's something useful in documenting the why and how of a transition while you're still in the thick of it, before the revisionist history of completion sets in. Early last year, we built out a system for running background jobs directly against Postgres within Django. This very quickly got abstracted out into a generic task runner — shout out to Brandur and many other people who have been beating this drum for a while. And as far as I can tell, this concept of shifting away from Redis and other less-durable caches for job infrastructure is regaining steam on the Rails side of the ecosystem, too. The reason we did it was mostly for ergonomics around graceful batch processing. It is significantly easier to write a poller in Django for stuff backed by the ORM than it is to try and extend RQ or any of the other task runner options that are Redis-friendly. Django gives you migrations, querysets, admin visibility, transactional guarantees — all for free, all without another moving part. And as we started using it and it proved stable, we slowly moved more and more things over to it. At the time of this writing, around half of our jobs by quantity — which represent around two-thirds by overall volume — have been migrated over from RQ onto this system. This is slightly ironic given that we also last year released django-rq-cron , a library that, if I have my druthers, we will no longer need. Fewer moving parts is the watchword. We're removing spindles from the system and getting closer and closer to a simple, portable, and legible stack of infrastructure.

1 views
Stone Tools 1 weeks ago

Scala Multimedia on the Commodore Amiga

The ocean is huge. It's not only big enough to separate landmasses and cultures, but also big enough to separate ideas and trends. Born and raised in the United States, I couldn't understand why the UK was always eating so much pudding. Please forgive my pre-internet cultural naiveté. I should also be kind to myself for thinking the Video Toaster was the be-all-end-all for video production and multimedia authoring on the Amiga. Search Amiga World metadata on Internet Archive for "toaster" and "scala" and you'll see my point. "Toaster" brings up dozens of top-level hits, and "Scala" gets zero. The NTSC/PAL divide was as vast as the ocean. From the States, cross either ocean and Scala was everywhere, including a full, physical-dongle-copy-protection-removed, copy distributed on the cover disk of CU Amiga Magazine , issue 96. Listening to Scala founder, Jon Bøhmer, speak of Scala 's creation in an interview on The Retro Hour , his early intuition on the Amiga's potential in television production built Scala into an omnipresent staple across multiple continents. Intuition alone can't build an empire. Bøhmer also had gladiatorial-like aggression to maintain his dominance in that market. As he recounted, "A Dutch company tried to make a Scala clone, and they made a mistake of putting...the spec sheet on their booth and said all those different things that Scala didn't have yet. So I took that spec sheet back to my developers (then, later) lo and behold before those guys had a bug free version out on the street, we had all their features and totally eradicated their whole proposal." Now, of course I understand that it would have been folly to ignore the threat. Looked at from another angle, Scala had apparently put themselves in a position where their dominance could face a legitimate threat from a disruptor. Ultimately, that's neither here nor there as in the end, Scala had early momentum and could swing the industry their direction. Scala (the software) remains alive and well even now, in the digital signage authoring and playback software arena. You know the stuff, like interactive touchscreens at restaurant checkouts, or animated displays at retail stores. As with the outliner/PIM software in the ThinkTank article , the world of digital signage is likewise shockingly crowded. Discovering this felt like catching a glimpse of a secondary, invisible world just below the surface of conscious understanding. Scala didn't find success without good reason. It solved some thorny broadcast production issues on hardware that was alone in its class for a time. A unique blend of software characteristics (multitasking, IFF, ARexx) turned an Amiga running Scala into more than the sum of its parts. Scala by itself would have made rumbles. Scala on the Amiga was seismic. At heart, I'm a print guy. Like anyone, I enjoy watching cool video effects, and I once met Kiki Stockhammer in person. But my brain has never been wired for animation or motion design. My 3D art was always static; my designs were committed to ink on paper. I liked holding a physical artifact in my hands at the end of the design process. Considering the sheer depths of my video naivete, for this investigation I will need a lot of help from the tutorials. I'll build the demo stuff from the manual, and try to push myself further and see where my explorations take me. CU Amiga Magazine issues 97 - 102 contain Scala MM300 tutorials as well, so I'll check those out for a man-on-the-streets point of view. The first preconception I need to shed is thinking Scala is HyperCard for the Amiga. It flirts with certain concepts, but building Myst with this would be out of reach for most people. I'll never say it's "impossible," as I don't like tempting the Fates that way, but it would need considerable effort and development skills. A little terminology is useful before we really dig in. I usually start an exploration of GUI applications by checking out the available menus. With Scala , there aren't any. I don't mean the menubar is empty, I mean there isn't a menubar, period. It does not exist. I am firmly in Scala Land and Scala 's vision of how multimedia work gets done. As with PaperClip , I find its opinionated interface comforting. I have serious doubts about common assumptions of interface homogeneity being a noble goal, but that's a discussion for a future post. Despite its plain look, what we see when the program launches is richly complex. Anything in purple (or whatever your chosen color scheme uses) is clickable, and if it has its own boundaries it does its own thing. Across the top we have the Scala logo, program title bar, and the Amiga Workbench "depth gadget." Clicking the logo is how we save our project and/or exit the program. Then we have what is clearly a list, and judging from interface cues its a list of pages. This list ("script") is akin to a HyperCard stack with transitions ("wipes") between cards ("pages"). Each subsection of any given line item is its own button for interfacing with that specific aspect of the page. It's approachable and nonthreatening, and to my mind encourages me to just click on things and see what happens. The bottom-sixth holds an array of buttons that would normally be secreted away under standard Amiga GUI menus. On the one hand, this means if you see it, it's available; no poking through dozens of greyed-out items. On the other hand, keyboard shortcuts and deeper tools aren't exposed. There's no learning through osmosis here. Following the tutorial, the first thing to do is define my first "page." Click "New," choose a background as a visual starting point if you like, click "OK", choose a resolution and color depth (this is per-screen, not per-project), and click "OK" to finish. The program steps me through the process; it is clear how to proceed. The design team for Scala really should be commended for the artistic craftsmanship of the product. It is easy to put something professional together with the included backgrounds, images, and music. Everything is tasteful and (mostly) subdued, if aesthetically "of its time" occasionally. Thanks to IFF support, if you don't like the built-in assets, you can create your own in one of the Amiga's many paint or music programs. That visual care extends to the included fonts, which are a murderer's row of well-crafted classics. All the big stars are here! Futura, Garamond, Gill Sans, Compact, and more. Hey, is that Goudy I see coming down the red carpet? And behind them? Why it's none other than Helvetica, star of their own hit movie that has the art world buzzing! And, oh no! Someone just threw red paint all over Franklin Gothic. What a shame, because I'm pretty sure that's a pleather dress. The next screen is where probably 85% of my time will be spent. One thing I've noticed with the manual is a lack of getting the reader up to speed on the nomenclature of the program. This screen contains the "Edit Menu" but is that what I should call this screen? The "Edit Menu" screen? Screen layouts are called "pages." Is this the "Page Edit" screen? Anyway, the "Edit Menu" gives a lot of control, both fine and coarse, for text styling, shape types, creating buttons, setting the color palette, coordinating object reveals, and more. Buttons with hide extra options, for styling or importing other resources, and it could be argued the interface works against itself a bit. As Scala has chosen to eschew typical Amiga GUI conventions, they walk a delicate line of showing as much as possible, while avoiding visual confusion. It never feels overwhelming, but only just and could stand to borrow from the MacOS playbook's popup menus, rather than cycling of options. Entering text is simple; click anywhere on the screen and begin typing. Where it gets weird is how Scala treats all text as one continuous block. Every line is ordered by Y-position on screen, but every line is connected to the next. Typing too much on a given line will spill over into the next line down, wherever it may be, and however it may be styled. 0:00 / 0:17 1× Text weirdness in the Edit Screen. (I think I had a Trapper Keeper in that pattern.) The unobtrusive buttons "IN" and "OUT" on the left define how the currently selected object will transition into or out of the screen. Doing this by mouse selection is kind of a drag, as there is no visible selection border for the object being modified. There is an option to draw boxes around objects, but there is no differentiation of selected vs. unselected objects, except when there is. It's a bit inconsistent. The "List" button reveals a method for assigning transitions and rearranging object timings precisely. It is quickly my preferred method for anything more complex than "a simple piece of text flies into view." As a list we can define only a pure sequence. Do a thing. Do a second thing. Do a third thing. The end. Multiple items can be "chained" to perform precisely the same wipe as the parent object, with no variation. It's a grouping tool, not a timing tool. 0:00 / 0:04 1× "List" editing of text effect timings. Stay tuned for the sequel: "celeriac and jicama" I'm having a lot of fun exploring these tools, and have immediately wandered off the tutorial path just to play around. Everything works like I'd expect, and I don't need to consult the manual much at all. There are no destructive surprises nor wait times. I click buttons and see immediate results; my inquisitiveness is rewarded. Pages with animation are all good and well, but it is interactivity which elevates a Scala page over the stoicism of a PowerPoint slide. That means it's time for the go-to interaction metaphor: the good ole' button. Where HyperCard has the concept of buttons as objects, in Scala a button is just a region of the screen. It accepts two events: and , though it burdens these simple actions with the confusing names and . I mix up these terms constantly in my mind. To add a button, draw a box. Alternately, click something you've drawn and a box bound to that object's dimensions will be auto-generated. Don't be fooled! That box is not tethered to the object. It just happens to be sized precisely to the object's current dimensions and position on screen, as a helpful shortcut to generate the most-likely button for your needs. Button interactions can do a few things. First, it can adjust colors within its boundaries. Amiga palettes use indexed color, so color swaps are trivial and pixel-perfect. Have some white text that should highlight in red when the mouse enters it? Set the "mark" (mouse enter) palette to remap white to red. Same for "select" (mouse click), a separate palette remap could turn the white to yellow on click. Why am I talking about this when I can just show you? 0:00 / 0:24 1× I intentionally drew the button to be half the text height to illustrate that the button has no relation to the text itself. Color remapping occurs within button boundaries. The double palettes represent the current palette (top), and the remapped palette (bottom). Buttons can also contain simple logic, setting or reading global variable states to determine how to behave at any given moment. IF-THEN statements can likewise be embedded to route presentation order based on those variables. So, a click could add +1 to a global counter, then if the counter is a certain value it could transition to a corresponding page. 0:00 / 0:03 1× If we feel particularly clever with index color palette remapping, it is possible to give the illusion of complete image replacement. Buttons do not need any visible attributes, nor do they need to be mouse-clicked to perform their actions. If "Function Keys" are enabled at the Scala "System" level, the first 10 buttons on a page are automatically linked to F1 - F10. A sample script which ships with Scala demonstrates F-Key control over a page in real-time, altering the values of sports scores by set amounts. This is a clever trick, and with deeper thought opens up interesting possibilities. If every page in a script were to secretly contain such a set of buttons, a makeshift control panel could function like a "video soundboard" of sorts. F-Keys could keep a presentation dynamic, perhaps reacting to live audience participation. I mention this for no particular reason and it is not a setup for a later reveal. ahem Once we've made some pages, its time to stitch them together into a proper presentation, a "script" in Scala parlance. This all happens in the "Main Menu" which works similarly to the "List" view when editing page text elements, with a few differences. "Wipe" is the transition from the previous page to the selected page. If you want to wipe "out" from a page with transition X, then wipe "in" to next page with transition Y, a page must be added in-between to facilitate that. The quality of the real-time wipe effects surprises me. Again, my video naivete is showing, because I always thought the Amiga needed specialized hardware to do stuff like this, especially when there is video input. The wipes are fun, if perhaps a little staid compared to the Toaster 's. In Scala 's defense, they remain a bit more timeless in their simplicity. "Pause" controls, by time or frame count, how long to linger on a page before moving on to the next one. Time can be relative to the start of the screen reveal, or absolute so as to coordinate Scala animations with known timestamps on a pre-recorded video source. A mouse click can also be assigned as the "pause," waiting for a click to continue. "Sound" attaches a sound effect, or a MOD music file, to the reveal. There are rudimentary tools for adjusting pitch and timing, and even for trimming sounds to fit. An in-built sampler makes quick, crunchy, low-fidelity voice recordings, for when you need to add a little extra pizazz in a pinch, or to rough out an idea to see how it works. Sometimes the best tool for the job is the one you have with you. There are hidden tools on the Main Menu. Like many modern GUI table views, the gap between columns is draggable. Narrowing the "Name" column reveals two hidden options to the right: Variables and Execute. Now I'm finally getting a whiff of HyperCard . Unlike HyperCard , these tools are rather opaque and non-intuitive. Right off the bat, there is no built-in script editor. Rather, Scala is happy to position itself as one tool in your toolbox, not to provide every tool you need out of the box. It's going to take some time to get to know how these work, perhaps more than I have allocated for this project, but I'll endeavor to at least come to grips with these. The Scala manual says, "The Scala definition of variables (closely resembles) ARexx, since all variable operators are performed by ARexx." After 40 years, I guess it's time to finally learn about ARexx. ARexx is the Amiga implementation of the REXX scripting language . From ARexx User's Reference Manual, "ARexx is particularly well suited as a command language. Command programs, sometimes called "scripts" or "macros", are widely used to extend the predefined commands of an operating system or to customize an applications program." This is essentially the Amiga's AppleScript equivalent, a statement which surely has a pedant somewhere punching their 1084 monitor at my ignorance. Indeed, the Amiga had ARexx before Apple had AppleScript, but not before Apple had HyperCard . Amiga Magazine , August 1989, described it thusly, "Amiga's answer to HyperCard is found in ARexx, a programming and DOS command language, macro processor, and inter-process controller, all rolled into one easy-to-use command language." "Easy-to-use" you say? Commodore had their heart in the right place, but the "Getting Acquainted" section of the ARexx manual immediately steers hard into programmer-speak. From the jump we're hit with stuff like, "(ARexx) uses the double-precision math library called "mathieeedoubbas.library" that is supplied with the Amiga WorkBench disk, so make sure that this file is present in your LIBS: directory. The distribution disk includes the language system, some example programs, and a set of the INCLUDE files required for integrating ARexx with other software packages." I know exactly what I'd have thought back in the day. What is a "mathieeedoubbas?" What is a "library?" Is "LIBS" and "library" the same thing? What is "double-precision?" What is "INCLUDE"? What is a "language system?" You, manual, said yourself on page 2, "If you are new to the REXX language, or perhaps to programming itself, you should review chapters 1 through 4." So far, that ain't helpin'. Luckily for young me, now me knows a thing or two about programming and can make sense of this stuff. Well, "sense" in the broadest definition only. What this means for Scala is that we have lots of options for handling variables and logic in our project. The manual says, "Any ARexx operators and functions can be used (in the variable field)." However, a function like "Say," which outputs text to console, doesn't make any sense in a Scala context, so I'm not always 100% clear where lie the boundaries of useful operators and functions. In addition to typical math functions and simple string concatentation, ARexx gives us boolean and equality checks, bitwise operators, random number generation, string to digit conversion, string filtering and trimming, the current time, and a lot more. Even checking for file existence works, which possibly carried over from Scala 's roots as a modem-capable automated remote video-titler. Realistically, there's only so much we can do given the tiny tiny OMG it's so small interface into which we type our expressions. My aspirations are scoped by the interface design. This is not necessarily a bad thing , IMHO. " Small, sharp tools " is a handy mental scoping model. Variables are global, starting from the page on which they're defined. So page 1 cannot reach variables defined on page 2. A page can display the value of any currently defined variable by using the prefix in the on-screen text, as in . 0:00 / 0:08 1× I was trying to do Cheifet's melt effect, but I couldn't get animated brushes to work in Scala . Still, I was happy to get even this level of control over genlock/ Scala interplay. "Execution" in the Main Menu means "execute a script." Three options are available: Workbench, CLI, and ARexx. For a feature that gets two pages in the manual with extra-wide margins, this is a big one, but I get why it only receives a brief mention. The only other recourse would be to include hundreds of pages of training material. "It exists. Have fun." is the basic thrust here. "Workbench" can launch anything reachable via the Workbench GUI, the same as double-clicking it. This is useful for having a script set up the working environment with helper apps, so an unpaid intern doesn't forget to open them. For ARexx stuff, programs must be running to receive commands, for example. "CLI" does the same thing as Workbench, except for AmigaDOS programs; programs that don't have a GUI front-end. Maybe open a terminal connection or monitor a system resource. "ARexx" of course runs ARexx scripts. For a program to accept ARexx commands, it must have an active REXX port open. Scala can send commands, and even its own variable data, to a target program to automate it in interesting ways. I saw an example of drawing images in a paint program entirely through ARexx scripting. Scala itself has an open REXX port, meaning its own tools can be controlled by other programs. In this way, data can flow between software, even from different makers, to form a little self-enclosed, automation ecosystem. One unusually powerful option is that Scala can export its own presentation script, which includes information for all pages, wipes, timings, sound cues, etc, as a self-contained ARexx script. Once in that format, it can be extended (in any text editor) with advanced ARexx commands and logic, perhaps to extract data from a database and build dynamic pages from that. Now it gets wild. That modified ARexx file can then be brought back into Scala as an "Execute" ARexx script on a page. Let me clarify this. A Scala script, which builds and runs an entire multi-page presentation, can itself be transformed into just another ARexx script assigned to a single page of a Scala project. One could imagine building a Scala front-end with a selection of buttons, each navigating on-click to a separate page which itself contains a complete, embedded presentation on a given topic. Scripts all the way down. There's one more scripting language Scala supports, and that's its own. Dubbed Scala Lingo (or is it Lingua?), when we save a presentation script we're saving in Lingo. It's human-readable and ARexx-friendly, which is what made it possible to save a presentation as an ARexx script in the previous section. Here's pure Lingo. This is a 320x200x16 (default palette) page, solid blue background with fade in. It displays one line of white text with anti-aliasing. The text slides in from the left, pauses 3 seconds, then slides out to the right. Here's the same page as an ARexx script. Looks like all we have to do is wrap each line of Lingo in single quotes, and add a little boilerplate. So, we have Scala on speaking terms with the Amiga and its applications, already a thing that could only be done on this particular platform at the time. Scala's choice of platform was further benefited by one of the Amiga's greatest strengths. That was thanks to the "villain" of the PaperClip article , Electronic Arts. The hardware and software landscape of the 70s and 80s was a real Wild West, anything goes, invent your own way, period of experimentation. Ideas could grow and bloom and wither on the vine multiple times over the course of a decade. Why, enough was going on a guy could devote an entire blog to it all. ahem While this was fun for the developers who had an opportunity to put their own stamp on the industry, for end-users it could create a bit of a logistical nightmare. Specifically, apps tended to be siloed, self-contained worlds which read and wrote their own private file types. Five different art programs? Five different file formats. Data migration was occasionally supported, as with VisiCalc's use of DIF (data interchange format) to store its documents. DIF was not a "standard" per se, but rather a set of guidelines for storing document data in ASCII format. Everyone using DIF could roll their own flavor and still call it DIF, like Lotus did in extending (but not diverging from) VisiCalc's original. Microsoft's DIF variant broke with everyone else, a fact we'll just let linger in the air like a fart for a moment. Let's really breathe it in, especially those of us on Windows 11 . More often than not, especially in the case of graphics and sound, DIF-like options were simply not available. Consider The Print Shop on the Apple 2. When its sequel, The New Print Shop , arrived it couldn't even open graphics from the immediately previous version of itself . A converter program was included to bring original Print Shop graphics into New Print Shop . On the C64, the Koala file format became semi-standard for images, simply by virtue of its popularity. Even so, there was a market for helping users move graphics across applications on the exact same hardware. While other systems struggled, programs like Deluxe Video on the Amiga were bringing in Deluxe Music and Deluxe Paint assets without fuss. A cynic will say, "Well yeah, those were all EA products so of course they worked together." That would be true in today's "silos are good, actually" regression of computing platforms into rent extractors. But, I will reiterate once more, there was genuinely a time when EA was good to its users. They didn't just treat developers as artists, they also empowered users in their creative pursuits. EA had had enough of the file format wars. They envisioned a brighter future and proposed an open file standard to achieve precisely that. According Dave Parkinson's article "A bit IFFy," in Amiga Computing Magazine , issue 7, "The origins of IFF are to be found in the (Apple) Macintosh's clipboard, and the file conventions which allow data to be cut and pasted between different Mac applications. The success of this led Electronic Arts to wonder — why not generalize this?" Why not, indeed! In 1985, working directly in conjunction with Commodore, the Electronic Arts Interchange File Format 1985 was introduced; IFF for short. It cannot be overstated how monumental it was to unlocking the Amiga's potential as a creative workhorse. From the Scala manual, "Unlike other computers, the Amiga has very standardized file formats for graphics and sound. This makes it easy to exchange data between different software packages. This is why you can grab a video image in one program, modify it in another, and display it in yet another." I know it's hard for younger readers to understand the excitement this created, except to simply say that everything in computing has its starting point. EA and the Amiga led the charge on this one. So, what is it? From "A Quick Introduction to IFF" by Jerry Morrison of Electronic Arts, "IFF is a 2-level standard. The first layer is the "wrapper" or “envelope” structure for all IFF files. Technically, it’s the syntax. The second layer defines particular IFF file types such as ILBM (standard raster pictures), ANIM (animation), SMUS (simple musical score), and 8SVX (8-bit sampled audio voice)." To assist in the explanation of the IFF file format, I built a Scala presentation just for you, taken from Amiga ROM Kernel Reference Manual . This probably would have been better built in Lingo, rather than trying to fiddle with the cumbersome editing tools and how they (don't) handle overlapping objects well. What's done is done. 0:00 / 0:07 1× I used the previously mentioned "link" wipe to move objects as groups. IFF is a thin wrapper around a series of data "chunks." It begins with a declaration of what type of IFF this particular file is, known as its "FORM." Above we see the ILBM "FORM," probably the most prevalent image format on the Amiga. Each chunk has its own label, describes how many bytes long it is, and is then followed by that many data bytes. That's really all there is to it. IDs for the FORM and the expected chunks are spec'd out in the registered definition document. Commodore wanted developers to always try to use a pre-existing IFF definition for data when possible. If there was no such definition, say for ultra-specialized data structures, then a new definition should be drawn up. "To prevent conflicts, new FORM identifications must be registered with Commodore before use," says Amiga ROM Kernel Reference Manual . In Morrison's write-up on IFF, he likened it to ASCII. When ASCII data is read into a program, it is sliced, diced, mangled, and whatever else needs to be done internally to make the program go. However, the data itself is on disk in a format unrelated to the program's needs. Morrison described a generic system for storing data, of whatever type, in a standardized way which separated data from software implementations. At its heart, IFF first declares what kind of data it holds (the FORM type), then that data is stored in a series of labelled chunks. The specification of how many chunks a given FORM needs, the proper labels for those chunks, the byte order for the raw data, and so on are all in the FORM's IFF definition document. In this way, anyone could write a simple IFF reader that follows the registered definition, et voila! Deluxe Paint animations are suddenly a valid media resource for Scala to consume. It can be confusing when hearing claims of "IFF compatibility" in magazines or amongst the Amiga faithful, but this does not mean that any random Amiga program can consume any random IFF file. The burden of supporting various FORMS still rests on each individual developer. FORM definitions which are almost identical, yet slightly different, were allowed. For example, the image FORM is "almost identical to " with differences in the chunk and the requirement of a new chunk called . "Almost identical" is not "identical" and so though both RGBN and ILBM are wrapped in standardized IFF envelopes, a program must explicitly support the ones of interest. Prevalent support for any given FORM type came out of a communal interest to make it standard. Cooperation was the unsung hero of the IFF format. "Two can do something better than one," has been on infinite loop in my mind since 1974. How evergreen is that XKCD comic about standards ? Obviously, given we're not using it these days, IFF wound up being one more format on the historical pile. We can find vestiges of its DNA here and there , but not the same ubiquity. There were moves to adopt IFF across other platforms. Tom Hudson, he of DEGAS Elite and CAD-3D , published a plea in the Fall 1986 issue of START Magazine for the Atari ST development crowd to adopt IFF for graphics files. He's the type to put up, not shut up, and so he also provided an IFF implementation on the cover disk, and detailed the format and things to watch out for. Though inspired by Apple originally, Apple seemed to believe IFF only had a place within a specific niche. AIFF, audio interchange file format, essentially standardized audio on the Mac, much like ILBM did for Amiga graphics. Despite being an IFF variant registered with Commodore, Scala doesn't recognize it in my tests. So, again, IFF itself wasn't a magical panacea for all file format woes. That fact was recognized even back in the 80s. In Amazing Computing , July 1987, in an article "Is IFF Really a Standard?" by John Foust, "Although the Amiga has a standard file format, it does not mean Babel has been avoided." He noted that programs can interpret IFF data incorrectly, resulting in distorted images, or outright failure. Ah well, nevertheless . Side note: One might reasonably believe TIFF to be a successful variant of IFF. Alas, TIFF shares "IFF" in name only and stands for "tagged image file format." One more side note: Microsoft also did to IFF what they did to DIF. fart noise The last major feature of note is Scala's extensibility. In the Main Menu list view, we have columns for various page controls. The options there can be expanded by including EX modules, programs which control external systems. This feels adjacent to HyperCard's XCMDs and XFCNs, which could extend HyperCard beyond its factory settings. EX modules bundled with Scala can control Sony Laserdisc controllers, enable MIDI file playback, control advanced Genlock hardware, and more. Once installed as a "Startup" item in Scala , these show up in the Main Menu and are as simple to control as any of Scala's built-in features. As an EX module, it is also Lingo scriptable so the opportunity to coordinate complex hardware interactions all through point-and-click is abundant. I turned on WinUAE's MIDI output and set it to "Microsoft GS Wave Table." In Amiga Workbench, I enabled the MIDI EX for Scala . On launch, Scala showed a MIDI option for my pages so I loaded up Bohemian-Rhapsody-1.mid . Mamma mia, it worked! I haven't found information about how to make new EXes, nor am I clear what EXes are available beyond Scala's own. However, here at the tail end of my investigation, Scala is suddenly doing things I didn't think it could do. The potential energy for this program is crazy high. No, I'm not going to be doing that any time soon, but boy do I see the appeal. Electronic Arts's documentation quoted Alan Kay for the philosophy behind the IFF standard, "Simple things should be simple, complex things should possible." Scala upholds this ideal beautifully. Making text animate is simple. Bringing in Deluxe Paint animations is simple. Adding buttons which highlight on hover and travel to arbitrary pages on click is simple. The pages someone would typically want to build, the bread-and-butter stuff, is simple. The complex stuff though, especially ARexx scripting, is not fooling around. I tried to script Scala to speak a phrase using the Amiga's built-in voice synthesizer and utterly failed. Jimmy Maher wrote of ARexx in The Future Was Here: The Commodore Amiga , "Like AmigaOS itself, it requires an informed, careful user to take it to its full potential, but that potential is remarkable indeed." While Scala didn't make me a video convert, it did retire within me the notion that the Toaster was the Alpha and Omega of the desktop video space. Interactivity, cross-application scripting, and genlock all come together into a program that feels boundless. In isolation, Scala a not a killer app. It becomes one when used as the central hub for a broader creative workflow. A paint program is transformed into a television graphics department. A basic sampler becomes a sound booth. A database and a little Lingo becomes an editing suite. Scala really proves the old Commodore advertising slogan correct, "Only Amiga Makes it Possible." 0:00 / 1:48 1× I'm accelerating the cycle of nostalgia. Now, we long for "four months ago." The more I worked with Scala , the more I wanted to see how close I could get to emulating video workflows of the day. Piece by piece over a few weeks I discovered the following (needs WinUAE , sorry) setup for using live Scala graphics with an untethered video source in a Discord stream. Scala can't do video switching*, so I'm locked to whatever video source happens to be genlocked to WinUAE at the moment. But since when were limitations a hindrance to creativity? * ARexx and EX are super-powerful and can extend Scala beyond its built-in limitations, but I don't see an obvious way to explore this within WinUAE. This is optional, depending on your needs, but its the fun part. You can use whatever webcam you have connected just as well. Camo Camera can stream mobile phone video to a desktop computer, wirelessly no less. Camo Camera on the desktop advertises your phone as a webcam to the desktop operating system. So, install that on both the mobile device and desktop, and connect them up. WinUAE can see the "default" Windows webcam, and only the default, as a genlock source; we can't select from a list of available inputs. It was tricky getting Windows 11 to ignore my webcam and treat Camo Camera as my default, but I got it to work. When you launch WinUAE , you should see your camera feed live in Workbench as the background. So far, so good. Next, in Scala > Settings turn on Genlock. You should now see your camera feed in Scala with Scala's UI overlaid. Now that we have Scala and our phone's video composited, switch over to OBS Studio . Set the OBS "Source" to "Window Capture" on WinUAE. Adjust the crop and scale to focus in on the portion of the video you're interested in broadcasting. On the right, under "Controls" click "Start Virtual Camera." Discord, Twitch , et al are able to see OBS as the camera input for streaming. When you can see the final output in your streaming service of choice (I used Discord 's camera test to preview), design the overlay graphics of your heart's desire. Use that to help position graphics so they won't be cut off due to Amiga/Discord aspect ratio differences. While streaming, interactivity with the live Scala presentation is possible. If you build the graphics and scripts just right, interesting real-time options are possible. Combine this with what we learned about buttons and F-Keys, and you could wipe to a custom screen like "Existential Crisis - Back in 5" with a keypress. 0:00 / 0:35 1× Headline transitions were manually triggered by the F-Keys, just to pay off the threat I made earlier in the post. See? I set'em up, I knock'em down. I also wrote a short piece about Cheifet , because of course I did. Ways to improve the experience, notable deficiencies, workarounds, and notes about incorporating the software into modern workflows (if possible). WinUAE v6.0.2 (2025.12.21) 64-bit on Windows 11 Emulating an NTSC Amiga 1200 2MB Chip RAM, 8MB Z2 Fast RAM AGA Chipset 68020 CPU, 24-bit addressing, no FPU, no MMU, cycle-exact emulation Kickstart/Workbench 3.1 (from Amiga Forever ) Windows directory mounted as HD0: For that extra analog spice, I set up the video Filter as per this article Scala Multimedia MM300 Cover disk version from CU Amiga Magazine , issue 96 (no copy protection) I didn't have luck running MM400 , nor could I find a MM400 manual Also using Deluxe Paint IV and TurboText Nothing to speak of. The "stock" Amiga 1200 setup worked great. I never felt the need to speed boost it, though I did give myself as much RAM as possible. I'll go ahead and recommend Deluxe Paint IV over III as a companion to Scala , because it supports the same resolutions and color depths. If you wind up with a copy of Scala that needs the hardware dongle, WinUAE emulates that as well. Under are the "red" (MM200) and "green" (MM300 and higher) variants I'm not aware of any other emulators that offer a Genlock option. I did not encounter any crashes of the application nor emulator. One time I had an "out of chip RAM" memory warning pop up in Scala . I was unclear what triggered it, as I had maxed out the chip RAM setting in WinUAE . Never saw it again after that. I did twice have a script become corrupted. Scripts are plain text and human-readable, so I was able to open it, see what was faulting, and delete the offending line. So, -6 points for corrupting my script; +2 points for keeping things simple enough that I could fix it on my own. F-Keys stopped working in Scala 's demonstration pages. Then, it started working again. I think there might have been an insidious script error that looked visually correct but was not. Deleting button variable settings and resetting them got it working again. This happened a few times. I saw some unusual drawing errors. Once was when a bar of color touched the bottom right edge of the visible portion of the screen, extra pixels were drawn into the overscan area. Another time, I had the phrase "Deluxe Paint" in Edit Mode, but when I viewed the page it only said "Deluxe Pa". Inspecting the text in "List" mode revealed unusual characters (the infinity symbol?!) had somehow been inserted into the middle of the text. I outlined one option above under "Bonus: Streaming Like Its 1993" above. OBS recording works quite well and is what I used for this post. WinUAE has recording options, but I didn't have a chance to explore them. I don't yet know how to export Scala animations into a Windows-playable format. For 2026, it would surely be nice to have native 16:9 aspect ratio support. Temporary script changes would be useful. I'd love to be able to turn off a page temporarily to better judge before/after flow. It can be difficult to visualize an entire project flow sometimes. With page transitions, object transitions, variable changes, logic flow, and more, understanding precisely what to do to create a desired effect can get a little confusing. Scala wants to maintain a super simple interface almost to its detriment. Having less pretty, more information dense, "advanced" interface options would be welcome. I suppose that's what building a script in pure ARexx is for. I'd like to be able to use DPaint animated brushes. Then I could make my own custom "transition" effects that mix with the Scala page elements. Maybe it's possible and I haven't figured out the correct methodology? The main thing I wanted was a Genlock switch, so I could do camera transitions easily. That's more of a WinUAE wishlist item though.

0 views
Nelson Figueroa 1 weeks ago

Using time.Sleep() in Go

You can use to pause your program for a predetermined amount of time, similar to most programming languages. First, you should know that the package has useful constants that allow you to conveniently specify time in units. They’re accessed with the notation. (e.g. ) You can use these constants with the function. For example, if we want to pause execution for 1 second, we can write the following code: We can do some multiplication with to pause a program for 30 seconds: You can do multiplication with all of the other constants mentioned as well. Here are some more examples using the other constants. Not really sure why you would use this, but just know it’s possible. My main motivation for writing this is that I think the official documentation is way too dense and doesn’t show several examples of . I just needed some examples to understand the syntax and move on with my day. https://pkg.go.dev/time

0 views

Education is not one-size-fits-all for the latest CTO Fellows

Today, I’m honored to introduce you to the latest cohort of Now Go Build CTO Fellows. Individuals from around the world with diverse backgrounds (engineering, the humanities, business, healthcare, entrepreneurship, law) that have found their calling at the intersection of technology and education.

0 views
Xe Iaso 2 weeks ago

Backfilling Discord forum channels with the power of terrible code

Hey all! We've got a Discord so you can chat with us about the wild world of object storage and get any help you need. We've also set up Answer Overflow so that you can browse the Q&A from the web. Today I'm going to discuss how we got there and solved one of the biggest problems with setting up a new community or forum: backfilling existing Q&A data so that the forum doesn't look sad and empty. All the code I wrote to do this is open source in our glue repo . The rest of this post is a dramatic retelling of the thought process and tradeoffs that were made as a part of implementing, testing, and deploying this pull request . Ready? Let's begin! There's a bunch of ways you can think about this problem, but given the current hype zeitgeist and contractual obligations we can frame this as a dataset management problem. Effectively we have a bunch of forum question/answer threads on another site, and we want to migrate the data over to a new home on Discord. This is the standard "square peg to round hole" problem you get with Extract, Transform, Load (ETL) pipelines and AI dataset management (mostly taking your raw data and tokenizing it so that AI models work properly). So let's think about this from an AI dataset perspective. Our pipeline has three distinct steps: When thinking about gathering and transforming datasets, it's helpful to start by thinking about the modality of the data you're working with. Our dataset is mostly forum posts, which is structured text. One part of the structure contains HTML rendered by the forum engine. This, the "does this solve my question" flag, and the user ID of the person that posted the reply are the things we care the most about. I made a bucket for this (in typical recovering former SRE fashion it's named for a completely different project) with snapshots enabled, and then got cracking. Tigris snapshots will let me recover prior state in case I don't like my transformations. When you are gathering data from one source in particular, one of the first things you need to do is ask permission from the administrator of that service. You don't know if your scraping could cause unexpected load leading to an outage. It's a classic tragedy of the commons problem that I have a lot of personal experience in preventing. When you reach out, let the administrators know the data you want to scrape and the expected load– a lot of the time, they can give you a data dump, and you don't even need to write your scraper. We got approval for this project, so we're good to go! To get a head start, I adapted an old package of mine to assemble User-Agent strings in such a way that gives administrators information about who is requesting data from their servers along with contact information in case something goes awry. Here's an example User-Agent string: This gives administrators the following information: This seems like a lot of information, but realistically it's not much more than the average Firefox install attaches to each request: The main difference is adding the workload hostname purely to help debugging a misbehaving workload. This is a concession that makes each workload less anonymous, however keep in mind that when you are actively scraping data you are being seen as a foreign influence. Conceding more data than you need to is just being nice at that point. One of the other "good internet citizen" things to do when doing benign scraping is try to reduce the amount of load you cause to the target server. In my case the forum engine is a Rails app (Discourse), which means there's a few properties of Rails that work to my advantage. Fun fact about Rails: if you append to the end of a URL, you typically get a JSON response based on the inputs to the view. For example, consider my profile on Lobsters at https://lobste.rs/~cadey . If you instead head to https://lobste.rs/~cadey.json , you get a JSON view of my profile information. This means that a lot of the process involved gathering a list of URLs with the thread indices we wanted, then constructing the thread URLs with slapped on the end to get machine-friendly JSON back. This made my life so much easier. Now that we have easy ways to get the data from the forum engine, the next step is to copy it out to Tigris directly after ingesting it. In order to do that I reused some code I made ages ago as a generic data storage layer kinda like Keyv in the node ecosystem . One of the storage backends was a generic object storage backend. I plugged Tigris into it and it worked on the first try. Good enough for me! Either way: this is the interface I used: By itself this isn't the most useful, however the real magic comes with my adaptor type . This uses Go generics to do type-safe operations on Tigris such that you have 90% of what you need for a database replacement. When you do any operations on a adaptor, the following happens: In the future I hope to extend this to include native facilities for forking, snapshots, and other nice to haves like an in-memory cache to avoid IOPs pressure, but for now this is fine. As the data was being read from the forum engine, it was saved into Tigris. All future lookups to that data I scraped happened from Tigris, meaning that the upstream server only had to serve the data I needed once instead of having to constantly re-load and re-reference it like the latest batch of abusive scrapers seem to do . So now I have all the data, I need to do some massaging to comply both with Discord's standards and with some arbitrary limitations we set on ourselves: In general, this means I needed to take the raw data from the forum engine and streamline it down to this Go type: In order to make this happen, I ended up using a simple AI agent to do the cleanup. It was prompted to do the following: I figured this should be good enough so I sent it to my local DGX Spark running GPT-OSS 120b via llama.cpp and manually looked at the output for a few randomly selected threads. The sample was legit, which is good enough for me. Once that was done I figured it would be better to switch from the locally hosted model to a model in a roughly equivalent weight class (gpt-5-mini). I assumed that the cloud model would be faster and slightly better in terms of its output. This test failed because I have somehow managed to write code that works great with llama.cpp on the Spark but results in errors using OpenAI's production models. I didn't totally understand what went wrong, but I didn't dig too deep because I knew that the local model would probably work well enough. It ended up taking about 10 minutes to chew through all the data, which was way better than I expected and continues to reaffirm my theory that GPT-OSS 120b is a good enough generic workhorse model, even if it's not the best at coding . From here things worked, I was able to ingest things and made a test Discord to try things out without potentially getting things indexed. I had my tool test-migrate a thread to the test Discord and got a working result. To be fair, this worked way better than expected (I added random name generation and as a result our CEO Ovais, became Mr. Quinn Price for that test), but it felt like one thing was missing: avatars. Having everyone in the migrated posts use the generic "no avatar set" avatar certainly would work, but I feel like it would look lazy. Then I remembered that I also have an image generation model running on the Spark: Z-Image Turbo . Just to try it out, I adapted a hacky bit of code I originally wrote on stream while I was learning to use voice coding tools to generate per-user avatars based on the internal user ID. This worked way better than I expected when I tested how it would look with each avatar attached to their own users. In order to serve the images, I stored them in the same Tigris bucket, but set ACLs on each object so that they were public, meaning that the private data stayed private, but anyone can view the objects that were explicitly marked public when they were added to Tigris. This let me mix and match the data so that I only had one bucket to worry about. This reduced a lot of cognitive load and I highly suggest that you repeat this pattern should you need this exact adaptor between this exact square peg and round hole combination. Now that everything was working in development, it was time to see how things would break in production! In order to give the façade that every post was made by a separate user, I used a trick that my friend who wrote Pluralkit (an accessibility tool for a certain kind of neurodivergence) uses: using Discord webhooks to introduce multiple pseudo-users into one channel. I had never combined forum channels with webhook pseudo-users like this before, but it turned out to be way easier than expected . All I had to do was add the right parameter when creating a new thread and the parameter when appending a new message to it. It was really neat and made it pretty easy to associate each thread ingressed from Discourse into its own Discord thread. Then all that was left was to run the Big Scary Command™ and see what broke. A couple messages were too long (which was easy to fix by simply manually rewriting them, doing the right state layer brain surgery, deleting things on Discord, and re-running the migration tool. However 99.9% of messages were correctly imported on the first try. I had to double check a few times including the bog-standard wakefulness tests. If you've never gone deep into lucid dreaming before, a wakefulness test is where you do something obviously impossible to confirm that it does not happen, such as trying to put your fingers through your palm. My fingers did not go through my palm. After having someone else confirm that I wasn't hallucinating more than usual I found out that my code did in fact work and as a result you can now search through the archives on community.tigrisdata.com or via the MCP server ! I consider that a massive success. As someone who has seen many truly helpful answers get forgotten in the endless scroll of chats, I wanted to build a way to get that help in front of users when they need it by making it searchable outside of Discord. Finding AnswerOverflow was pure luck: I happened to know someone who uses it for the support Discord for the Linux distribution I use on my ROG Ally, Bazzite . Thanks, j0rge! AnswerOverflow also has an MCP server so that your agents can hook into our knowledge base to get the best answers. To find out more about setting it up, take a look at the "MCP Server" button on the Tigris Community page . They've got instructions for most MCP clients on the market. Worst case, configure your client to access this URL: And bam, your agent has access to the wisdom of the ancients. But none of this is helpful without the actual answers. We were lucky enough to have existing Q&A in another forum to leverage. If you don't have the luxury, you can write your own FAQs and scenarios as a start. All I can say is, thank you to the folks who asked and answered these questions– we're happy to help, and know that you're helping other users by sharing. Connect with other developers, get help, and share your projects. Search our Q&A archives or ask a new question. Join the Discord . Extracting the raw data from the upstream source and caching it in Tigris. Transforming the cached data to make it easier to consume in Discord, storing that in Tigris again. Loading the transformed data into Discord so that people can see the threads in app and on the web with Answer Overflow . The name of the project associated with the requests (tigris-gtm-glue, where gtm means "go-to-market", which is the current in-vogue buzzword translation for whatever it is we do). The Go version, computer OS, and CPU architecture of the machine the program is running on so that administrator complaints can be easier isolated to individual machines. A contact URL for the workload, in our case it's just the Tigris home page. The name of the program doing the scraping so that we can isolate root causes down even further. Specifically it's the last path element of , which contains the path the kernel was passed to the executable. The hostname where the workload is being run in so that we can isolate down to an exact machine or Kubernetes pod. In my case it's the hostname of my work laptop. Key names get prefixed automatically. All data is encoded into JSON on write and decoded from JSON on read using the Go standard library. Type safety at the compiler level means the only way you can corrupt data is by having different "tables" share the same key prefix. Try not to do that! You can use Tigris bucket snapshots to help mitigate this risk in the worst case. Discord needs Markdown, the forum engine posts are all HTML. We want to remove personally-identifiable information from those posts just to keep things a bit more anonymous. Discord has a limit of 2048 characters per message and some posts will need to be summarized to fit within that window. Convert HTML to Markdown : Okay, I could have gotten away using a dedicated library for this like html2text , but I didn't think about that at the time. Remove mentions and names : Just strip them out or replace the mentions with generic placeholders ("someone I know", "a friend", "a colleague", etc.). Keep "useful" links : This was left intentionally vague and random sampling showed that it was good enough. Summarize long text : If the text is over 1000 characters, summarize it to less than 1000 characters.

0 views
Anton Zhiyanov 2 weeks ago

Interfaces and traits in C

Everyone likes interfaces in Go and traits in Rust. Polymorphism without class-based hierarchies or inheritance seems to be the sweet spot. What if we try to implement this in C? Interfaces in Go  • Traits in Rust  • Toy example  • Interface definition  • Interface data  • Method table  • Method table in implementor  • Type assertions  • Final thoughts An interface in Go is a convenient way to define a contract for some useful behavior. Take, for example, the honored : Anything that can read data into a byte slice provided by the caller is a . Quite handy, because the code doesn't need to care where the data comes from — whether it's memory, the file system, or the network. All that matters is that it can read the data into a slice: We can provide any kind of reader: Go's interfaces are structural, which is similar to duck typing. A type doesn't need to explicitly state that it implements ; it just needs to have a method: The Go compiler and runtime take care of the rest: A trait in Rust is also a way to define a contract for certain behavior. Here's the trait: Unlike in Go, a type must explicitly state that it implements a trait: The Rust compiler takes care of the rest: Either way, whether it's Go or Rust, the caller only cares about the contract (defined as an interface or trait), not the specific implementation. Let's make an even simpler version of — one without any error handling (Go): Usage example: Let's see how we can do this in C! The main building blocks in C are structs and functions, so let's use them. Our will be a struct with a single field called . This field will be a pointer to a function with the right signature: To make fully dynamic, let's turn it into a struct with a function pointer (I know, I know — just bear with me): Here's the "method" implementation: The is pretty obvious: And, finally, the function: See how easy it is to turn a into a : all we need is . Pretty cool, right? Not really. Actually, this implementation is seriously flawed in almost every way (except for the definition). Memory overhead . Each instance has its own function pointers (8 bytes per function on a 64-bit system) as "methods", which isn't practical even if there are only a few of them. Regular objects should store data, not functions. Layout dependency . Converting from to like only works if both structures have the same field as their first member. If we try to implement another interface: Everything will fall apart: and have different layouts, so type conversion in ⓧ is invalid and causes undefined behavior. Lack of type safety . Using a as the receiver in means the caller can pass any type, and the compiler won't even show a warning: C isn't a particularly type-safe language, but this is just too much. Let's try something else. A better way is to store a reference to the actual object in the interface: We could have the method in the interface take a instead of a , but that would make the implementation more complicated without any real benefits. So, I'll keep it as . Then will only have its own fields: We can make the method type-safe: To make this work, we add a method that returns the instance wrapped in a interface: The and functions remain quite simple: This approach is much better than the previous one: Since our type now knows about the interface (through the method), our implementation is more like a basic version of a Rust trait than a true Go interface. For simplicity, I'll keep using the term "interface". There is one downside, though: each instance has its own function pointer for every interface method. Since only has one method, this isn't an issue. But if an interface has a dozen methods and the program uses a lot of these interface instances, it can become a problem. Let's fix this. Let's extract interface methods into a separate strucute — the method table. The interface references its methods though the field: and don't change at all: The method initializes the static method table and assigns it to the interface instance: The only difference in is that it calls the method on the interface indirectly using the method table ( instead of ): stays the same: Now the instance always has a single pointer field for its methods. So even for large interfaces, it only uses 16 bytes ( + fields). This approach also keeps all the benefits from the previous version: We can even add a separate helper so the client doesn't have to worry about implementation detail: There's another approach I've seen out there. I don't like it, but it's still worth mentioning for completeness. Instead of embedding the method table in the interface, we can place it in the implementation ( ): We initialize the method table in the constructor: now takes a pointer: And converts to with a simple type cast: This keeps pretty lightweight, only adding one extra field. But the cast only works because is the first field in . If we try to implement a second interface, things will break — just like in the very first solution. I think the "method table in the interface" approach is much better. Go has an function that copies data from a source (a reader) to a destination (a writer): There's an interesting comment in its documentation: If implements , the copy is implemented by calling . Otherwise, if implements , the copy is implemented by calling . Here's what the function looks like: is a type assertion that checks if the reader is not just a , but also implements the interface. The Go runtime handles these kinds of dynamic type checks. Can we do something like this in C? I'd prefer not to make it fully dynamic, since trying to recreate parts of the Go runtime in C probably isn't a good idea. What we can do is add an optional method to the interface: Then we can easily check if a given is also a : Still, this feels a bit like a hack. I'd rather avoid using type assertions unless it's really necessary. Interfaces (traits, really) in C are possible, but they're not as simple or elegant as in Go or Rust. The method table approach we discussed is a good starting point. It's memory-efficient, as type-safe as possible given C's limitations, and supports polymorphic behavior. Here's the full source code if you are interested: The struct is lean and doesn't have any interface-related fields. The method takes a instead of a . The cast from to is handled inside the method. We can implement multiple interfaces if needed. Lightweight structure. Easy conversion from to . Supports multiple interfaces.

0 views

Model-Market Fit

In June 2007, Marc Andreessen published what became the defining essay on startup strategy. “The Only Thing That Matters” argued that of the three elements of a startup—team, product, and market— market matters most . A great market pulls the product out of the startup. The product doesn’t need to be great; it just has to basically work. Andreessen’s insight has guided a generation of founders. But nineteen years later, something has changed. A new variable has entered the equation. One that determines whether the market can pull anything at all. That variable is the model. For AI startups, there is a prerequisite layer beneath product-market fit: the degree to which current model capabilities can satisfy what a market demands . I call it Model-Market Fit, or MMF . When MMF exists, Andreessen’s framework applies perfectly. The market pulls the product out. When it doesn’t, no amount of brilliant UX, go-to-market strategy, or engineering can make customers adopt a product whose core AI task doesn’t solve their job to be done. The pattern is unmistakable once you see it. A model crosses a capability threshold. Within months, a vertical that had been dormant for years suddenly explodes with activity. For years, legal tech AI was stuck below scale. There were plenty of companies but none broke through. Document review tools that required more human oversight than they saved. Contract analysis that missed critical clauses. Every legal startup before 2023 struggled to cross $100M ARR. I remember this firsthand. I founded Doctrine in 2016, which grew to become the leading AI legal platform in Europe. But it was incredibly hard to raise money because all companies were sub-scale and the market wasn’t hot at all. Investors saw legal AI as a niche with limited upside. The market existed. Law firms desperately wanted automation. But the state-of-the-art models couldn’t handle the core tasks lawyers needed. BERT and similar transformer models excelled at classification like sorting documents, identifying contract types, flagging potential issues. But legal work requires generation and reasoning: drafting memos that synthesize complex case law, summarizing depositions while preserving nuanced arguments, generating discovery requests tailored to specific fact patterns. Traditional ML could categorize a contract as “employment” or “NDA,” but it couldn’t write a coherent brief explaining why a non-compete clause was unenforceable under California law. Then GPT-4 arrived in March 2023. Within eighteen months, Silicon Valley startups raised over hundreds of millions. Doctrine’s business is on fire. Thomson Reuters acquired Casetext for $650 million. Dozens of legal AI startups emerged. The legal AI market minted more unicorns in 12 months than in the previous 10 years combined. The market hadn’t changed. The model capability threshold had been crossed. Similarly, coding assistants existed before Sonnet. GitHub Copilot had millions of users. But there’s a difference between autocomplete that occasionally helps and an AI that genuinely understands your codebase and creates high-quality code for you. I experienced this firsthand. I tried Cursor early on, before Sonnet. It was meh. I installed it, tested it for a few days, deleted it. Did the same thing again a month later. Same result… interesting demo, not a workflow. Then Claude 3.5 Sonnet dropped! Within a week, I couldn’t work without Cursor. Neither could anyone on my team. The product became the workflow. We weren’t “using an AI assistant,” we were pair programming with something that understood our entire codebase. Cursor’s growth went vertical. Not because they shipped some brilliant new feature. Because the underlying model crossed the threshold that made their product actually work . They got Model Market Fit. The most important thing is MMF. The startups that won weren’t necessarily first, but they were prepared when the model capability threshold was finally crossed. So far in coding or legal, none of the incumbents won. It was always new players. Today’s leading legal startups had spent months understanding exactly how lawyers work like what output formats they need, what compliance requirements exist, how associates actually research cases. The race doesn’t go to the first mover. It goes to the first to product-market fit after model-market fit exists. The corollary is equally important: when MMF doesn’t exist, the market cannot pull. The demand is there. The willingness to pay is there. But the core task doesn’t work. Let’s review some examples. Mathematicians would love an AI that could prove novel theorems. The market is real, research institutions, defense contractors, and tech companies would pay millions for genuine mathematical reasoning. But even the most advanced models can’t do it consistently. They can verify known proofs. They can assist with mechanical steps. They can occasionally produce insights on bounded problems. But originating novel proofs on open problems? The capability threshold remains uncrossed. GPT-5, o1, o3... each generation improves incrementally, but we’re not at the point where you can feed an AI an open conjecture and expect a rigorous proof. Yet. Investment banks and hedge funds desperately want AI that can perform comprehensive financial analysis. The market is massive; a single successful trade or M&A deal can generate hundreds of millions in fees. But AI remains surprisingly bad at the core tasks that matter most. Excel output is still unreliable when dealing with complex financial models. More critically, AI struggles to combine quantitative analysis with qualitative insights from 200-page documents... exactly what analysts spend their days doing. A human analyst reads through earnings calls, regulatory filings, and industry reports, then synthesizes that qualitative intelligence with spreadsheet models to make investment recommendations. AI can handle pieces of this workflow, but the end-to-end reasoning that justifies million-dollar positions? The capability gap is wide today. This will obviously change soon. But for now, the human remains in the loop not as oversight, but as the primary decision-maker. The difference between verticals with MMF and those without is stark. Compare two benchmarks from Vals.ai: LegalBench (legal reasoning tasks): Top models hit 87% accuracy . Gemini 3 Pro leads at 87.04%, with multiple models clustered above 85%. This is production-grade performance. Accurate enough that lawyers can trust the output with light review. Finance Agent (core financial analyst tasks): Top models hit 56.55% accuracy . Even GPT-5.1, the current leader, barely crosses the halfway mark. Claude Sonnet 4.5 with extended thinking sits at 55.32%. That’s a 30-point gap. Legal has MMF. Finance doesn’t. The benchmarks reveal what intuition suggests: models have crossed the threshold for legal reasoning but remain fundamentally unreliable for financial analysis. You can ship a legal AI product today. A finance AI product that does the actual job of an analyst? Very soon but not now. The pharmaceutical industry has invested billions in AI-driven drug discovery. The market is enormous because a single successful drug is worth tens of billions. Yet the breakthroughs remain elusive. AI can accelerate certain steps: identifying candidate molecules, predicting protein structures (AlphaFold was transformative here), optimizing clinical trial design. But the end-to-end autonomous discovery that would justify the valuations? It doesn’t exist. The human remains in the loop not because the workflow is designed that way, but because the AI can’t actually do the job. There’s a reliable signal for missing MMF: examine how “human-in-the-loop” is positioned . When MMF exists, human-in-the-loop is a feature. It maintains quality, builds trust, handles edge cases. The AI does the work; the human provides oversight. When MMF doesn’t exist, human-in-the-loop is a crutch. It hides the fact that the AI can’t perform the core task. The human isn’t augmenting, they’re compensating. Strip away the human, and the product doesn’t work. The test is simple: if all human correction were removed from this workflow, would customers still pay? If the answer is no, there’s no MMF. There’s only a demo. This creates a brutal strategic dilemma. Do you build for current MMF or anticipated MMF? If MMF doesn’t exist today, building a startup around it means betting on model improvements that are on someone else’s roadmap. You don’t control when or whether the capability arrives. You’re burning runway while Anthropic and OpenAI decide your fate. Worse, you might be wrong about what capability is needed. Models might scale differently than you expect. The 80% to 99% accuracy gap that your vertical requires might be five years away, or it might never close in the way you imagined. Of course, if you believe in Artificial General Intelligence, then you know that models will eventually be able to do pretty much anything. But “eventually” is doing a lot of work in that sentence. The question isn’t whether AI will solve the problem; it’s when, and whether your startup survives long enough to see it (which is a function of your runway). But there’s a counterargument often shared at Ycombinator, and it’s compelling. When MMF unlocks, you need more than just model capability. You need: - Domain-specific data pipelines - Regulatory relationships - Customer trust built over years - Deep workflow integration - Understanding of how professionals actually work Legal startups didn’t just plug in GPT-4. They had already built the scaffolding. When the model arrived, they were ready to run. There’s also the question of influence. The teams closest to the problem shape how models get evaluated, fine-tuned, and deployed. They’re not passively waiting for capability; they’re defining what capability means in their vertical. The question isn’t whether to be early. It’s how early, and what you’re building while you wait. The dangerous zone is the middle: MMF that’s 24 to 36 months away. Close enough to seem imminent. Far enough to burn through multiple funding rounds waiting. This is where conviction and runway become everything. If you’re betting on MMF that’s 2+ years out, you better be in a gigantic market worth the wait. Consider healthcare and financial services. These markets are so massive that even Anthropic and OpenAI are going all-in despite very mixed current results. The potential upside justifies positioning early, even if the models aren’t quite there yet. When you’re targeting trillion dollar markets, the risk-reward calculation changes entirely. The math is simple: expected value = probability of MMF arriving × market size × your likely share . Product-market fit has famously resisted precise measurement. Andreessen described it qualitatively: “ You can always feel when product/market fit isn’t happening... And you can always feel product/market fit when it’s happening. ” MMF is similarly intuitive, but we can be more specific. Can the model, given the same inputs a human expert receives, produce output that a customer would pay for without significant human correction? This test has three components: 1. Same inputs : The model gets what the human would get—documents, data, context. No magical preprocessing that a real workflow couldn’t provide. 2. Output a customer would pay for : Not a demo. Not a proof of concept. Production-quality work that solves a real problem. 3. Without significant human correction : The human might review, refine, or approve. But if they’re rewriting 50% of the output, the model isn’t doing the job. In unregulated verticals, 80% accuracy might be enough. An AI that writes decent first drafts of marketing copy creates value even if humans edit heavily. In regulated verticals—finance, legal, healthcare—80% accuracy is often useless. A contract review tool that misses 20% of critical clauses isn’t augmenting lawyers; it’s creating liability. A medical diagnostic that’s wrong one time in five isn’t a product; it’s a lawsuit haha! The gap between 80% and 99% accuracy is often infinite in practice. It’s the difference between “promising demo” and “production system.” Many AI startups are stuck in this gap, raising money on demos while waiting for the capability that would make their product actually work. There’s a second capability frontier that most discussions of MMF miss: the ability to work autonomously over extended periods . Current MMF examples (legal document review, coding assistance) are fundamentally short-horizon tasks today. Prompt in, output out, maybe a few tool calls. The model does something useful in seconds or minutes. But the highest-value knowledge work isn’t like that. A financial analyst doesn’t answer one question; they spend days building a model, stress-testing assumptions, and synthesizing information across dozens of sources. A strategy consultant doesn’t produce a single slide; they iterate through weeks of research, interviews, and analysis. A drug discovery researcher doesn’t run one experiment; they design and execute campaigns spanning months. These workflows require something models can’t yet do reliably: sustained autonomous operation . The agentic threshold isn’t just “can the model use tools.” It’s: - Persistence : Can it maintain goals and context across hours or days? - Recovery : Can it recognize failures, diagnose problems, and try alternative approaches? - Coordination : Can it break complex objectives into subtasks and execute them in sequence? - Judgment : Can it know when to proceed versus when to stop and ask for guidance? Today’s agents can handle tasks measured in minutes. Tomorrow’s need to handle tasks measured in days. That’s not an incremental improvement—it’s a phase change in capability. This is why finance doesn’t have MMF despite models being “good at reading documents.” Reading a 10-K is a 30-second task. Building an investment thesis is a multi-day workflow requiring the agent to gather data, build models, test scenarios, and synthesize conclusions—all while maintaining coherent reasoning across the entire process. The next wave of MMF unlocks will come from smarter models AND models that can work for days on the same task. Andreessen’s core insight was that market matters more than team or product because a great market pulls the product out of the startup. The market creates the gravitational force. The AI corollary: model capability is the prerequisite for that gravitational pull to begin . No market, however large and hungry, can pull a product that doesn’t work. And in AI, “doesn’t work” is determined by the model, not by your engineering or design. You can build the most beautiful interface, the most elegant workflow, the most sophisticated data pipeline… and if the underlying model can’t perform the core task, none of it matters. MMF → PMF → Success. Skip the first step, and the second becomes impossible. This is both constraint and opportunity. For founders, it means being ruthlessly honest about where capability actually is versus where you hope it will be. For investors, it means evaluating not just market size and team quality, but the gap between current model capability and what the market requires. And for everyone building in AI: the question isn’t just whether the market wants what you’re building. It’s whether the models can deliver it. That’s the only thing that matters.

1 views
Uros Popovic 3 weeks ago

Writing your first compiler

Build your first compiler with minimal, high-level, modern code. With only a few files of Go and C code, we can set up a workflow that dynamically fetches everything needed, including the LLVM library itself, and builds a portable compiler. This is a modern stack, it's reproducible, and you do not need to read dozens of pages to get started.

0 views
Armin Ronacher 4 weeks ago

Porting MiniJinja to Go With an Agent

Turns out you can just port things now. I already attempted this experiment in the summer, but it turned out to be a bit too much for what I had time for. However, things have advanced since. Yesterday I ported MiniJinja (a Rust Jinja2 template engine) to native Go, and I used an agent to do pretty much all of the work. In fact, I barely did anything beyond giving some high-level guidance on how I thought it could be accomplished. In total I probably spent around 45 minutes actively with it. It worked for around 3 hours while I was watching, then another 7 hours alone. This post is a recollection of what happened and what I learned from it. All prompting was done by voice using pi , starting with Opus 4.5 and switching to GPT-5.2 Codex for the long tail of test fixing. MiniJinja is a re-implementation of Jinja2 for Rust. I originally wrote it because I wanted to do a infrastructure automation project in Rust and Jinja was popular for that. The original project didn’t go anywhere, but MiniJinja itself continued being useful for both me and other users. The way MiniJinja is tested is with snapshot tests: inputs and expected outputs, using insta to verify they match. These snapshot tests were what I wanted to use to validate the Go port. My initial prompt asked the agent to figure out how to validate the port. Through that conversation, the agent and I aligned on a path: reuse the existing Rust snapshot tests and port incrementally (lexer -> parser -> runtime). This meant the agent built Go-side tooling to: This resulted in a pretty good harness with a tight feedback loop. The agent had a clear goal (make everything pass) and a progression (lexer -> parser -> runtime). The tight feedback loop mattered particularly at the end where it was about getting details right. Every missing behavior had one or more failing snapshots. I used Pi’s branching feature to structure the session into phases. I rewound back to earlier parts of the session and used the branch switch feature to inform the agent automatically what it had already done. This is similar to compaction, but Pi shows me what it puts into the context. When Pi switches branches it does two things: Without switching branches, I would probably just make new sessions and have more plan files lying around or use something like Amp’s handoff feature which also allows the agent to consult earlier conversations if it needs more information. What was interesting is that the agent went from literal porting to behavioral porting quite quickly. I didn’t steer it away from this as long as the behavior aligned. I let it do this for a few reasons. First, the code base isn’t that large, so I felt I could make adjustments at the end if needed. Letting the agent continue with what was already working felt like the right strategy. Second, it was aligning to idiomatic Go much better this way. For instance, on the runtime it implemented a tree-walking interpreter (not a bytecode interpreter like Rust) and it decided to use Go’s reflection for the value type. I didn’t tell it to do either of these things, but they made more sense than replicating my Rust interpreter design, which was partly motivated by not having a garbage collector or runtime type information. On the other hand, the agent made some changes while making tests pass that I disagreed with. It completely gave up on all the “must fail” tests because the error messages were impossible to replicate perfectly given the runtime differences. So I had to steer it towards fuzzy matching instead. It also wanted to regress behavior I wanted to retain (e.g., exact HTML escaping semantics, or that must return an iterator). I think if I hadn’t steered it there, it might not have made it to completion without going down problematic paths, or I would have lost confidence in the result. Once the major semantic mismatches were fixed, the remaining work was filling in all missing pieces: missing filters and test functions, loop extras, macros, call blocks, etc. Since I wanted to go to bed, I switched to Codex 5.2 and queued up a few “continue making all tests pass if they are not passing yet” prompts, then let it work through compaction. I felt confident enough that the agent could make the rest of the tests pass without guidance once it had the basics covered. This phase ran without supervision overnight. After functional convergence, I asked the agent to document internal functions and reorganize (like moving filters to a separate file). I also asked it to document all functions and filters like in the Rust code base. This was also when I set up CI, release processes, and talked through what was created to come up with some finalizing touches before merging. There are a few things I find interesting here. First: these types of ports are possible now. I know porting was already possible for many months, but it required much more attention. This changes some dynamics. I feel less like technology choices are constrained by ecosystem lock-in. Sure, porting NumPy to Go would be a more involved undertaking, and getting it competitive even more so (years of optimizations in there). But still, it feels like many more libraries can be used now. Second: for me, the value is shifting from the code to the tests and documentation. A good test suite might actually be worth more than the code. That said, this isn’t an argument for keeping tests secret — generating tests with good coverage is also getting easier. However, for keeping code bases in different languages in sync, you need to agree on shared tests, otherwise divergence is inevitable. Lastly, there’s the social dynamic. Once, having people port your code to other languages was something to take pride in. It was a sign of accomplishment — a project was “cool enough” that someone put time into making it available elsewhere. With agents, it doesn’t invoke the same feelings. Will McGugan also called out this change . Lastly, some boring stats for the main session: This did not count the adding of doc strings and smaller fixups. Pi session transcript Narrated video of the porting session Parse Rust’s test input files (which embed settings as JSON headers). Parse the reference insta snapshots and compare output. Maintain a skip-list to temporarily opt out of failing tests. It stays in the same session so I can navigate around, but it makes a new branch off an earlier message. When switching, it adds a summary of what it did as a priming message into where it branched off. I found this quite helpful to avoid the agent doing vision quests from scratch to figure out how far it had already gotten. Agent run duration: 10 hours ( 3 hours supervised) Active human time: ~45 minutes Total messages: 2,698 My prompts: 34 Tool calls: 1,386 Raw API token cost: $60 Total tokens: 2.2 million Models: and for the unattended overnight run

0 views
Simon Willison 1 months ago

My answers to the questions I posed about porting open source code with LLMs

Last month I wrote about porting JustHTML from Python to JavaScript using Codex CLI and GPT-5.2 in a few hours while also buying a Christmas tree and watching Knives Out 3. I ended that post with a series of open questions about the ethics and legality of this style of work. Alexander Petros on lobste.rs just challenged me to answer them , which is fair enough! Here's my attempt at that. You can read the original post for background, but the short version is that it's now possible to point a coding agent at some other open source project and effectively tell it "port this to language X and make sure the tests still pass" and have it do exactly that. Here are the questions I posed along with my answers based on my current thinking. Extra context is that I've since tried variations on a similar theme a few more times using Claude Code and Opus 4.5 and found it to be astonishingly effective. I decided that the right thing to do here was to keep the open source license and copyright statement from the Python library author and treat what I had built as a derivative work, which is the entire point of open source. After sitting on this for a while I've come down on yes, provided full credit is given and the license is carefully considered. Open source allows and encourages further derivative works! I never got upset at some university student forking one of my projects on GitHub and hacking in a new feature that they used. I don't think this is materially different, although a port to another language entirely does feel like a slightly different shape. Now this one is complicated! It definitely hurts some projects because there are open source maintainers out there who say things like "I'm not going to release any open source code any more because I don't want it used for training" - I expect some of those would be equally angered by LLM-driven derived works as well. I don't know how serious this problem is - I've seen angry comments from anonymous usernames, but do they represent genuine open source contributions or are they just angry anonymous usernames? If we assume this is real, does the loss of those individuals get balanced out by the increase in individuals who CAN contribute to open source because they can now get work done in a few hours that might previously have taken them a few days that they didn't have to spare? I'll be brutally honest about that question: I think that if "they might train on my code / build a derived version with an LLM" is enough to drive you away from open source, your open source values are distinct enough from mine that I'm not ready to invest significantly in keeping you. I'll put that effort into welcoming the newcomers instead. The much bigger concern for me is the impact of generative AI on demand for open source. The recent Tailwind story is a visible example of this - while Tailwind blamed LLMs for reduced traffic to their documentation resulting in fewer conversions to their paid component library, I'm suspicious that the reduced demand there is because LLMs make building good-enough versions of those components for free easy enough that people do that instead. I've found myself affected by this for open source dependencies too. The other day I wanted to parse a cron expression in some Go code. Usually I'd go looking for an existing library for cron expression parsing - but this time I hardly thought about that for a second before prompting one (complete with extensive tests) into existence instead. I expect that this is going to quite radically impact the shape of the open source library world over the next few years. Is that "harmful to open source"? It may well be. I'm hoping that whatever new shape comes out of this has its own merits, but I don't know what those would be. I'm not a lawyer so I don't feel credible to comment on this one. My loose hunch is that I'm still putting enough creative control in through the way I direct the models for that to count as enough human intervention, at least under US law, but I have no idea. I've come down on "yes" here, again because I never thought it was irresponsible for some random university student to slap an Apache license on some bad code they just coughed up on GitHub. What's important here is making it very clear to potential users what they should expect from that software. I've started publishing my AI-generated and not 100% reviewed libraries as alphas, which I'm tentatively thinking of as "alpha slop" . I'll take the alpha label off once I've used them in production to the point that I'm willing to stake my reputation on them being decent implementations, and I'll ship a 1.0 version when I'm confident that they are a solid bet for other people to depend on. I think that's the responsible way to handle this. That one was a deliberately provocative question, because for a new HTML5 parsing library that passes 9,200 tests you would need a very good reason to hire an expert team for two months (at a cost of hundreds of thousands of dollars) to write such a thing. And honestly, thanks to the existing conformance suites this kind of library is simple enough that you may find their results weren't notably better than the one written by the coding agent. You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options .

1 views
ava's blog 1 months ago

a week without caffeine

I've recently decided to stop drinking anything caffeinated for a month. My top offender has been matcha, which has a surprisingly high caffeine content depending on the kind and the amount you consume. Second is other green tea, and black tea. Third is the occasional coffee I get elsewhere, or flavored cubes to dissolve in water that contain caffeine. I don't consume soda or energy drinks, and I don't have coffee or a coffee machine at home. This comes after at least half a year of intentional use, or sometimes abuse, of it. I made no secret on this blog that 2025 felt like two years in one , and I achieved a lot - but I also used caffeine to push my limits in ways that weren't good. I wanted to feel normal and not limited by my illnesses, poor sleep, or anything really. So instead of just drinking maybe once a day for the flavor, I started drinking for the effect, too, making the teas stronger. So it slowly became more cups a day, and consumed later in the day. I often used it to be able to make it through 2-4 hours of university Zoom sessions or a workout, or to try and fight the fatigue from my illnesses, or to make up for a night of bad sleep due to pain, or staying up late with friends playing games until past midnight. I've just been feeling really burnt out in the last month of 2025, and I thought about what I could do to change that. I chose to take younger me’s advice: Years ago, I barely drank black tea every couple months, matcha maybe once a week or less, and no coffee at all. I only drank for the taste. I didn’t even know the stuff I was drinking had any significant caffeine, and I made it mild enough. I never wanted to become a person that relies on caffeine or uses it to push across limits . I looked at people who felt burnt out and thought: “ Yeah, if I used substances to quiet my body telling me it needs rest or food or whatever constantly for months or years, I would feel burnt out too. I would at least lay off the caffeine and heed the signals to help myself get out of that. ” Now I’m the one feeling burnt out, and I did use caffeinated drinks to push myself further than I should have! Younger me was right, and by using my favorite little comfort drinks that way, I just borrowed from the future every time. That’s energy that will be missing the next few days, weeks, months… unless I do the same again, but maybe with more caffeine this time. But I don’t wanna dig myself that hole. Shortly after NYE, I managed to drink two very huge cups of matcha, one big cup of strong black tea, a normal-sized cup of coffee, and then some more matcha again at a friend's house to keep me awake for a board game evening late into the night, and it was horrible. I didn't wanna treat myself this way. So going forward, I will need to respect my limits. I don’t further wanna normalize ignoring my needs like this for productivity. If it protects my well-being long-term, I will just study 2 hours less, or I can’t study that day at all, or arrive at work an hour later, or can’t participate in a game night until 2am. Of course it will suck sometimes, because abusing it intentionally made me feel more capable and enabled a truly busy year; but I’ll just have to accept it. In the end, I’m not opposed to resorting to it for important stuff (a deadline, complex work...), but not constantly. A week has now passed ( 23 days left to go ) of consuming no caffeine (and not even decaf, because that still contains some), and I wanted to give a first update on my experience! Headaches starting past 3pm and went on until I had to sleep; took an aspirin to help it. Had a bit of nausea too, felt brainfogged, but calm. The headaches feel like my exercise headaches, which makes sense, considering both a lack of caffeine and intense exercise expand the blood vessels in the brain. I didn’t think I would be affected like this, and I should probably have tapered instead of going cold turkey. First day back in the office after the holidays. I notice a bit of a headache again past 8am, but they went away after 10am or so; didn’t even notice exactly when it got better. I feel a difference in how I focus and work. Caffeinated drinks immediately create more energy and a drive to work on something big/demanding for me, but if the work is not enough to fulfill that desire (mundane, repetitive and small), I’d struggle to get myself started or work on it uninterrupted. I’d take more frequent breaks to check stuff on my phone, I’d play music or YouTube videos to keep that eager part of my brain busy enough to get the boring work done. It seems like the caffeine boost made me more dopamine-seeking. I was craving anything that would fully utilize me mentally and then searching for a replacement when that didn’t happen. Now without the caffeine, there is no intense energy spike or crash, no frantic seeking of more intense work that would make me a bit anxious, and no search for something that soothes and distracts me from that sensation. Instead, I was able to continuously work without much breaks, distraction or distress for hours. It was easier for me to focus, to get into the zone, in a sort of flow state, even without music or videos. While I am still bored of my current repetitive work, I felt better equipped to deal with that, as I had no strong urge for a challenge inside of me that’d make me uncomfortable if I couldn’t find one. My focus and motivation felt more sustainable and persistent, instead of coming in short, intense bursts. I felt happy for no specific reason during my lunch walk, which was a nice change from the overwhelm and feeling of being hunted that I got so used to. Had some intense headaches again in the late evening; I think I am more sensitive to very bright screen light, because it always starts when I boot up Hello Kitty Island Adventure on the TV, and I’m currently in a very bright area. Very brief headache this morning in the tram that didn’t come back, not even in the evening. I may have put the worst behind me. I notice I am less sensitive to my environment; the glaring lights, the tram sounds and people. I’m still a bit sensitive in general aside from caffeine or not, but it doesn’t feel heightened. I sit there present, aware, no noise cancelling, and feel… content. At work, I feel like I have more… time? To arrive, to slowly get started in my own pace, and as said in the previous day, keep a comfortable momentum. I’m not suddenly extremely “on”, feeling rushed by the caffeine buzz. I like this. I also feel like I’m more comfortable with switching tasks than I’ve been the last few months. I’m also more comfortable with rest and intentional boredom. I felt very very tired close before 10am, but made a great recovery somehow that kept me going until 10pm without feeling exhausted or fatigued in between. The caffeine withdrawal headaches and light sensitivity seem to be gone for good. What remains is craving the reward, the treat; those were my comfort drinks, irrespective of their caffeine content, but that maybe that also played a role chemically. I miss it for a sort of mental relief. I notice effects on my hunger! It feels more controlled, and less urgent. There is less food noise in my head. It could be that increased stress and anxiety that were exacerbated by caffeine raised cortisol and made me hungrier, or smaller/skipped meals via caffeine lowering appetite makes the hunger return with a vengeance later. Or I seek to comfort and soothe myself after becoming frustrated of not finding mentally stimulating work while on caffeine, and I crave food for that. I underestimated how much it really affected my mood and anxiety. Everything feels calmer and more manageable now, and I no longer feel like I am constantly drowning. Rest feels truly restful. I blamed it on some challenges and problems in my life, but I guess a lot of it really was the caffeine, and I didn't notice how truly bad the baseline anxiety had gotten. On the first day, I even said to people that it doesn't make me anxious. I guess it did, though. What still remains is the need for reward I talked about, and seeking comfort, knowing it would brighten up my day a little. I want to work on some non-work things that are a bit demanding, each in different ways (a secret blog project, job applications, studying for my exams in March, translating for GDPRhub...) and it would be great right now to borrow a bit of drive and alertness on what feels like the click of a button. I wanna rip myself from the afternoon drowsiness, but I have to do it "on my own" right now. I really have to make sure to drink enough without my go-to choices. It's getting harder to do so when I can't drink the stuff I love or even crave! I have to be more intentional about drinking enough, when it hasn't been a problem before. For when I continue in 23 days! Reply via email Published 11 Jan, 2026 I will reserve caffeinated drinks for when it really matters (harder, more complex and important tasks; not just because, and not to keep up with people). If the task is not later in the day, it’s preferable to not consume any caffeine after noon. I will make/order the drinks to have a lot less caffeine. I will keep in mind that appetite suppressed or lowered by caffeine means more ferocious hunger comes later, so I have to feed myself well regardless.

0 views
Dan Moore! 1 months ago

MVP = embarrassing

This is a re-post of an article I wrote in 2019 for the Gocode Colorado blog which is no longer available. Thank you Wayback Machine ! “So, we need this feature to work, and it has to tie into this API, and we should put it all on the blockchain.” “What about feature X? And we need admin screens, and roles and groups for different kinds of users.” “You’re right! Let’s add those to the list. We need to make something we’re proud of.” I heard some version of this conversation over and over again at my last Go Code Colorado mentoring session. And I sympathize with the sentiment, I really do. But instead of hitting it out of the park the goal should be to create a piece of software that achieves the bare minimum, or a minimum viable product (MVP). With a high-risk venture team members should aim to show features to the end user as soon as possible, and to let their interactions guide the future of development. It’s far too easy to get distracted by all the possibilities and build features that won’t be used. Even worse, developers may compare their application to other applications they use and find it wanting. The level of polish an MVP needs is far lower than a production application like Gmail, but because you use production ready apps every day, their UX and polish can feel like a requirement. Building features or adding UX polish can delay shipping an MVP. You want to wait until you are “ready”. You are never “ready”, my friend. If you’re not embarrassed by the first version of your product, you’ve launched too late. – Reid Hoffman , LinkedIn Founder Keep your focus not on the software but on the user.  Spend time talking to your target market and putting either mockups or working code in front of them as frequently as you can. Finding these people is another blog post entirely, but hopefully, you have some idea who they are and where they hang out. It can be scary to put what you’ve built in front of people. It’s often much easier to sit back and build new features than it is to ship. I have felt it myself–as a startup co-founder, I built a web app that I was, frankly, embarrassed to show potential customers. It was missing features I considered crucial, was full of holes and bugs, and didn’t have a consistent user interface. But showing it to potential customers early and often was the best choice. They pointed out missing features and also explained what was unnecessary. We got great feedback and I was better able to understand the types of problems the customer faced. There are many ways you can show potential users what you are planning to build or are building without having a fully finished product. When building an MVP, use tools you know. Whenever I’m working on a project, I balance technical risk and business risk. If you’re building a true MVP, the business risk is very high, because you don’t know if the market actually exists. Therefore, minimize the technical risk by building with what you know. But wait, aren’t customers expecting a polished application? Some may. Early adopters who are looking to have a problem solved often can look past the rough edges and see the potential. It also depends on the domain and the competition. For instance, if you are starting an Instagram competitor aimed at consumers, the quality bar will be pretty high. If you are building a scheduling tool for tattoo parlors and your main competition is a spreadsheet, a web application built with any modern framework will likely wow your potential customers. It’s also important to show your customers that the application is continuing to improve–that will make them more forgiving of the inevitable issues. You’d be surprised by how forgiving people can be, especially if you are building something to help them do their job better. Remember, if you aren’t a little bit embarrassed when you show someone your application, you should have shown it to them sooner. Additional resources: You can show them mockups, either a paper draft, a series of powerpoint screens or a clickable prototype (Adobe XD or Balsam IQ are solutions). This is the cheapest way to get feedback because changing a screen in powerpoint is far easier than changing a screen in code. Enroll potential customers in a beta program. Customers are more forgiving if they know this isn’t the final product, and they’ll give you suggestions. Don’t take each suggestion as truth, but do try to find out what problem the suggestion is aiming to solve–that’s gold. Offer people in your beta program a discount when you start charging–that gives them the incentive to give good feedback and can seed your customer base. Build out mock features. Instead of building a full file upload facility, I have added a screen to an app with a link to “email us to add a file”, and fulfilled it manually. If enough people mailed a file, we’d know we needed to build the feature. Have someone walk through the application in a video chat (using GoToMeeting or Zoom). Similar to a beta, people are more forgiving when they are shown something and you will be able to see where issues arise (“how do I do task A?”). This experience can be humbling and frustrating at the same time, like watching this . Lean Startup Talk @google Wikipedia on MVPs

28 views
Jim Nielsen 1 months ago

The AI Security Shakedown

Matthias Ott shared a link to a post from Anthropic titled “Disrupting the first reported AI-orchestrated cyber espionage campaign” , which I read because I’m interested in the messy intersection of AI and security. I gotta say: I don’t know if I’ve ever read anything quite like this article. At first, the article felt like a responsible disclosure — “Hey, we’re reaching an inflection point where AI models are being used effectively for security exploits. Look at this one.” But then I read further and found statements like this: [In the attack] Claude didn’t always work perfectly. It occasionally hallucinated […] This remains an obstacle to fully autonomous cyberattacks. Wait, so is that a feature or a bug? Is it a good thing that your tool hallucinated and proved a stumbling block? Or is this bug you hope to fix? The more I read, the more difficult it became to discern whether this security incident was a helpful warning or a feature sell. With the correct setup, threat actors can now use agentic AI systems for extended periods to do the work of entire teams of experienced hackers: analyzing target systems, producing exploit code, and scanning vast datasets of stolen information more efficiently than any human operator. Less experienced and resourced groups can now potentially perform large-scale attacks of this nature. Shoot, this sounds like a product pitch! Don’t have the experience or resources to keep up with your competitors who are cyberattacking? We’ve got a tool for you! Wait, so if you’re creating something that can cause so much havoc, why are you still making it? Oh good, they address this exact question: This raises an important question: if AI models can be misused for cyberattacks at this scale, why continue to develop and release them? The answer is that the very abilities that allow Claude to be used in these attacks also make it crucial for cyber defense. Ok, so the article is a product pitch: But that’s my words. Here’s theirs: A fundamental change has occurred in cybersecurity. We advise security teams to experiment with applying AI for defense in areas like Security Operations Center automation, threat detection, vulnerability assessment, and incident response. We also advise developers to continue to invest in safeguards across their AI platforms, to prevent adversarial misuse. The techniques described above will doubtless be used by many more attackers—which makes industry threat sharing, improved detection methods, and stronger safety controls all the more critical. It appears AI is simultaneously the problem and the solution. It’s a great business to be in, if you think about it. You sell a tool for security exploits and you sell the self-same tool for protection against said exploits. Everybody wins! I can’t help but read this post and think of a mafia shakedown. You know, where the mafia implies threats to get people to pay for their protection — a service they created the need for in the first place. ”Nice system you got there, would be a shame if anyone hacked into it using AI. Better get some AI to protect yourself.” I find it funny that the URL slug for the article is: That’s a missed opportunity. They could’ve named it: Reply via: Email · Mastodon · Bluesky We’ve reached a tipping point in security. Look at this recent case where our AI was exploited to do malicious things with little human intervention. No doubt this same thing will happen again. You better go get our AI to protect yourself.

0 views
James Stanley 1 months ago

A parametric mannequin for FreeCAD

I am toying with the idea of building a car. Check out Cyclekarts , they are a small and simple go-kart-like vehicle styled like ~1920s sports cars. Most importantly, check out Geoff May's "Maybug" , which is road legal! How cool is that? But I don't want to build a cyclekart as such, they are a bit too small and underpowered. Geoff's cyclekart is road legal under the "heavy quadricycle" classification, which is way easier than full car spec, the main requirements being to stay under 450kg (under 250kg if you don't want seatbelts), and under 20hp, and then you get regulated more like a motorbike instead of a car. Cyclekart engines are generally under 10hp so I would find something better. And cyclekarts are normally single-seaters and ideally I would like to have room for 2. Anyway, I wanted to mess about in FreeCAD and see what sort of size and layout would work, and I found that I didn't have a good idea of how big it would need to be to fit people inside it. So I have made a mannequin for FreeCAD. Get it on github . It is based on the "average male" dimensions from this diagram that I found online. You can change the dimensions of the body using the "S" Spreadsheet in the model, and reposition the limbs by selecting one of the LinkGroups in the tree (they are named with "Joint" suffix) and rotating using the Transform tool. You will want to use at least FreeCAD 1.1 otherwise the Transform tool rotates about the centroid instead of the origin. Here is my mannequin contorted to fit on the toy tractor: If you want to use it, I recommend save a local copy of mannequin.FCStd , edit the dimensions to suit your body if required, and then copy and paste him into whatever projects you want mannequins in. There are other FreeCAD mannequins available, in particular Mannequin_mp from the FreeCAD library. But I didn't manage to find one that can have the joints posed without laboriously having to relocate everything downstream of that joint so that it stays connected.

0 views
Filippo Valsorda 1 months ago

go.sum Is Not a Lockfile

I need everyone to stop looking at , especially to analyze dependency graphs. It is not a “lockfile,” and it has zero semantic effects on version resolution. There is truly no use case for ever parsing it outside of cmd/go. is only a local cache for the Go Checksum Database . It’s a map of module versions to their cryptographic hashes. Those versions may or may not be in use; it doesn’t matter to package resolution. was not even enabled by default in the original modules design, precisely because it has no observable effect on builds! 1 Its (important) purpose is exclusively tightening the security story: the Checksum Database ensures the whole ecosystem shares the same contents for a given module version, regardless of how it is downloaded, and makes that guarantee local and self-contained. Instead, just look at . It lists the precise version at which all dependencies are built. Since Go 1.17 (released August 2021), it includes all transitive dependencies needed to build the main module and its tests. 2 You can either parse with golang.org/x/mod/modfile , run to get its JSON representation, 3 or parse it according to its specification . This is the end of the Public Service Announcement. Read on for some nerdery. The enduring confusion around and is due to the fact that most other languages also have two package-related files, but theirs both matter to version resolution. These two files are usually called manifest and lockfile. The manifest (e.g. , , ) usually lists some dependencies along with potentially complex rules for which versions are supported. These rules usually apply transitively to dependents, making version resolution extremely hard and/or slow in the general case, and sometimes unsolvable. The manifest is not always guaranteed to list all direct dependencies, and no automated mechanism ensures your code actually works with e.g. the minimum allowed manifest version of its dependencies. The lockfile (e.g. , , ) is a relatively recent innovation in some ecosystems, and it lists the actual versions used in the most recent build. It is not really human-readable, and usually doesn’t apply recursively to dependents, allowing the rapid spread of supply-chain attacks . I honestly find the manifest version ranges essentially useless, and get endlessly confused trying to remember which commands modify the lockfile (and when/why) and which ones respect it. In Go, serves as both manifest and lockfile, and more: it lists all dependencies, direct and transitive, and their exact version to be used when the module is the main module. Semantic versioning is assumed, and those versions are also the minimum versions applied to dependents’ module graphs. Different major versions of the same module are considered essentially separate modules. Notice how there is no way to accidentally use a feature introduced in a version that your dependents won’t have. Also, when adding a dependency, you don’t automatically get the latest—potentially untested/compromised—version of all its dependencies. Finally, there can’t be diamond dependency conflicts. All that with a single, human-readable file: . All commands take a flag. If set to , missing dependencies can be added to automatically if necessary, and partial manual changes are reconciled. If set to , those are errors. and (effectively) default to ; all other commands default to . Go modules truly don’t get enough credit for how much simpler they are compared to the alternatives. In other ecosystems, package resolution time going down below 1s is celebrated (and is indeed an impressive technical achievement given the design’s requirements!). In Go, no one ever noticed package resolution happening, so there is nothing to celebrate. For more ecosystem feature appreciation posts, follow me on Bluesky at @filippo.abyssdomain.expert or on Mastodon at @[email protected] . I had a great time at 39c3 during the holidays. The Chaos Communication Congress is a magical place with a very strict photo policy, so it’s pretty hard to convey its atmosphere. This is the best I could do without recognizable humans in the frame. In Fairy Dust we trust! My work is made possible by Geomys , an organization of professional Go maintainers, which is funded by Smallstep , Ava Labs , Teleport , Tailscale , and Sentry . Through our retainer contracts, they ensure the sustainability and reliability of our open source maintenance work and get a direct line to my expertise and that of the other Geomys maintainers. (Learn more in the Geomys announcement .) Here are a few words from some of them! Teleport — For the past five years, attacks and compromises have been shifting from traditional malware and security breaches to identifying and compromising valid user accounts and credentials with social engineering, credential theft, or phishing. Teleport Identity is designed to eliminate weak access patterns through access monitoring, minimize attack surface with access requests, and purge unused permissions via mandatory access reviews. Ava Labs — We at Ava Labs , maintainer of AvalancheGo (the most widely used client for interacting with the Avalanche Network ), believe the sustainable maintenance and development of open source cryptographic protocols is critical to the broad adoption of blockchain technology. We are proud to support this necessary and impactful work through our ongoing sponsorship of Filippo and his team. I still think it’s important and it was the first thing I remember advocating for when I joined the Go team, because it makes the module cryptographically self-contained, and because the Go Checksum Database transparency story is not great in ephemeral environments like CI. These are security effects, though, not semantic ones.  ↩ These are the only dependencies you care about, even for security. If the main module imports and a separate imports , there is no way for to affect the build or run code on the developer’s machine, so you don’t need to consider it a dependency. This is actually very powerful, allowing libraries to segregate dependencies (e.g. the AWS SDK) in optional packages, reducing the transitive trust tree of dependents that don’t use that feature.  ↩ ↩ Why not , you ask? Because that prints the whole module graph, which includes modules that don’t contribute to the build 2 and are not included in . A closer approximation would be , but this command applies the local build constraints, like GOOS/GOARCH. There is an open proposal for a flag to do -like resolution in .  ↩ I still think it’s important and it was the first thing I remember advocating for when I joined the Go team, because it makes the module cryptographically self-contained, and because the Go Checksum Database transparency story is not great in ephemeral environments like CI. These are security effects, though, not semantic ones.  ↩ These are the only dependencies you care about, even for security. If the main module imports and a separate imports , there is no way for to affect the build or run code on the developer’s machine, so you don’t need to consider it a dependency. This is actually very powerful, allowing libraries to segregate dependencies (e.g. the AWS SDK) in optional packages, reducing the transitive trust tree of dependents that don’t use that feature.  ↩ ↩ Why not , you ask? Because that prints the whole module graph, which includes modules that don’t contribute to the build 2 and are not included in . A closer approximation would be , but this command applies the local build constraints, like GOOS/GOARCH. There is an open proposal for a flag to do -like resolution in .  ↩

0 views
Manuel Moreale 1 months ago

Yearly reminder to use RSS

The year is 2026, and RSS is still, by far, the best way to keep up with sites on the web. If you already know what RSS is but you’re not currently using it, consider this a reminder for you to dust off that RSS reader of yours and put it back to use. And don’t listen to the party-poopers that claim that RSS is dead. It is not. If instead you don’t know what RSS is, here’s a very brief explanation: RSS is a technology that allows you to create your own personal feed, using an RSS reader app, where content from different sources is aggregated and displayed—usually—in reverse chronological order. The same way you use a browser to access my site, you can use an RSS reader app to access the RSS feeds available on my website. Keep in mind that not all sites have RSS feeds available. It used to be the norm, but then the web got enshittified. I wrote a longer post about RSS years ago , but the web is full of resources if you want to get into RSS. And you should, because RSS is awesome. So go get an RSS reader app , stop being spoon-fed slop by algorithmic platforms, and start consuming content at your own pace. Thank you for keeping RSS alive. You're awesome. Email me :: Sign my guestbook :: Support for 1$/month :: See my generous supporters :: Subscribe to People and Blogs

0 views
Anton Zhiyanov 1 months ago

Go 1.26 interactive tour

Go 1.26 is coming out in February, so it's a good time to explore what's new. The official release notes are pretty dry, so I prepared an interactive version with lots of examples showing what has changed and what the new behavior is. Read on and see! new(expr)  • Type-safe error checking  • Green Tea GC  • Faster cgo and syscalls  • Faster memory allocation  • Vectorized operations  • Secret mode  • Reader-less cryptography  • Goroutine leak profile  • Goroutine metrics  • Reflective iterators  • Peek into a buffer  • Process handle  • Signal as cause  • Compare IP subnets  • Context-aware dialing  • Fake example.com  • Optimized fmt.Errorf  • Optimized io.ReadAll  • Multiple log handlers  • Test artifacts  • Modernized go fix  • Final thoughts This article is based on the official release notes from The Go Authors and the Go source code, licensed under the BSD-3-Clause license. This is not an exhaustive list; see the official release notes for that. I provide links to the documentation (𝗗), proposals (𝗣), commits (𝗖𝗟), and authors (𝗔) for the features described. Check them out for motivation, usage, and implementation details. I also have dedicated guides (𝗚) for some of the features. Error handling is often skipped to keep things simple. Don't do this in production ツ Previously, you could only use the built-in with types: Now you can also use it with expressions: If the argument is an expression of type T, then allocates a variable of type T, initializes it to the value of , and returns its address, a value of type . This feature is especially helpful if you use pointer fields in a struct to represent optional values that you marshal to JSON or Protobuf: You can use with composite values: And function calls: Passing is still not allowed: 𝗗 spec • 𝗣 45624 • 𝗖𝗟 704935 , 704737 , 704955 , 705157 • 𝗔 Alan Donovan The new function is a generic version of : It's type-safe and easier to use: is especially handy when checking for multiple types of errors. It makes the code shorter and keeps error variables scoped to their blocks: Another issue with is that it uses reflection and can cause runtime panics if used incorrectly (like if you pass a non-pointer or a type that doesn't implement ): doesn't cause a runtime panic; it gives a clear compile-time error instead: doesn't use , executes faster, and allocates less than : Since can handle everything that does, it's a recommended drop-in replacement for new code. 𝗗 errors.AsType • 𝗣 51945 • 𝗖𝗟 707235 • 𝗔 Julien Cretel The new garbage collector (first introduced as experimental in 1.25) is designed to make memory management more efficient on modern computers with many CPU cores. Go's traditional garbage collector algorithm operates on graph, treating objects as nodes and pointers as edges, without considering their physical location in memory. The scanner jumps between distant memory locations, causing frequent cache misses. As a result, the CPU spends too much time waiting for data to arrive from memory. More than 35% of the time spent scanning memory is wasted just stalling while waiting for memory accesses. As computers get more CPU cores, this problem gets even worse. Green Tea shifts the focus from being processor-centered to being memory-aware. Instead of scanning individual objects, it scans memory in contiguous 8 KiB blocks called spans . The algorithm focuses on small objects (up to 512 bytes) because they are the most common and hardest to scan efficiently. Each span is divided into equal slots based on its assigned size class , and it only contains objects of that size class. For example, if a span is assigned to the 32-byte size class, the whole block is split into 32-byte slots, and objects are placed directly into these slots, each starting at the beginning of its slot. Because of this fixed layout, the garbage collector can easily find an object's metadata using simple address arithmetic, without checking the size of each object it finds. When the algorithm finds an object that needs to be scanned, it marks the object's location in its span but doesn't scan it immediately. Instead, it waits until there are several objects in the same span that need scanning. Then, when the garbage collector processes that span, it scans multiple objects at once. This is much faster than going over the same area of memory multiple times. To make better use of CPU cores, GC workers share the workload by stealing tasks from each other. Each worker has its own local queue of spans to scan, and if a worker is idle, it can grab tasks from the queues of other busy workers. This decentralized approach removes the need for a central global list, prevents delays, and reduces contention between CPU cores. Green Tea uses vectorized CPU instructions (only on amd64 architectures) to process memory spans in bulk when there are enough objects. Benchmark results vary, but the Go team expects a 10–40% reduction in garbage collection overhead in real-world programs that rely heavily on the garbage collector. Plus, with vectorized implementation, an extra 10% reduction in GC overhead when running on CPUs like Intel Ice Lake or AMD Zen 4 and newer. Unfortunately, I couldn't find any public benchmark results from the Go team for the latest version of Green Tea, and I wasn't able to create a good synthetic benchmark myself. So, no details this time :( The new garbage collector is enabled by default. To use the old garbage collector, set at build time (this option is expected to be removed in Go 1.27). 𝗣 73581 • 𝗔 Michael Knyszek In the Go runtime, a processor (often referred to as a P) is a resource required to run the code. For a thread (a machine or M) to execute a goroutine (G), it must first acquire a processor. Processors move through different states. They can be (executing code), (waiting for work), or (paused because of the garbage collection). Previously, processors had a state called used when a goroutine is making a system or cgo call. Now, this state has been removed. Instead of using a separate processor state, the system now checks the status of the goroutine assigned to the processor to see if it's involved in a system call. This reduces internal runtime overhead and simplifies code paths for cgo and syscalls. The Go release notes say -30% in cgo runtime overhead, and the commit mentions an 18% sec/op improvement: I decided to run the CgoCall benchmarks locally as well: Either way, both a 20% and a 30% improvement are pretty impressive. And here are the results from a local syscall benchmark: That's pretty good too. 𝗖𝗟 646198 • 𝗔 Michael Knyszek The Go runtime now has specialized versions of its memory allocation function for small objects (from 1 to 512 bytes). It uses jump tables to quickly choose the right function for each size, instead of relying on a single general-purpose implementation. The Go release notes say "the compiler will now generate calls to size-specialized memory allocation routines". But based on the code, that's not completely accurate: the compiler still emits calls to the general-purpose function. Then, at runtime, dispatches those calls to the new specialized allocation functions. This change reduces the cost of small object memory allocations by up to 30%. The Go team expects the overall improvement to be ~1% in real allocation-heavy programs. I couldn't find any existing benchmarks, so I came up with my own. And indeed, running it on Go 1.25 compared to 1.26 shows a significant improvement: The new implementation is enabled by default. You can disable it by setting at build time (this option is expected to be removed in Go 1.27). 𝗖𝗟 665835 • 𝗔 Michael Matloob The new package provides access to architecture-specific vectorized operations (SIMD — single instruction, multiple data). This is a low-level package that exposes hardware-specific functionality. It currently only supports amd64 platforms. Because different CPU architectures have very different SIMD operations, it's hard to create a single portable API that works for all of them. So the Go team decided to start with a low-level, architecture-specific API first, giving "power users" immediate access to SIMD features on the most common server platform — amd64. The package defines vector types as structs, like (a 128-bit SIMD vector with sixteen 8-bit integers) and (a 512-bit SIMD vector with eight 64-bit floats). These match the hardware's vector registers. The package supports vectors that are 128, 256, or 512 bits wide. Most operations are defined as methods on vector types. They usually map directly to hardware instructions with zero overhead. To give you a taste, here's a custom function that uses SIMD instructions to add 32-bit float vectors: Let's try it on two vectors: Common operations in the package include: The package uses only AVX instructions, not SSE. Here's a simple benchmark for adding two vectors (both the "plain" and SIMD versions use pre-allocated slices): The package is experimental and can be enabled by setting at build time. 𝗗 simd/archsimd • 𝗣 73787 • 𝗖𝗟 701915 , 712880 , 729900 , 732020 • 𝗔 Junyang Shao , Sean Liao , Tom Thorogood Cryptographic protocols like WireGuard or TLS have a property called "forward secrecy". This means that even if an attacker gains access to long-term secrets (like a private key in TLS), they shouldn't be able to decrypt past communication sessions. To make this work, ephemeral keys (temporary keys used to negotiate the session) need to be erased from memory immediately after the handshake. If there's no reliable way to clear this memory, these keys could stay there indefinitely. An attacker who finds them later could re-derive the session key and decrypt past traffic, breaking forward secrecy. In Go, the runtime manages memory, and it doesn't guarantee when or how memory is cleared. Sensitive data might remain in heap allocations or stack frames, potentially exposed in core dumps or through memory attacks. Developers often have to use unreliable "hacks" with reflection to try to zero out internal buffers in cryptographic libraries. Even so, some data might still stay in memory where the developer can't reach or control it. The Go team's solution to this problem is the new package. It lets you run a function in secret mode . After the function finishes, it immediately erases (zeroes out) the registers and stack it used. Heap allocations made by the function are erased as soon as the garbage collector decides they are no longer reachable. This helps make sure sensitive information doesn't stay in memory longer than needed, lowering the risk of attackers getting to it. Here's an example that shows how might be used in a more or less realistic setting. Let's say you want to generate a session key while keeping the ephemeral private key and shared secret safe: Here, the ephemeral private key and the raw shared secret are effectively "toxic waste" — they are necessary to create the final session key, but dangerous to keep around. If these values stay in the heap and an attacker later gets access to the application's memory (for example, via a core dump or a vulnerability like Heartbleed), they could use these intermediates to re-derive the session key and decrypt past conversations. By wrapping the calculation in , we make sure that as soon as the session key is created, the "ingredients" used to make it are permanently destroyed. This means that even if the server is compromised in the future, this specific past session can't be exposed, which ensures forward secrecy. The current implementation only supports Linux (amd64 and arm64). On unsupported platforms, invokes the function directly. Also, trying to start a goroutine within the function causes a panic (this will be fixed in Go 1.27). The package is mainly for developers who work on cryptographic libraries. Most apps should use higher-level libraries that use behind the scenes. The package is experimental and can be enabled by setting at build time. 𝗗 runtime/secret • 𝗣 21865 • 𝗖𝗟 704615 • 𝗔 Daniel Morsing Current cryptographic APIs, like or , often accept an as the source of random data: These APIs don't commit to a specific way of using random bytes from the reader. Any change to underlying cryptographic algorithms can change the sequence or amount of bytes read. Because of this, if the application code (mistakenly) relies on a specific implementation in Go version X, it might fail or behave differently in version X+1. The Go team chose a pretty bold solution to this problem. Now, most crypto APIs will just ignore the random parameter and always use the system random source ( ). The change applies to the following subpackages: still uses the random reader if provided. But if is nil, it uses an internal secure source of random bytes instead of (which could be overridden). To support deterministic testing, there's a new package with a single function. It sets a global, deterministic cryptographic randomness source for the duration of the given test: affects and all implicit sources of cryptographic randomness in the packages: To temporarily restore the old reader-respecting behavior, set (this option will be removed in a future release). 𝗗 testing/cryptotest • 𝗣 70942 • 𝗖𝗟 724480 • 𝗔 Filippo Valsorda , qiulaidongfeng A leak occurs when one or more goroutines are indefinitely blocked on synchronization primitives like channels, while other goroutines continue running and the program as a whole keeps functioning. Here's a simple example: If we call and don't read from the output channel, the inner goroutine will stay blocked trying to send to the channel for the rest of the program: Unlike deadlocks, leaks do not cause panics, so they are much harder to spot. Also, unlike data races, Go's tooling did not address them for a long time. Things started to change in Go 1.24 with the introduction of the package. Not many people talk about it, but is a great tool for catching leaks during testing. Go 1.26 adds a new experimental profile designed to report leaked goroutines in production. Here's how we can use it in the example above: As you can see, we have a nice goroutine stack trace that shows exactly where the leak happens. The profile finds leaks by using the garbage collector's marking phase to check which blocked goroutines are still connected to active code. It starts with runnable goroutines, marks all sync objects they can reach, and keeps adding any blocked goroutines waiting on those objects. When it can't add any more, any blocked goroutines left are waiting on resources that can't be reached — so they're considered leaked. Here's the gist of it: For even more details, see the paper by Saioc et al. If you want to see how (and ) can catch typical leaks that often happen in production — check out my article on goroutine leaks . The profile is experimental and can be enabled by setting at build time. Enabling the experiment also makes the profile available as a net/http/pprof endpoint, . According to the authors, the implementation is already production-ready. It's only marked as experimental so they can get feedback on the API, especially about making it a new profile. 𝗗 runtime/pprof • 𝗚 Detecting leaks • 𝗣 74609 , 75280 • 𝗖𝗟 688335 • 𝗔 Vlad Saioc New metrics in the package give better insight into goroutine scheduling: Here's the full list: Per-state goroutine metrics can be linked to common production issues. For example, an increasing waiting count can show a lock contention problem. A high not-in-go count means goroutines are stuck in syscalls or cgo. A growing runnable backlog suggests the CPUs can't keep up with demand. You can read the new metric values using the regular function: The per-state numbers (not-in-go + runnable + running + waiting) are not guaranteed to add up to the live goroutine count ( , available since Go 1.16). All new metrics use counters. 𝗗 runtime/metrics • 𝗣 15490 • 𝗖𝗟 690397 , 690398 , 690399 • 𝗔 Michael Knyszek The new and methods in the package return iterators for a type's fields and methods: The new methods and return iterators for the input and output parameters of a function type: The new methods and return iterators for a value's fields and methods. Each iteration yields both the type information ( or ) and the value: Previously, you could get all this information by using a for-range loop with methods (which is what iterators do internally): Using an iterator is more concise. I hope it justifies the increased API surface. 𝗗 reflect • 𝗣 66631 • 𝗖𝗟 707356 • 𝗔 Quentin Quaadgras The new method in the package returns the next N bytes from the buffer without advancing it: If returns fewer than N bytes, it also returns : The slice returned by points to the buffer's content and stays valid until the buffer is changed. So, if you change the slice right away, it will affect future reads: The slice returned by is only valid until the next call to a read or write method. 𝗗 Buffer.Peek • 𝗣 73794 • 𝗖𝗟 674415 • 𝗔 Ilia Choly After you start a process in Go, you can access its ID: Internally, the type uses a process handle instead of the PID (which is just an integer), if the operating system supports it. Specifically, in Linux it uses pidfd , which is a file descriptor that refers to a process. Using the handle instead of the PID makes sure that methods always work with the same OS process, and not a different process that just happens to have the same ID. Previously, you couldn't access the process handle. Now you can, thanks to the new method: calls a specified function and passes a process handle as an argument: The handle is guaranteed to refer to the process until the callback function returns, even if the process has already terminated. That's why it's implemented as a callback instead of a field or method. is only supported on Linux 5.4+ and Windows. On other operating systems, it doesn't execute the callback and returns an error. 𝗗 Process.WithHandle • 𝗣 70352 • 𝗖𝗟 699615 • 𝗔 Kir Kolyshkin returns a context that gets canceled when any of the specified signals is received. Previously, the canceled context only showed the standard "context canceled" cause: Now the context's cause shows exactly which signal was received: The returned type, , is based on , so it doesn't provide the actual value — just its string representation. 𝗗 signal.NotifyContext • 𝗖𝗟 721700 • 𝗔 Filippo Valsorda An IP address prefix represents an IP subnet. These prefixes are usually written in CIDR notation: In Go, an IP prefix is represented by the type. The new method lets you compare two IP prefixes, making it easy to sort them without having to write your own comparison code: orders two prefixes as follows: This follows the same order as Python's and the standard IANA (Internet Assigned Numbers Authority) convention. 𝗗 Prefix.Compare • 𝗣 61642 • 𝗖𝗟 700355 • 𝗔 database64128 The package has top-level functions for connecting to an address using different networks (protocols) — , , , and . They were made before was introduced, so they don't support cancellation: There's also a type with a general-purpose method. It supports cancellation and can be used to connect to any of the known networks: However, a bit less efficient than network-specific functions like — because of the extra overhead from address resolution and network type dispatching. So, network-specific functions in the package are more efficient, but they don't support cancellation. The type supports cancellation, but it's less efficient. The Go team decided to resolve this contradiction. The new context-aware methods ( , , , and ) combine the efficiency of the existing network-specific functions with the cancellation capabilities of : I wouldn't say that having three different ways to dial is very convenient, but that's the price of backward compatibility. 𝗗 net.Dialer • 𝗣 49097 • 𝗖𝗟 490975 • 𝗔 Michael Fraenkel The default certificate already lists in its DNSNames (a list of hostnames or domain names that the certificate is authorized to secure). Because of this, doesn't trust responses from the real : To fix this issue, the HTTP client returned by now redirects requests for and its subdomains to the test server: 𝗗 Server.Client • 𝗖𝗟 666855 • 𝗔 Sean Liao People often point out that using for plain strings causes more memory allocations than . Because of this, some suggest switching code from to when formatting isn't needed. The Go team disagrees. Here's a quote from Russ Cox: Using is completely fine, especially in a program where all the errors are constructed with . Having to mentally switch between two functions based on the argument is unnecessary noise. With the new Go release, this debate should finally be settled. For unformatted strings, now allocates less and generally matches the allocations for . Specifically, goes from 2 allocations to 0 allocations for a non-escaping error, and from 2 allocations to 1 allocation for an escaping error: This matches the allocations for in both cases. The difference in CPU cost is also much smaller now. Previously, it was ~64ns vs. ~21ns for vs. for escaping errors, now it's ~25ns vs. ~21ns. Here are the "before and after" benchmarks for the change. The non-escaping case is called , and the escaping case is called . If there's just a plain error string, it's . If the error includes formatting, it's . Seconds per operation: Bytes per operation: Allocations per operation: If you're interested in the details, I highly recommend reading the CL — it's perfectly written. 𝗗 fmt.Errorf • 𝗖𝗟 708836 • 𝗔 thepudds Previously, allocated a lot of intermediate memory as it grew its result slice to the size of the input data. Now, it uses intermediate slices of exponentially growing size, and then copies them into a final perfectly-sized slice at the end. The new implementation is about twice as fast and uses roughly half the memory for a 65KiB input; it's even more efficient with larger inputs. Here are the geomean results comparing the old and new versions for different input sizes: See the full benchmark results in the commit. Unfortunately, the author didn't provide the benchmark source code. Ensuring the final slice is minimally sized is also quite helpful. The slice might persist for a long time, and the unused capacity in a backing array (as in the old version) would just waste memory. As with the optimization, I recommend reading the CL — it's very good. Both changes come from thepudds , whose change descriptions are every reviewer's dream come true. 𝗗 io.ReadAll • 𝗖𝗟 722500 • 𝗔 thepudds The package, introduced in version 1.21, offers a reliable, production-ready logging solution. Since its release, many projects have switched from third-party logging packages to use it. However, it was missing one key feature: the ability to send log records to multiple handlers, such as stdout or a log file. The new type solves this problem. It implements the standard interface and calls all the handlers you set up. For example, we can create a log handler that writes to stdout: And another handler that writes to a file: Finally, combine them using a : I'm also printing the file contents here to show the results. When the receives a log record, it sends it to each enabled handler one by one. If any handler returns an error, doesn't stop; instead, it combines all the errors using : The method reports whether any of the configured handlers is enabled: Other methods — and — call the corresponding methods on each of the enabled handlers. 𝗗 slog.MultiHandler • 𝗣 65954 • 𝗖𝗟 692237 • 𝗔 Jes Cok Test artifacts are files created by tests or benchmarks, such as execution logs, memory dumps, or analysis reports. They are important for debugging failures in remote environments (like CI), where developers can't step through the code manually. Previously, the Go test framework and tools didn't support test artifacts. Now they do. The new methods , , and return a directory where you can write test output files: If you use with , this directory will be inside the output directory (specified by , or the current directory by default): As you can see, the first time is called, it writes the directory location to the test log, which is quite handy. If you don't use , artifacts are stored in a temporary directory which is deleted after the test completes. Each test or subtest within each package has its own unique artifact directory. Subtest outputs are not stored inside the parent test's output directory — all artifact directories for a given package are created at the same level: The artifact directory path normally looks like this: But if this path can't be safely converted into a local file path (which, for some reason, always happens on my machine), the path will simply be: (which is what happens in the examples above) Repeated calls to in the same test or subtest return the same directory. 𝗗 T.ArtifactDir • 𝗣 71287 • 𝗖𝗟 696399 • 𝗔 Damien Neil Over the years, the command became a sad, neglected bag of rewrites for very ancient Go features. But now, it's making a comeback. The new is re-implemented using the Go analysis framework — the same one uses. While and now use the same infrastructure, they have different purposes and use different sets of analyzers: By default, runs a full set of analyzers (currently, there are more than 20). To choose specific analyzers, use the flag for each one, or use to run all analyzers except the ones you turned off. For example, here we only enable the analyzer: And here, we enable all analyzers except : Currently, there's no way to suppress specific analyzers for certain files or sections of code. To give you a taste of analyzers, here's one of them in action. It replaces loops with or : If you're interested, check out the dedicated blog post for the full list of analyzers with examples. 𝗗 cmd/fix • 𝗚 go fix • 𝗣 71859 • 𝗔 Alan Donovan Go 1.26 is incredibly big — it's the largest release I've ever seen, and for good reason: All in all, a great release! You might be wondering about the package that was introduced as experimental in 1.25. It's still experimental and available with the flag. P.S. To catch up on other Go releases, check out the Go features by version list or explore the interactive tours for Go 1.25 and 1.24 . P.P.S. Want to learn more about Go? Check out my interactive book on concurrency a vector from array/slice, or a vector to array/slice. Arithmetic: , , , , . Bitwise: , , , , . Comparison: , , , , . Conversion: , , . Masking: , , . Rearrangement: . Collect live goroutines . Start with currently active (runnable or running) goroutines as roots. Ignore blocked goroutines for now. Mark reachable memory . Trace pointers from roots to find which synchronization objects (like channels or wait groups) are currently reachable by these roots. Resurrect blocked goroutines . Check all currently blocked goroutines. If a blocked goroutine is waiting for a synchronization resource that was just marked as reachable — add that goroutine to the roots. Iterate . Repeat steps 2 and 3 until there are no more new goroutines blocked on reachable objects. Report the leaks . Any goroutines left in the blocked state are waiting for resources that no active part of the program can access. They're considered leaked. Total number of goroutines since the program started. Number of goroutines in each state. Number of active threads. First by validity (invalid before valid). Then by address family (IPv4 before IPv6). Then by masked IP address (network IP). Then by prefix length. Then by unmasked address (original IP). Vet is for reporting problems. Its analyzers describe actual issues, but they don't always suggest fixes, and the fixes aren't always safe to apply. Fix is (mostly) for modernizing the code to use newer language and library features. Its analyzers produce fixes are always safe to apply, but don't necessarily indicate problems with the code. It brings a lot of useful updates, like the improved builtin, type-safe error checking, and goroutine leak detector. There are also many performance upgrades, including the new garbage collector, faster cgo and memory allocation, and optimized and . On top of that, it adds quality-of-life features like multiple log handlers, test artifacts, and the updated tool. Finally, there are two specialized experimental packages: one with SIMD support and another with protected mode for forward secrecy.

0 views
Higashi 1 months ago

Go generate meets vibes: vibe code Go one interface at a time using govibeimpl

Vibe-code Golang one interface at a time. During the holidays, I was working on a personal project in Go, and I wanted to use AI to help me do a few things (e.g. implement a downloader that downloads a file from Google Drive). However, I’m not a huge fan of having AI IDEs creating new directory structures or introducing abstractions that I need to read through and understand. Instead, I thought it would be cool to: And then I thought it would be great to combine this with where for every interface i define, i can just attach a tag so AI can fill in the rest at compile time. Therefore, I built govibeimpl (https://github.com/yuedongze/govibeimpl), a CLI tool that works with go generate that allows me to tag an interface for AI to implement, and seamlessly integrate that as part of my development flow. define an interface that i will need to use expect AI to write an impl to that interface i can just expect i will receive an instance of that interface at run time perhaps i’ll need to read about the api contract to see what concrete data types i need to pass in and read out profit i guess

2 views