AI and Irontown

For our last movie night we watched Princess Mononoke. I remembered loving that movie when I was younger, but watching it as an adult made me once again realize how Miyazaki has a way of expressing diverging sides without picking one. The film shows people, animals and spirits with legitimate and largely incompatible needs, and each faction is further engaged in infighting. A central arc of the story is Lady Eboshi’s Irontown, which gives people who were largely rejected by society work, protection and dignity. She gives shelter and work to those who have leprosy and to former sex workers. The mine and the weapon factory provide them with a livelihood, but they also destroy the forest around them. The forest’s inhabitants, in particular the boars, response choose war against the humans to defend themselves. Eboshi is not portrayed as a villain. She does real good for the people of Irontown, but also is an arms manufacturer who is destroying the forest around it. The film anchors around a character called Ashitaka, who has been condemned to death by an unfortunate encounter with a god that was turned into a demon after being fatally wounded by one of Eboshi’s guns. He doesn’t take sides, but he wants to understand and find the truth. Another character is San (whom they call Mononoke), a girl who was raised by wolves and, despite being human, considers herself part of the forest. This movie’s backdrop is human progress, and it reminded me so much of the challenges we are facing today with AI. AI is useful, empowering and consequential beyond the people who benefit from it. It’s similar to the iron in the story. Irontown gives the outcast purpose and power. There are people today who see themselves in an environment just like that: people who were never let in before and…

Custom Voices With Qwen3-TTS

With my kids I’m currently building a little dungeron crawler and we’re trying to learn a bit about game development along the way. However given that we live in the modern times of increasing capabilities we wanted to get a narrator and voice over. But we also wanted it to not sound like everyone’s standard AI voices so I looked into what options there are on the table. After some investigation I narrowed down to using Qwen3-TTS which uses voice embeddings for custom voices. With a bit of creativity you can also do more with those custom voices than appears initially. In case this is of interest to you, this post just covers what we learned and with some examples. You can probably point your agent to it and it can do something with it. What Qwen Offers Qwen3 is not the only TTS model in town, and it wans’t even my first choice. We initially used Kokoro. However Qwen3 supports voice cloning and we wanted to have it feel a bit more unique. In practice it’s a bit tricker though. Qwen3 ships three kinds of models: 1.7B-Base (and 0.6B): clones a voice from a few seconds of reference audio 1.7B-CustomVoice: comes with nine built-in speakers and instructions 1.7B-VoiceDesign: invents a voice from a text description The base model clones either in context or from the speaker embedding alone but there is no documented way to clone a voice and direct it with instructions. That means unlike the custom voice and voice design models you cannot say “speak sad” or “whisper” with a cloned base model. At present the official options for a custom voice are fine-tuning Base on a single speaker or to make a clip with VoiceDesign and clone it with Base. That is not all that bad, but it means instructions are gone. Playing With It For this blog post I took 28 seconds of me from my talk at…

What is Codemode

More than a year ago I wrote a few posts here that recommended people not to load custom tools into their context (or MCP servers) but to just use more scripts. Most importantly I wrote that Code Is All You Need and I wrote about that MCP needs code. With Pi 1.0 we now added MCP support via Codemode which in some ways is a long time coming, but then also maybe somewhat surprising to some. So I want to share some updated thoughts on this blog on what this all means. What Are Tools When a harness like Pi provides tools for an LLM to call, it does so by supplying some tool definitions which then translate into some token structure on the server side. Whether a model is encouraged to call a tool is the result of the reinforcement learning process. Something I wrote about before if you want to learn more. One of the reasons we strongly lean towards CLI and bash is because it allows easy composition of calls, and because the model also learns how the file system works when it’s trained. So when it invokes a tool like echo foo > /tmp/test.txt the model also learns that after that tool call, there is now a file called test.txt in /tmp. However bash has one fundamental limitation which is that it can only compose programs that run. And there are some things, which are not programs, but native tools to the LLM and they sort of have to be. The most obvious example here is read or viewimage. If a multimodal model needs to read an image, it cannot use cat for that because the harness needs to inject the actual image payload into the protocol of the LLM. Another quite vivid example are sub agents. In order to spawn and orchestrate sub agents, it’s tricky to avoid tools that are provided by the harness. While in theory the agent could provide a CLI tool that talks to the outer…

Deser: Rethinking Rust Serialization

Serde is an amazing serialization library for Rust and it has been a huge reason why I felt productive with it for years. However already while at Sentry I got quite frustrated with some of the limitations with it but actually replacing Serde is tricky because of the might that it has in the ecosystem. Also because it’s quite hard to actually do better without also making some potentially painful compromises. Here are three examples of Serde corner cases that show poor interactions of Serde features or unexpected limitations: A number that is a map An internally tagged enum, with serdejson‘s arbitraryprecision feature turned on: #[derive(Deserialize)] #[serde(tag = "type")] enum Shape { Circle { radius: f64 }, } serdejson::fromstr::<Shape>(r#"{"type": "Circle", "radius": 1.5}"#) // error: invalid type: map, expected f64 Serde’s data model has no place for arbitrary precision numbers, so serdejson uses in-band signalling with a map with a magic key. The enum has to buffer the fields until it has seen the tag, and the buffer does not know about the magic key. Because Cargo features are unified, it’s enough for any crate in your dependency graph to turn the feature on. Flattening breaks integer keys #[derive(Deserialize)] struct Stats { scores: HashMap<u32, u32>, } #[derive(Deserialize)] struct Report { name: String, #[serde(flatten)] stats: Stats, } serdejson::fromstr::<Report>(r#"{"name": "x", "scores": {"42": 23}}"#) // error: invalid type: string "42", expected u32 at line 1 column 35 Stats on its own parses {"scores": {"42": 23}} just fine. JSON keys are always strings, and serdejson only turns them into integers if the type asks for one. However once flatten buffers the value, "42" is just a string. The error also points at the end of the document rather than at the…

Interpreting Pangram

Yesterday David Sacks wrote a tweet and within a few minutes people did, what they usually do, and they asked Pangram if it was AI. And Pangram said it’s entirely AI generated. To which David replied that these AI detectors are bogus. Now Pangram has a pretty low false positive rate, but if you have ever used an LLM as a writing assitant, you will have probably noticed that it claims your posts 100% AI, even though you don’t feel like they are. Pangram itself is a trained model, that attempts to detect segments of text as being definitely human, definitely AI and a mixture of the two. If you want to know how it works, they published a paper. The short summary is that they are manufacturing its own training data by starting from collections of known human authored text. An LLM is then tasked to understand the text and write a fresh new text on the same topic. They also let the LLM perform partial edits on that original human text and through that they can pick up on these co-authored details. Pangram claims their model to have rates of 0.0041% false AI accusations and 0.34% missed AI text. So now that we know this I figured it might be fun to have an LLM re-create David’s tweet. I first came up with a prompt. And when I say I came up with that prompt I in fact used an LLM to propose to me from that tweet what I might want to say for the structure. I’m sure if you ask Pangram about if the above text is AI, it will probably say so, but that’s not really the point. The point is that I then used Opus 5 to generate a text which reads entirely AI generated. If you are curious, this is the prompt I used: Original prompt to generate a David Sacks like tweet Please write an 8 paragraph tweet about “Pacing the Frontier”, not longer than 350 words. Context of this tweet is this…

P(doom)

This week some flavor of “AI is going to kill us all” went viral. In particular one where an employee put his personal probability of that happening above 10%. Which made me go to the Wikipedia page of P(doom) and I realized that Dario Amodei’s apparent probability of something bad happening seems to be between 10-25%. And well, Dario then wrote about pacing the frontier . And Sam read it and wants to pace too. And well, so does Musk. I encourage you strongly to read the post, because I think it’s a good one. And yet, when I read the post I could not help but feel in strong opposition to it, despite the fact that I think I’m on the same page with regard to all observations and, to a large degree, the concerns. I thought it might be interesting to write down my present-day thoughts on this, even if for no other reason than for myself to look back at it a year or two from now. What Is Doom? What I really appreciate about Dario’s post is that he lays out a scenario that is not a huge stretch but also one that describes a clear, unfortunate outcome we should fight: persistent botnets and other forms of nuisance. And well, we don’t have to look very far to see the issues left and right. Wikipedia has a page called 2026 OpenAI agent cyberattacks which gives you at least some overview of what we figured out agents have hacked up to this point. Except I know it’s not up to date, because for instance they also poisoned RubyGems. Today these systems might be annoying, but they can be turned off when we figure out where they are. Except, it seems like OpenAI and Anthropic are operating at such a scale that they seemingly can be completely blind to what their systems are doing. I don’t think we are anywhere close to a world where an agent might decide to hack into core inference…

Astra for Coding: Why Are We Doing This Again?

I’m more and more convinced that all of AI engineering is Neijuan (内卷, meaning curl inwards). In China it describes a system that demands ever more effort and competition without improving output. The way in which it sometimes shows up in the West is the 996 nonsense. The English term for Neijuan is “Involution” from the book Agricultural Involution. Agricultural involution describes the intensification of farming that raises productivity per square meter while leaving productivity per head unchanged. That’s how I feel about AI right now. Which brings me to GPT 6 Astra. Astra is by all accounts an incredibly impressive model. There is really not much I can say against this. It’s amazing at computer use, understands images and complex topics, and it’s relentless in its pursuit of completion. It is absolutely impressive; these types of models are going to change the world in one form or another. But at least for the moment I don’t know how to work with it for actual software engineering. Since that got quite a bit of attention on Twitter, I figured I might summarize my thoughts and just share what kind of code comes out of this thing. My Slop Factory “Armin, you should run a software factory!” I’ve heard that a few times now, so I figured I might celebrate the release of it by running a little software factory over the weekend. If everybody builds slop 3D games, then I should do something useful with it. My software factory was intentionally set up to let the model decide the how of the workflow entirely. It was free to manage its own context and could maintain its own records in an agent-notes folder. Then it spun off subagents to work on stuff. The goal? What if we had a Python with virtual threads and lexical scoping. And well, I burned a full reset’s worth of ChatGPT…

Latent Powers

A few weeks ago I felt like it would be fun to see if I can make one of those cheap Chinese CarPlay dongles run something other than the stock firmware. The idea was that rather than just forwarding CarPlay, why not do something more interesting with them? They all work quite similarly: they act as bridges between your car and the phone. From there they deal with video and audio streams and pass some other data through. Most of them also bring up a custom UI for pairing and have a web interface that your phone can reach for updates. Long story short: I had a conversation with Fable and Sol via Pi about what could be done with such a dongle or whether I should use a Raspberry Pi instead if I wanted to do my own thing there. I figured it might be quite fun to run my own code while still allowing regular CarPlay to pass through. Through working with the LLM I learned about CatPlay, which is a Rust reimplementation of the CarPlay protocol that can run on Carlinkit devices. In particular, it can run on the Carlinkit Mini Ultra, which I figured would be easy enough to buy. I do have a few CarPlay adapters around, but I did not have that particular model, so I bought one on Amazon. Twenty-four hours later, I had a device in my hand that was branded as a Carlinkit Mini Ultra, but instead of being the Ingenic device that the original author used, it turned out to be something else. This is normally where the story would stop. However, it’s 2026. Armed with a bit of knowledge about how these systems work, I managed to have some fruitful discussions with Kimi K3 and Sol and figure out how flash the device and in turn, how to make CatPlay compile for that SoC. I guess that hacking these USB devices is not necessarily hard, but it’s laborious and you can easily end up bricking your…

Anger, Anxiety and Agency

Sean Goedecke wrote a post arguing that you should never be angry at work — a post with which I strongly agree. Anger can be a useful signal, but being angry at work rarely improves the situation. More often, it makes life worse for the people around you, many of whom have no more power over the source of your anger than you do. I did learn that lesson, but it did not come naturally. One thing in particular that I learned is that in a company there is a shared vision, and if you don’t agree with it and are not in a position to change it, you should not start a mutiny, not even a small-scale one. Nothing good comes from that. In the discussion around that topic, one of the most upvoted comments on the Lobsters thread asked a question I had to think about quite a bit: How can you work in tech right now and not be angry? In the context of the thread, this was clearly also about AI and agents. For me, the emotions I would expect in tech vis-a-vis these new developments are disorientation and anxiety, but not anger. Anxiety as an emotion does not require someone to blame. Right now, I find it reasonable to feel anxious about an uncertain future. Who knows what our professions will turn into and what kind of world my kids will find themselves in when they enter the workplace? And if you’ve been in the industry for a long time, will the skills you’ve spent years acquiring still matter? But anger is different from anxiety because anger needs to be directed somewhere. The feeling of anger suggests that somebody or something is doing something to you. Who are you going to be angry at and why are you angry in the first place? One narrative that is pretty pervasive is that if AI will usher in productivity gains, those gains are going to benefit companies rather than employees. And…

Fast and Hard Code

One of the memes on Twitter is that “programming is solved now.” I’m not sure to what degree it is, but one thing is pretty clear: the act of familiarizing yourself with a language no longer matters and some of the friction that mattered for humans does not matter for agents. As a result, LLMs make language choice much less consequential than it used to be. If you don’t like the choice, you can seemingly rewrite it in another language and you can make it pick a language that you, as a programmer, are entirely unfamiliar with. Which in turn means that people can, and do, choose based on the marketing of languages much more. As a long-term Rust programmer I found it quite fascinating to see people now ship Rust code who previously might not have chosen it. I attribute at least one part of this to two recent vibe shifts: there is a lot more talk about wanting fast software, and about LLMs being exceptional at optimizing code without regressing behavior. Folks like Mitchell Hashimoto, Charlie Marsh, Jarred Sumner, Daniel Lemire and quite a few others always carried a certain level of obsession with fast and performant software and they also all happen to be receptive to agents writing code. Maybe as a result, or unrelated others are now joining in. That’s because with things like autoresearch you don’t even necessarily need to know all the tricks: you just need to put an agent on it — though knowledge greatly helps! If you look around, there are plenty of projects that want to be fast and small, and they increasingly pick “hard languages”. And it’s not just Rust that is benefiting. Even Zig — despite the fact that the creators and parts of the core community are pretty negative on the whole AI thing — is too. For instance Cloudflare’s new Artifacts service uses a pure-Zig…

What Is Reasoning

A few weeks ago a paper was shared that showed how to extract reasoning traces from closed-weight models. Together with online discussions about tricking models into leaking them, it made me investigate it more out of curiosity. Twitter seems full of half-truths and confusion about how this works, so perhaps this helps some to understand what is happening. Hiding Traces Reasoning traces are usually hidden from us. We have lamented this, but mostly have to accept it. Open-weight models thankfully reveal them, and from their behavior you can see that their traces can be long and confusing. This is probably a good reason to separate them from what is normally shown to users. At minimum, UIs need to detect them. The industry has done a good job at making reasoning traces sound special and exotic, but they really are just text: the model is trained to emit its thinking into a scratchpad as part of its response, before its final answer. GPT-OSS’s Harmony response format makes this easy to see: <|channel|>analysis<|message|> I need to work this out ... <|end|><|start|>assistant<|channel|>final<|message|> The answer is ... <|return|> The markers are special tokens, but the reasoning between them uses “the same text” as the final answer (just that GPT chain-of-thought text sounds really funny). When the model samples the analysis channel token, a parser routes the following text into a separate stream exposed through the Responses API. For closed models, presumably a simple model redacts and summarizes it. Reasoning Effort How much budget goes to reasoning? Earlier APIs exposed reasoning token budgets, making it seem like a property of the sampling process. In reality, reasoning effort is baked into the system prompt. GPT-OSS puts this into the system prompt: Reasoning: low…

Codeberg Divides

Codeberg recently changed its terms to exclude projects that are largely written with generative AI. Considering I want GitHub to get some competition I have thoughts about this. GitHub’s governance has never been democratic and there is plenty about the platform that I dislike. Yet I do not need my infrastructure to be democratic but I need it to be predictable and reasonably neutral towards the Open Source software hosted on it. A democratic provider without a clear constitution can be worse at those things than a corporation is. Codeberg is entirely within its rights to do run the platform like they want. It is a German association with members and a democratic process, and that process produced a result. But a democratic vote says nothing about whether the decision is a good one, particularly for the people already depending on the platform. A majority can still decide that certain projects and people no longer belong. The new terms prohibit projects that mostly consist of code written by generative AI tools. That’s fine, but these days I could not assign authorship percentages to my recent projects. For me this rule is quite vague and I would bet that it makes it hard to enforce. In practice my assumptoin is that the center will leave. If anything a harsher line would probably be preferable. If Codeberg wants no LLM involvement, it should say so. On the other hand if the objection is just spam and abusive resource consumption, it should write rules for those instead. Now it defers the details of the policy to moderators and the communit which already draws a much harder boundary than the text does, judging by the tone of the discussion around it. It is a shame that the Open Source and Free Software communities are splitting this deeply over LLMs and agents. These…

Never Enough

Two recent stories about Silicon Valley are hard to read without wondering if everyone involved has lost their mind. Once a place that celebrated “the crazy ones, the misfits and rebels who saw things differently,” it now seems to have found a new kind of crazy that isn’t born of rebellion but fear of falling behind. Everyone is chasing the person ahead of them, with little time to remember where they meant to go. One story talks about a couple that earns a combined $550,000 a year and still feels behind because someone at Anthropic might buy the dream house first. The husband asked his wife to take over almost all parenting so he could spend days, nights and weekends becoming his company’s top user of AI. And she did. The other story covers a founder who has Claude review her first dates and tell her whether she was engaging and empathetic enough, thanks to recording every single one of them. Two people meet and afterwards, rather than talk to the other person or trust her own judgment, one of them sits alone with a machine and asks how well she performed. Technology was supposed to make room for life but instead for more and more people life is slowly being rearranged around AI. People fear being replaced by machines and respond by giving those machines more of their judgment, attention and time. And for what? Every “saved” hour is returned to a race with no finish line. If the valley shows us what it means to live in the future, perhaps falling behind is not the worst thing that could happen. Maybe we need the misfits who know when to stop. Who leave a conversation unrecorded. Who play with their children without agents running in the background. Who can admire another person’s success without feeling diminished by it. Who find peace not by getting ahead, but by no…

The Tower Keeps Rising

I feel that some vibecoded software changes somewhat randomly and unexpectedly. That made me think about Bruegel’s “The Tower of Babel” which shows an already quite chaotic depiction of the Tower of Babel. The story is one of pride and ambition and ultimately why people no longer speak the same language. But it is also a story about the unity that makes technological progress work. The text begins with a technology upgrade: And they said one to another, Go to, let us make brick, and burn them thoroughly. And they had brick for stone, and slime had they for morter. They use it for a civilizational project: let us build us a city and a tower, whose top may reach unto heaven But when God assesses the situation the bricks are not what concern him: the people is one, and they have all one language, […] and now nothing will be restrained from them.1 They get their power through coordination which they have because they share a language. They can use this to coordination to combine their powers and build something no one of them could build alone. God does not take away the bricks or their knowledge of how to make them but their ability to understand one another. With AI-assisted programming we should get better tools which lets us build more ambitious software. That is certainly true at the level of the individual and without doubt a developer with an agent can change a codebase dramatically quicker. But large software projects have never been limited only by how quickly an individual can produce code but they are limited by how well people can coordinate their understanding of the system they are changing. The shared language of a software project is the common understanding shared among its developers. This language is rarely written down in one place but it lives in…

Better Models: Worse Tools

A very strange Pi issue sent me down a rabbit hole over the last two days. The short version is that newer Claude models sometimes call Pi’s edit tool with extra, invented fields in the nested edits[] array. And not Haiku or some small model: Opus 4.8. The edit itself is usually correct but the arguments do not match the schema as the model invents made-up keys and Pi thus rejects the tool call and asks to try again. That alone is not too surprising as models emit malformed tool calls sometimes. Particularly small ones. What surprised me is that this is getting worse with newer Anthropic models as both Opus 4.8 and Sonnet 5 show it but none of the older models. In other words, the SOTA models of the family are worse at this specific tool schema than their older siblings. In case you are curious about Fable: I intentionally did not test it because I was not sure if the classifiers they are running might downgrade me to Opus silently. Tool Calls Are Text If you have not spent too much time looking at LLM tool calling internals, the important thing to understand is that tool calls are not magic and use some rather crude in-band signalling. The model receives a transcript, a system prompt and a list of available tools. The server munches that into a large prompt with special marker tokens. Because the model was trained and reinforced on examples of that format, at some point during generation it emits something that the API or client interprets as “call this tool with these arguments”. For a file edit tool, the intended invocation payload might say something like this: { "path": "some/file.py", "edits": [ { "oldText": "text to replace", "newText": "replacement text" } ] } A harness then validates the arguments, performs the edit, and feeds the result back into the model.…