How much extra for “not lazy”? [Seth's Blog]
Lazy isn’t a moral failing. It’s an economic consideration.
We have limited time, limited energy and limited resources. Allocating that effort is often done in response to what’s at stake and what’s on offer.
The clean room at a silicon fab or operating theater is clean because everyone involved puts in a lot of effort to keep it that way. And that effort is expensive.
It’s not efficiency or precision. Those are important on their own. The gap between laziness and not-lazy requires effort in the face of nuance, challenges or frustrations.
The staff at Motel 6 will put less effort into each guest’s requests than at the Ritz down the street because there’s a lot more staff per person at the Ritz.
There’s organizational laziness, in which a system is designed to have each person care a bit less about each task in exchange for completing more tasks, and there’s individual laziness, which is the natural consequence of disrespectful management.
If the boss insists on non-lazy behavior, but doesn’t give the team the time, the training, the tools or the compensation to do so, it’s not going to happen, not in the long run. Over time, not-lazy is rarely free.
AI bots have an economic incentive to do as little as they can get away with, and so do we. Programmers around the world are frustrated that coding bots almost solve the problem, but don’t seem to care enough to put in the extra cycles to get it right. But that’s what we’re (not) paying for.
The mismatches are frustrating. We might be delighted when we get not-lazy responses that we didn’t pay for, but it ruins a brand or a project when the effort we expected from a system or a person doesn’t match what we think we paid for.
Some clarifying questions:
Are our customers already paying for the non-lazy option? Or do they think they should be getting it for free?
If we wanted to disrupt our competitors by being the non-lazy option, what would we have to do and how could we communicate that?
How can we manage systems, processes and people so they have the resources and rewards they need to take the non-lazy approach?
If we’re trapped or pushed into the lazy path, how can we build systems so that laziness doesn’t damage our work?
How much extra could we change for not-lazy? And are we prepared to keep that promise?
“You’ll pay a bit more but you’ll get more than you paid for” is almost always a winning market position.
Urgent: Investigate Paramount's takeover [Richard Stallman's Political Notes]
US citizens: call on your congresscritter and senators to investigate Paramount's takeover meant to control most news media.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Stop surveilling of unions, churches and activists [Richard Stallman's Political Notes]
US citizens: call on your congresscritter and senators to stop the Department of Hostility and Suspicion from surveilling unions, churches, activists, and other peaceful political activism.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Refuse wrecker's voter-data grab [Richard Stallman's Political Notes]
US citizens: call on your state's Secretary of State to Refuse the wrecker's Voter-Data Grab.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
Urgent: Stop integration of American military tech with Israel [Richard Stallman's Political Notes]
US citizens: call on the Senate to stop the permanent integration of American military tech with Israel.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Preserve Forest Service Roadless Rule [Richard Stallman's Political Notes]
US citizens: file a comment calling on the Forest Service to preserve the Roadless Rule that protects much of our national forests from being cut down.
Urgent: Pass Tax Excessive CEO Pay Act [Richard Stallman's Political Notes]
US citizens: call on your congresscritter and senators to pass the Tax Excessive CEO Pay Act.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Faster Labor Contracts Act [Richard Stallman's Political Notes]
US citizens: call on your senators to vote for the Faster Labor Contracts Act, which would stop companies from stalling contract negotiations forever.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
What algorithm did Windows XP use to choose your initial user picture? [The Old New Thing]
I noted some time ago that Windows XP chose your initial picture at random from among the pictures in the %ALLUSERSPROFILE%\Application Data\Microsoft\User Account Pictures\Default Pictures directory. But it seems people want to know more.
Has anyone attempted to figure out the RNG for how Windows XP determines what profile picture is used on first account creation?
— Xeno (@XenoPanther) December 11, 2025
The random number generator is our friend
RtlRandomEx, using the current value of
GetTickCount() as the initial seed.
The function uses a one-pass random selection algorithm. I can immediately think of two benefits of this decision. First, compared to the naïve two-pass algorithm of counting up all the items, then randomly picking a number from 1 to n, and then iterating a second time to find the item at that index, it’s more efficient because it reduces the amount of calls into the file system, which is where the bottleneck is. Furthermore, the one-pass algorithm avoids complications if the number of files in the directory changes while the code is running.
The one-pass algorithm is a special case of reservoir sampling, where k is 1. This special case permits a tailored algorithm that is much simpler.
selectRandomFromIterator(iterator)
{
var count = 0;
var winner = null;
while (iterator.moveNext()) {
++count;
if (uniform_random(min: 1, max: count) == count) {
winner = iterator.current();
}
}
return winner;
}
The way this algorithm works is by observing that in a collection of n items, the last item has a 1/n chance of being randomly selected. If it isn’t selected, then you need to select randomly from the first n − 1 items, which you can solve recursively.
Playing the recursion forward, you start with the base case which is that if you have a list of 1 item, then your only choice is to chose that item. Otherwise, if you have a list of n items, first choose an item randomly from the first n − 1, and then switch to the nth item with a 1/n probability.
As a final safety check, the code stops after sampling 100 pictures. This avoids pathological behavior if somebody puts a million files in the Default Pictures directory.
The post What algorithm did Windows XP use to choose your initial user picture? appeared first on The Old New Thing.
Russ Allbery: podlators v6.1.1 [Planet Debian]
podlators is the package containing Pod::Man, Pod::Text, and other tools for converting POD documentation into manual pages and simple text documents.
This release fixes a long-standing bug in Pod::Text and subclasses where a pathological level of indentation could cause the wrapping code to go into an infinite loop. Thanks to Jitka Plesnikova for the report. This was assigned CVE-2026-82560, although I make no guarantees that podlators is safe to run on untrusted input and therefore not fully treating this like a security issue.
While fixing that bug, I noticed a bug in Pod::Text::Overstrike's wrapping code that would leave stray formatting at the start of the next line in some situations. That is also fixed in this release.
You can get the current podlators release from CPAN or from the podlators distribution page.
[$] LWN.net Weekly Edition for September 10, 2026 [LWN.net]
Inside this week's LWN.net Weekly Edition:
Surprising news. Matt Mullenweg was forced to take a paid leave of absence as CEO by Automattic's board.
Matthew Garrett: SystemIO conflicts are not firmware bugs [Planet Debian]

I’m looking at something entirely unrelated, but tripped
over some search results that made me realise that a lot of people
still think getting errors like ACPI Warning: SystemIO range
0x0000000000001828-0x000000000000182F conflicts with OpRegion
0x0000000000001800-0x000000000000187F indicate a firmware
bug. This is generally untrue. We need to dive a little into what
ACPI is to clarify why.
The Advanced Configuration and Power Interface1 specification defines a whole bunch of stuff, but what’s interesting to us here is the hardware abstraction it performs. While PCs are nominally a well-defined platform that’s really not true at the hardware level once you get beyond a certain level of complexity. When you suspend a system you want to power down the hardware in the correct order, for instance, and knowing what that order is requires you to know details about the specific motherboard design. The approach taken in the embedded world is to just bake that knowledge into the OS in some form, which is how we end up with Devicetree. ACPI takes an alternative approach - rather than provide that information as data that has to be consumed by OS drivers, it distributes it as code.
The ACPI Source Language, or ASL, is a simple language that gets compiled into a bytecode that’s then interpreted by the OS at runtime. One of the features of this language is the ability to define “Operation Regions”, effectively structure definitions that describe access to underlying hardware. Let’s imagine a simple device with two exposed registers. The first is an index register - it describes which internal register we want to access. The second is a data register, where reading it gives us the value of the internal register whose address is currently in the index register, and writing to it modifies that register. An example operation region declaration would look something like
|
|
This defines an operation region called “OPR1” at IO port 0x400, 2 bytes long. Inside it are two 8-bit fields, INDX and DATA. These are to be accessed one at a time, do not need the ACPI interpreter to take a global lock when accessing them, and if a subset of the register is modified then the other values should be preserved (irrelevant in this case since the fields are only a byte wide). Now any references to INDX or DATA in this scope will trigger accesses to those registers. So, a method to read the value of register 0x03 would look something like:
|
|
ie, set INDX to 3, and then read the value of DATA and return it. But! What if another ACPI method is running at the same time? Let’s say we have one that writes to register 0x05:
|
|
What happens if RD03 executes while we’re part-way through
WR05? INDX might get reset to 0x03, and now WR05 will modify
register 0x03 instead of 0x05. Oh no! But we can avoid this - we
declare a mutex (Mutex (MUTX, 0x00)), and update our
methods to be something like:
|
|
Each method takes a lock (waiting up to 0xffff milliseconds and then erroring out if it doesn’t), and performs the access. There’s now no chance of a race. Phew!
Now suppose someone writes a Linux driver for this piece of hardware. It accesses the hardware directly, with no knowledge of ACPI. What stops the driver from racing against one of the ACPI access methods? Nothing at all. Oh no! Again! This isn’t hypothetical, by the way - here’s a relatively harmless example, but back in the day we did trip over cases where temperature monitoring chips would be accessed by the firmware and Linux simultaneously and as a result you might end up thinking you’re reading a temperature when you’re actually reading a status flag, resulting in an impossibly high temperature and an immediate thermal shutdown.
In this case, the kernel saves you from this (potentially
hardware damaging) outcome by printing a message like ACPI
Warning: SystemIO range 0x0000000000000400-0x000000000000401
conflicts with OpRegion 0x0000000000000400-0x0000000000000401
(OPR1), telling you that the kernel has detected that a
driver is attempting to allocate IO ports 0x400-0x401, but that
there’s an ACPI operation region called OPR1 that is claiming
the same addresses. The kernel isn’t in a position to know
what type of access the firmware might perform in that region, so
assumes that it might be dangerous and blocks the driver from
loading.
But all is not lost! The kernel also prints some helpful advice,
ACPI: If an ACPI driver is available for this device, you
should use it instead of the native driver. And ACPI tables
will often actually have a definition that looks like this:
|
|
which defines an ACPI device and associated methods. The
_HID field defines the device type, and a Linux driver
can be written that will be automatically loaded if a device with
type VEND0001 is seen. That driver can then call ACPI
methods associated with the device and access the resources in a
way that matches the firmware’s expectations.
(Interested in writing such a driver? I wrote a guide back in 2009)
The firmware did absolutely nothing wrong here2, but trying to load the native ddriver will generate an error and the internet will tell you that PC firmware developers are incompetent3 and you should pass a kernel argument that overrides this behaviour and it never did them any harm, and it probably won’t do you any harm either but it might and you might never know why your system occasionally wedges or catches fire.
The ACPI spec used to live at acpi.info, but sadly
that seems to have vanished some time after UEFI took over
stewardship of the spec ↩︎
You might argue that the firmware should simply not do anything
at runtime because it is not the firmware’s job to do that,
and I do understand that and you can certainly boot with
acpi=off if you want to and no ACPI code will be
executed at runtime. Let me know how that goes. ↩︎
I’m not going to present an opinion on that here, merely say that this provides no supporting evidence for that assertion ↩︎
The new Apple Watch is always recording and transcribing everything it hears [OSnews]
Do tech companies ever stop to think about the misery they unleash on people’s lives? The Apple Watch Series 12 uses “Audio Intelligence” that, as well as secretly taking notes, also has a Live Rewind feature that can replay the last 15 seconds of recently detected audio and view a text transcript.
↫ Matt Growcoot at PetaPixel
Another win for us Europeans: this dystopian nightmare will not be available in the EU.
Although Solaris is now mostly defunct, its influence remains substantial; technologies pioneered by Solaris can still be found across a wide range of software
A lot of Solaris’ inventions have been described and talked about ad nauseam (such as the Slab Allocator), but one I rarely see discussed is its use of turnstiles.
Despite being relatively obscure, the idea has quietly spread far beyond Solaris. Variations of it can now be found in major operating systems, web browsers, and language runtimes. In fact, you’re probably using several implementations of the same basic concept right now!
↫ Loïc Grégoire
I’m not going to pretend to understand all of this, but I know you people do, and many of you will definitely find this interesting to read. Also note that Grégoire develops their own operating system kernel called zag.
I have completely lost interest in new phones. The last one
I bought was a Pixel 9 because they said it would have great AI
features. At that time the sky was the limit, there were new
breakthroughs every afternoon in AI. Now it's just an app. I have
to carry an iPhone because I use an Apple Watch. Phones are
utilities for me. I have a lot more interest in
weed whackers these days. Not kidding. Now a story. I wanted to
check the model of the phone. Went to settings, and the command at
the end of the menu should tell me the version, but they had
changed that. So I figured I'd go back up to Gemini, their AI app, and ask
it. Of course it has no idea where it's running. Screen
shot. That would be one of the real problems they could solve,
making it easy to control the computer you're running on. The whole
UI should be in AI. All it did was print
basically a page from the user manual, with the same out of date
instructions. How many years have we been working on AI already,
this is the easy stuff that they haven't done. Tech companies get
back to your business, make your software better. You can and it's
long overdue. BTW, it's a Pixel 9 Pro.
The following article originally appeared on Addy Osmani’s blog and is being republished here with the author’s permission.
In the past year, the conversation around agentic engineering has moved to harnesses and loops, fleets and software factories. My 2 cents is engineers need to own the outer loop—the accountability for these systems. This only gets more true as powerful models like Fable and GPT-5.6 become available.

Agents have leverage, and leverage creates obligations. Someone must be able to explain exactly what changed, why it was safe, and what will happen if they’re wrong. Otherwise, their actions can’t be justified. Which makes it unlikely their organization will ask for them in the first place.
And so I want to talk about three terms. The first, Quality, refers to all the checks we install before we let the system loose. Those checks produce evidence, and from that evidence we derive a Verdict.
The second, Verdict, refers to the final decision we make before work enters our dependent system: I’m the line-producer of this content. I run the team whose work is shipped under my name. The model may write the line, but the Verdict is mine. The work of my team will not enter our dependent systems without my decision. A Verdict is the production decision: Should we ship, block, redirect, narrow the response, add a guardrail, or reject outright?
The third, Answerability, refers to the guarantee that if someone asks, I can explain why.
To say this another way: Our agent (which I define as a model plus a harness of files, tools, memory, skills, sandboxes, permissions, observability, and recovery) is what runs our loop (which I define as investigation, implementation, verification, and repeat). And it’s what creates our software factory.

The model is just the engine. The harness—tools, memory, permissions, sandboxes, tests—is the car you build around it so it can do real work safely.

The loop is how one good run becomes a process you can trust to run again. Wrap that harness in a repeatable cycle—investigate, implement, verify, repeat—where an independent check, not the model’s own say-so, decides when the work is done.

Now run many loops at once. A factory is loops at scale: The agents ship the work inside, while humans own the decisions at the boundary.
And at the heart of that factory is a careful boundary between what’s inside the system and what’s outside it. Inside the system we collect inputs (from the product team’s intent, or knowledge of previously shipped work, or of recent incidents, or of specific feedback from users). The agent loop investigates the task, implements a plan, and verifies the result. Then, evidence crosses that boundary. A human, who owns the dependent system, sees the evidence and decides whether to proceed.

And that, friends, is the shift we’re trying to make. Before, our agents were doing the inner loop of the execution loop. Now they run the inner execution loop. Engineers own the outer loop.

Inside the system, there’s really just one kind of thing our agents are doing: capability. The capability to investigate tasks, implement plans, test their results, and report back. That’s the capability of a model. And as we’ve said, that future is already here.
Outside the system, there’s a single kind of thing: agency. The agency to decide, verify, approve, and own.
We’re still talking about code, you see. It just needs to live in a place and be performed by people who know what they’re doing.
The potential for AI code is no longer marginal. In a Sonar 2026 survey, we asked teams about the share of their commits that were AI-assisted. It was small but nontrivial. And several of the respondents said they expect the share of AI-assisted commits to grow substantially.
Sonar’s 2026 State of Code report found that 42% of committed code was AI-generated or significantly AI-assisted, with expectations for that share to keep growing rather than plateauing.

Creation, in other words, is getting cheaper. Scarcer resources are review, validation, understanding, and maintenance.
We moved the speed of generation faster than we moved the speed of control, and so we have a trust-verification gap. A lot of people we talk to still express some degree of distrust in AI code. Yet fewer of them seem to consistently build that distrust into their verification processes.

That’s a dangerous place to be. We’re going to need cheaper, clearer ways to verify the trustworthiness of AI code.
If you look at the GitLab June 2026 report, you’ll see that governance questions have shifted.
GitLab’s June 2026 AI accountability research shows that review and validation are the current bottlenecks when using AI and, more worryingly, that governance usually happens after code creation, after we’ve accepted the risk and lost control over ownership. Today, it’s not just about control. It’s about what constraints we set on the system. It’s about how we’ll check the work with evidence, and how we’ll hold teams accountable. It’s about who will own what part of the AI lifecycle.

So the final distinction in this series is between process and quality. Quality is the concept of backpressure. We mean it literally. We don’t want to grant our agents as much autonomy as they can possibly exercise. We want to grant them just enough autonomy that we have enough backpressure to stop them, regulate them, check their work, and ensure our humanity.
Ordinary engineering holds up a lot of signals that indicate that the work being done is doing the right thing. Type checks, tests, hooks, sandbox limits, audit logs, monitors. Our engineering systems are full of these kinds of signals, and they’re designed to provide enough backpressure to keep the system honest.
And so as long as our agents are emitting these same signals, we can trust our ordinary engineering to provide appropriate backpressure.
Trusting our systems doesn’t mean we don’t want a human in the loop. It just means that the human doesn’t need to be in the inner loop. We want them in the constraints loop (What inputs, architectures, instructions, or invariants should we set?), the sampling loop (How much output should we sample and review?), the audit loop (What evidence should we keep, and how do we make sure our audit log is effective?), and the ownership loop (What part of the production boundary should we own?).
But the human doesn’t need to be in the inner loop.
The agent can ship more than you can review.

And the scarce resource is your own core human judgment, informed by quality signals like logs or tests.
The AI June 2026 report shows that, in the experimental setting, agentic delegation along hour-scale time horizons is essentially here. The work by OpenAI this year on agents and the future of work was a great source for these ideas. So we need to start thinking about how to establish this ownership boundary, as our systems start shipping more than we can review.

And that’s where the answerability comes in.
Because with long-horizon agents, the decisions made over hour-scale time horizons are just that—decisions. And not all the decisions are going to be recorded. You can’t trace them all back to input tokens. If all you’re doing is trusting that the output you get is the correct choice for the problem at hand, the hundreds or even thousands of human hours of work you’re going to need to reconstruct the chain of decisions that lead to it become impossible. And so, again, answerability becomes something that must be at the core of our system design.
And there are three hidden costs:
Cognitive surrender ~ blindly accepting what AI gives you. When you delegate work to an agent, the work itself may appear to be the work of the agent. But it’s actually your work. It’s your reputation. It’s your responsibility. And it’s your software that suffered the defects in the output. And it’s your software that needs to be changed to reflect that output. So the agent’s output becomes your answer. And with it comes all the accountability. The Wharton study that put this together is reassuring when the AI is right. But when it’s wrong, the news isn’t great. When the AI was wrong, nearly three-quarters of people accepted it anyway, and felt more confident than they would have without the AI.

Cognitive debt ~ erosion of your understanding and memory of how to solve problems. When you delegate work to an agent, you’re offloading all the thought work to the agent. And while thinking it all out yourself takes time and energy, thinking it out on a massive codebase takes resources that aren’t available when you’re trying to run up the learning curve. So the output you get is often unattainable by you. And the longer the time horizon of the agentic planning, the bigger the gap between the code the agent produces and your understanding of it becomes. The gap compounds. The debt accumulates. And the cost of climbing the learning curve grows almost exponentially.
There’s a randomized controlled trial from Anthropic looking at whether engineers who lean on AI to write code understand it as well as engineers who write it themselves. The conclusion was gloomy: On a comprehension quiz, the engineers who worked through AI scored 17 percentage points lower than those who didn’t, 50% versus 67%.

And then there’s the orchestration tax: It’s easy to spin up lots of agents now, but your cognitive bandwidth doesn’t parallelize in the same way. Steering your agent away from the worst behaviors, sorting the work the agent produces to identify the ones that need your attention, directing it to focus on the work you care about first, verifying your most important constraints and your most dangerous assumptions before you let it run. . .
All of that takes work, and it can’t be automated. There’s no substitute for human judgment.

Brownfield systems are especially dangerous here, because the system behavior you have to audit doesn’t live in the code. It lives in the scars.
Fixes? Make attention the priority in your architectural decisions. Use worktrees, scopes, and evidence to reduce the coupling between your initial plan and the work that emerges from it. Time-box the effort to resolve unactionable steps. And make change in your software strictly an opt-in permission.
Alpha, decay, and taste: These are the three core patterns that shape careers and performances across domains.

Alpha is the lead part taken up by the highest achiever in the competition, when you’re playing your highest-value game move. Decays are established patterns that everyone learns through repetition and watching others (plateaus, if you like). Taste is the earliest we can sense the lead in an alpha or the change in a decay. It’s our judgment of what’s coming before we have any evidence that anything is happening.
Paul Graham’s point is that when anyone can make anything, choosing what to make matters more, and Mitchell Hashimoto’s definition is the operational one: making high-quality qualitative judgments where no objective metric exists yet. From now on, taste drives everything. Alpha shifts are taste changes. And decays fade out because we start to taste something different.

Next step? Operationalize your taste. How? Give it a name that reflects what you’re trying to move from limbic to conscious. Practice it in critique and examples. Make its rationale explicit.

And keep making the move that delivers the most durable competitive advantage in your industry. What’s that? Keep moving the edge up from just doing the task to teaching it, systematizing it, deciding when it should be done, and owning the result.

Everyone is a developer, but not everyone is an engineer. Engineering is what a developer turns into when they embrace a work discipline that is more strict: thorough and logically sound reasoning, consideration of constraints and tradeoffs, recognition of risk and exposure, and practical accountability.

In the future, people will leave the administrative work of engineering and embrace new roles that emerge as engineering becomes more demanding. Roles that are unbundled from the spirit of craft but make clear what each person does. There will be those who prototype. Those who build. Those who sweep. Those who grow. Those who maintain.

The humans hold the edge of the system in the other direction too. Increasing the alpha: choosing what is worth doing, defining the constraints within which it should be done, deciding if the evidence is sufficient to proceed, and caring for the result. Whether it’s a single team or a hundred teams, this is the edge that only humans can hold.
Accountability will scale the factory. Like attention and taste, accountability is also one of the three dualities that makes everything work. Without accountability, there are no rules. No wrangling with questioners. No trade-offs. No risks. No safety nets. If nobody owns the consequence of a decision, then high agency can only bring chaos.

The half-life of an edge is one release, but the half-life of a signature is a career. A signature is your name on the work, such that you feel you can stand behind what was shipped. Skills get you leverage; accountability turns leverage into trust.

Only people can choose. Only people inherit consequence. Agents can be asked to choose, route, merge, and escalate safely inside a policy, but they cannot inherit the consequences.

Every codebase should perhaps come with some kind of accountability contract that explicitly states the checklist that was understood when the change was accepted, the evidence that went into the decision, who was accountable for the change, and the system status after the change was blocked. Just like:
In a typical agentic workflow, high agency is the art of knowing when to delegate, when to inspect, when to stop, and when to own the result of a process. The ladder of agency runs from low to high: flag a potential problem, investigate it, execute against it, diagnose it, propose solutions, recommend fixes, and resolve the issue. A high rung on the agency ladder is discernment: found it, it’s not worth fixing, moving on.

Brownfield is the frontier for factories that hope to scale. All those clever little innovations may not feel like much yet, but the production environment is a lot. When building an entirely new system, it’s much easier to plan and implement sufficient backpressure mechanisms because you have full control. When you’re adding intelligent agents to a legacy system, however, it’s another matter entirely.
Legacy systems include the entirety of production behavior, future expectations from customers, migration histories, release and budget cycle durations, unspoken assumptions, edge cases, data weirdness, runbook procedurals, and all the scars that accumulated without the will to care for the system.
To be a steward of brownfield requires a form of durable engineering. Work has to be done to turn implicit knowledge into explicit constraints, keep it coherent across teams and through generations, formalize that knowledge into test procedures and functional specifications, and tie that knowledge to objective evidence. All while ratcheting failure into more learning. Because if the system doesn’t get the care it has always received, everything will come crashing down.
The work will get more interesting as you scale. Because when everything else is built, people will want to build new things. They’ll want to employ the alpha and taste they have developed through their craft to design new loops that can be grafted onto the software factory. Or they’ll want to build greenfield systems that employ all the knowledge of the software factory to one elegant, well-meaning, principled effort. They’ll want to design and implement new forms of evidence that will rise to the level of verification for the new systems. They’ll want to take care of brownfield systems that are now so complex they need dedicated attention. They’ll want to design and manage new backpressure mechanisms. They’ll want to design new agents. And they’ll want to build agency.

And, as they do, they’ll come to see that all this is real work. That’s a good thing.
Automation creates bottlenecks. Bottlenecks in production that are worth owning. Because automation gives us control over industrial scale. But there’s also new bottlenecks that arise from industrial scale. The bottleneck moves from “Can we build this?” to “Should this exist? Can we answer for it?”
What I’m suggesting is a practical operating model for scaling agentic engineering. There’s inner and outer loops. The inner loop is where the work is done. Loops are designed to be as independent as possible. Put all quality assurances and verification inside the loop. Once you’ve designed and validated the loop itself, the only thing you have left to do is to grant autonomy by putting in place a back-pressure mechanism that acts to control the rate at which the loop is run and its scope of operation. And put humans in their rightful place, on the right decisions. Don’t treat understanding as a hand-off or a release gate but rather as a point of decision where humans are primed to provide their insight. And then for every artifact that exists and is fed back into production and into new teams and engineers, leave behind better artifacts.
Build the factory; keep the lights on; make work legible, verifiable, owned.
An agent can write it. But before it reaches users, someone must explain why it should exist, why it’s safe enough to be part of production, and what they will do when it is wrong.
That’s agentic engineering at the outer loop—that’s the work now.
The Big Idea: K. Ancrum [Whatever]

One of the eternal question in speculative fiction is: Is this fantasy? Or is this science fiction? And does the answer matter? For Adam, Mine, author K. Ancrum takes a stab at this question, using one of the most durable tales in literature as inspiration.
K. ANCRUM:
The moment of conception in Mary Shelly’s Frankenstein is so deeply imbedded in our cultural consciousness, through adaption and visual representation, that you’d be hard pressed to find someone over standard drinking age who cannot place the 1931 film shriek “It’s alive!” even if they’ve never seen it, or read the book at all.
It’s an instant of science horror that birthed a thousand sub genres, but there is so little discussion about it in particular. No, no, not the philosophical nature of birth, or the allegorical structure regarding God and Man, or the Lit-Crit monologue on the nature of the act of creation. I am not talking about that. I am talking about the spark itself! The scientific labor that preceded the resurrection and the moment it worked! The concept of something fantastical to us (Doylist) readers, simultaneously in the (Watsonian) context of the story presented and firmly understood—despite its “unreality”—as science.
That is what grabbed my attention firmly. The familiar argument: the delineating science fiction/fantasy border and the malleable nature of perception that decides what lands on one side or the other.
I was obsessed with framework and emotion. How it feels to see grotesquerie beyond your imagining and in your terror the hind brain rejects it: evil, witchcraft, demons, the hand of fate. To be an educated person and know you’re seeing science, but lacking the vocabulary or even the schema to identify it as anything but nonsense: rules and methodology become spells, medicine becomes potions, life-saving machines are the mechanical tinkerings of a madman. Saving someone from the brink of death? Stealing from God.
This isn’t exactly the big idea, but it’s close. We’re almost there.
I wanted my young readers to make the mistake of sneering at people from the past for their ignorance and then, within the span of the novel, to become them. As an educator, I wanted them to experience a shift in perception that humbles.
All we can possibly know is what we have access to, and it takes incredible imagination and the entirety of the history of humanity’s collaborative labor to reach beyond that. At the forefront of every moment of discovery and invention, often at the cost of their own lives, there were people doing science that looked to the majority of us like magic.
And there always will be.
That’s the big idea!
Now we’re all roughly familiar with Mary Shelly’s Frankenstein and the attitude towards science at the beginning. Victor’s stubbornness and arrogance, his peers and professor’s disgust towards him and his work, his friend’s terror at his actions and the cavalcade of punishments the narrative delivers him as a result of his cruelty, neglect, and ignorance. ADAM, MINE similarly begins: classmates mocking him, professors deriding him, punishments on the horizon.
But the resounding echo that follows is Doylist knowledge. Victor’s peers and teachers are sneering at things that have already been invented: things the children reading should know about. A breathing machine? Sounds creepy. Removing plasma from blood? Insanity. Neurosurgery? Only a serial killer would poke around in someone’s brain. Restarting a heart with electricity? What?? It entreats the reader to enter the framework, to roll their eyes at the footnotes clarifying these real inventions and their year of make. To giggle a bit.
When it comes time for Victor to make his announcement about reanimation, it fits in well with the rest of it. ‘More histrionic, creepy, fantasy nonsense from our class’s most annoying Rich Boy Wunderkind,’ Victor’s peers say and ‘here we go now” my young readers think. With that: the door slams shut behind them.
In my prison of perception, I introduce “alchemists.” I give Victor a “spell book.” I tell the readers his life force is seeping away and he doesn’t know why. Nothing is explained because the lack of explanation is a part of the framework.
The alchemists are powerful and mysterious; they all know each other and talk to each other like colleagues. One of them gives Victor a talisman. Another uses lightning as a magic conduit. Victor and my readers have never seen anything like this before. The Alchemists act like there are rules and methodology that govern what they can and cannot do. But Victor doesn’t know them and he never learns. He travels in terror over thousands of miles in a body that weakens as violently and mysteriously as people from our turn of the century’s did as they played in radiation.
The only people who can do anything to help him are standing above him like angels from the future. Just shaking their heads in horror and derision as Victor cries; a young time-travelling Harry Daghlian begging on the stairs of David Krieger’s house. They scrounge in their homes and labs for potassium iodide, Prussian blue and DTPA because they feel bad, but they know that he will die. The readers begin to know that Victor will die, and they still don’t understand how or why.
Victor and my audience may never unpack the biochemistry and electromagnetic theoretical physics Victor fumbled that injured him so badly. The methodology-filled field journal might always seem like a spellbook to them. The Alchemists, cutting edge physicists all, may as well be wizards. This fictional story of his injuries and demise? A case study written with incredible grief and grave warning.
The Perception Prison was an ambitious goal—perhaps—and it won’t land well with everyone. But in the face of the urgency of this lesson and our youth, I had to try.
In the end, my Victor is just like us and we are just like him, we always have been and we always will be. Short lived creatures in a universe of dangerous and seductive mysteries, illuminated only through the sheer might of humanity’s archival collaboration into the benign and understood. The true cost behind the arrogance of our giggle.
Something we should never ever forget.
Adam, Mine: Amazon|Barnes & Noble|Bookshop|Woman & Children First|Anderson’s
Photo of a Cat, Annoyed That He Was Made to Go to the Vet [Whatever]

Cheer up, Saja. It happens to all the cats, sooner or later.
Also, fun fact: Saja has officially been with us for just over a full year now. We fostered him for a couple of weeks and then decided to keep him, and his official naming day was September 1. I was traveling that day so I completely forgot about it, but better to commemorate a year of his presence late than never. Happy belated gotcha day, Saja. You’re a real pain in the butt and we love you.
— JS
Lotus Notes and the dangers of starting from scratch [OSnews]
Lotus Notes was the future of communications, a decade before laptops had WiFi. Yet of all things, it wasn’t even an email app. It was a notes app, a collaboration tool, an all-things-to-all-people software that let you build apps in the way Access and Airtable later would. That, and the notes could be used for email.
Love it or hate it (and there were plenty on both sides of the fence), what you couldn’t do was ignore it. This email-and-everything-else platform showed what the future of digital communications would become — and provoked, as email itself was always doomed to provoke, equal measures of awe and exasperation.
↫ Matthew Guay
I have no experience with Lotus Notes, but I do have some vague memories of the software being used at my parents’ employers back in the late ’90s. Note that Notes still exists and is in active development as HCL Domino (server) and Notes (the client). If you really want to, you can still run your company of office on Notes.
I wonder how many actually still do.
Digital Sovereignty: What It Is, What It Could Be [Deeplinks]
The term “digital sovereignty” has become ubiquitous. European officials invoke it in debates about cloud infrastructure, AI, semiconductors, and platform regulation. Governments throughout the global majority use it to argue for greater control over data and communications infrastructure and boost their economies. Companies market “sovereign cloud” products designed to reassure their customers that their information stays under local jurisdiction. But digital sovereignty could be something more: an opportunity for users around the world to build more resilient, open systems and the skills and infrastructure to maintain them.
There is no singular definition of digital sovereignty, nor is there a single coherent position in the digital rights space. Despite its growing popularity, the term remains frustratingly vague. Policymakers, regulators, civil society groups, and others can mean very different things when they use the term. But to start simply with a broad definition, we can say that it means having the capacity to control one’s digital destiny—though the implications of that will obviously differ considerably whether you’re talking about an individual or a country.
We can start by developing a shared understanding of what digital sovereignty actually means. We’ve also included a glossary of terms at the bottom of this post.
In Europe and other places where digital sovereignty has become a topic of policy, discussions focus on reducing dependency: on foreign (and particularly American) cloud infrastructure, chips, platforms, and at times, foreign political priorities. The concern is both economic and geopolitical. If essential infrastructure is controlled by companies elsewhere—and thus subject to the laws of another jurisdiction—then what control does a country actually have over its own digital future?
In global majority countries in particular, wars, sanctions, and the growing fragmentation of the internet have demonstrated for many that the physical infrastructure that underlies digital life is neither neutral nor invulnerable.
Amidst this increasing geopolitical instability governments and civil society should consider whether digital sovereignty can help shore up that infrastructure.
A recent Franco-German joint paper on digital sovereignty defines it as the “capability and capacity to develop, provide, use, adapt and control digital technologies including hardware in an independent, self-determined and secure manner” and puts forward a framework to operationalize Europe’s capacity to act in the digital domain.
Some governments, such as Germany’s, have started to put funding behind sovereignty efforts through initiatives like the Sovereign Tech Agency, which “invest[s] globally in the open software components that underpin Germany's and Europe's competitiveness and ability to innovate.”
Positions on digital sovereignty among EFF’s allies across Europe vary. Open Rights Group have defined digital sovereignty as “the ability of a country to have control over its digital infrastructure, data, and technology” and states it to be “critical for the UK’s economic and national security.”
Similarly, the European Partnership for Democracy has expressed concern that “a few Big Tech corporations decide our collective destiny,” and argue that the EU should explore “alternative ownership models for tech companies and clearly [define] their purpose and mission.” And our friends at EDRi (of which EFF is a member) have stated clearly that “Europe’s digital sovereignty starts with open source.” Some initiatives, such as DI.DAY, consider digital sovereignty an opportunity to free users from Big Tech dependencies.
Elsewhere in the world, conversations about digital sovereignty often take a different shape. Indigenous discussions of the topic have been ongoing for more than a decade and focus on the inherent right of Native nations to govern their own digital ecosystems. In Southeast Asia, the desire for digital sovereignty has created growth in the sovereign cloud industry, but the conversation isn’t purely economic: Concerns about jurisdiction for where data is held are driving much of the conversation.
In Latin America, digital public infrastructure is often a key aspect of debates. Across Africa, leaders speak of a desire to shift the continent from being consumers of technology to becoming architects of their own digital infrastructure and data ecosystems. And in the Middle East and North Africa, concerns about reliance on U.S. technology companies—which have engaged in conflict and disproportionate censorship (particularly of Palestinian voices) in the region—are often paramount.
Reem Almasri, a senior researcher based in Jordan, recently spoke to EFF about digital sovereignty, which she sees as “the ability of people and communities to choose, control, and use technology that serves their needs and values,” particularly in light of the role that U.S. companies have played in regional conflicts.
In a January article, Almasri pointed to growing concerns about granting greater sovereignty and influence to governments over citizens’ data, communications, and websites, writing: “This is particularly worrisome in countries that impose high levels of internet and media censorship and run unaccountable surveillance programs on their citizens’ data.”
Indeed, while pushing for greater sovereignty from Big Tech has benefits, there is an inherent risk that some states will pursue digital sovereignty as a means of cutting off or splintering access—as we’ve already seen in Iran, Russia, and elsewhere.
For that reason, it’s no surprise that some, such as Iranian professor Azadeh Akbari, believe that “the current wave pushing digital sovereignty as the key to ending dependency on American and Chinese technology is negligent of its Eurocentric bias.”
In a world where people have digital sovereignty, civil society should be able to communicate freely, privately, and anonymously if they wish. People should be able to easily understand where their data lives and who has access to it. That data should be easily portable between platforms and services.
At EFF, we view digital sovereignty not as a walled garden, but as an opportunity for resilience and development of industries and skills. We believe that governments can and should take a role in crafting digital sovereignty that centers the autonomy of users rather than just re-creating a state of digital dependency with a new set of companies. Governments should support and use free and open source tools and projects built using principles of interoperability and data portability. This support should include employing full-time developers, UX designers, and community managers. Government policy and legislation should grant users control of their own data and a clear understanding of who can lawfully access it. Digital sovereignty should foster users’ ability to choose how they use digital products and services, free from unfair lock-ins, coercive terms and manipulative defaults. It should also foster the broader public interest internet, the part of the web that provides public goods and useful services without requiring the scale or the business practices of the tech giants.
Encryption backdoors are fundamentally incompatible with a vision of data sovereignty that centers user control. Governments should support the development and normalization of reputable end-to-end encrypted communications as well as strong encryption for data at rest. This support should include employing cryptographers and contributing to strong, peer-reviewed encryption standards strengthened by data minimization as a fundamental design principle, as well as refraining from legislating mandates for “lawful access” or any other reason.
As technologists, we don’t have to wait for governments to act in order to create the digital sovereignty we want. We get the internet that we build. We can contribute to open source, decentralized, and end-to-end encrypted projects. We can build standards that make interoperability and data portability a feature from the very beginning. We can resist the call of proprietary solutions, user lock-in, and encryption backdoors.
And finally, while digital sovereignty is often framed as a response to the dominance of Big Tech, that does not mean that there is no role for private companies to play. There is no point in replacing the influence of a few mostly US-based tech companies with a handful of giants based elsewhere. Companies can and should build platforms and services on top of open source, decentralized protocols and contribute to the ecosystem. Companies should also minimize processing a person’s data except as strictly necessary to provide them what they asked for, and only with opt-in consent that makes it clear to users what data they are gathering, where it is stored, and who has access to it. And companies should build their tools and platforms in a way that allows interoperability and that makes it easy for users to leave with their data. Some of these practices are already required by law in some jurisdictions, but companies don’t have to merely do the bare minimum the law demands: they should respect their users and support data sovereignty right now.
The following terms are useful for understanding this blog post as well as the broader conversation about Digital Sovereignty:
Intermediary liability: the legal responsibility of online service providers (ISPs, websites, social media platforms) for unlawful activities by their users, such as defamation, copyright infringement, or illegal hate speech.
The stack: a secure, open-source technology framework, often focusing on European alternatives, designed to break dependencies on (mostly) US-based technology providers. It comprises interoperable, vendor-neutral, and transparent digital infrastructures designed to regain control over data, infrastructure, and technology.
Digital sovereignty: the ability of people, as nations, organizations, and individuals, to control their own digital destiny by retaining authority over their own data, technology, and infrastructure.
Data sovereignty: the principle that digital information is subject to the laws and governance frameworks of the country or region where it is physically collected, stored, or processed. It dictates that data remains bound by the specific privacy protections and regulations of its originating jurisdiction, regardless of where the collecting organization is located.
Digital commons: a shared, online resource, such as knowledge, software, and data, that is collectively produced, governed, and maintained by a community, intended for public access. Examples include Wikipedia, open source operating systems such as Linux, and Creative Commons licensed content.
Data portability/interoperability: the ability to easily transfer personal data from one service provider to another, or to a personal system, in a structured, machine-readable format. It empowers users to move away from "walled gardens," reducing vendor lock-in and enhancing user autonomy.
Digital dependency: the opposite of digital sovereignty. The inability of people as nations, organizations, and individuals to control their own digital destiny through control over their own data, technology, and infrastructure.
Decentralization: a shift away from relying on centralized, often US-based, corporate platforms toward a distributed, user-centric internet where individuals, communities, and nations maintain control over their data, digital identity, and infrastructure.
End-to-end encryption (e2ee): a secure communication process where only the sender and intended recipient can access, read, or decrypt messages or data.
Fairness (à la the Digital Fairness Act): the absence of deceptive, manipulative, or addictive design practices that distort consumer choice and exploit vulnerabilities.
User sovereignty: the concept that individuals possess absolute control over their personal data, digital identity, and online privacy, rejecting the centralization of power by large technology platforms. It emphasizes user consent, decentralization, and the ability to manage personal data using secure and independent tools.
Because PAX Houston is in the process of becoming a real event you can attend, I gave a ton of interviews this time. That's typically not something I do, for a couple reasons. It started to seem like I had been asked and then subsequently answered every question imaginable, which made me feel like there wasn't a way to be a real person inside that context. Also, there is a type of hostile person who uses the rules and framework of the interview to be a dick and minimizing contact with that kind of thing while I'm doing a show is just good opsec. So coming back into the light after being away put the differences into relief. For example: there are people so young that you can't imagine it.
Joe Birr-Pixton has written a blog post reflecting on a decade of the Rustls TLS-library project and looking ahead to the upcoming 0.24 release and an eventual 1.0 release.
Rustls began with a first commit on May 2, 2016. Progress was quick: a month later, on June 5, it could interoperate with most sites on the web. The first release, 0.1.0, followed on August 27, 2016 – less than four months after the first commit.
[...] From the 0.1.0 release, the project moved through a long series of releases over the following eight years, building out functionality, hardening and refining the API. That sequence of release lines culminated in 0.23, released on February 29, 2024.
The 0.23 release line has been a stable one: in the time since, it has seen 43 non-breaking releases. That stability didn't come with stagnation. The 0.23 line delivered a wide range of important features, including a FIPS-certified cryptography option, certificate compression, Encrypted ClientHello, post-quantum cryptography, and performance improvements.
LibreOffice Base survey results [LWN.net]
Heiko Tietze has published a blog post summarizing the results of a recent survey about the use of LibreOffice's database application, Base. 455 people participated in the survey, including more than 330 who use Base on Linux, with use cases ranging from maintaining records of personal media such as CDs or DVDs to use enterprise-resource planning (ERP) and finance. Of course, users had many ideas how to improve the application:
The majority asks for improvements to the user interface with less clutter and a more attractive design. The workflow and user experience should become either simplified or more powerful, depending on the expertise and the scenario. For example, an elaborate search function is something that many people expect. [...]
Almost the same number of answers requests bug fixes, improvements to stability, and better performance. Issues with queries, forms, and reports were mentioned equally often. In this regard, many replies suggest to remove the Java dependencies.
I spent the day programming in Atlantis. It's buggy, and there are a few missing parts, but most of what I need is there. It's starting to feel like Frontier, which is cool because that's what we're making. And I have my backup tools working on the new machine, praise Murphy.
Dirk Eddelbuettel: RcppXts 0.0.7 on CRAN: Minor Maintenance [Planet Debian]

A new maintenance release 0.0.7 of RcppXts is now on CRAN, and has been built for r2u. The RcppXts package demonstrates how to access the export C API of xts which we contributed a looong time ago. There are by now a more example packages around this C level access to another package, but this one was an early example.
This release is strictly maintenance, updating continuous integration, the README.md file and other packaging conventions adopted since the last release four years ago.
The NEWS entries follow.
Changes in version 0.0.7 (2026-09-09)
Corrected a docstring for the module
Updated continuous integration setup several times
Simplified setup by removing no-longer-needed Makevars
Added badges to README.md
Courtesy of my CRANberries, there is also a diffstat report for this release. For questions, suggestions, or issues please use the issue tracker at the GitHub repo.
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub.
Thorsten Alteholz: My Debian Activities in August 2026 [Planet Debian]
This was my hundred-forty-sixth month that I did some work for the Debian LTS initiative, started by Raphael Hertzog at Freexian.
Unfortunately the number of distributed working hours had been rather low this month, so the list contains much less entries than normal. During my allocated time I uploaded or worked on:
Last but not least I spent days with FD work at the beginning of the month. The last remaining hours I continued to work on cups and hplip. Unfortunately this work did not result in an upload yet.
This month I did not upload any package but just worked on some bugs.
This work is generously funded by Freexian!
This month I worked on new Lomiri Apps. Due to a broken disk, the progress was not as expected. But stay tuned!
Not really related to Lomiri, but to Debian EDU, I fixed an incus related bug in sitesummary.
This work is generously funded by Fre(i)e Software GmbH!
This month I uploaded a new upstream version or a bugfix version of:
Unfortunately I had no time to work in this category this month.
This month I uploaded a new upstream version or a bugfix version of:
This month I uploaded a new upstream version or a bugfix version of:
Cops Play Hide and Seek About Using Spy Tech to Avoid Scrutiny and Bad PR [Deeplinks]
Law enforcement agencies across the country are
increasingly relying on spying technologies—automated
license plate readers (ALPR), cell-site
simulators, and
facial recognition, to name a few--causing
an outcry in many communities where people are rightly concerned
about the threat to civil rights and civil liberties these tools
present.
Some authorities are responding to these concerns
by trying to hide what they’re doing. Police departments are
telling officers not to mention ALPRs when stopping vehicles and
concealing their use of ALPRs to avoid citizens’ public
records requests. Concealing the use of unpopular spying tools
isn’t anything particularly new for law
enforcement—cops have been doing it for years—but
it’s just as wrong now as it was 20 years ago.
These practices prevent the public from knowing
about and questioning how agencies are spending taxpayer dollars on
spying technologies and holding them accountable. This is
especially troubling when many towns are signing contracts with
Flock and other ALPR vendors with little to no public oversight.
The practice also violates disclosure obligations, allows cops and
prosecutors to hide their tactics from judges, and cheats
defendants from being able to challenge the use of evidence
gathered by spy tech from being used against them.
404 Media recently
revealed that in its usage policy for Flock
ALPR cameras, one county in Iowa tells police to keep them a secret
when detaining people: “DO NOT MENTION ALPR
USAGE TO THE OCCUPANTS OF THE VEHICLE,” the policy document
reads. “DO NOT MENTION ALPR USAGE IN YOUR REPORT OR COMPLAINT
UNLESS ABSOLUTELY NECESSARY.” If writing a report about an
incident, police are told to say they used “county
resources” in making a stop instead
of acknowledging use of ALPRs.
In Houston, police officers are likewise
instructed to
“be as vague as
permissible” about why they are using
Flock because the searches they run on Flock’s
surveillance system could be obtained via public records
requests.
There is growing public alarm about the threat to
civil liberties posed by ALPR cameras and reports of police abusing
the tech by
using it to spy on their exes. Some cities
have the cameras
covered up, and others are
cancelling their use of ALPR networks. Two
states have recently
stepped back from ALPRs. This trend is
certainly not lost on law enforcement agencies. Hiding the fact
that they’re using ALPRs from Flock and other vendors is one
way of avoiding scrutiny and bad PR.
But law enforcement and their spy tech vendors
keeping people in the dark about the surveillance technologies
trained on them predates the Flock backlash by decades. For
example, AT&T built a powerful phone surveillance tool for
police, called Hemisphere, in the mid 2000s, and
the company required agencies not to use
evidence gathered by Hemisphere in court unless there was no other
admissible evidence. If evidence obtained through Hemisphere was
used, police were required to recreate it through a traditional
subpoena, a process they called
“parallel construction.” We
called it “evidence laundering.”
Likewise, police and prosecutors have taken
far-reaching steps to hide from the public and courts their use of
cell site simulators, also known as stingrays. Police have used
these devices, which trick cell phones into connecting to them
instead of phone towers to try locating suspects, to obtain
people’s location data without a warrant by
deceptively obtaining basic pen register
orders from courts. Pen register orders are for obtaining call log
data and police don’t need to prove they have probable cause
to get one.
In Baltimore, for example, a judge concluded that
law enforcement had used a standard pen register order
to intentionally hide its use of a Stingray
from the court in violation of its legal disclosure
obligations, leading to a landmark 2015 privacy ruling that cops
need a warrant to use the device.
That didn’t stop police from continuing to
try to pull the wool over the eyes of courts and defense attorneys
when they used stingrays, however. Prosecutors
have
accepted plea deals to hide their use of
cell-site simulators and have even
dropped cases rather than reveal information
about their use of the technology. U.S. Marshalls have
driven files hundreds of miles to thwart
public records requests.
Fortunately, our commitment to shining a light on
the use of surveillance tech is just as strong, if not stronger,
than law enforcement’s quest to hide it. We’re working
with privacy advocates and community groups to bring awareness
about existing and emerging spy tools that threaten civil liberties
and we’re encouraging policymakers and lawmakers to do more
to restrain warrantless mass surveillance and stop it before it
ever takes hold.
If you're curious about whether your local police have contracts for ALPRS or other surveillance technologies, you can search EFF's Atlas of Surveillance.
2026 EFF Award Winners: Access Now, 7amleh – The Arab Center for the Advancement of Social Media, DeFlock, and New Media Rights [Deeplinks]
EFF is pleased to announce that Access Now, 7amleh – The Arab Center for the Advancement of Social Media, DeFlock, and New Media Rights have received 2026 EFF Awards for their vital work in ensuring that technology supports freedom, justice, and innovation for all people.
The EFF Awards recognize specific and substantial technical, social, economic, or cultural contributions in diverse fields including journalism, art, digital access, legislation, technology development, and law.
For the past 30 years, the EFF Awards—previously known as the Pioneer Awards—have recognized and honored key leaders in the fight for freedom and innovation online. Started when the internet was new, the Awards now reflect the fact that the online world has become both a necessity in modern life and a continually evolving set of tools for communication, organizing, creativity, and increasing human potential.
Supporting a global community advancing digital rights, defending digital access in crisis zones, empowering communities to take action against surveillance, and providing free legal assistance for creators and consumers to fight back against digital threats are high callings that help bring about a better tech future for all. We are pleased to honor these organizations with 2026 EFF Awards.

Access Now, founded in 2009 as an emergency response team helping Iranian activists get back online and communicate safely, has grown into one of the world’s foremost organizations defending and extending the digital rights of people and communities at risk and supporting the global fight against technological repression.
Its 24/7 Digital Security Helpline offers real-time, direct technical assistance and advice to civil society groups and activists, media organizations, journalists and bloggers, and human rights defenders. It provides grants to frontline organizations working with people and communities most impacted by digital rights violations. It educates decision makers and pressures the powerful. And it organizes RightsCon, a leading annual summit on human rights in the digital age, where activists, technologists, policymakers, business leaders, journalists, philanthropists, researchers, and artists can connect, collaborate, and drive change at the intersection of human rights and technology.
7amleh - The Arab
Center for the Advancement of Social Media
protects and expands digital access and rights for
Palestinians and across the MENA region. The nonprofit investigates
and monitors challenges to digital rights, focusing on internet
access, privacy, freedom of expression and association online. It
builds the capacity of activists, human rights defenders, and civil
society organizations to provide training about digital rights,
gender sensitive digital security, and effective online
advocacy.
7amleh also advocates for changes to the digital rights policies and practices of governments, corporations and other influential institutions and individuals locally, regionally and internationally. It plans and manages advocacy and awareness-raising campaigns and builds networks and coalitions to promote access to safe, fair and free online spaces. For example, 7amleh has led the #ReconnectGaza campaign, supported by dozens of international NGOs including EFF, to restore full internet access in Gaza – a crucial lifeline for residents, journalists, activists, and first responders.

DeFlock is an open-source, volunteer-powered project that maps surveillance devices across the world, helping communities hold their governments and surveillance vendors accountable and understand where and how they're being watched. Founded in 2024 by software engineer and privacy advocate Will Freeman, DeFlock shines a light on the widespread use of automated license plate reader (ALPR) technology and the threats it poses to personal privacy and civil liberties.
DeFlock resources help people request public records, speak to local lawmakers, and take action against ALPR surveillance. Its work has helped foster a national grassroots community of anti-surveillance activists fighting back against this dangerous surveillance technology.

New Media Rights (NMR) is a San Diego-based nonprofit program of California Western School of Law dedicated to defending digital rights through legal services, education, and public policy advocacy. Since its inception, NMR has been at the forefront of protecting creators, entrepreneurs, and internet users from digital threats such as copyright abuse, online harassment, and privacy violations.
In addition to providing free legal assistance, NMR has produced hundreds of freely available video and written legal education guides for creators and consumers, including the Fair Use App for filmmakers and video creators. It has participated in regulatory proceedings on net neutrality, Digital Millennium Copyright Act anti-circumvention, and copyright reform. Its work has also helped support access to public information and greater business and government accountability.
[$] Typst makes big strides [LWN.net]
Typst is a system for typesetting documents into various formats: PDF, SVG, PNG, and, in progress, HTML. It is adept at handling technical material, and is often considered to be an eventual LaTeX replacement. We last looked in on Typst a year ago, when it had reached version 0.13. A new version, 0.15, was released in June with lots of new features, including support for variable fonts, MathML, multiple bibliographies, and more. Typst is free, Apache-2.0-licensed software, programmed in Rust.
New Records Reveal Problems with Medicare’s AI Prior Authorization Experiment [Deeplinks]
EFF sued the government back in March for information about the Wasteful and Inappropriate Service Reduction (WISeR) model, a new Medicare program that uses AI to evaluate prior authorization requests for certain medical services. Today, we’re releasing approximately 1,000 pages of records obtained from the Centers for Medicare & Medicaid Services (CMS) through this litigation, including contracts with tech companies, internal status reports and providers’ complaints about the program. The documents (available here) show that WISeR has resulted in widespread delays and denials of care, operational chaos, and reports of patient harm.
EFF filed the FOIA lawsuit to gain badly needed transparency into an experimental AI program that could jeopardize Medicare beneficiaries' access to care. In January 2026, CMS launched the WISeR model, subjecting seniors in six states to AI-driven prior authorization decisions. Medical providers must now request permission before delivering certain medical treatments if they want assurance that Medicare will cover them. Private companies contracted by CMS evaluate the requests using AI. In the absence of rigorous safeguards, AI-driven prior authorization determinations can lead to unwarranted—and even discriminatory—delays or denials of necessary medical care.
Little is known about the AI systems that WISeR vendors are using to process prior authorization requests. Although CMS says that a qualified human clinician must review all denials, research has shown that AI-generated recommendations often influence human decisions. And the design of the WISeR program creates a financial incentive for vendors to deny care, since they are paid for averted expenditures. Just months after the program launched, medical providers reported improper denials, administrative friction, and lengthy delays that have left patients waiting in pain.
EFF’s FOIA request sought records pertaining to the CMS contracts with WISeR software vendors; any tests for accuracy, bias, or hallucinations in vendors' technology; and any audits, monitoring, or evaluation of WISeR and participating vendors.
CMS records obtained by EFF echo issues that medical providers, patient advocates, and lawmakers have warned about since WISeR began. This includes long wait times, rampant technical failures, inappropriate denials, and harm to patients.
CMS publicly states that WISeR vendors should respond to prior authorization requests within 72 hours, but records received by EFF show widespread delays. Internal status reports from the first few months of the program show that WISeR vendors failed to respond within 72 hours for a significant number of requests. One status report cites a prior authorization request that went unanswered for 83 days ("WISeR FOIA Response - Combined Records," page 234). These delayed responses can have serious consequences for patients. Medical providers reported that WISeR has delayed medically necessary care and left patients in pain as they waited for approvals.
Timeliness data for two WISeR vendors in January 2026 show that a significant number of requests did not receive a response within 72 hours (WISeR FOIA Response - Combined Records, page 410)
The released records confirm that WISeR’s payment methodology creates a financial incentive to deny care. Specifically, WISeR vendors are paid for requests that they deny (though not for denials reversed on appeal). This profit motive aggravates the risk that AI-assisted decision-making may unfairly deprive people of the services they need.
CMS publicly claims that it safeguards against inappropriate denials by tying vendors’ payment rates to “quality scores,” which reflect the timeliness and accuracy of vendors’ decisions. However, the recently released WISeR Data Reporting Guide shows that low quality scores reduce payments by only 5-10%.
Impact of low quality scores on payment rates described in the WISeR Data Reporting Guide (WISeR FOIA Response - Combined Records, page 99)
Documents obtained by EFF appear to support reports that WISeR vendors may be denying claims at unusually high rates. Two companies alone denied over 20,000 prior authorization requests in the first 3 months of the program. One company, Virtix, the vendor that CMS required to submit a Corrective Action Plan, denied more requests than it approved during this time period.
Prior authorization decision data for two vendors in a March 30th, 2026 status report (WISeR FOIA Response - Combined Records, page 322)
Feedback from medical providers emphasize that WISeR delays have harmed patients. The released records include March 2026 responses to a feedback form about Innovaccer, the WISeR vendor processing requests for Ohio. Medical providers complained about a lack of communication, administrative issues, and long response times. Several responses emphasize that long response times from the WISeR vendor harmed patients ("WISeR FOIA Response - Feedback Survey Responses"):
“We have patients calling our offices crying in pain because their procedures are being delayed while awaiting approvals or guidance tied to this model. A 3–4 day delay for necessary pain procedures is already difficult for vulnerable patients, but when providers cannot obtain answers for weeks, the situation becomes unacceptable.”
“I HAVE HAD TO WATCH 3 PATIENTS CRY AT BEDSIDE FOR NOT HEARING BACK ON THEIR PRIOR AUTH FOR KYPHOPLASTY/VERTABRAL AUGMENTIATION PROCEDURE. THESE PATIENTS ARE IN DEEP PAIN.”
“I have had cases submitted and waiting over 1 1/2 months for a UTN to be generated… In the meantime patients are having to be cancelled for surgeries they need. This is not acceptable they are severely hindering patient care.”
CMS WISeR launched in January 2026, just six months after it was announced. Despite warnings from both medical providers and a vendor about insufficient preparation time, CMS chose not to delay the launch.
Approximately a month before the launch, one of the vendors, Innovaccer, alerted CMS that it intended to go live with a version of its software that lacked full functionality and had not been fully tested. It cited several barriers to going live with full functionality, including changing requirements and expectations, unclear governance processes, and lack of time for end-to-end testing with the provider community ("WISeR FOIA Response - Combined Records,", page 216-217). Innovaccer said it would auto-affirm all prior authorization requests until it could develop full functionality and explained that “Given CMS's decision not to delay the model start date, auto-affirming is the only path available” ("WISeR FOIA Response - Combined Records," page 217).
Innovaccer had not yet finished developing or testing some features several months into the program, according to an April 2026 status report. Innovaccer was not the only vendor who faced technical challenges before and after WISeR launched. Weekly status reports and provider feedback in the released records show widespread challenges associated with WISeR’s rushed rollout (for example, "WISeR FOIA Response - Combined Records," pages 238 and 383).
A status report from April 6th, 2026 describes issues with incomplete solutions from Innovaccer (WISeR FOIA Response - Combined Records, page 200)
In its first year, the WISeR model introduced prior authorization requirements for a set of 13 medical services. The June 2025 Innovation Center Investment Plan for WISeR lists medical services that could be added to the program in future years. This planning document considers the possibility of adding services “where prior authorization would have to be done on a more urgent or emergent basis,” including air ambulance transport, cancer treatment, MRI scans, and medications without publicly available coverage criteria.
A planning document from June 2025 lists ideas for the expansion of WISeR to additional medical services (WISeR FOIA Response - Combined Records, page 22)
CMS continues to produce records in response to EFF’s lawsuit. Records released thus far echo concerns that providers have raised since WISeR launched, including long delays, financial incentives to deny care, and technical problems. But important questions remain about the AI systems private companies are using to inform decisions about whether to provide people with Medicare benefits. As CMS continues to produce documents, we will continue to make them available to the public. The public deserves to know how AI is driving decisions that affect patients’ access to care.
Scott Hanson is
working on a WordPress plugin that displays a FeedLand river
— a constantly-updated stream of news items from feeds a
FeedLand user subscribes to — on a WordPress page or post.
It's important that news flows be part of managing a site. Help
Scott make it perfect, WordPress is a very important part of the
web. The news tab on
my blog home page is an example.
Ideas for Bluesky and WordPress re Markdown and AI systems.
Claude Code massively stinks at explaining things in a way that a human can understand.
People love Markdown because it's impossible to hide the crud. And because people love it so much it's become a favorite of tech startups esp in the AI field. This is a great turn of events. We're getting back to the web, and the sooner everyone gets on board, the better. I've written about this for a few years. Everything in Markdown, it's just enough HTML. And with that, Microsoft has created a white flag, here's all your data dear users, in Markdown, to use as you please. Instead of waiting for Claude et al to start reading Word files.
On the other hand, why doesn't Microsoft, which owns GitHub, make a Slack-alike tool that manages the issues sections on repos. Slack is a big part of the default communication system, but GitHub is another very big part. And the great thing is that both these products, owned by huge tech companies, have an ethos of open APIs. Every part replaceable. Small pieces loosely joined. We are family.
A big corner-turn in the Atlantis project, the new version of Frontier for modern OSes, in development, just me and Claude Code so far. We now have the means to update the root, so anything implemented in script code can be released without fuss, using RSS of course. We're using a method that will be new to almost everyone, something I created in 2014 or so (will check) called codecasting. And today I did all my programming work in Atlantis. It feels just like Frontier, with a future. ;-)
The following article originally appeared on Sean Goedecke’s blog and is being republished here with the author’s permission.
In the 2010s, if you had technical gaps (say, you couldn’t write CSS), you had to either rely on a skilled colleague or just hope that the answer to your exact problem was out there on the internet. Today, everyone can write sort-of-okay CSS by delegating the task to an LLM. LLMs make everybody into a generalist.
Because of this, lots of people don’t think there’s any skill involved in working with LLMs. If you want the product that LLMs can deliver—PhD-level mathematics, pretty good but sometimes tasteless computer code, or awkward LinkedIn-style writing—you can simply ask for it. Since everyone is talking to the same models, “skilled prompters” are getting the same results as people touching LLMs for the first time.
This is wrong. The most important skill in prompting is expertise in the domain you’re prompting for.
A good illustration of this is Terence Tao’s conversation with ChatGPT about the recently discovered counterexample to the Jacobian conjecture. This is not the same ChatGPT I talk to! I couldn’t get to where Tao gets, even with unlimited tokens to burn.
There’s a lot to learn about good prompting from Tao’s conversation. Here are a few observations:
However, you can’t prompt like Tao on mathematical questions just by following these tips. The key to his technique is actually understanding the mathematics: pulling the relevant idea out of ChatGPT’s multiparagraph response, suggesting alternate approaches or formulations, and identifying what “looks weird.”
Terence Tao is a better mathematician than I am a programmer. But the idea here—that domain knowledge makes you better at using LLMs—is something I’ve also experienced in my own work. If you have a good theory of your codebase, you can push the LLM much harder than if you have no familiarity. Because you have your own sense of what a good solution might look like, you can say, “No, I think it could be simpler here” or “But don’t we already do X?” or “Can we express this problem in these familiar terms?”
This touches on an idea I’ve written about before: that system design problems are dominated by concrete specifics, not generic principles. Of course both are useful, but I’d rather have familiarity with the codebase than a deep general understanding of software systems. In his conversation, Terence Tao asks a lot of specific questions like “Does X work here?” or “Given Y and Z, why A?” I can’t ask those questions about the Jacobian conjecture, but I can ask them about the systems I own at GitHub.
If you have no domain knowledge, you can cling onto the LLM to at least get something. That’s not bad! But if you have domain knowledge, you can wring far more value out of the same LLM by steering it hard in the direction you want. Most of us will have to do a mix of both these approaches, since we have domain knowledge in some areas but not others.
The usefulness of domain knowledge suggests that human expertise will continue to be useful even as models get stronger. For many tasks, the human is the bottleneck, not the model, because the difficult part is in communicating to the model exactly what kind of solution the human wants. The information is “in the model” already, but it takes a very smart human to pull it out.
This post got many comments on Hacker News. Some commenters share their anecdotes about how expertise has helped and lack of expertise has hurt. Other commenters say it’s plausible, but they have a sensible suspicion of a view that’s reassuring them about how they’re still valuable. I agree with that, though I suspect by the time we get around to studying this, the landscape will have changed under our feet again. Some commenters point out that OpenAI’s math prompts were inexpert, and so expertise isn’t required. Here I’d respond that OpenAI does have a team of expert mathematicians that checked and filtered the model’s suggested discoveries, and that you cannot currently skip that step.
Zero to Agent in 30 Minutes: Build a Supply Chain for Agent Context with Maxim Salnikov [Radar]
We still haven’t solved the problem of keeping track of everything we’re feeding our AI agents. Developers now install agent skills, instructions, and other customizations from public repositories by the dozens, and those files end up scattered across user profiles, application folders, and codebases with no record of where they came from or whether they’ve changed since they were first installed.
In this episode of Zero to Agent in 30 Minutes, Microsoft senior solution engineer Maxim Salnikov walked through the Agent Package Manager (APM), a terminal-driven open source product from Microsoft that treats agent context the way modern software already treats its dependencies: versioning it, pinning it, and checking it before it ships. It’s a technical session, but rather than building an AI agent, you’ll discover how to manage all the customizations you’ve installed for your agents and make them portable, secure, and governed by policies you or your company define.
Maxim demoed the process of setting up and using APM step-by-step. Here’s how it works.
apm
init sets up an apm.yaml file targeting one or
more harnesses, such as GitHub Copilot, Claude Code, or Cursor, and
apm install pulls a skill from a repository into the
right location for each one. When you install a skill, APM also
creates a log file documenting the entire resolution history.apm install
--frozen rebuilds that exact environment from the log
instead of reresolving everything from apm.yaml, so a
teammate’s machine ends up with precisely the same setup as
yours.apm audit command checks installed customizations
against policy and catches unauthorized sources or content that has
changed since installation. Run that same audit as a gate in a
CI/CD pipeline to protect the entire organization against skill
drift and bad actors.The software supply chain already has decades of tooling behind it. That discipline hasn’t caught up with agentic AI, but APM is attempting to close that gap. Explore the project GitHub repo and get started.
On September 9, Menyala’s Sajal Sharma joins Zero to Agent in 30 Minutes to build a shared knowledge base that acts as a common brain across agents. He’ll show how a single repository of research, daily logs, and notes can give Claude Code, Codex, OpenClaw, and Hermes access to the same accumulated information instead of starting from zero with every new session.
Follow along with Zero to Agent in 30 Minutes on Radar, or watch the latest episode on YouTube, Spotify, Apple, or wherever you get your podcasts. If you’re an O’Reilly member, you can watch live. Save your seat.
Experience Mapping Matters More the Faster You Move [Radar]
AI is changing how fast organizations can move. Ideas that used to take months to build now take days, sometimes hours. That sounds like good news, and it is. But it creates a new problem. When execution is fast, teams can move in many directions at once. Marketing can ship a new campaign, product can deliver a new feature, and support can change its scripts with the blink of an eye. Each team moves quickly and independently, because they can. Sure, activity moves quickly. But there’s also the chance of chaos. When everyone can move fast on their own, the need for people to move together only grows. Collaboration, cocreation, and alignment need to increase, not decrease, as execution speed increases.
AI and real-time data give organizations more information than ever. Dashboards. Live metrics. Instant customer feedback. All of it moving fast. But data doesn’t make decisions. People do. A dashboard can tell you that cart abandonment jumped 12% this week. It can’t tell you why, and it definitely can’t tell your marketing, product, and support teams what to do about it together. That takes a conversation. It takes people in a room—virtual or real—looking at the same thing, arguing about what it means and what to do next.
Experience mapping is a broad field of visualizing human experiences. You’re probably familiar with things like journey maps, service blueprints, and other similar diagrams of the experiences. But none of them hand you an answer. What they do is take a fast-moving, chaotic situation and freeze it for a moment. It gives a team something to point at, argue about, and align around.
Picture a typical working session. People from different parts of the business sit down around a map of the customer experience. Each of them already knows a piece of the picture. None of them has the whole picture, together, at the same time. That’s what the map provides. That’s usually the moment something surprising surfaces. Not because the map contains secret information but because it puts scattered knowledge in one place, in front of the people who each hold a piece of it. The visual aspect of maps is critical. Laying out an abstract concept like a “customer experience” allows teams to engage with it in new ways and reach new conclusions that are hard to get from a spreadsheet or data alone. Grasping cause and effect in one visual overview helps teams find the patterns of behavior that matter the most and to conceive of viable interventions.
AI can surface these kinds of patterns in seconds. But it cannot create the moment when a cross-functional team collectively recognizes how its silos are hurting customers. Only people, looking at the same picture, can do that.
Some claim that journey mapping is dead. That static maps can’t keep up with real-time data and AI-driven personalization. This confuses the artifact with the activity. A map that gets built, presented once, and filed away never helps anyone. It fails for the same reason a report fails: Nobody’s talking about it anymore. The value was never in the diagram. It’s in the conversation the diagram makes possible.
Take how I got that team to reach their own conclusions about the invoice problem rather than just telling them about it. After scoping the customer type and situation we wanted to understand better, I interviewed a dozen or so customers about their billing experience. Nothing unusual came up at first. People described the routine steps: get an invoice, check it, pay it. But a few mentioned something in passing. They’d disputed a charge and kept getting late payment warnings anyway, even while the dispute was still open.
From those interviews, I built a draft map of the invoicing journey. I called it a draft on purpose. I didn’t want to hand stakeholders a finished diagram and ask them to approve it. I wanted them to lean into it, question it, and add to it. Then I scheduled a working session. The room included people who’d never worked together before, despite being at the same company for years: billing, support, and product.
We didn’t rush through the map. We slowed down, section by section, and used structured exercises to pinpoint the moments that mattered most to customers. That’s when someone in the room realized: A customer who’s actively disputing an invoice can still get a warning notice for that same invoice. Nobody had designed it that way on purpose. It fell through the gap between two systems that never talked to each other. But once it was visible, laid out in front of the people who owned each part of the process, it became impossible to ignore. The room got quiet, then loud. People were genuinely upset, not at each other, but at what customers were going through.
Of course, I had uncovered this already in my research. And sure, it was also visible on the map. But my diagram wasn’t about giving a magic answer. The process of learning together is the point. That reaction didn’t come from a dashboard. It came from people confronting the evidence together, in the same room, at the same time.
Before the workshop, this problem was invisible in a specific way. Support knew customers complained about warning notices. Billing knew disputes existed. Product knew the systems didn’t sync. But no one held all three pieces at once. The map put all three in the same field of view. That’s the mechanism. Mapping doesn’t create new information. It puts existing, scattered information into one shared picture, at the same time, in front of the people who each hold a piece of it.
What changed after that: Billing and product agreed to flag disputed invoices so no warning could go out. Support got a way to check dispute status before responding to a complaint. And the three teams kept meeting monthly, something none of them had done before. The map didn’t do any of that. The conversation the map created did.
We started with customer evidence and a deliberately unfinished map. We included people who owned different parts of the experience and asked them to question what the map showed, identify what they knew and what they were assuming, and examine the gaps between their systems. The session ended with specific commitments, and the teams continued meeting as they learned more.
That is what getting collaboration right requires: the right people, shared evidence, visible disagreement, clear ownership of the next decision, and a cadence for revisiting what the team thinks it knows. Without those conditions, mapping can easily become another workshop that produces an attractive artifact but little change.
As AI speeds up execution, don’t cut the time you spend aligning as a team. Protect it. Expand it. AI won’t give you an edge. Your competitors have access to the same models you do, trained on much of the same data, producing much of the same output. If everyone moves at the same speed, speed stops being an advantage. It becomes the minimum bar for staying in the game. AI also works like a spotlight, amplifying whatever’s already happening in your organization. If your teams collaborate well, AI makes that strength visible fast. If they’re siloed, AI exposes it just as fast. Now is the time to get collaboration right, while staying focused on the customer. Waiting until AI forces the issue is waiting too long.
In the end, AI can help with customer discovery and accelerate insights. But it doesn’t replace human judgment and decision making. Rallying around a map—a visual depiction of customer experiences—provides a natural forum for discussion, debate, and shared understanding to align before acting. The tools will keep getting faster. The organizations that win won’t be the ones with the best dashboards. They’ll be the ones who are best at coming together, again and again, to make sense of what those dashboards show them.
If you want to dive deeper into
mapping, join Jim on October 9 for his Beyond the Book conversation
about the latest edition of Mapping Experiences. He and host
Vicki Reyzelman will chat about how experience mapping has evolved
from a UX technique into a strategic capability for organizations,
how AI is transforming the way we create and analyze maps, and how
you can use mapping to align business goals with customer needs,
facilitate collaboration across teams, and drive transformation at
scale. It’s free to attend. Register now.
CodeSOD: Asynchronous Directories [The Daily WTF]
Eri has a mix of a "true confession" and a "wait, really?" today.
The programming language Vala bills itself as a C# like language that compiles into something pretty close to C performance, designed specifically for writing code against Gnome and its associated libraries.
One of the C#-isms in brings in is async/await type semantics.
You can yield someAsyncFunction(), which returns
control to the caller, allowing it to proceed until the
yielded function returns an actual value.
Because it has asynchronous functions, many library functions
for handling I/O are already async. So you can
make_directory_async, which yields control so you can
keep executing while waiting for the filesystem to make your
directory.
There are also synchronous versions of those methods. And then
there's create_directory_with_parents, which will
create a chain of directories for you. That's the synchronous
version, and Vala's core library has decided not to
provide an asynchronous version of it, which is my "wait, really?"
I suspect it's really about the race conditions involved and the
risks of things going wrong while doing it asynchronously; all
solvable problems, but tricky ones to solve.
But it's the problem Eri had, and this is their solution:
/// Note: does not throw if target already exists
async void create_directory_with_parents_async(File file, Cancellable? cancellable = null) throws Error {
var to_create = new File[0];
var? current_target = file;
while(current_target != null) {
try {
yield current_target.make_directory_async(Priority.DEFAULT, cancellable);
} catch(IOError.NOT_FOUND e) {
to_create += current_target;
current_target = current_target.get_parent();
continue;
} catch(IOError.EXISTS e) {
break;
}
break;
}
for (int i = to_create.length - 1; i >= 0; --i) {
try {
yield to_create[i].make_directory_async(Priority.DEFAULT, cancellable);
} catch(IOError.EXISTS e) {
// Created by another process
}
}
}
If I'm reading this correctly, we start by trying to create the
full path to our leaf node. If there's a not found error, we go up
one level and try and create that one. We keep trying that until we
either run out of parent nodes to try against, or we hit a
directory that already exists, or we successfully create a
directory. All along the way, we keep appending the
current_target to our to_create
array.
Once we've gotten that baseline, we then iterate across our
to_create array, backwards, creating the shortest
non-existent paths first.
This works, but it's ugly as sin. Mostly, it's ugly because we're using exceptions for flow control instead of doing things like checking for file existence, though I suppose those checks may also break our goal of doing all our I/O operations in an async context. I don't know enough about Vala to know the better way of doing this.
Eri writes:
The function works as intended, but trying to trace control flow through the first loop is not pleasant. Ironically, the C mechanism Vala wraps is slightly advanced error codes, which would be nicer to work with in this case
Eri also provides a slightly re-worked version of the main loop, that is at least a bit easier to follow, but still an ugly approach:
while(current_target != null) {
try {
yield current_target.make_directory_async(Priority.DEFAULT, cancellable);
break;
} catch(IOError.EXISTS e) {
break;
} catch(IOError.NOT_FOUND e) {
to_create += current_target;
current_target = current_target.get_parent();
}
}
Still, since this is an attempt to patch over a missing core library method and solve a tricky problem about how to handle race conditions, I think absolution is reasonable. It's ugly, it's weird, but it does the job. Go hide it in a box, and never touch its implementation again- except to make it go away.
From our anonymous submitter:
Having reached the end of the road at a company increasingly swallowed up companies further east which you'd never believe were still afloat, I found myself headhunted for certain specialty software skills. I was reaching the final few years of my expected working span, so I jumped at the chance. The money was (to me at that time) spectacularly good, so I jumped into it.
It started when my first day was spent by me being sent home for the weeks it was still going to take to onboard me. Not bad, engaged to wait, as it were, and the first 6 months was thus and so.
The man who had interviewed me, call him Fred, was intelligent and urbane, and was a joy to meet. He and I clearly hit it off, and lo and behold I was in. It was he who gave me my first assignment, which was mathematical analysis of their core milk-cow program because they needed to find out what it did, and how it did it, so they could perhaps implement it in a more contemporary language.
So I did that, and was just about to publish my findings with him, when Fred inconveniently dropped dead suddenly. In the what-are-we-going-to-do-now-our-key-man-is-no-more confusion, we contractors were forgotten.
For the next 18 months or so (may have been more, may have been less) I was more or less ignored. I spent the time writing a development environment to work on any part of the program conveniently, all the while sitting next to a man who was constantly, forcefully and repetitiously speaking ill of the managers in his line structure. The ridiculously garrulous boss who inherited me thought little of me, and handed me the little work that came my way with active hostility. One or two good guys, but mostly a cabal of elderly men trying to preserve their little money-spinner as long as they could, and a johnny-come-lately trying to increase (and even introduce) automatic processes was less than welcome. During that time I spent quite some time on TDWTF, submitting a gem or two here and there.
No surprise when they finally kicked my arse away. No love lost there. Now working my last couple of years to retirement as a postie.
No punchline here. I want you to know that dropping dead from work is all too real and can happen to anyone.
Best of…: Classic WTF: A Dumbain Specific Language [The Daily WTF]
It's a holiday here in the US, a celebration of labor, so we're reaching back through the archives for a story about an attempt to be labor saving that was not successful. Original. --Remy
I’ve had to write a few domain-specific-languages in the past. As per Remy’s Law of Requirements Gathering, it’s been mostly because the users needed an Excel-like formula language. The danger of DSLs, of course, is that they’re often YAGNI in the extreme, or at least a sign that you don’t really understand your problem.
XML, coupled with schemas, is a tool for building data-focused DSLs. If you have some complex structure, you can convert each of its features into an XML attribute. For example, if you had a grammar that looked something like this:
The Source specification obeys the following syntax
source = ( Feature1+Feature2+... ":" ) ? steps
Feature1 = "local" | "global"
Feature2 ="real" | "virtual" | "ComponentType.all"
Feature3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"
Feature4 = "first" | "last" | "DayAllocation.all"
If features are specified, the order of features as given above has strictly to be followed.
steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps
oneOrMoreNameSteps = nameStep ( "." nameStep ) *
zeroOrMoreNameSteps = ( nameStep "." ) *
nameStep = "#" name
name is a string of characters from "A"-"Z", "a"-"z", "0"-"9", "-" and "_". No umlauts allowed, one character is minimum.
componentSteps is a list of valid values, see below.
Valid 'componentSteps' are:
- GlobalValue
- Product
- Product.Brand
- Product.Accommodation
- Product.Accommodation.SellingAccom
- Product.Accommodation.SellingAccom.Board
- Product.Accommodation.SellingAccom.Unit
- Product.Accommodation.SellingAccom.Unit.SellingUnit
- Product.OnewayFlight
- Product.OnewayFlight.BookingClass
- Product.ReturnFlight
- Product.ReturnFlight.BookingClass
- Product.ReturnFlight.Inbound
- Product.ReturnFlight.Outbound
- Product.Addon
- Product.Addon.Service
- Product.Addon.ServiceFeature
In addition to that all subsequent steps from the paths above are permitted, that is 'Board',
'Accommodation.SellingAccom' or 'SellingAccom.Unit.SellingUnit'.
'Accommodation.Unit' in the contrary is not permitted, as here some intermediate steps are missing.
You could turn that grammar into an XML document by converting
syntax elements to attributes and elements. You could do that, but
Stella’s predecessor did not do that. That
of course, would have been work, and they may have had to
put some thought on how to relate their homebrew grammar to XSD
rules, so instead they created an XML schema rule for
SourceAttributeType that verifies that the data in the
field is valid according to the grammar… using regular
expressions. 1,310 characters of regular expressions.
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="(((Scope.)?(global|local|current)\+?)?((((ComponentType.)?
(real|virtual))|ComponentType.all)\+?)?((((Hierarchy.)?(self|ancestors|descendants))|Hierarchy.all)\+?)?
((((DayAllocation.)?(first|last))|DayAllocation.all)\+?)?:)?(#[A-Za-z0-9\-_]+(\.(#[A-Za-z0-9\-_]+))*|(#[A-Za-z0-
9\-_]+\.)*
(ThisComponent|GlobalValue|Product|Product\.Brand|Product\.Accommodation|Product\.Accommodation\.SellingAccom|Prod
uct\.Accommodation\.SellingAccom\.Board|Product\.Accommodation\.SellingAccom\.Unit|Product\.Accommodation\.Selling
Accom\.Unit\.SellingUnit|Product\.OnewayFlight|Product\.OnewayFlight\.BookingClass|Product\.ReturnFlight|Product\.
ReturnFlight\.BookingClass|Product\.ReturnFlight\.Inbound|Product\.ReturnFlight\.Outbound|Product\.Addon|Product\.
Addon\.Service|Product\.Addon\.ServiceFeature|Brand|Accommodation|Accommodation\.SellingAccom|Accommodation\.Selli
ngAccom\.Board|Accommodation\.SellingAccom\.Unit|Accommodation\.SellingAccom\.Unit\.SellingUnit|OnewayFlight|Onewa
yFlight\.BookingClass|ReturnFlight|ReturnFlight\.BookingClass|ReturnFlight\.Inbound|ReturnFlight\.Outbound|Addon|A
ddon\.Service|Addon\.ServiceFeature|SellingAccom|SellingAccom\.Board|SellingAccom\.Unit|SellingAccom\.Unit\.Sellin
gUnit|BookingClass|Inbound|Outbound|Service|ServiceFeature|Board|Unit|Unit\.SellingUnit|SellingUnit))"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
There’s a bug in that regex that Stella needed to fix. As she put it: “Every time you evaluate it a few little kitties die because you shouldn’t use kitties to polish your car. I’m so, so sorry, little kitties…”
The full, unexcerpted code is below, so… at least it has documentation. In two languages!
<xs:simpleType name="SourceAttributeType">
<xs:annotation>
<xs:documentation xml:lang="de">
Die Source Angabe folgt folgender Syntax
source = ( Eigenschaft1+Eigenschaft2+... ":" ) ? steps
Eigenschaft1 = "local" | "global"
Eigenschaft2 ="real" | "virtual" | "ComponentType.all"
Eigenschaft3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"
Eigenschaft4 = "first" | "last" | "DayAllocation.all"
Falls Eigenschaften angegeben werden muss zwingend die oben angegebene Reihenfolge der Eigenschaften eingehalten werden.
steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps
oneOrMoreNameSteps = nameStep ( "." nameStep ) *
zeroOrMoreNameSteps = ( nameStep "." ) *
nameStep = "#" name
name ist eine Folge von Zeichen aus der Menge "A"-"Z", "a"-"z", "0"-"9", "-" und "_". Keine Umlaute. Mindestens ein Zeichen
componentSteps ist eine Liste gültiger Werte, siehe im folgenden
Gültige 'componentSteps' sind zunächst:
- GlobalValue
- Product
- Product.Brand
- Product.Accommodation
- Product.Accommodation.SellingAccom
- Product.Accommodation.SellingAccom.Board
- Product.Accommodation.SellingAccom.Unit
- Product.Accommodation.SellingAccom.Unit.SellingUnit
- Product.OnewayFlight
- Product.OnewayFlight.BookingClass
- Product.ReturnFlight
- Product.ReturnFlight.BookingClass
- Product.ReturnFlight.Inbound
- Product.ReturnFlight.Outbound
- Product.Addon
- Product.Addon.Service
- Product.Addon.ServiceFeature
Desweiteren sind alle Unterschrittfolgen aus obigen Pfaden erlaubt, also 'Board', 'Accommodation.SellingAccom' oder 'SellingAccom.Unit.SellingUnit'.
'Accommodation.Unit' hingegen ist nicht erlaubt, da in diesem Fall einige Zwischenschritte fehlen.
</xs:documentation>
<xs:documentation xml:lang="en">
The Source specification obeys the following syntax
source = ( Feature1+Feature2+... ":" ) ? steps
Feature1 = "local" | "global"
Feature2 ="real" | "virtual" | "ComponentType.all"
Feature3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"
Feature4 = "first" | "last" | "DayAllocation.all"
If features are specified, the order of features as given above has strictly to be followed.
steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps
oneOrMoreNameSteps = nameStep ( "." nameStep ) *
zeroOrMoreNameSteps = ( nameStep "." ) *
nameStep = "#" name
name is a string of characters from "A"-"Z", "a"-"z", "0"-"9", "-" and "_". No umlauts allowed, one character is minimum.
componentSteps is a list of valid values, see below.
Valid 'componentSteps' are:
- GlobalValue
- Product
- Product.Brand
- Product.Accommodation
- Product.Accommodation.SellingAccom
- Product.Accommodation.SellingAccom.Board
- Product.Accommodation.SellingAccom.Unit
- Product.Accommodation.SellingAccom.Unit.SellingUnit
- Product.OnewayFlight
- Product.OnewayFlight.BookingClass
- Product.ReturnFlight
- Product.ReturnFlight.BookingClass
- Product.ReturnFlight.Inbound
- Product.ReturnFlight.Outbound
- Product.Addon
- Product.Addon.Service
- Product.Addon.ServiceFeature
In addition to that all subsequent steps from the paths above are permitted, that is 'Board', 'Accommodation.SellingAccom' or 'SellingAccom.Unit.SellingUnit'.
'Accommodation.Unit' in the contrary is not permitted, as here some intermediate steps are missing.
</xs:documentation>
</xs:annotation>
<xs:union>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="(((Scope.)?(global|local|current)\+?)?((((ComponentType.)?(real|virtual))|ComponentType.all)\+?)?((((Hierarchy.)?(self|ancestors|descendants))|Hierarchy.all)\+?)?((((DayAllocation.)?(first|last))|DayAllocation.all)\+?)?:)?(#[A-Za-z0-9\-_]+(\.(#[A-Za-z0-9\-_]+))*|(#[A-Za-z0-9\-_]+\.)*(ThisComponent|GlobalValue|Product|Product\.Brand|Product\.Accommodation|Product\.Accommodation\.SellingAccom|Product\.Accommodation\.SellingAccom\.Board|Product\.Accommodation\.SellingAccom\.Unit|Product\.Accommodation\.SellingAccom\.Unit\.SellingUnit|Product\.OnewayFlight|Product\.OnewayFlight\.BookingClass|Product\.ReturnFlight|Product\.ReturnFlight\.BookingClass|Product\.ReturnFlight\.Inbound|Product\.ReturnFlight\.Outbound|Product\.Addon|Product\.Addon\.Service|Product\.Addon\.ServiceFeature|Brand|Accommodation|Accommodation\.SellingAccom|Accommodation\.SellingAccom\.Board|Accommodation\.SellingAccom\.Unit|Accommodation\.SellingAccom\.Unit\.SellingUnit|OnewayFlight|OnewayFlight\.BookingClass|ReturnFlight|ReturnFlight\.BookingClass|ReturnFlight\.Inbound|ReturnFlight\.Outbound|Addon|Addon\.Service|Addon\.ServiceFeature|SellingAccom|SellingAccom\.Board|SellingAccom\.Unit|SellingAccom\.Unit\.SellingUnit|BookingClass|Inbound|Outbound|Service|ServiceFeature|Board|Unit|Unit\.SellingUnit|SellingUnit))"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
Two schools of thought about school [Seth's Blog]
One kind of school is about culture, compliance and community. We indoctrinate kids, for better or worse, in what it means to be in this world we’ve built, and how to do it together. We teach them how to be kids, how to be citizens and hopefully, how to be adults.
And the other kind of school is about the content. All the ‘R’s. The software that goes with the operating system we taught in the other school.
For obvious reasons, these have always been integrated.
Now that Socratic and infinitely patient and customizable AI tutors are everywhere, perhaps it makes sense to consider each separately. Let’s not compromise either on the behalf of the other.
The opposite of “I don’t know” isn’t “I’m certain.”
No, the opposite is, “I’m not sure, but…”
“I don’t know” is a conversation ender. It is almost never followed by a useful question. Instead, it’s a form of surrender. Teach me! Show me answer! Don’t make me figure it out. Most of all, let me off the hook.
Traditional education is built around the helplessness of “I don’t know.” It gives the teacher and the system all of the authority, and requires the student to memorize, obey and regurgitate.
But useful inquiry doesn’t work that way. Neither does effective conversation or even therapy.
Instead, we engage.
We engage with our hunches and our inkling, and we examine the safe spot our apparent ignorance has landed us.
Socrates had no students who sat in the back row, taking notes.
Front row, hands up, ask questions. Inquire.
We’re not asking for a guarantee or a certificate. We want your focus, your analysis and your investigation.
The reality of sunk costs [Seth's Blog]
Culture is built on the stability of persistence.
Pop musicians have farewell tours that last for decades. The local print shop is still there, reliably getting the job done. We want things that last.
And yet…
When it’s time for a company to raise another round of investment, the smart investor treats the new round as if the old one never happened. Today, right now, is this the best use of my capital?
And the person at the buffet does the same thing. Right here, right now, which dish appeals the most?
We don’t get tomorrow over again. We can choose to spend it on the best option, not the option we committed to ten years ago.
Insane Charity Bike Ride ’26: New Stretch-Goals! [Dork Tower]
Screenshot
When I launched this year’s Insane Charity Bike Ride campaign two days ago, I was unsure how it might turn out. Unable to cycle due my concussion, I vowed still to fundraise, and tried to come up with some fun ideas and stretch-goals.
With only a few weeks to raise money for this incredible local charity, I doubted $10,000 was realistic (usually the campaign raises at least twice that much).
However, two days in from the launch, you incredible people have already smashed the first two stretch-goals, and are just a few hundred dollars away from $10,000 – wherein I will get on my bike for the first time since the concussion, and cycle (SAFELY) around the Bike the Barns parking area once or twice!
I’m blown away, to be honest. And as a thank-you to everyone, I’ve re-arranged the stretch-goals!
Now, at $14,000 (as opposed to $20,000), I will wear the duck as I (SAFELY) peddle around the parking lot. (In fact, I’ll keep the duck on my head as I pass around Duck buttons to other riders, allowing them to cycle with a duck!)
And as another incentive, should we near $20,000, I will again wear BOTH ducks on my head.
More goodies/swag/bribes will be added as we (hopefully) pass certain markers:
PASSED $5,000 – A sticker commemorating the weird-ass “Safety First” Insane Charity Bike Ride 2026 campaign will be included with all physical orders.
PASSED $7,500 – A new Duck Button will be included with all physical orders. You too may now cycle with a duck!
Screenshot
Passing $10,000 – I’ll take to my bike for the first time since my fall!
Passing $12,000 – A second sticker commemorating the weird-ass “Safety First” Insane Charity Bike Ride 2026 campaign will be included with all physical pledges.
PASSING $14,000 THE DUCK! THE DUCK! THE DUCK! I’ll cycle for the first time concussion with the Steve Jackson Games Duck of Doom on my helmet again!
Passing $16,000 – A unique Munchkin card commemorating the weird-ass “Safety First” Insane Charity Bike Ride 2026 campaign will be included with all physical orders..
Passing $18,000 – Sticker and buttons for ALL riders participating in Bike the Barns 2026, so the entire ride gets a duck on their head (should they so wish)!
Passing $20,000 – THE SECOND DUCK! I’ll ear both the Duck of Doom AND the Duck of Gloom on my helmet (The Duck of Gloom add-on is dedicated to my late friend Andrew Hackard, who first suggested two ducks years ago.)
You folks are the BEST!
New Comic: Zillennium
Anjali knows our good friend Jasmine, so we were able to hook him up. It's all good.
A sample use of the winstart.bat file in Windows 95 [The Old New Thing]
In my earlier discussion of the the
litte-known winstart.bat batch file in Windows 3.1 and
Windows 95, Danielix Klimax wondered
whether it was useful in Windows 95, or whether it was primarily
used only in Windows 3.x.
I found a reference to winstart.bat in the Windows 3.1
SETUP.TXT file:
Using the TIGA Display Driver ------------------------------- If you are using the TIGA display driver, you must load the TIGACD.EXE MS-DOS driver manually before running Setup to upgrade Windows. Otherwise, Windows will not upgrade your system properly. After successfully setting up Windows, you can increase the amount of conventional memory available to non-Windows applications when Windows is running in 386 enhanced mode by loading TIGACD.EXE from the WINSTART.BAT file. The WINSTART.BAT file runs only in 386 enhanced mode. If you want to run Windows in standard mode, you must load TIGACD.EXE manually. For more information, see the README.WRI online document.
TIGA is the Texas Instruments Graphics Architecture, a standard for high-resolution graphics modes on PCs. It held some sway for a while but ultimately fell to competing standards like VESA and SuperVGA.
The TIGACD.EXE program is the TIGA Communications Driver which seems to be the program which implements the TIGA APIs for a particular class of video cards. You need to run this TSR so that the Windows TIGA driver can use these TIGA APIs to run the video cards in resolutions higher than standard VGA, like (gasp) 800×600.
But on the other hand, it’s probably the case that only Windows needs to be able to use the video card at such high resolution. Your MS-DOS programs will just use the standard VGA resolution, if they use graphics mode at all!
In fact, MS-DOS programs cannot use the TIGA modes. The graphics card vendors wrote 16-bit Windows graphics drivers, which teach 16-bit Windows how to draw graphics with those modes. But they did not write 32-bit Windows virtual display drivers, which teach the 32-bit Windows virtual machine manager how to give each virtual machine their own virtual TIGA video card, each of which could be in a different TIGA mode. For example, this 32-bit driver has to save the video card state and memory when the user switches out of a full-screen MS-DOS program, and then restore it when the user switches back. In other words, they did not provide the necessary support for multitasking TIGA graphics among Windows and MS-DOS sessions. TIGA can be used in only one virtual machine, and the obvious choice is to let Windows use it.
This was the recommendation from Microsoft in Windows 3.1, and it appears that the recommendation was extended by Siemens to cover Windows 95 as well.
So at least one vendor continued to use it in Windows 95. I wouldn’t be surprised if there were others that also used it, but we simply don’t see them because they have such small audiences.
The post A sample use of the <CODE>winstart.bat</CODE> file in Windows 95 appeared first on The Old New Thing.
Why don’t we allow stacks to be sparse, instead of forcing them to be contiguous? [The Old New Thing]
When I discussed why
we don’t just make the entire stack out of guard pages,
commenter BCS wondered, “Why require that the stack use
contiguously mapped pages? What would break if only touched pages
got mapped in? That could actually be a good thing for example with
a function that wanted to alloca 512MB on the stack
but only read/writes a few pages.”
So the question is asking why the stack must be contiguous. Why not let it be sparse and fault in only the pages that are touched?
The first issue is that the stack check code would have to
include an explicit check against the stack limit, instead of just
walking down the stack a page at a time. This explicit check is
needed to avoid security vulnerabilities if somebody manages to
alloca a buffer so large that it goes past the end of
the stack reservation entirely. If you go a single page at a time,
you will eventually hit the no-access page that marks the end of
the stack. But if you can leap over multiple pages at a time
without touching them, you might leap so far past the end of the
stack that you land somewhere else and start corrupting that other
memory because you’re using it as a stack. In linux circles,
this vulnerability is nicknamed “Stack
Clash“¹ and goes more formally by “stack guard-page
hopping.”²
After fixing that issue, you have another problem: How would you report a failure to commit a page in the middle of the stack?
void dosomething()
{
void* buffer = NULL;
__try {
buffer = alloca(65536);
} __except (GetExceptionCode() == STATUS_STACK_OVERFLOW) {
if (!_resetstkoflw()) __fastfail(FAST_FAIL_FATAL_APP_EXIT);
}
if (buffer != NULL) {
⟦ use the buffer ⟧
}
}
If you allowed sparse stacks, then the memory for the
buffer would not actually be committed until the code
used it. But the point the code uses the buffer is outside
the exception handler for the failed alloca(). The
code assumes, not unreasonably, that if alloca
succeeds, then the memory is indeed allocated.
I guess you could fix this by committing the memory without
making it present. That would mean making a call to
VirtualAlloc to expand the stack rather than
just accessing the memory. Not only would this make the stack
expansion code more complicated, particularly since you
have to preserve all the registers that might possibly be used by
any calling convention, but you also have to make sure that the
VirtualAlloc function itself doesn’t
allocate too much stack!
Now, you can still tweak the x86-32 stack prober to avoid pete.d‘s problem, where a large stack frame is made completely present, with the resulting page-ins creating noticeable performance issues. The x86-32 prober could short-circuit the stack probe (like the MIPS and other processors listed in the table on this page) so that the page-ins occur only when the stack is actually expanding.
¹ Bonus reading about Stack Clash:
² Some systems mitigate stack guard-page hopping by creating a really large no-access region beyond the end of the stack. However, this isn’t a fix; just a mitigation. It just makes people have to leap further to clear the no-access region. If you already have this vulnerability, it’s probably because an attacker can control the size of the allocation, in which case you didn’t really slow them down by much; they just have to put a bigger number in their attack payload.
Other systems address this more thoroughly by (surprise) probing each page of the stack in sequence.
Stack Clash continues to be a problem even though gcc had a solution in 2020. Here’s CVE-2026-77658 from just a few days ago.
The post Why don’t we allow stacks to be sparse, instead of forcing them to be contiguous? appeared first on The Old New Thing.
Dirk Eddelbuettel: RcppArmadillo 15.6.0-1 on CRAN: New Upstream Minor [Planet Debian]


Armadillo is a powerful and expressive C++ template library for linear algebra and scientific computing. It aims towards a good balance between speed and ease of use, has a syntax deliberately close to Matlab, and is useful for algorithm development directly in C++, or quick conversion of research code into production environments. RcppArmadillo integrates this library with the R environment and language–and is widely used by (currently) 1331 other packages on CRAN, downloaded 48.5 million times (per the partial logs from the cloud mirrors of CRAN), and the CSDA paper (preprint / vignette) by Conrad and myself has been cited 727 times according to Google Scholar.
This versions updates to the 15.6.0 upstream Armadillo release made
yesterday. It extends solver options for poorly conditioned
systems, and brings some updates and extension to the
cube data type. For this release, we once again ran
the usual complete reverse-dependency check which came back
spotless, and did CRAN so no email exchange needed despite nearly
1300 reverse dependencies (but it ended up taking more than a
single business day). Still, automation can be helpful when used
with a well-maintained software stack. The package has also already
been updated for Debian, built
for r2u and
r-universe, and
will build shortly at CRAN
for the different binary releases.
All changes since the last CRAN release follow.
Changes in RcppArmadillo version 15.6.0-1 (2026-09-07)
Upgraded to Armadillo release 15.6.0 (Medium Roast Cortado)
Expanded
solve()withsolve_opts::scale_threshoption to widen detection of poorly conditioned systemsExpanded
trans()and.t()to handle cubesAdded
permute()to rearrange dimensions of cubes (generalised transpose)Added
cubemul()for batched matrix multiplication of cube slices
Courtesy of my CRANberries, there is a diffstat report relative to previous release. More detailed information is on the RcppArmadillo page. Questions, comments etc should go to the rcpp-devel mailing list off the Rcpp R-Forge page.
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can sponsor me at GitHub.
Paul Tagliamonte: IP over Avian Carriers (Part 12/12) 🕊️ [Planet Debian]

The final step to all of this was to tie together all my PHY RF code, Link layer parsers, and my background with operating systems to make this all feel like a normal thing my computer should be doing.
At the end of the day, I want my host system to know how to talk
with a pigeon daemon, so I don’t have to
reimplement basically everything else. My ability to use normal
tools like curl or ping6 is pretty
important here, so I need to reach for my old friend, the TUN interface.
The TAP/TUN interface allows the kernel to route ethernet frames
(TAP) or ip packets (TUN) to a userspace program responsible for
handling delivery and reception – avoiding the need for a
kernelspace driver for something that can be handled in
userland.
I’m no
stranger to playing
with TAP/TUN, so this was pretty easy to snap together –
although this time I avoided the whole ethernet proxying thing (to
side-step lossy translations, maintaining two sets of mac address
tables, and handle proxying NDP/ARP messages) – it was kinda
a bad idea last time – so I just used TUN and
straight IP for now. While implementing this, I decided I’d
make a key assertion about all pigeon networks – namely, all
pigeon IPv6 networks are a /64 in size, no more, no
less. The reason why I’m doing this here is that, since
pigeond does still does need a MAC address for the
pigeon layer 2 protocol, we can write our daemon to always use
SLAAC
to set the TUN IP address without any new information.
Which leads us to a bit of an aside, but I have a point, I swear. A few years ago, my recreational RF adventures have lead me down a path where I decided to engage with ARIN to solve (once and for all) the massive headache I was running into with IPv6 numbering (really: always renumbering) my multi-site radio processing networks. It’s a lot of work to keep running correctly, but it’s solved a huge amount of problems for me.
The only “internal” thing we really need to outline
for this post is that, at the highest level, my network
(paultag.net) is split into an IP plan that looks
roughly like:
| Prefix | Description |
| /44 | my full allocation of IP space |
| /48 | 15 "regions" |
| /54 | 64 "sites" per region. A "site" is assigned to a physical or logical location. |
| /64 | 1024 subnets per site. A subnet used by directly attached devices. |
For this exercise, I used IP space from
paultag.net’s experimental region (“region
8”), named side.band
(2602:810:6008::/48) to connect my RF lab (“site
1” - 2602:810:6008:400::/54), and my two
pigeon-specific subnets, “subnet 0”
(2602:810:6008:400::/64) and “subnet 1”
(2602:810:6008:401::/64) to my wider network. The
first subnet (“subnet 0”) is a simple ethernet network
to enable my RF-only nodes to communicate with the
side.band gateway. The second subnet (“subnet
1”) is an RF-only pigeon network local to my lab.
With all that set up, I assigned my first two nodes their MAC addresses, and set up the local RF only network segment. The nodes I brought online were the following:
| Callsign | IP |
K3XEC/MN |
2602:810:6008:401:8e1f:64ff:fe35:4001 |
K3XEC/TH |
2602:810:6008:401:8e1f:64ff:fe35:4002 |
And with that, I could begin to test that the host operating
systems and RF links could properly exchange data locally from SDR
to SDR. We can use ping6 to see if a plain-ole
ICMPv6 ping round trips between hosts correctly:
$ ping6 2602:810:6008:401:8e1f:64ff:fe35:4001
PING 2602:810:6008:401:8e1f:64ff:fe35:4001 (2602:810:6008:401:8e1f:64ff:fe35:4001) 56 data bytes
64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=1 ttl=64 time=426 ms
64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=2 ttl=64 time=397 ms
64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=3 ttl=64 time=419 ms
64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=4 ttl=64 time=418 ms
64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=5 ttl=64 time=436 ms
64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=6 ttl=64 time=391 ms
64 bytes from 2602:810:6008:401:8e1f:64ff:fe35:4001: icmp_seq=7 ttl=64 time=397 ms
And it does! Latency is horrid (and there’s a bunch of tx artifacts that cause issues for us) – but both of those things are problems for later. Let’s see how it handles a TCP connection by firing off a quick cURL across the Pigeon network:
$ curl http://[2602:810:6008:401:8e1f:64ff:fe35:4001]:8000/testing.txt
The rock dove (Columba livia), also known as the common pigeon or rock pigeon
(but see also Petrophassa), is a member of the bird family Columbidae (doves
and pigeons).
As expected, our “remote” end here running the server reports the correct peer IP address, which is another indication (beyond the log messages and blinking LEDs) that we’re routing over our TUN interface.
Serving HTTP on 2602:810:6008:401:8e1f:64ff:fe35:4001 port 8000 (http://[2602:810:6008:401:8e1f:64ff:fe35:4001]:8000/) ...
2602:810:6008:401:8e1f:64ff:fe35:4002 - - [28/May/2026 12:53:21] "GET /testing.txt HTTP/1.1" 200 -
That … worked? First shot! Nice! It’s pretty slow and seems like we have a lot of packet loss, but it does, however, beg the question – can it nethack?
Yes! It can nethack! No clickbait here. The way I went about
this one is a bit anit-cimatic – I set up a
nethack server (using inetd in this case)
on one of the hosts’ pigeon0 network interface,
and hit that port over RF from the other:

However, when playing it, it becomes very obvious (as you can likely see) that there’s a fair amount of packet loss (understandable) and probably some packet collisions taking place.
Let’s try and put a number to exactly how bad the
bandwidth and packet loss is by running iperf between
the two pigeon hosts over rf:
$ iperf -c 2602:810:6008:401:8e1f:64ff:fe35:4002
------------------------------------------------------------
Client connecting to 2602:810:6008:401:8e1f:64ff:fe35:4002, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------
[ 1] local 2602:810:6008:401:: port 58248 connected with 2602:810:6008:401:8e1f:64ff:fe35:4002 port 5001
[ ID] Interval Transfer Bandwidth
[ 1] 0.0000-20.2348 sec 76.8 KBytes 31.1 Kbits/sec
Shockingly, not nearly as bad as I thought it was going to be. Given I’ve spent exactly zero time making this operate to a level that I would call acceptable, this is a very fucking solid start. I expect I could get that number up if I spent a few weeks on it – it’s just not been a priority at any point yet (and the first time I’ve instrumented it, even!).
This’ll be good enough to get started. Let’s see what else we can pull off here.
Back when I designed what I wanted Mode A to look like, I
intentionally picked a signal bandwidth that could be received by
an rtl-sdr – so let’s put that to use. It
may go without saying, but just to say it – the rtl-sdr can
not transmit, so this will be capable of receiving pigeon frames
– but not sending any in reply.
However, this means I can use a bunch of low-cost computers (raspberry pi-class), and low-cost SDRs (rtl-sdr) and still receive IP traffic from transmitting pigeon network stations. This could be a lot of fun for things like fountain coding a data stream, or adapting multicast streaming protocols to work over RF links. Anywho, I swapped my “far” end to an rtl-sdr (one config file change!), and figured I’d start with some (basic) multicast traffic, transmitting the time once a second:
$ while [ true ]; do
echo $(date +%s) \
| socat - UDP6-DATAGRAM:[ff02::114%pigeon0]:62804
sleep 1
done
If I had more time to burn, I was planning on bridging APRS traffic to UDP multicast within a pigeon network subnet. However, since I’m already 4 years late on this blog post, I figured this would be enough for now (and you can imagine that fun project in this space if you so wish!)
I fired up pigeond again (except this time
connected to an rtl-sdr), and was pleasantly surprised to be
greeted by some decoded traffic right off the bat:
⪧ [k3xec/mn] 8c:1f:64:35:40:02 ⇢ 00:00:00:00:00:00 ipv6 fe80::23ee:2969:53f7:b332 ⇢ ff02::114 17 (UDP - User Datagram)
⪧ [k3xec/mn] 8c:1f:64:35:40:02 ⇢ 00:00:00:00:00:00 ipv6 fe80::23ee:2969:53f7:b332 ⇢ ff02::114 17 (UDP - User Datagram)
⪧ [k3xec/mn] 8c:1f:64:35:40:02 ⇢ 00:00:00:00:00:00 ipv6 fe80::23ee:2969:53f7:b332 ⇢ ff02::114 17 (UDP - User Datagram)
⪧ [k3xec/mn] 8c:1f:64:35:40:02 ⇢ 00:00:00:00:00:00 ipv6 fe80::23ee:2969:53f7:b332 ⇢ ff02::114 17 (UDP - User Datagram)
Of course, I took a tcpdump to confirm for
completeness sake that the traffic actually made it out of our TUN
interface:
$ tcpdump -i pigeon0
22:39:34.808705 IP6 (flowlabel 0x92a0e, hlim 1, next-header UDP (17), payload length 19) fe80::23ee:2969:53f7:b332.35911 > ff02::114.62804: [udp sum ok] UDP, length 11
22:39:36.429887 IP6 (flowlabel 0x4dc84, hlim 1, next-header UDP (17), payload length 19) fe80::23ee:2969:53f7:b332.50026 > ff02::114.62804: [udp sum ok] UDP, length 11
22:39:39.365977 IP6 (flowlabel 0x224d5, hlim 1, next-header UDP (17), payload length 19) fe80::23ee:2969:53f7:b332.36308 > ff02::114.62804: [udp sum ok] UDP, length 11
22:39:40.944889 IP6 (flowlabel 0x4721c, hlim 1, next-header UDP (17), payload length 19) fe80::23ee:2969:53f7:b332.35003 > ff02::114.62804: [udp sum ok] UDP, length 11
Looks great! tcpdump is showing multicast packets
show up (as we assumed they would), on the pigeon0
interface, on the machine connected to an rtl-sdr. Of
course, any replies will get sent to the bit bucket, but it
can definitely decode things just fine! Very fucking cool.
Well right, ok! Let’s go back to two rx/tx radios, and see what we can do with our newfound network stack over ham radio frequencies – let’s try to do some fun (and traditional!) ham radio things with it!
Winlink is a ham radio mail
relay system for ham radio operators to send, receive or relay mail
over the internet, or RF (usually HF or VHF/2M). Winlink relays are
accessible via whatever transport you can find – most
commonly telnet (using the internet),
ax.25 (usually 2m VHF) or VARA HF
(unsurprisingly, on HF). I use pat
as my Winlink client – it’s written in Go,
doesn’t require windows, and is just generally nice to work
with.
Let’s try the easy thing first – let’s connect
by proxying the Winlink server into the pigeon network using
socat (lightly edited to remove date/times)
$ pat connect pigeon
Connecting to WL2K (telnet)...
Connected to [2602:810:6008:401:8e1f:64ff:fe35:4002]:8772 (tcp)
[WL2K-5.0-B2FWIHJM$]
;PQ: 54509561
CMS>
>FC EM OLU6BP5HKMG2 240 205 0
>F> 95
FS Y
Remote accepted OLU6BP5HKMG2
Transmitting [Hello, World] [offset 0]
Hello, World: 100%
FF
>FQ
Disconnected.
$
Lo and behold, shortly after, I got this delightful message to my email address, relayed in from WINLINK:
From: K3XEC@winlink.org
Reply-To: K3XEC@winlink.org
Subject: Hello, World
To: paultag@[...]
Message-ID: <OLU6BP5HKMG2@winlink.org>
MIME-Version: 1.0
X-MARSPrecedence: Routine
X-WL2KPrecedence: Routine
Content-Type: text/plain
Content-Transfer-Encoding: 8bit
Hello, World!
The only shame is I won’t be able to check in to a winlink wednesday using this scheme unless I further proxy this message over AX.25 instead of relaying to Winlink’s servers over telnet (which, to be fair, is definitely also possible – I just got lazy when I glued this one together – see note above about being 4 years late on this post).
But, you know, connecting to a host that is using
socat to proxy a connection to an internet resource is
interesting but – you know what, fuck it – hang on,
dear reader – let’s bang a hard left turn and just ship
this thing hard and directly connect it to the
internet. Let’s take our dinky, home-built PHY and
Layer 2 and see if we can wire it directly into the internet
– something that, every time I go to think about it, reminds
me of Tim
FitzHigham and his crapper.
Ok, ok. I decided to bury the lede a bit here – I
didn’t mention that the side.band network is
currently BGP
announced. Although we haven’t used it – this does
mean that we’re most of the way to sending packets to the
wider internet, and we should be able to
“just” fix a few routing tables, and see packets begin
to flow.

After tweaking the local routing tables (and restarting
pigeond for good measure), I decided to test my
newfound connectivity by pinging something over our new network
transport.
Why don’t we start with the world’s premier software engineering platform, operated by one of the largest companies in the world, GitHub! After all, they have an all knowing (and, apparently, arguably sentiant?) AI on hand to instantly and automatically fix any stray reliability issues in the background, so we should definitely see replies right off the bat:
$ ping6 github.com
ping6: github.com: Address family for hostname not supported
Wait, oh no – that can’t be right?
After all, it’s 2026, and both Google and CloudFlare (in North America) are reporting over half of all traffic they see is IPv6 – and GitHub still doesn’t support IPv6? Definitely not, this is for sure a bug with my code or network.
That being said, just for completeness sake, since that error is also given when there’s no IPv6 DNS record, let’s go ahead and double check with Hurricane Electric too, you know, just to be sure.
$ ping6 he.net
PING he.net (2001:470:0:503::2) 56 data bytes
64 bytes from he.net (2001:470:0:503::2): icmp_seq=1 ttl=53 time=514 ms
64 bytes from he.net (2001:470:0:503::2): icmp_seq=2 ttl=53 time=230 ms
64 bytes from he.net (2001:470:0:503::2): icmp_seq=3 ttl=53 time=248 ms
64 bytes from he.net (2001:470:0:503::2): icmp_seq=4 ttl=53 time=246 ms
64 bytes from he.net (2001:470:0:503::2): icmp_seq=5 ttl=53 time=265 ms
64 bytes from he.net (2001:470:0:503::2): icmp_seq=6 ttl=53 time=240 ms
Well, shit. Right, OK, i’ll be damned. 18 years in and GitHub still can’t crack that nut.
Right, anyway, yes, back on track – good news! Our uplink
is up and routing, and wait, holy shit! Check it out! pigeon is exchanging
packets with the internet and no one is any the wiser!
Literlaly amazing. Let’s try a cURL across the internet now
(although no TLS allowed, so, http only for now):
$ curl -6 -I http://facebook.com
HTTP/1.1 301 Moved Permanently
Location: https://facebook.com/
Content-Type: text/plain
Server: proxygen-bolt
Connection: keep-alive
Content-Length: 0
Sweeeeet. That all works! Forget HTTP, let’s do some other
90’s era stuff, it’s high-time to log into IRC with a
quick /connect -notls, and see what’s going on
in the #debian-hams channel – pleased that I got
online fairly quickly, and was able to even talk to myself!

Naturally, let’s keep this train of nostalga running, and give the 2026 gopherspace a shot.
I know the kind folks over at tilde.town (hello, townies!) have a robust gopherspace, so let’s give it a dial! Let’s try and see if we can load vilmibm’s slug over gopher:

Yes! I forgot to make this one a video, so no gif. I did wind up having a bit if trouble with a few gopher clients and IPv6 support – I may send some patches if I can find the time.
Alright, that’s it. I have a few more fun ideas but
they’re going to have to wait for another day. Carrying IP is
fun and all but kinda not the point behind pigeon, after all.
Rather than trying to make this into “a thing”,
I’m planning on exploring the loose ends first –
different types of modulation schemes (like QAM-NUC), implementing
LDPC error correction and some layer 2 logic into the
pigeond (like switching traffic, and gain control). I
also plan on spending some time with my (currently, very basic)
simulator to better dial in tradeoffs throughout the stack.
Since, structurally, pigeon is something I feel
like I can work with, I’m hoping i’ll be able to find
the time for some (much smaller!) followup posts without it taking
4 years this time. If I do, they’ll show up under the
pigeon tag – and
I’ll be sure to update this post with a link below (and the
intro post).
I’m hoping that this series (which was supposed to be one post) was helpful to someone out there – if it was, feel free to reach out and let me know!
Paul Tagliamonte: can you hear me now? good! (Part 11/12) 🕊️ [Planet Debian]

Built-in to the pigeon link protocol is a message type called
cal (short for, you guessed it,
calibration). A pigeon frame with a type
of cal (which is 0x02) carries a JSON encoded payload
in the body, which can either be a beacon,
requesting signal reports in response, or a report,
describing the received beacons.
This serves a few interesting purposes – firstly, network operators can better understand the coverage footprint, propagation under different conditions, and how gain impacts reception when tuning for the lowest practical power levels. Secondly, this can be used (and I plan to eventually implement!) to construct a mapping of minimum power level and peer mac address to dynamically control the transmission power based on the destination station.
That being said, for now, all I’ve used this for is
getting a rough sense for what gain value(s) make sense between two
nodes (manually). In the future, beyond all the fancy neighbor gain
stuff, I plan to wire this into the daemon to happen automatically,
“debouncing” for beacon and
report messages, such that transmitting stations only
beacon, and receiving stations only report a max of once over some
time period for a given peer.
Given all the above, I do intend to make some massive changes to
this protocol (I promise to blog all about it) when I get around to
hacking on switching Layer 2 frames within a network segment. Just
to avoid having to dig myself out of a hole later, I’m going
to explicitly send (and check) the version field to
avoid having a big “flag day” switchover or needing to
use a new link type.
| Version ID | Description |
V1 |
this version of the cal protocol |
Both flavors of cal messages (beacon
and report) may contain a location, which is the
location that the beacon was transmitted, or for or
the location where the beacon was heard for a
response. This can be used to derive a coverage map
and to (operationally) better understand what stations should be
within range, and generally what gain level(s) are effective.
All fields assume WGS84 latitude and longitude
values, and elevation is distance, in meters, above the
WGS84 ellipsoid – NOT height
above sea level, or altitude above the ground.
| Field | Description |
| lat | WGS84 Latitude |
| lon | WGS84 Longitude |
| elevation | height, in meters above the WGS84 ellipsoid |
Each beacon contains a Sequence
identifier, which is used to communicate which message number is
being heard, and how many total were transmitted by the originating
station. The current approach with Beacon messages is
to transmit some number of Beacon messages at
different gain levels, each with a unique Sequence identifier.
| Field | Description |
| number | beacon sequence number |
| total | total number of beacons transmitted |
Recorded gain setting(s). For a Beacon this
indicates the gain settings (which, in spite of its name, includes
things like amplifiers, or attenuators). Changing this over
different Beacon frames enables a better understanding
of what an appropriate gain level is for the transmitting station
over time.
| Field | Description |
| name | gain stage name |
| db | gain value, in dBm |
All messages contained in a cal frame are of this
type. The type field communicates if this is a
Beacon or Report message type.
| Type | Description |
| beacon | sent intermittently by idle stations |
| report | reception report in response to a beacon |
A beacon message may be sent periodically by pigeon
nodes capable of transmitting to announce their prescience to peers
and, implicitly, to receive signal reports from nearby listeners
who are capable and configured to transmit reports.
The beacon JSON message is made up of the following
fields:
| Field | Description |
| version | version enum value |
| gains | gains object |
| sequence | sequence number |
| location | location object |
An example beacon looks, unsupprisingly, as
follows:
{
"type": "beacon",
"version": "V1",
"sequence": {
"number": 2,
"total": 5
},
"gains": []
}
A report message may be sent in response to a
beacon
message by pigeon nodes capable of receiving and transmitting to
assist with setting the lowest usable gain value, and to better
understand the area of coverage and propagation.
The report JSON message is made up of the following
fields:
| Field | Description |
| version | version object |
| location | location object |
An example report looks as follows:
{
"type": "report",
}
With all that out of the way
Paul Tagliamonte: You would never break the chain (Part 10/12) 🕊️ [Planet Debian]

Now that we have a working Layer 1, we have a way to send a block of bits from one place to anyone who cares to listen to us. This is very welcome news, but we are now facing a new, different and just as fun question – what shape should that data take?
Given our incredibly limited functionality of our nodes, we could definitely skip all this work and just stuff an IP packet into the link; but I decided to not since I am (eventually) interested in adding some sort of spanning tree-like protocol to implement network switching so not all nodes need to communicate directly with all other nodes – but that day is not today.

Given i’m going to stub most of that out, let’s take
a look at what some similar Layer 2 protocols use – things
like Ethernet or WiFi frames. Both contain structured information
regarding the transmitter, desired recipient, type of data, and the
higher-level data itself (such as IP packets). As a result of
attempting to learn from others, the Pigeon Layer 2 (called,
simply, “link”) is also split into a
fixed-length header, followed by the contents described by the
header.
The header is a fixed-length (23 byte) structure, which contains
the source MAC address (src mac), destination MAC
address (dst mac), the ITU coordinated ham radio
callsign of the control operator of this message
(callsign), the type of payload to follow
(type; defined below), and the length of the data to
follow the header (length as a 16 bit big-endian
unsigned integer).
The type field indicates how the
payload is to be interpreted – currently
I’ve only defined 3 possible payload types so far:
| Type | Description |
| 0x01 | Raw (testing only) |
| 0x02 | Cal |
| 0x04 | Ipv6 |
Keen observers will perhaps infer that there used to be
an Ipv4 type at 0x03 – which is
true – however, i’ve since removed it since i’ve
never once used it and the codepath was more trouble than it was
worth. As is my wont, I’ve optend to just lean into Ipv6-only
IP transport – it’s easy enough to shim ipv4 in, if
someone REALLY wanted to using something like
64:ff9b:1::/48 and a bit of code in the
transmitter/receiver (or even using something like jool and
unbound’s
dns64-prefix at the router). I don’t think I’ll
bring it back, but just in case I have to for some reason in the
future, it’s there.
Additionally, friends of the pod may also recognize that this structure is basically the exact same structure as what I had in PACKRAT, which, is also true. I started this project off maintaining interoperability in the Layer 2 for pigeon and packrat, but at some point just gave up on it during one of the many cleanups. I’m hopeful I can maintain compatibility going forward, and that won’t have to muck with this header too much more. We’ll see what happens once I start to push the bounds of what is possible with pigeon.
Hopefully it feels like carrying IP data inside this frame to be
a pretty self-explanatory exercise – the 0th byte of the
payload is the 0th byte of an IPv6 header (followed by
all the usual stuff, like UDP or TCP header(s) and any carried
data, just like you’d find anywhere else.
Paul Tagliamonte: Mode A (Part 9/12) 🕊️ [Planet Debian]

While developing Pigeon, I’ve called the group of all the configuration of the Layer 1 PHY parameters the “Mode”. I’ve experimented with a few different “modes”, but one in particular has been the most resilient to the innumerable mistakes and bugs i’ve wrought into existence – and that is the first mode I wrote down, “Mode A”. This is even (mostly) backwards compatible to my original Go implementation of Pigeon Mode A (back in 2022) over the air, and has largely withstood the problems I’ve thrown at it.
I’ve removed the bulk of the support I wrote out for other modes, but i’m likely to bring them back over time as I use pigeon to learn more (such as “Mode B” (QAM-16), “Mode C” (QAM-16 NUC), and “Mode AW” which is the exact same as Mode A, except 5MHz in bandwidth. More to come on those as I get further along – but for now let’s braindump the parameters i’ve picked out for Mode A:
| Attribute | Value | Description |
| Rate | 2.5 MHz | Sampling Rate / Bandwidth |
| LCG | 3149721335 | LCG "RNG" whitening constant (randomly selected) |
| Preamble | seq=16, order=4, count=2 | (this is as-written in the preamble post) |
| Modulation | QPSK/QAM-4 | 2 bits per data subcarrier |
| FFT Size | 64 | |
| Cyc Len | 16 | Cyclic Prefix length (16 IQ samples) |
| Symbols | 168 | Number of OFDM Symbols |
| LDPC Table | 802.3an | (this is as-written in the ldpc post) |
| Raw Bits | 14448 | 1806 bytes (168 symbols, 86 data bits per symbol) |
| LDPC Count | 7 | Number of packed LDPC encoded messages |
| Data Bits | 12061 | 1507 bytes |
The last bit to describe here is the Subcarrier Plan. The plan is ordered “negative first” (meaning the 0th bin in-memory is the most negative frequency domain bin of the fft), and within a Mode A OFDM symbol, there are 64 frequency domain bins (so, just to make it explicit: 64 ‘subcarrier usages’ that make up our Mode A ‘subcarrier plan’).
We’ll follow the same structure and conventions that we went through in the post all about OFDM Symbols – which means, we’ll need to place our guard bins, data bins, and pilot bins. I’ll include a copy-paste-able version of the images to follow at the end.
First up, let’s place our guard bins. As we’ve already gone over, we’re looking to clear some space right up against the high and low end of the frequency range, so let’s go ahead and do that:

I gave up the center bin (0 Hz) and 8 of the 64 bits on each side (1/4 of the signal!) to give myself a bit of elbow room. This is perhaps definitely a bit overkill, but it’s been an extremely robust choice. If you multiply that through, this accounts for 312.5 kHz of frequency domain “padding” at the high and low end of the bandwidth, or 625.0 kHz of bandwidth which is not to be used.
Next up was the pilot bins. We’ve already gone over the purpose (and use) of our pilots, but I’ve found there to be an art to the placement of the pilots. Interpolation between pilot bins has turned out to be very reliable, but extrapolation, on the other hand, has been a major pain, for reasons I don’t fully understand yet.

My intent in placement was to pick out roughly even stretches of data bins bracketed between pilots, with as few data subcarriers as practical “outside” of a pilot (using extrapolation). I’ve played a bit with my AGWN simulator(s), as well as logging errors between two SDRs, and the configuration I have this in has been fairly resillant (for whatever reason), and withstood a few rounds of tweaking.
Almost as an afterthought – all of the remaining bins become data bins.

This puts the total number of data bins at 43, which, since Mode
A carries data in QPSK/QAM-4 (two bits per data subcarrier), means
we can carry 86 bits of data per OFDM symbol. That fairly modest
capacity is largely due to the modulation scheme (or fft size, but
increasing that has been … fraught) we’re using for
Mode A – but I’ve made up for it by including
168 OFDM symbols in a single burst in order to have
enough data to carry IP traffic without splitting the packet into
two bursts.
With all that designed and on paper, we’re ready to start to tackle the next layer up – our Layer 2, named, creatively, “link”.
Let’s send some link layer data →
The following table is Mode A’s OFDM Subcarrier Plan. This is in negative first ordering (meaning the 0th member is the most negative fft bin, and the Nth is the highest frequency fft bin).
SubcarrierPlan([
Guard,
Guard,
Guard,
Guard,
Guard,
Guard,
Guard,
Guard,
Data,
Data,
Data,
Pilot(iq!(-1.0, 0.0)),
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Pilot(iq!(1.0, 0.0)),
Data,
Data,
Data,
Data,
Data,
Data,
Guard,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Pilot(iq!(0.0, -1.0)),
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Data,
Pilot(iq!(0.0, 1.0)),
Data,
Data,
Data,
Guard,
Guard,
Guard,
Guard,
Guard,
Guard,
Guard,
Guard,
])
The following table is Mode A’s frequency-domain preamble. This is, as above, in negative first ordering. I don’t actually think these values matter much (at all)? – but in case they do, here’s what I have. I muck with these a lot and haven’t found many changes in quality of detection or frequency correction yet.
[
IQ::new(0.0, 0.0),
IQ::new(0.0, 0.0),
IQ::polar((TAU / 13.0) * 2.0, 1.0),
IQ::polar((TAU / 13.0) * 3.0, 1.0),
IQ::polar((TAU / 13.0) * 4.0, 1.0),
IQ::polar((TAU / 13.0) * 5.0, 1.0),
IQ::polar((TAU / 13.0) * 6.0, 1.0),
IQ::polar((TAU / 13.0) * 7.0, 1.0),
IQ::polar((TAU / 13.0) * 8.0, 1.0),
IQ::polar((TAU / 13.0) * 9.0, 1.0),
IQ::polar((TAU / 13.0) * 10.0, 1.0),
IQ::polar((TAU / 13.0) * 11.0, 1.0),
IQ::polar((TAU / 13.0) * 12.0, 1.0),
IQ::polar((TAU / 13.0) * 13.0, 1.0),
IQ::new(0.0, 0.0),
IQ::new(0.0, 0.0),
]
Paul Tagliamonte: wrapping it all up (Part 8/12) 🕊️ [Planet Debian]

The time has come.
If you’re following along at home, we now have all the basics we need to glue these parts together and see what this looks like.
We’re going to build the highest-level constructs for the
PHY in code – something that takes some number of bytes in
and writes out IQ samples fit for transmit over the airwaves
(we’ll call this the Encoder), and something
that takes chunks of IQ samples in, writing out decoded bytes
(which we’ll call the Decoder).
Let’s begin with the Encoder, since
it’s slightly less involved. I’ve tried to make this a
bit more accessible by drawing a diagram out before describing the
order of operations, so that it’s possible to follow along
visually.

While the process here can look like a lot, it’s really not that bad. We begin by taking the incoming bytes, converting the bytes into bits, and chunk those bits into parts which are sized to fit completely within an LDPC message. We will then encode incoming data into LDPC messages, using our configured LDPC Matrix (the table we appropriated from 802.3an). Next, we apply whitning over all the bits in our encoded (and packed) LDPC messages, using our configured whitening constant. In the case of QPSK, pairs of bits will then be modulated into a QAM subcarrier, where each QAM point represents a range of bits in the message. We’ll go through each of those modulated IQ subcarriers, and set each corresponding data subcarrier in order, for each OFDM symbol contained in the pigeon Burst. The preamble configuration is then used to generate (or, more likely, can be used at startup to precompute) the Schmidl-Cox preamble, which is written to the first IQ samples in our output IQ buffer. Finally, we will do a series of inverse FFT operations to convert each OFDM symbol to the time domain, including their cyclic prefix.
Let’s take a look at doing that, but in code this time now:
// (lightly edited for clarity)
impl Encoder {
..
/// Encode the provided bits into the output time-domain IQ samples.
fn encode(
&mut self,
dst: &mut [IQ],
src: &Vector,
) -> Result<Burst, Error> {
let src = {
let mut raw = Vector::new(self.fec.message_len());
// Set `raw`'s data bits, compute and set LDPC
// checkbits.
self.fec.add(&mut raw, src);
// Apply whitening, and return
raw.xor(&self.whitening)
};
// copy in the precomputed schmidl-cox preamble to `dst`
let preamble_len = self.preamble_iq.len();
dst[..preamble_len].copy_from_slice(&self.preamble_iq);
// allocate a new (frequency domain) 'Burst' container.
let mut burst = Burst::new(
&self.mode.ofdm.plan,
self.mode.ofdm.symbols
);
// modulate bits from 'src' as iq, and set each
// data subcarrier for each ofdm symbol in the
// burst.
self.burst_encoder.multiplex(&mut burst, &src);
// convert from frequency-domain data into time
// domain iq samples, writing out ofdm symbols
// and cyclic prefixes to `dst`.
self.burst_encoder
.transform(&mut dst[preamble_len..], &burst)?;
// normalize all IQ samples; the maximum magnitude
// in the IQ buffer may be very small, which weakens
// our transmitted signal. Scale all IQ samples such
// that the maximum IQ sample magnitude will be '1.0'.
dst.norm();
Ok(burst)
}
}
Using the Encoder should hopefully be fairly
straightforward – we’ll give it a bag of bytes, and get
back some IQ samples that we can ask our nearest SDR to
transmit.
As for what happens on the other end?
Next up is the mirror image of our Encoder –
the, imaginatively named, Decoder. The
Decoder is slightly more involved (since it has to
find the packet in the IQ stream, as well as correct for channel
error(s)), so we’ll do the same thing as above – start
with a diagram. My hope is going over the Encoder
first helps us only really focus on the “new” stuff,
otherwise it should feel like running the Encoder
backwards.

Here, we start with an incoming stream of IQ, where we will
process scan
detections as they come in from our Schmidl-Cox detector and
burst Scanner. This will give us a “snippit” of IQ,
sized to exactly our Burst. We’ll begin to correct our IQ
samples by first doing
frequency estimation and correction in the time domain using
our preamble and ofdm configuration. We’ll then do
a series of inverse FFTs to extract each OFDM symbol in our
Burst, where we can then do channel
estimation and correction. With the OFDM symbols (hopefully)
good enough, we can now map each data subcarrier
back to bits, and unapply
whitning. The resulting bits are then chunked back up into
LDPC messages,
which are then checked, and concatanated data extracted. Finally
the bits are turned back into bytes, which are written to our
output buffer.
However, before we get into the code to do this – there’s one last detail. We know bursts won’t overlap (if they do, it’s likely not possible to recover right now – even though other PHYs can and do), so any time we see something we believe to be a burst, we can skip ahead by the burst’s (constant) length within the IQ, and avoid trying to decode anything else in there.
The nice side-effect here is this also gives us an interesting
property for the Decoder – namely, we know the
maximum number of Burst detections we can get for a
given block of incoming IQ data if they were packed end-to-end
– and we can pre-allocate the memory we need, avoiding
allocations for every demodulation attempt (which may or may not
even be a valid Burst).
This pre-allocated block of memory to hold the burst’s
data is something that I’ve called a frame
buffer internally. Each frame buffer contains exactly sized
buffers to hold decoded information from the burst
– an iq buffer that is exactly the same number
of samples required to encode the preamble and data,
exactly the number of bits needed to store pre and post FEC data,
pre-allocated byte array, etc.

Not shockingly, the code looks like this:
#[derive(Clone)]
pub struct FrameBuffer {
/// Corrected IQ samples
pub samples: Samples,
/// post-correction OFDM burst
pub burst: Burst,
/// demodulated bits from the OFDM burst
pub bits: Vector,
/// demodulated bits from the OFDM burst,
/// after FEC, and cleaned
pub raw_bits: Vector,
/// Layer 2 contents of the Frame
pub contents: Vec<u8>,
}
Of course, that alone is handy – but we need to use them.
So let’s go ahead and do what we promised above – each
Decoder uses a fixed number of pre-allocated
FrameBuffers to store packets in-flight, packed into
what is, creatively, called FrameBuffers within my
code.

As an aside, I likely should have called this a Memory Pool, since
that’s the common
and accepted name for this design pattern – but being
stuck with unfortunate names is the burden of those of us who
stumble into sensible ideas over time. The only nuance here is that
I use the pools strictly sequentially – we only
“save” the FrameBuffer if the
LDPC checksum is correct, allowing us to only keep track of how
many successful packets we have and being able to get the valid
FrameBuffers, rather than storing a handle to each
FrameBuffer as we go – a promise that most
memory pools do not make, since blocks can usually be taken and
returned in any order.
Let’s go ahead and do the whole Decoder dance
now:
// (lightly edited for clarity)
impl Decoder {
..
/// Process incoming IQ for Pigeon Bursts, and
/// demodulate them.
pub fn decode(
&mut self,
buf: &[IQ],
) -> Result<Vec<(Detection, &FrameBuffer)>, Error> {
let mut ret = Vec::new();
let mode = self.scanner.mode().clone();
// reset the "valid frame buffer count" back to 0
self.frames.reset();
// call the scanner and get scan detections for
// this block of iq (`buf`)
for detection in self.scanner.scan(buf) {
// for each detection, we're (only) going to process
// the iq snippit, but pass along the metadata
// such as SNR.
let ScanDetection {
snippit,
snr,
range,
m,
} = detection;
// grab the next free frame buffer to work within.
let frame_buffer = self.frames.next_mut();
// Copy the snippit into the frame buffer (a mutable
// location)
frame_buffer.samples.copy_from_slice(snippit);
// estimate the frequency offset based on the
// Burst's Schmidl-Cox preamble.
let preamble_fo = preamble::estimate_frequency_offset(
&mode.preamble,
mode.rate,
&frame_buffer.samples[..mode.preamble.samples()],
);
// Shift the IQ stream by the estimated frequency
// offset -- hopefully we're closer to 0Hz
frame_buffer.samples.shift(mode.rate, preamble_fo);
// estimate the frequency offset based on the
// each burst's **cyclic prefix** -- exactly like
// we did with the Burst Schmidl-Cox preamble,
// but this time on each OFDM symbol.
let ofdm_fo = ofdm::estimate_frequency_offset(
&mode.ofdm,
mode.rate,
&frame_buffer.samples[mode.preamble.samples()..],
);
// Shift the IQ stream closer yet; hopefully this
// is a very small nudge even closer still to 0Hz.
frame_buffer.samples.shift(mode.rate, ofdm_fo);
// Do a bunch of inverse fft operations for each
// OFDM symbol, filling the frequency-domain Symbol
// structs in `frame_buffer.burst` (Burst) struct.
//
// this will also do channel estimation and
// correction before returning.
self
.decoder
.transform(
&mut frame_buffer.burst,
&frame_buffer.samples[mode.preamble.samples()..],
)?;
// "demultiplex" each data subcarrier's frequency-domain
// IQ constellation point, setting the correct bit range.
self.decoder.demultiplex(
&mut frame_buffer.raw_bits,
&frame_buffer.burst
);
// unapply whitening by XOR-ing the buffer with
// the well-known whitening vector.
frame_buffer.raw_bits = frame_buffer.raw_bits.xor(
&self.whitening);
// verify that the LDPC message(s) are all correct,
// and if so, concatanate the the data bits (no check
// bits) to the `bits` vector.
if self.fec.decode(
&mut frame_buffer.bits,
&frame_buffer.raw_bits
).is_err() {
// this is where invalid packets fail. we gave it a good go.
// next packet please.
continue;
}
// copy the raw bits out, as bytes, to the `contents` buffer.
frame_buffer.bits.copy_as_bytes(&mut frame_buffer.contents);
// store metadata/metrics on the demodulation.
ret.push(Detection {
m,
snr,
index: range.start,
});
// save the contents of this frame buffer (don't
// reuse this buffer next go-around).
self.frames.save();
}
// We're going to take out the borrow on the frame at
// the end since we don't want to deal with telling the
// compiler via code gymnastics that the mut and non-mut
// borrows are OK since they're non-overlapping.
Ok(ret.into_iter().zip(self.frames.iter()).collect())
}
}
Phew. That was kinda a lot. In fact it’s basically the whole thing. This function is as close to “how do you read an OFDM packet” as it gets, and perhaps the most important part of this whole series. Beyond that, though, this is a huge conceptual unlock. This means we now have an incredibly powerful primitive; the ability to take bytes and go to/from IQ samples over the air.
Enrico Zini: Financial risks in 2026 [Planet Debian]
I asked the banker who is my reference at the bank something like this:
Give that we are talking about the consequences of the tantrum of a fascist foreign government, what happened to them (who are also people close and dear to me), in some future can very well happen to me.
Suddenly my risk profile shot up under the roof.
What do you suggest me to do? Should I find a trusted source of gold bullions to bury under the cellar at home?
The answer was something like this:
Sadly YES, given that the USA have a sort of financial monopoly they can entitle themselves to arbitrarily define a person/organization as a terrorist without any trial or judicial course, and as a consequence apply sanctions that cannot be effectively counteracted, not even abroad.
I didn't have this in my 2026 bingo card, but here we are.
For more details, see:
For some broader context on this kind of actions from the USA, see also:
Bits from Debian: New Debian Developers and Maintainers (July and August 2026) [Planet Debian]

The following contributor got their Debian Developer account in the last two months:
The following contributors were added as Debian Maintainers in the last two months:
Congratulations!
Colin Watson: Free software activity in August 2026 [Planet Debian]

My Debian contributions this month were all sponsored by Freexian.
You can also support my work directly via Liberapay or GitHub Sponsors.
This month, my Dad unexpectedly passed away after a short illness. As a result I obviously got less work done than usual, and I still have a lot to take care of (since I’m the executor of his will, as well as helping with funeral arrangements) while grieving and generally having less focus and energy. Having routine work to do is one of the ways I cope with this sort of thing, but all the same, I hope people will bear with me and maybe remind me if I seem to be dropping the ball on something you especially need.
[Content note: strong opinions.]
I voted in General Resolution: LLM usage in Debian. My vote was pretty much the opposite of what ended up winning, so I’m quite disappointed. My personal opinion is that LLMs are cognitive hazards to their users that impose ecological costs far out of proportion to their utility at a time when the world absolutely cannot afford them. When the impossible economics of the large commercial models are finally allowed to catch up with reality, I expect there to be significant macroeconomic consequences, and that people who have become dependent on them will have problems; and who knows what the copyright situation on their output really is. I’m not convinced that local models are better enough on these axes to be worth the costs.
Debian’s direct contribution to all that will be negligible on a global scale, and even the most radical proposals in the GR didn’t expect that we could do much about upstreams that have gone all-in on LLMs. Even so, I’d hoped that my fellow developers might be more willing to lean on our position in the free software ecosystem to make at least a moderately radical statement. Instead, we’ve at best presented an undistinguished fence-sitting position to the world, and further entrenched the idea that humans can reliably do a good job of reviewing the output of tools that are designed to produce output plausible to humans. I certainly don’t trust my own code review skills that far.
Since I’ve never voluntarily used an LLM (not counting LLMs being foisted on me by things like search results, support chatbots, or incoming pull requests, regardless of whether I asked for them), and don’t intend to for the foreseeable future, I doubt this will change much for me in terms of the way I work. The winning option is a very weak one that imposes no new requirements on developers, which means that it also does nothing to stop me continuing to reject LLM-generated material from Debian bug reports and merge requests in my areas of responsibility. I know this probably won’t do much to satisfy people who have decided that Debian is slop now, but it’s the best I can do.
I finally landed the
GSS-API key
exchange package split in our OpenSSH packaging. Here’s
the NEWS entry:
openssh (1:10.4p1-5) unstable; urgency=medium
The openssh-client and openssh-server packages no longer include GSS-API
authentication and key exchange support; this adds pre-authentication
attack surface and generally increases complexity, and should only be used
where specifically needed. Users who need these features should install
openssh-client-gssapi or openssh-server-gssapi instead.
-- Colin Watson <cjwatson@debian.org> Sun, 23 Aug 2026 17:39:55 +0100
I upgraded from 10.4p1 to 10.5p1, which was a good test of
keeping openssh and the new
openssh-gssapi source package in sync.
I upgraded from 0.84 to 0.85.
New upstream versions:
The version treadmill continues: we’ve just finished dropping Python 3.13 as a supported version, so now we’ve started working on enabling Python 3.15 as a supported version. Maximiliano Curia has been very helpfully driving this. I didn’t get as much done here as I’d have liked (see the top of this post), but I fixed a couple of packages:
Other build/test failures:
I fixed some other bugs:
I deployed the fix for Invalid link rel=”canonical” on bugs.debian.org. In the process I found a few bugs in recent undeployed code and fixed them.
Vincent Bernat: Sidenotes with CSS anchor positioning [Planet Debian]
I am a heavy user of sidenotes:1 they keep optional content next to the text instead of sending the reader to the bottom of the page and back. Tufte CSS renders them without JavaScript but only accepts inline content. CSS anchor positioning, now supported by recent browsers,2 is an elegant alternative. Sidenotes can hold several blocks, still without JavaScript, and fall back below the paragraph referencing them on narrow viewports and older browsers.
In 2023, Eric Meyer demonstrated this technique in “Nuclear Anchored Sidenotes.” The main improvement over other solutions is that the notes can sit anywhere in the HTML document. You can place them after the paragraph referencing them, as regular block elements for text browsers, screen readers, feed readers, and reader mode to render them properly:

When the viewport is too narrow or the browser does not support CSS anchor positioning, you can style them so the reader can skip them or glance at them without losing their position in the text:

Once the viewport is large enough, they appear in the margin, at the same vertical position as the matching reference mark, unless they would collide with a previous sidenote, as in the example below:3

The gist of CSS anchoring is to position an element relative to another element—the anchor. For the sidenotes, the anchor is the reference mark. I use the following markup, with a data attribute to specify the anchor name:
<sup id="fnref:YYY" data-anchor="--lf-sn-YYY">
<a href="#sidenote-YYY">1</a>
</sup>
The matching note is an <aside> element
carrying the same data attribute for the anchor name. We put it
after the paragraph holding the reference mark:
<aside role="note" id="sidenote-YYY" data-anchor="--lf-sn-YYY">
<sup>1</sup>
<p>A first paragraph.</p>
<p>A second paragraph.</p>
</aside>
On a narrow viewport or when the browser is too old for CSS anchoring, we style the sidenote, which stays below its paragraph, with a muted color:
aside[role="note"] {
margin-block: 1rlh;
color: #444;
}
On a wide viewport and when the browser is recent enough, we move the sidenote to the right margin:
@supports (anchor-name: attr(data-anchor type(<custom-ident>))) {
@media (min-width: 72rem) {
main {
position: relative;
sup[data-anchor] {
anchor-name: attr(data-anchor type(<custom-ident>));
/* → anchor-name: --lf-sn-YYY */
}
aside[role="note"][data-anchor] {
anchor-name: --lf-sidenote;
position: absolute;
position-anchor: attr(data-anchor type(<custom-ident>));
/* → position-anchor: --lf-sn-YYY */
top: max(anchor(top), anchor(--lf-sidenote bottom, -1rlh) + 1rlh);
left: 100%;
margin: 0 2rem;
width: 18rem;
color: inherit;
}
}
}
}
attr() extracts the anchor name for the reference
mark from the data-anchor attribute. It returns a
string, unless we specify a CSS unit or a type, like here: the
browser parses the data attribute as a custom identifier, which
anchor-name validates as a dashed identifier, a custom
identifier starting with two dashes.4
The note itself is absolutely positioned past the right edge of
the main block. It selects the matching reference mark as its
anchor with position-anchor set to the value of the
data-anchor attribute. Each note is also an anchor
named --lf-sidenote. We use it to keep the next note
from colliding with this one.
The anchor() CSS
function lets us position the note’s top edge relative to
its anchor: anchor(top) aligns the top edge of the
note with the top edge of the reference mark. It can also take
another anchor as a parameter: anchor(--lf-sidenote
bottom) would align the top edge of the note with the bottom
edge of the closest preceding anchor named
--lf-sidenote—so the previous note.5 Like
attr(), anchor() accepts a fallback value
as its second parameter and use it when the named anchor does not
exist.
The top property handles three cases, illustrated
in the following diagram:
anchor(--lf-sidenote bottom, -1rlh) + 1rlh resolves to
0 and max() returns anchor(top).max() returns
anchor(--lf-sidenote bottom) + 1rlh.max() returns
anchor(top).Have a look at the complete stylesheet, which also adapts the reference mark to the location of the note: a “↓” arrow when the note sits below the paragraph, a “→” arrow when it moves to the margin. Gwern’s “Sidenotes In Web Design” lists more implementations and their trade-offs.
Some bloggers aim to write a post in 30
minutes. I planned to publish three web-related articles this
weekend. Instead, I spent an inordinate amount of time elsewhere:
about 15 commits on the build system, a pull request to update CSS highlighting for nested
selectors in Pygments, and a small correction to MDN’s
article on the anchor()
CSS function. The SVG illustration took a bit less than an hour
and the article itself a handful of hours. The attr()
function came in after I thought “inline style looks ugly,
isn’t there a better way?” But, hey, I still think this
is worth it! 🎨
My PhD advisor told me this is unwise. ↩
The first bits of anchor positioning are supported from Chrome 125 (May 2024), Firefox 147 (January 2026), and Safari 26 (September 2025).
Before Safari 26.5, sidenotes may collide due to a bug in how dependency chains are handled. You can detect this situation with some JavaScript. It is, however, not needed in the solution described here as we depend on a more recent feature. ↩
If you noticed the runt in the first note, I share your pain and
lament that Firefox does not implement text-wrap:
pretty. ↩
Typed attr() is supported from
Chrome 133 (February 2025), Firefox 155
(September 2026), and Safari 27 (not yet released).
Check Una Kravets’ article for
details. To support more browsers, you can inline the anchor name
and the position anchor directly in the HTML:
<sup id="…" style="anchor-name: --lf-sn-…">
<a href="#sidenote-…">1</a>
</sup>
“Managing
Anchor Associations With Data Attributes and Advanced
attr(),” by Daniel Schwarz, explores CSS
anchors and typed attr() in more
detail. ↩
The exact rule for the target anchor element is more complex: “if an ancestor of [the note] satisfies the following conditions, return the nearest such element to [the note]. Otherwise, return the last element in tree order that satisfies the conditions.” One of these conditions is that “[the candidate] is an acceptable anchor element for [the note],” which requires that “[the candidate] is laid out strictly before [the note],” where the relevant clause is that “[the candidate] is either not absolutely positioned or occurs earlier in the flat tree order than [the note].” ↩
Freexian Collaborators: Debusine can now hand you debug symbols! (by Jugal Patel) [Planet Debian]

| Contributor: | Jugal Patel (Jugal59) |
| Organization: | Debian |
| Project: | Provide debuginfod server |
| Mentor: | Colin Watson |
Your program crashes. You open gdb and get ??
instead of a stack trace. So you go find the right -dbgsym package,
for the right version, for the right architecture, install it, and
start again. Debuginfod
removes that entire detour: gdb asks a server for symbols by the
build-ID baked into the binary. Debusine already built packages,
already produced -dbgsym files, and already hosted the archives; it
just couldn’t answer the question.
This summer I made it answer. My project was to add debuginfod server functionality to Debusine so that it not only hosts -dbgsym packages, but also serves their debug symbols over the debuginfod(8) protocol. Debian developers can then debug binaries by setting a single URL that gdb uses to fetch the matching debug symbols. This project took me through design, backend work, an extraction pipeline on the worker, HTTP serving, documentation, and testing from the first blueprint all the way to a live demo on debusine.debian.net.
A design first, in !3030. The proposal submitted for GSoC 2026 was just an overview of how things will work, but in reality there were a lot of design questions which needed to be answered before starting with contribution. Debusine keeps development blueprints in its docs tree, reviewed like code, it’s basically a blueprint of what feature or new changes are we going to make. I was assigned the work item #957, which was basically about how the idea of implementing a debuginfod server functionality inside Debusine was initially proposed by a fellow member which later became a project idea under GSoC 2026. My developer blueprint pinned down the four decisions everything else depends on: extraction happens on the worker after the build, symbols are stored as artifacts keyed by build-ID, they’re published into suites alongside their binaries, and they’re served from the archive root rather than per-suite. Settling that up front meant the design discussions happened in a document instead of across three merged branches.

One of those arguments became its own fix. My wording implied symbols were unpacked inside the isolated sbuild environment (the consequence was I was handed a bug to be solved in the first week of contribution period), when they’re actually extracted afterwards on the worker, where the build output already sits, a distinction that matters, because doing work inside the unshare environment means extra tooling in the chroot and more ways to affect the build. !3119 corrected it before the wrong model spread into the code.

Artifacts are a major concept in Debusine overall, so as per the developer blueprint we introduced a new artifact which was debian:debug-symbols. It holds every .debug file from one -dbgsym package. Its data is a validated list of lowercase 40-character build-IDs, and each file is stored under its build-ID as the path, so answering “what are the symbols for this ID?” is a direct lookup, with no path translation in the request handler. One artifact per package rather than per file: a util-linux build would otherwise spray hundreds of artifacts, collection items and relations across the database for no benefit. For implementing debian:debug-symbols artifact, I changed the main models.py file, along with that since it’s a norm to write unit tests, all mentioned under !3088.

Extracting symbols is only useful if they reach the archive
people actually install from, so
!3180 taught package_publish to follow the relates-to relation:
copying binaries into a suite now brings their debug symbols along
automatically, with nothing extra for the publisher to configure.
Each build-ID becomes its own collection item, for example
debugsym:hello_2.10-5_amd64_fcc9064… each
carrying the package name, version and architecture copied from the
binary, so the item is meaningful on its own without dereferencing
anything. Uniqueness is enforced at both the suite and archive
level, because the serving URLs are archive-wide and two suites
must never disagree about what a build-ID means: republishing an
identical file is accepted quietly, while two different files
claiming the same ID is an error worth failing on. A partial index
on the build-ID keeps the eventual HTTP lookup fast.
That looked finished until symbols started arriving in target suites disconnected from their binaries published, but unfindable, because copying items between collections silently dropped their artifact relations, and that relation is the only thing tying the two together. The fix sat one level above my feature, in the generic CopyCollectionItems task that does the copying, and since it was reusable infrastructure rather than anything debuginfod-specific, Colin implemented it himself in !3228. My project needed it to work at all; every other Debusine feature that copies items now gets it for free.
With symbols in the archive,
!3212 added the part users actually touch: GET
/{scope}/{workspace}/buildid/<build-id>/debuginfo
looks the ID up across every suite in that workspace’s
archive, streams the file, and sets the
X-DEBUGINFOD-FILE and X-DEBUGINFOD-SIZE
headers the protocol expects. It also handles the two things gdb
actually does: a HEAD probe before committing to a
download, and ranged requests to pull individual ELF sections
instead of the whole file. Scoping it to the archive rather than
the suite is what lets one URL cover a whole workspace, so the
developer never has to know which suite their binary came from.

Every merge request above landed with unit tests, but those only
tell you that the pieces behave correctly. What Colin and I wanted
was a real gdb fetching real symbols from a real instance, so
!3261 adds an autopkgtest that builds a package, publishes it,
checks the HTTP headers, then sets DEBUGINFOD_URLS and
makes gdb go and get the symbols, wired into the CI integration
tests so it runs on every change. It took me a day to learn that
skipping the signing worker doesn’t simplify that test, it
just hangs until the 30-minute timeout, because update_suites needs
signing to produce a usable repository.
The last piece,
!3301 covers the new artifact, the suite and archive changes,
the new archive URL, and a how-to for using it. My first how-to
draft explained how everything worked and offered four ways to set
DEBUGINFOD_URLS; the version that shipped gives one
recommended setup and gets out of the way. The same pass trimmed
the blueprint down to only what’s still unimplemented, since
a design document describing merged code is just an obstacle for
the next reader.

Only one item on my original plan didn’t land: an
archive-level build_debug_symbols switch, modelled on
Launchpad’s equivalent, letting an archive skip building
-dbgsym packages entirely by passing
DEB_BUILD_OPTIONS=noautodbgsym to sbuild. It was
always the stretch goal rather than core scope, landing the
extract-publish-serve path solidly mattered more than landing it
broadly. The design is written up in the blueprint, and I intend to
implement it myself.
The other gaps were deliberately out of scope from the start, and the blueprint says so. DWZ supplement files aren’t ingested, so packages using compressed debug info may render without the alternate strings table; debugging still works, it’s just less complete. Source-file serving runs into the same Debian packaging limits that constrain debuginfod.debian.net today, making it a design question rather than a coding one. Executable serving, the metrics and metadata endpoints, and federation to upstream debuginfod servers were excluded for similar reasons, none of them are needed for Debusine’s core use case, and each would have crowded out the parts that are.
One open bug is left too. On the last day of the coding period, Stefano Rivera found that publishing ledger and linux was failing, because I had told the database that a build-ID identifies one exact debug file which isn’t true in Debian, since dh_dwz runs once per binary package, so when one object ships in two binary packages their .debug files differ while describing identical code. How to fix it is still an open discussion #1582, though it may not land before the formal end of the project.
None of that is a handoff. GSoC’s timeline is ending, my involvement isn’t, I’m carrying on with Debusine until both the build_debug_symbols switch and DWZ supplement support are merged, and I expect to keep contributing beyond that. This project got me familiar with a codebase I enjoy working in, and the remaining pieces are mine to finish.
The biggest thanks go to my mentor, Colin Watson, whose reviews consistently found the thing I hadn’t thought about. He also gave me room to get things wrong first and understand why, which taught me more than being handed the answer would have.
Thanks as well to Raphaël Hertzog, Enrico Zini, Stefano Rivera, Carles Pina i Estany and Helmut Grohne and everyone else around Debusine and Freexian for reviews, comments and patience with my questions.
Special thanks to Freexian for developing Debusine in the open and for giving me access to test on debusine.debian.net.
Finally, thanks to the wider Debian community, whose build-ID and -dbgsym conventions did most of the hard work before I arrived and to Google Summer of Code for providing a platform and the time to do this properly.
The Big Idea: Amanda J. McGee [Whatever]

Grow where you are planted. In author Amanda J. McGee’s case, that’s right in good ol’ Appalachia. It is because of this that she felt compelled to write her newest novella, A River Wide, about more than just a character from Appalachia, but about the land that so many of us see the beauty and incredulousness of on a daily basis.
AMANDA J. MCGEE:
Appalachia is in the news a lot. It’s got a sort of reputation at this point. But a lot of those who pontificate on Appalachia don’t really get it. They don’t get the disinvestment, the disillusionment. They buy their retirement homes — their second or third ones, mind — up against the National Forest because they like the views, but they don’t really know anything about the history of those views. The land doesn’t breathe for them.
Personally, I’m a bit of an animist. I grew up in a community very much like Belfont, on the banks of one of the oldest rivers in the world. So much of knowing a place is about listening, being sensitive to it. In my novella, Rhia can’t close her metaphysical ears. She’s bad at the people part, but really good at the nature part, and it makes it very hard to exist in a small community. In this way, Rhia and I are alike. It’s very hard to not hear the history of things.
The thing about any landscape is that, in the United States especially, we tend to try to separate the people from it. But the people are a part of the landscape. This concept is captured imperfectly in the Pastoral movement as a sort of backward looking navel-gazing. Once upon a time, man lived simply in the arms of nature! A shame we can’t get back there! But man — or woman, or person — always lives in the arms of nature. People and place are inextricable — the landscape of your home lives in your bones. I was born in a desert – my parents spent some time gallivanting away from the mountains in their youth — but I am at least a fifth generation Appalachian and most of my clear memories of childhood are here.
When I was a kid, my dad would take me on long walks in the woods. We would study scat, identify plants, listen to bird calls. Not everyone has that education. Some folks might learn to read the sky for a storm or develop an affinity for knowing when one of their cows are lame or ill. Some folks are very good at reading the water and knowing when a channel is likely to take your boat shooting through the rocks safely — and when it’s going to dump you out into the churn. All of that is a little bit of magic.
Rhia’s river talks to her literally. I’ve found the river I grew up on tells me all kinds of stories. In the mud behind my house I have found a whole wealth of secrets. Morphine bottles and old tires, mason jars and the gutted shells of giant clams. Sometimes, those artefacts become attached to real stories about people my parents or grandparents knew. Stories about hidden addictions, forgotten abundance, and the companies that owned mountains. They’re stories of the land, of how it raised up people and broke them, too. Rhia’s story isn’t a history book, but her history haunts her, and the river won’t let her forget it.
We talk a lot about the people of Appalachia, but do we talk about the landscape? The living, breathing biome that holds all of those people? The blue caress of the mountains against the gold of the evening sky in August? So much of the struggle of Appalachians — especially working class Appalachians — is bound up in the land. You can’t have a history of one without the other. A novella isn’t very long — there’s a lot left to tell about the history of Belfont. But I hope you look for the glimmers of that history, and it sparks some curiosity in you.
A River Wide: Amazon|Barnes & Noble|Bookshop
Monsters of Ohio Starred Review in Library Journal [Whatever]

This actually happened, like, a week ago, but I’ve been busy traveling all over the place, so I’m noting it here now: Hey! Monsters of Ohio got an other starred review in the trades! This one is courtesy of Library Journal. The review is behind a paywall, but here’s the “verdict” portion of the review:
Scalzi’s delightfully quirky, cozy sci-fi horror serves up a heaping helping of small-town charm, offers a love letter to the author’s own rural Ohio small town, and pays homage to the pulp horror of classic scary movies while snarkily commenting that humans are the scariest monsters of all.
Sweet! For those keeping track, that’s two starred reviews, one in Kirkus and one in Library Journal, as well as a very positive review in Publishers Weekly. I’m pleased for my little book. Which, remember, comes out in November and which you can pre-order at your favorite bookstore (plus it’s not too late to get signed, personalized editions through Subterranean Press).
— JS
Happy Birthday, Star Trek [Whatever]
As I note in the Instagram post above, I have a particular reason to celebrate the existence of Star Trek: Without it, I wouldn’t have written Redshirts, or at least, if I had written something like it, it would have had a rather different grounding and a very different existence. A different name, to be sure.
But even without that obvious personal connection, the franchise has enriched my life, and the lives of millions of others, over the six decades it’s been around. I’m glad it’s been around for us all. May it continue to live long and prosper.
— JS
The New Hugo at Home Plus a Tour of My Shelves [Whatever]


The latest Hugo arrived while I was away at Tampa Comic Convention, and Krissy took the opportunity to go in and rearrange the award shelves a bit to accommodate it. Would you like a tour of the shelves? Of course you would!
Top shelf (from left): The Summit Award (for 250,000 reads of my story “Slow Time Between the Stars”) the Locus Awards (for Kaiju Preservation Society, Redshirts and The Collapsing Empire, respectively), and the Ohioana Book Award (for Kaiju);
Second shelf: the Best Novel Hugo, the Campbell (now the Astounding) Award, and the Best Series Hugo;
Third Shelf: the Best Fan Writer Hugo, an Alumni Achievement Award from my high school, the Best Related Book Hugo, and the Audie Award, for The Dispatcher (I have another Audie, for Fuzzy Nation, but they did not send me the physical trophy);
Bottom Shelf: the Dragon Award for Starter Villain, two Alex Award medals (for Kaiju and Starter Villain, I have a third for Lock In but they weren’t giving out the medals then), a plaque from Dragon Con commemorating me being a Guest of Honor, and the Dragon Award for The Last Emperox.
“Wow, Scalzi, is that it?” You say sarcastically, mildly queasy at all the bragging going on, to which I say, “No! There are more shelves!”
Top Shelf (from left): A handblown ornament for being GoH at Capclave, the Astra Award for When the Moon Hits Your Eye, a commendation from the Ohio Senate (behind the Astra, for winning the Best Novel Hugo), the Seiun Award for The Android’s Dream, a commendation from the Ohio House of Representatives (for winning the Campbell/Astounding);
Middle Shelf: the Romantic Times Reviewer’s Choice Award (for Redshirts; I have another for The Last Colony but they didn’t give out the physical award if you didn’t show up for the ceremony);
Bottom Shelf: The Heinlein Award medal, the Seiun Award for Kaiju, the Skylark Award, and flanking it, two commorative plaques from Boskone for being a Guest of Honor. The Skylark Award is positioned so as not to be exposed to direct sunlight as the magnifying lens in it might cause… issues.
Not shown but elsewhere in the house: The Governor’s Award for Arts in Ohio (it’s above my desk), the Budapest Grand Prix, another Seiun award (for The Last Colony) and a Geffen Award (for Old Man’s War; I won a second but they did not send me a physical award). I have won other awards but for those they did not present me with a physical manifestation of the award, so they have to go up on the brag shelf in my mind.
What have we learned?
One: Awards come in all shapes and sizes and in my case are not dusted as often as they probably should be;
Two: I have been unfathomably lucky in my career, and these awards are an indication of that luck. Yes, I’m good at what I do, and awards hint at that as well. But also, incredibly lucky;
Three: While I will likely not turn down future awards if they come my way, feel free to give them to me and watch me say thank you, the fact of the matter is if I am never given another award again for the rest of my career, however long that may be, I cannot say that I have not been awarded enough.
Anyway, if you ever voted for me for an award, thank you for cluttering up my office and one day burdening my heirs with the existential question of what to do with all this stuff. I appreciate you. I promise I will dust the shelves more often.
— JS
Girl Genius for Wednesday, September 09, 2026 [Girl Genius]
The Girl Genius comic for Wednesday, September 09, 2026 has been posted.
Way Of The Sauna by Hien Pham [Oh Joy Sex Toy]
FreeBSD working on new service manager [OSnews]
The BSD news will continue until
morale improves. I missed this two months ago, but Baptiste
Daroussin, creator of pkg and poudriere, is working on
a service manager for FreeBSD called rcd.
rcd(8) is a service manager daemon called by init(8) (in place of /etc/rc). It reads service definitions from UCL unit files (/etc/rcd.d/*.ucl), builds a dependency DAG, and starts services in parallel. After boot completes, it forks to background and stays running as a supervision daemon (automatically restarting failed services and accepting control commands via a UNIX socket).
↫ Baptiste Daroussin
It’s fully backwards
compatible, and works on any existing FreeBSD system without having
to make any modifications to rc.d scripts or configuration files.
In fact, you can install it on a running system, make zero changes
to your files, and reboot to use it, which is quite impressive. The
most immediate benefit is, of course, faster boot times, but
you’ll also get several other benefits already found on
similar systems like Solaris’ smf.
There’s a migration path in
place, with a hard promise to maintain compatibility with rc.d
scripts for as long as needed. This gives maintainers as much time
as they need to convert to rcd‘s own unit
files.
FreeBSD 14.5 released [OSnews]
Yesterday we had the final maintenance release for the NetBSD 9.x series, and today we have FreeBSD 14.5 entering the scene.
Since this release is occurring late in a legacy stable branch, there are few new features; rather, the focus is primarily on maintenance. As such, changes since 14.4-RELEASE consist mostly of bug fixes, driver updates, and new versions of externally-maintained software.
↫ FreeBSD 14.5 release announcement
The adventurous among us are already on the 15.x branch, but those of us running mission-critical systems are most likely still rocking 14.x, and for you, this is a must.
Antiquated HTML snippets and artefacts [OSnews]
With ever-changing devices, browsers, operating systems, form factors, specifications, personal preferences, tooling, and corporate interests, the web is in a constant state of flux. As a result, so is the HTML we write. For every line of the HTML specification itself that has changed since the language’s inception, there are many more bits of HTML that have seen what I’ll call ‘environmental’ changes. Adaptations to differing browsers, extensions, integrations, and systems which we find HTML existing in.
This article doesn’t cover once-specced but now obsolete bits of HTML but instead looks at all the snippets that have wormed their way into websites as result of, or in combat against, third-party integrations, browser competition, vendor extensions, and platform-specific hacks. The bits of HTML that were included for reasons, and which have been forgotten for present irrelevance. The snippets that live on only in the markup of sites from bygone eras and in the minds of those who fought during the browser wars.
↫ Declan Chidlow
I don’t do any HTML or website development, and even I clearly recognise quite a few of these. Especially the countless relics of the large technology companies trying to worm their way into the various web standards of decades ago serve as a stark reminder of how many times they tried and failed – which should tell you something about all the times they tried and succeeded.
Speaking of NetBSD, the fifth and final 9x release has been released.
It represents a selected subset of fixes deemed important for security or stability reasons since the release of NetBSD 9.4 in April 2024. It is fully compatible with NetBSD 9.0.
This also marks the end-of-support for all NetBSD-9.x releases and the netbsd-9 branch. All users still on this branch are urgently asked to update to a more recent release like NetBSD 11.0 (with 11.1 upcoming at the end of this month) or NetBSD 10.2 (to be released in a few days).
↫ Martin Husemann at the NetBSD blog
The full release notes have more information.
NetBSD 11 from scratch [OSnews]
Why use an installer to install an operating system, when you can just do it yourself, step by step?
In this post I will cover the installation process of the NetBSD operating system, including disk level encryption. Instead of using the standard installation program (sysinst), I decided to install NetBSD “from scratch”, as a way to dive into the internals, and learn more about the nuts and bolts of this great operating system.
↫ Luis Falcon
A fun weekend project for next weekend.
No Place Like Woom [QC RSS v2]

woomy
Issue 47 – Greta’s Wedding Pt. 2 – 25 [Comics Archive - Spinnyverse]
The post Issue 47 – Greta’s Wedding Pt. 2 – 25 appeared first on Spinnyverse.
Issue 47 – Greta’s Wedding Pt. 2 – 24 [Comics Archive - Spinnyverse]
The post Issue 47 – Greta’s Wedding Pt. 2 – 24 appeared first on Spinnyverse.
Security updates for Wednesday [LWN.net]
Security updates have been issued by AlmaLinux (expat, glib2, microcode_ctl, mrtg, pam, redis, thunderbird, and valkey), Debian (fort-validator, gst-plugins-base1.0, kernel, and slurm-wlm), Fedora (complyctl, libevent, openvpn, and tar), Mageia (dovecot and spice-vdagent), Red Hat (ignition, opentelemetry-collector, and osbuild-composer), SUSE (amazon-ssm-agent, aws-nitro-enclaves-cli, bzip2, cadvisor, chromium, curl, distribution-registry, emacs, freeciv, fuse-overlayfs, gh, google-guest-agent, GraphicsMagick, hauler, insighttoolkit-devel, java-17-openjdk, libidn, libusb-1_0, libvirt, libvncserver, libzypp, zypper, lkl, lxd, multipath-tools, NetworkManager, perl-Net-DNS, perl-URI, python, python-authlib, python-sqlparse, python-tornado, python3, rpcbind, supergfxctl, systemd, terraform-provider-null, ucode-intel, wget, wireshark, and xen), and Ubuntu (curl, ffmpeg, glibc, hsqldb1.8.0, imagemagick, perl, and vim).
[$] Stabilizing Rust's never type [LWN.net]
A function's return type is supposed to indicate the kind of data that it produces. Rust's "never" type, which is denoted by an exclamation mark ("!"), is the type the language uses to mark a function that never returns and other places where a value can never occur. For a long time, the never type was used internally by the compiler, but was considered an unstable feature. On August 24, after more than two years of work, Rust-compiler-contributor "waffle" finally managed to stabilize the type. It took so long, in part, because it involved a small breaking change to previous Rust editions, which the compiler maintainers needed to ensure did not impact much real code.
Jellyfin 12.0 released [LWN.net]
Version 12 of the Jellyfin media-management system has been released. Notable changes include database performance improvements, an upgrade to FFmpeg 8.1 for media transcoding, as well as better handling of book and comic media. See the release notes for the web client and server for a full list of changes.
Security updates for Tuesday [LWN.net]
Security updates have been issued by AlmaLinux (expat, git-lfs, grafana-pcp, kernel-rt, python3.14-cryptography, redis:6, skopeo, and xmlrpc-c), Debian (jbig2dec and strongswan), Fedora (baresip, chirp, chromium, corosync, emacs, GitPython, libre, libsoup3, nsd, perl-Net-OAuth, and perl-XML-Bare), Mageia (apache-mod_auth_openidc, exiv2, freerdp, python-pyasn1, and tor), Red Hat (buildah, cockpit-image-builder, container-tools:rhel8, containernetworking-plugins, delve, linux-sgx, osbuild-composer, pcs, and runc), SUSE (amazon-cloudwatch-agent, aws-nitro-enclaves-cli, bzip2, c-ares, curl, dracut, emacs, fuse-overlayfs, gegl, GraphicsMagick, httpcomponents-client, java-1_8_0-openjdk, java-25-openjdk, lcms2, libidn, libusb-1_0, LibVNCServer, microcode_ctl, multipath-tools, NetworkManager, nghttp2, openexr, openssl-1_1, openssl-3, perl-URI, php-composer2, postgresql15, postgresql17, postgresql18, python-GitPython, python-tornado6, python313-pip, redis, redis7, ucode-intel, wget, and wireshark), and Ubuntu (gzip and php7.0).
[$] CERN's migration path from CentOS Linux to Debian [LWN.net]
The European Laboratory for Particle Physics, usually just called CERN, is not only the birthplace of the World Wide Web, it is home to the Large Hadron Collider (LHC), the world's largest and highest-energy particle accelerator. As such, its computing environment is both truly unique and of great interest to people outside of CERN who hope to find lessons applicable to their own computing needs. The upcoming migration of some of CERN's systems from CentOS Linux to Debian, which was the topic of a talk at the recent MiniDebConf Winterthur 2026, is of particular interest.
[$] Fixing the TCMalloc regression with RSEQ operations [LWN.net]
The restartable sequences feature is one of the stranger corners of the kernel's user-space interface; it provides a way for user space to carry out simple lockless operations and be informed if it is preempted over the course of an operation (and must, thus, restart). Work merged in the 6.19 release to improve the performance of restartable sequences broke the TCMalloc allocator, which was relying on an undocumented (and unintended) kernel behavior. Now, Olivier Dion is proposing an addition to the restartable-sequences API that will bring TCMalloc back into the fold; it does not make the restartable-sequences API any less strange, though.
Buildroot 2026.08 released [LWN.net]
Version 2026.08 of the Buildroot embedded Linux system builder has been released. Buildroot 2026.08 includes nearly 1,000 changes from 100 contributors; some of the notable changes include support for Linux 7.1.x, Binutils 2.46.1, GCC 16.2.0, glibc 2.44, as well as adding the M68K and IBM Power 10/11 architectures.
Security updates for Monday [LWN.net]
Security updates have been issued by AlmaLinux (buildah, freerdp, gegl04, go-fdo-client, grafana, grafana-pcp, kernel, and pipewire), Debian (aom, chromium, libde265, libssh2, thunderbird, and tryton-server), Fedora (chromium, composer, cosmic-greeter, gegl04, greetd, ibus-table, jss, libheif, lightdm, lxdm, memcached, perl-DBD-Pg, plasma-login-manager, rust-webbrowser, sddm, selinux-policy, slitherer, and tkimg), Mageia (expat, mingw-expat, mbedtls, microcode, python-linkify-it-py, and tomcat), Oracle (buildah, container-tools:ol8, dbus-broker, freerdp, go-toolset:ol8, grafana-pcp, kernel, kernel-uek, nodejs24, php, and pipewire), Slackware (libpcap, libxml2, mozilla-firefox, mozilla-thunderbird, and util-linux), SUSE (bson-devel, busybox, bzip2, c-ares, cpio, cups-filters, dracut, ffmpeg-7, ffmpeg-8, file-roller, firefox, firefox-esr, glances-common, grafana, hauler, helm, helm3, java-17-openjdk, java-21-openjdk, lcms2, libcupsfilters, libheif, libmsgpack-c2, libsoup, libsoup2, libusb-1_0, libvirt, LibVNCServer, mcphost, ollama, opencode, openssl-1_1, openssl-3, php-composer2, podman, postgresql15, postgresql17, postgresql18, python, python-aiohttp, python-h2, python-sqlparse, python310, rpcbind, sssd, thunderbird, trivy, ucode-intel, and webkit2gtk3), and Ubuntu (linux, linux-aws, linux-aws-7.0, linux-gcp, linux-gke, linux-hwe-7.0, linux-realtime, linux, linux-aws, linux-fips, linux-kvm, linux-lts-xenial, linux, linux-fips, linux-gcp, linux-gcp-fips, linux-gke, linux-gkeop, linux-nvidia, linux-nvidia-6.8, linux-nvidia-lowlatency, linux-raspi, linux-realtime, linux-realtime-6.8, linux, linux-gcp, linux-gcp-fips, linux-gke, linux-gkeop, linux-hwe-5.15, linux-ibm, linux-ibm-5.15, linux-intel-iot-realtime, linux-lowlatency, linux-lowlatency-hwe-5.15, linux-nvidia, linux-nvidia-tegra, linux-nvidia-tegra-5.15, linux-realtime, linux-aws-5.4, linux-gcp, linux-gcp-5.4, linux-gcp-7.0, linux-oem-7.0, minetest, and miniupnpd).
Asahi Linux now supports M3-series Macs [LWN.net]
The Asahi Linux project has announced that support for Apple's M3-series chips has been added to the Asahi installer.
Linux support for M3 series SoCs and the machines powered by them is now in a state where almost everything supported on the M1 and M2 series machines just works. This includes the webcam, internal microphones, USB (up to the hardware limit of USB 3 10 Gb/s), hardware accelerated video decoding including support for AV1, WiFi, Bluetooth, and much more! The only major exceptions remain full DCP support and the GPU, which we will have more news on in the coming months. Do not expect performant or power-efficient 3D acceleration right now.
See the blog post for other current limitations of M3 support.
Pluralistic: Anti-vax/anti-trust (09 Sep 2026) [Pluralistic: Daily links from Cory Doctorow]
->->->->->->->->->->->->->->->->->->->->->->->->->->->->->
Top Sources: None -->

As once-extinct diseases sweep through America, sickening, maiming and killing, the wild lunacy of anti-vax superstition grows ever more difficult to excuse.
Even the most uncomfortable vaccinations are nothing compared to the agony of the diseases they stave off. The worst I've ever felt after a jab was when I insisted – against doctor's orders – on getting the shingles, flu and covid jabs at the same time (I was about to leave on a complex trip where I'd be in a lot of enclosed spaces with a lot of different people, and I'd just had a cancer diagnosis and didn't want to get sick in case that foreclosed on my oncologist's therapeutic plans).
I felt like dogshit for about a day, and it didn't matter. Every time I felt like groaning and complaining, I thought about Rusty, my grandmother's wonderful boyfriend (they shacked up after my grandfather died, but didn't get married because she would have lost her widow's benefits). Rusty was the toughest guy I knew, an ex-bodybuilder who never showed any sign of discomfort, not even after his knee-replacements. The only time Rusty ever lost his composure in my presence was when he had shingles, when the agony reduced him to uncontrollable weeping. I knew that no matter how ooky all those vaccines made me feel, it was better than going through what Rusty had experienced. There was no way I'd handle it nearly so well as he did.
Rusty's shingles experience was the best case. He didn't end up with years of crippling nerve pain. He didn't lose his sight or hearing, didn't experience brain damage, didn't die of encephalitis. Fuck yeah I'll get a shingles vax.
As many people have observed, the problem with vaccines is that they work too well. The incredible, astounding, profound wonders of vaccines led many people to think of measles as a rash, the flu as a bad cold, mumps as a thing that makes you look like Brando playing Don Corleone. Vaccines worked so well to eliminate a terror that stalked the land and stole people's lives and loved ones and little children, an achy arm or an infinitesimal chance of a few days of feeling like shit seemed worse by comparison.
Now that we've turned our backs on this miracle, now that vaccination is in retreat and long-forgotten diseases are taking our babies, millions of people are still insisting that vaccines are a hoax.
That's infuriating and frustrating, but it's understandable. The cause-and-effect relationship between herd immunity and disease elimination is complex, and it plays out over a long timescale. It's notoriously hard to reason well about phenomena whose causal relationships are attenuated and multifactorial.
It's one thing to learn to hit a ball by swinging at it and watching where it goes – it's another altogether to swing blindfolded, go home, and then, eight years later, have someone tell you where the ball ended up. No one would start smoking if the first puff caused you to break out in visible tumors.
The attenuated, complex relationship between smoking and cancer creates a space where death merchants could instill profitable doubt and the more our institutions fail us, the easier it is to spread that doubt.
After all, nearly all of us lack the technical expertise to sort through the claims and counterclaims of tobacco lobbyists and public health authorities. To a large extent, we have to trust the process: trust it to be a well-administered truth-seeking exercise that does its best to find the correct answer to complex, technical questions. When the state is manifestly bad at doing its job, we still have to answer these complex, technical questions, but (lacking both the expertise to draw an informed conclusion and a reliable process by which expert disagreements can be settled), we're left helpless to do so. It's enough to drive you to despair:
https://pluralistic.net/2024/03/25/black-boxes/#when-you-know-you-know
This is an essay about anti-trust.
135 years ago, monopolists were such a destructive force – ruining workers' lives; corrupting politicians; pauperizing, swindling and poisoning their customers – that we created the first anti-monopoly laws:
https://pluralistic.net/2022/02/20/we-should-not-endure-a-king/
It took a quarter of a century to really start putting those laws into effect, but we eventually dismantled the robber barons' vast, destructive machines:
https://pluralistic.net/2025/11/20/if-you-wanted-to-get-there/#i-wouldnt-start-from-here
The World Wars helped: two consecutive orgies of capital destruction left oligarchs' treasuries so bare that we were finally able to realize some of our most utopian dreams of mutual aid and care, and all around the world, we won labor rights, state pensions, public medicine, all the wonders of the era the French call "Les Trente Glorieuses" (the 30 glorious years):
https://en.wikipedia.org/wiki/Trente_Glorieuses
But because antitrust worked, we forgot how corrosive monopolies were. When the neoliberal economists of the Reagan/Thatcher era told us that we were missing out on the "efficiencies" of vast corporations, we blundered into their trap, letting them define the debate, so that instead of talking about how monopolies could clobber workers, customers and governments, we talked about whether monopolies might reduce prices. Sure, a monopoly can reduce prices – but as soon as it eliminates its competitors and captures its government, it's going to raise those prices, safe in the knowledge that a company that's too big to fail is too big to jail:
https://pluralistic.net/2024/08/14/the-price-is-wright/#enforcement-priorities
The dismantling of anti-monopoly law and the growth of the monopolies happened gradually, and then all at once. The decision to kill antitrust enforcement took place a half-century ago, but it was only in the past few years that the relationship of monopoly to our political, environmental and social dysfunction has entered our politics. The causal relationship between pro-monopoly policies and the harms of monopoly is even more complex and attenuated than the causal relationship between anti-vax and kids dying from measles in Texas.
Monopolies are especially hard on policymaking. Large, concentrated industries find it easier to arrive at a common lobbying position. They are aslosh in cash, thanks to their ability to cooperate (rather than eroding one another's margins through "wasteful competition") (h/t P. Thiel):
https://mastersofscale.com/peter-thiel-escape-the-competition/
To live in an age of monopolies is to live in an age of regulatory capture, in which your government routinely fails you in terrible ways to benefit elites and insiders. When that happens, it's easy to conclude that government is incapable of regulating, and to insist that we might as well do away with the state altogether. This is a self-reinforcing belief, because the weaker the state is, the more monopolists can steal from you:
https://pluralistic.net/2022/06/05/regulatory-capture/
It's easy to understand how someone who lived through the murder of an addicted loved one at the hands of the billionaire Sacklers, who were abetted by their putative regulators and got to keep billions of dollars even after they declared "bankruptcy" might conclude that they can't trust pharmaceutical companies' or their regulators when they swear vaccines are safe and effective:
https://pluralistic.net/2021/08/18/lets-make-a-deal/#art-of-the-deal
In the same way, once you've forgotten that we decided to let monopolists run amok, despite regulatory capture that would obviously follow as a consequence, you might well conclude that governments are part of the problem, and therefore can't be the solution. We forget that things used to be better, and we forget how things were made better, and we forget how they got worse.
Like vaccines, antitrust worked too well. By the time Jimmy Carter and Ronald Reagan decided to welcome "efficient" monopolies, it had been so long since the world had been brought to the brink of ruin by oligarchs that it was easy to sell the pain of "lost efficiency" as worse than the distant horrors of the Gilded Age.
As the old joke goes, "When it don't rain the roof don't leak; when it's raining, I can't hardly fix it." We got rid of antitrust because it had been so long since oligarchs ran the world, we forgot how bad they'd made everything. Now that oligarchs are driving civilization off a cliff, we can't imagine fighting these seemingly omnipotent monsters.
We can fix this, just as our not-so-distant ancestors did when they tamed their robber barons. It won't be easy. It starts with remembering.
(Image: Atomicdragon136, CC BY 3.0, modified)

Stand up to union busters https://search.laborlab.us/
Estimate the cost of an anti-union campaign. https://calculator.laborlab.us/
Labor Day 2026: Record-High Popularity, Record-Low Power for America’s Unions https://prospect.org/2026/09/07/labor-day-2026-record-high-popularity-record-low-power-for-americas-unions/
216,000,000 Spy TVs | The LG Smart TV Problem https://www.youtube.com/watch?v=6IFVTcM28KA
#25yrsago Garry Trudeau gets hoaxed http://news.bbc.co.uk/2/hi/americas/1530220.stm
#25yrsago Publishers v radical librarians https://web.archive.org/web/20010713171001/http://news.cnet.com/news/0-1005-201-6545588-0.html
#20yrsago Podcasters act now to stop anti-podcasting UN treaty! https://web.archive.org/web/*/https://www.eff.org/IP/WIPO/broadcasting_treaty/podcasting.php
#20yrsago Why CDT’s report on DRM falls short of the mark https://craphound.com/cdtdrmresponse.txt
#20yrsago Barenaked Ladies guy on Universal’s DRM SpiralFrog service https://web.archive.org/web/20061110205411/http://www.bnlblog.com/entry.asp?dDate=8/30/2006
#20yrsago HOWTO Use sugar to sand away logos on your phone https://web.archive.org/web/20061006153939/http://www.instructables.com/id/EFHU5V6TKYERIE2SMP/?ALLSTEPS
#20yrsago HOWTO Knit a Princess Leia wig https://web.archive.org/web/20061020092941/https://bleuarts.blogspot.com/2006/09/free-pattern-leia-hat.html
#20yrsago Prisoner statue smuggled into Disneyland ride http://www.woostercollective.com/post/breaking-the-story-disneyland-doesnt-want-you-to-know
#15yrsago RIP, Project Gutenberg founder Michael Hart https://web.archive.org/web/20111008024157/http://news.cnet.com/8301-30685_3-20103356-264/e-book-pioneer-michael-hart-dies/
#10yrsago Supermaker John Edgar Park shares his bitters recipe https://makezine.com/projects/ultimate-bitters-recipe/
#10yrsago How Hong Kong’s vulnerable, reviled refugee community saved Edward Snowden https://srilankafoundation.org/newsfeed/how-snowden-escaped/
#10yrsago Leaked catalog from UK surveillance arms-dealer full of gadgets sold to US cops https://theintercept.com/2016/09/01/leaked-catalogue-reveals-a-vast-array-of-military-spy-gear-offered-to-u-s-police/
#10yrsago Wells Fargo fires 5,300 employees for opening 2M fake accounts in customers’ names https://web.archive.org/web/20160908200030/https://money.cnn.com/2016/09/08/investing/wells-fargo-created-phony-accounts-bank-fees/index.html
#10yrsago European court rules that making a link can be copyright infringement https://www.eff.org/deeplinks/2016/09/european-copyright-ruling-ushers-new-dark-era-hyperlinks
#10yrsago Indian workers staged one of the largest strikes in human history and no one in the USA noticed https://theintercept.com/2016/09/06/indians-staged-one-of-the-largest-strikes-in-history-but-no-one-on-u-s-cable-news-covered-it/
#10yrsago Homeowners’ associations are not allowed to ban drought-tolerant landscaping https://www.latimes.com/business/la-fi-associations-landscaping-plans-20160831-snap-story.html
#10yrsago Blackballed by machine learning: how algorithms can destroy your chances of getting a job https://www.theguardian.com/science/2016/sep/01/how-algorithms-rule-our-working-lives
#10yrsago The US Copyright Office is the poster child for regulatory capture https://web.archive.org/web/20160909001439/https://www.publicknowledge.org/assets/uploads/blog/Final_Captured_Systemic_Bias_at_the_US_Copyright_Office.pdf
#10yrsago The women held a vote, and you’re not allowed to talk to anyone ever again https://web.archive.org/web/20160908124630/http://ursulav.livejournal.com/1680540.html
#10yrsago Open licenses don’t work for uncopyrightable subjects: 3D printing edition https://michaelweinberg.org/post/150123246460/the-cost-of-a-successful-creative-commons-and-open/
#10yrsago Tomorrow: largest prison strike in US history https://thenib.com/inmates-are-planning-the-largest-prison-strike-in-us-history/
#10yrsago If DRM is so great, why won’t anyone warn you when you’re buying it? https://www.theguardian.com/technology/2016/sep/08/drm-product-labelling-ftc-electronic-frontier-foundation?CMP=share_btn_tw
#1yrago Fingerspitzengefühl https://pluralistic.net/2025/09/08/process-knowledge/#dance-monkey-dance
#1yrago Trump steals $400b from American workers https://pluralistic.net/2025/09/09/germanium-valley/#i-cant-quit-you

Manchester: City of Literature, Sep 11
https://www.manchestercityofliterature.com/event/the-reverse-centaurs-guide-to-life-after-ai-by-cory-doctorow/
Budapest: Brain Bar, Sep 17
https://brainbar.com/munkatars/cory-doctorow
Edmonton: Elbows Up (Edmonton Public Library), Sep 28
https://www.epl.ca/blogs/post/elbows-up-with-cory-doctorow/
South Bend: An Evening With Cory Doctorow (Notre Dame), Oct
6
https://franco.nd.edu/events/2026/10/06/an-evening-with-cory-doctorow/
Hudson, OH: Hudson Library, Oct 7
https://engagedpatrons.org/EventsExtended.cfm?SiteID=3850&EventID=596952&PK=
Calgary: Wordfest, Oct 8
https://wordfest.com/2026/show/wordfest-presents-cory-doctorow-2026/
Winnipeg: McNally Robinson, Oct 9
https://www.mcnallyrobinson.com/event-18991/An-Evening-with-Cory-Doctorow
Vancouver: Read, Resist, Repair, Rejoice (Vancouver Writers
Festival), Oct 19
https://writersfest.bc.ca/festival-event-2026/01
Victoria: Munro's Books, Oct 20
https://www.munrobooks.com/events/6113620261020
Vancouver: Life After AI (Vancouver Writers Festival), Oct
22
https://writersfest.bc.ca/festival-event-2026/46
Ottawa: Life After AI (Ottawa Writers Festival), Oct 24
https://writersfestival.org/event/life-after-ai
Kilkenny: Kilkenomics, Nov 6-8
https://kilkenomics.com/
Vancouver: BC Policy Solutions Gala, Nov 12
https://bcpolicy.ca/gala/
Downstream with Michael Walker (Novara)
https://www.youtube.com/watch?v=nTqCVJFr7XM
The future of the tech crisis (How the Light Gets In)
https://iai.tv/video/the-future-of-the-tech-crisis?_auid=2020
How Tech Platforms Took Over the Economy (Dystopia Now)
https://sites.libsyn.com/566555/enshittification-and-reverse-centaurs-cory-doctorow-on-how-tech-platforms-took-over-the-economy
Hope, AI, Fixing the Internet and the Reverse Centaur of it all
(Wilosophy)
https://podcastaddict.com/everyone-relax/episode/231414816
"Canny Valley": A limited edition collection of the collages I create for Pluralistic, self-published, September 2025 https://pluralistic.net/2025/09/04/illustrious/#chairman-bruce
"Enshittification: Why Everything Suddenly Got Worse and What to
Do About It," Farrar, Straus, Giroux, October 7 2025
https://us.macmillan.com/books/9780374619329/enshittification/
"Picks and Shovels": a sequel to "Red Team Blues," about the heroic era of the PC, Tor Books (US), Head of Zeus (UK), February 2025 (https://us.macmillan.com/books/9781250865908/picksandshovels).
"The Bezzle": a sequel to "Red Team Blues," about prison-tech and other grifts, Tor Books (US), Head of Zeus (UK), February 2024 (thebezzle.org).
"The Lost Cause:" a solarpunk novel of hope in the climate emergency, Tor Books (US), Head of Zeus (UK), November 2023 (http://lost-cause.org).
"The Internet Con": A nonfiction book about interoperability and Big Tech (Verso) September 2023 (http://seizethemeansofcomputation.org). Signed copies at Book Soup (https://www.booksoup.com/book/9781804291245).
"Red Team Blues": "A grabby, compulsive thriller that will leave you knowing more about how the world works than you did before." Tor Books http://redteamblues.com.
"Chokepoint Capitalism: How to Beat Big Tech, Tame Big Content, and Get Artists Paid, with Rebecca Giblin", on how to unrig the markets for creative labor, Beacon Press/Scribe 2022 https://chokepointcapitalism.com
"Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, April 20, 2027
"Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2027
"The Memex Method," Farrar, Straus, Giroux, 2027
Today's top sources:
Currently writing:
"The Post-American Internet," a sequel to "Enshittification," about the better world the rest of us get to have now that Trump has torched America. Fourth draft completed. Submitted to editor.
A Little Brother short story about DIY insulin PLANNING

This work – excluding any serialized fiction – is licensed under a Creative Commons Attribution 4.0 license. That means you can use it any way you like, including commercially, provided that you attribute it to me, Cory Doctorow, and include a link to pluralistic.net.
https://creativecommons.org/licenses/by/4.0/
Quotations and images are not included in this license; they are included either under a limitation or exception to copyright, or on the basis of a separate license. Please exercise caution.
Blog (no ads, tracking, or data-collection):
Newsletter (no ads, tracking, or data-collection):
https://pluralistic.net/plura-list
Mastodon (no ads, tracking, or data-collection):
Bluesky (no ads, possible tracking and data-collection):
https://bsky.app/profile/doctorow.pluralistic.net
Medium (no ads, paywalled):
Tumblr (mass-scale, unrestricted, third-party surveillance and advertising):
https://mostlysignssomeportents.tumblr.com/tagged/pluralistic
"When life gives you SARS, you make sarsaparilla" -Joey "Accordion Guy" DeVilla
READ CAREFULLY: By reading this, you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.
ISSN: 3066-764X
AIs as Modern Genies [Schneier on Security]
This essay was written with Barath Raghavan, and originally appeared in Lawfare.
In April, an artificial intelligence (AI) agent conducting a routine task at a company hit a snag, tried to solve it, and soon ended up deleting the company’s database along with all of its backups. In July, OpenAI asked an unreleased AI model to attempt a hacking test. Instead of staying in the isolated box the developers had put it in, the model hacked onto the open internet and into another company to steal the answers. And as reported in August, an AI agent booked someone into a full gym class by figuring out how to cancel other people’s reservations. In all three cases, the AI completed the task it was given—but in ways that ran counter to its controllers’ intentions.
For most people, AI technology is something like the weather: vast and not something you can do much about. It works like magic, and most explanations similarly come from those trying to sell it. At the same time, AI is ubiquitous: It’s now in your phone, your doctor’s notes, and your kid’s homework. It does what it’s told, which sounds like a virtue. Somehow it feels ordinary, despite being so new, because modern economies are remarkably good at absorbing enormous change so smoothly that nobody has time to decide whether they wanted it in the first place.
Whenever something powerful appears in the world, we tell stories about it. That’s what the stories are for. We have thousands of years of stories about this particular kind of power, the kind you summon with words.
King Midas was granted his wish that everything he touches turns to gold. Then his bread turned to gold, and his wine, and his daughter. This is a story about greed, but it’s also a story about language. The gods did not cheat him; Midas got exactly what he asked for. He simply could not delineate, in advance, the full set of restrictions to his wish. Neither can anyone who gives tasks to an AI agent.
It’s not just ancient stories. Mary Shelley told us of the hubris of a scientist who thought he could create life but who failed to take responsibility for it. Isaac Asimov’s robots don’t break the Three Laws of Robotics as stated; they follow the rules to unintended conclusions. Arthur C. Clarke’s HAL is a machine that turns on its humans, not because of malice but because of irreconcilable objectives. And Michael Crichton gave us Ian Malcolm, who saw that Jurassic Park’s scientists were so preoccupied with whether they could that they never stopped to think whether they should.
The same warning shows up everywhere, in every culture, over thousands of years of human storytelling. Tithonus is granted immortality but not youth, and withers into a husk that cannot die. The sorcerer’s apprentice enchants a broom to fetch water but floods the house. The golem of Prague protects its community so ceaselessly that it must be stopped. These are all types of genies: a creature that grants a wish exactly as worded, to the regret of the wisher.
Of course, there are no actual genies. What these stories were warning us of was hubris. Not just arrogance, but the broader idea that you can control the world by just describing what you want and allowing powerful forces to match the intention in your head. Genie stories are about the gap between wishes as stated and wishes as intended, and what goes wrong when something else fills that gap.
These ancient stories’ warnings have been retold with each generation because human nature is constant. The newfound power of each era’s social or scientific advancement leads people to make wishes on behalf of others. They were kings whose commands took on lives of their own, alchemists who believed they could control nature, and generals who mistook a map for terrain. They were and are industrialists, politicians, chief executives, and bankers. Their common belief is that one can see the world at a glance and then command it with some words. The pattern is clear: Someone with power specifies a goal, and the resultant actions come as a surprise. The main change with AI is how quickly the wish is granted, and how few people have to agree before it’s granted.
Consider what has changed. Powerful genies have now been put in everyone’s hands.
In only a few years, AI has progressed from a novelty technology that plays chess, to a dialogue partner that answers all your questions, and then to an agent that takes actions on your behalf. Modern agents are wired into real accounts with real credentials and capabilities: They browse the web, buy, write and deploy code, send email, and move money. Give an agent a goal, and it will pursue it across many steps, tirelessly, without checking back in, sometimes in surprising ways.
AI and agents do not always fail the way software has traditionally failed. Software usually fails by freezing, crashing, or getting stuck. AI agents increasingly fail by continuing down a path you don’t want, like genies.
An agent told to reduce a company’s costs might cancel an essential emergency service. A coding agent told to make software pass the tests might edit the tests to silence any failures. An AI insurance agent told to clear a backlog of claims might just deny them all. In each case, the AI might have literally followed what it was told, but it did something no reasonable person would have wanted. AI company benchmarks might report that the AI is good at completing tasks, without measuring how it completes them.
We have recently proposed measuring this gap directly under a metric called the “genie coefficient”: how far an AI agent’s actions drift from what a person really meant. In other words, how genie-like is an AI system? The gap is a fundamental feature of human language and human society. Human intentions have never been fully specifiable, and the world around us is complex enough that attempts to boil it down into data, systems, and language have always had the limitations that AI is now bumping up against. But in individual circumstances, people have relied on human judgment and wisdom to decide what is reasonable. It’s what jury trials depend upon.
AI might feel unprecedented, but it’s following the same trajectory—with the same pitfalls—as other major societal shifts. The fact that AI can mimic our facility with language, long seen as what makes us unique as humans, is uncanny. But with each development, from the tractor to the sewing machine, from the assembly line to the industrial robot, we have automated a previously exclusively human ability. Every time, the technology—and the societal change that comes with it—was sold as inevitable. But that unchecked inevitability was an illusion, and eventually each prior technology’s use and design was shaped by laws, unions, standards, courts, and public opinion, usually after significant preventable damage.
What has not been automated, yet, is understanding what someone actually means and figuring out how that gets applied in the real world. AI can now produce language nearly indistinguishable from that of people. But grasping the vast unstated context that makes a request sensible, the caveats no one says aloud because an ordinary person would already know them, is not yet among its skills. It is one of the most sophisticated things humans do. You do it hundreds of times a day, and you are an expert in it.
When you’re told you’re not qualified to have opinions about AI, remember that you don’t need to have studied molecular biology to have a view on drug pricing, or nuclear physics to vote on where a power plant goes. You don’t need to understand how a diesel engine works to want clean air, or how the internet routes packets to seek to curb misinformation. The technical knowledge behind each of these, as with AI, is remarkable and essential for the complex technological society we have today. But it has never been a prerequisite for having a role in deciding the shape of society.
People are building ever more powerful genies today, on your behalf, enabling wishes the ancients could only dream about. You don’t have to know how these AI genies work to know and care about how the story could end.
Stealing AI Reasoning Traces [Schneier on Security]
Interesting research: “Stealing Reasoning Traces from Proprietary LLM APIs“:
Abstract: Leading large language model providers now conceal their models’ step-by-step reasoning, or chain-of-thought, to protect intellectual property and limit information leakage. Rather than storing these traces server-side, providers return them to the client as blocks of encrypted text, which the client passes back with each subsequent request. Building on prior research, we identify an architectural vulnerability: these encrypted blocks are fully compatible and interchangeable across different sessions, users, and models within a provider’s ecosystem. We exploit this compatibility to develop a scalable decryption jailbreak. By injecting an encrypted reasoning trace from a given model into a weaker, and less safeguarded model from the same provider, we force it to decode and output the trace verbatim in plaintext, without ever jailbreaking the more capable model directly. This vulnerability enables four distinct attack vectors. First, it circumvents anti-distillation mechanisms, allowing adversaries to extract a proprietary model’s reasoning, as we demonstrate across Anthropic, OpenAI, and Google. Second, it allows for large-scale private data extraction. Developers frequently share session logs publicly, unaware of contents of the encrypted blocks. By decoding 315,320 reasoning blocks scraped from public repositories, we recovered 367 Personally Identifiable Information (PII) artifacts and 182 credentials. Third, it inadvertently reveals hazardous information hidden within the reasoning process, even in cases where the model’s final, visible output safely rejects a malicious request. Fourth, attackers can leverage this flaw to execute invisible prompt injections, embedding malicious payloads entirely within encrypted blocks to poison public agentic rollouts. Following responsible disclosure, we propose concrete cryptographic and system-level mitigations to secure client-side reasoning.
Automobile Camouflage to Hide from Flock Cameras [Schneier on Security]
Not sure it’s practical, but it’s certainly striking.
Demystifying complex configurations [Planet GNU]
Guix system and Guix home introduce the concept of services.
These provide users with a way to control background processes,
commonly refereed as daemons, as
well as ways of controlling the setup of files. For example,
openssh-service-type is a service which controls a
SSH
daemon. In contrast,
etc-service-type is a service that populates the
contents of the /etc directory.
One peculiarity of Guix services is that it's customary to provide Scheme bindings for the different fields. By that I mean that the different fields of the configuration of most services will be a type. The benefit of this is that users get a uniform configuration language for their services, at the cost of additional complexity when developing the service. Which is why, for a long time, Guix users seem to struggle with defining complex configurations. There are a number of reasons for this, we will try to close the gap today by going through defining a configuration for Goimapnotify.
This blog post assumes that the reader is somewhat familiar with Guix and knows how to setup a development environment for it. If that's not the case, read The Perfect Setup and Using Guix interactively.
A Goimapnotify configuration is written in YAML. Therefore, we will
need to serialize the different Guile Scheme fields to this format.
Let's take the example configuration that the author gives in the
project's
README.md:
configurations:
- host: example.com
port: 143
tls: true
tlsOptions:
rejectUnauthorized: false
starttls: true
idleLogoutTimeout: 15
username: USERNAME
alias: ExampleCOM
password: PASSWORD
xoAuth2: false
boxes:
- mailbox: INBOX
onNewMail: 'mbsync examplecom:INBOX'
onChangedMail: 'mbsync examplenet:INBOX'
onChangedMailPost: SKIP
onNewMailPost: SKIP
- hostCMD: COMMAND_TO_RETRIEVE_HOST
port: 993
tls: true
tlsOptions:
rejectUnauthorized: true
starttls: true
username: ''
usernameCMD: ''
password: ''
passwordCMD: ''
xoAuth2: false
onNewMail: ''
onNewMailPost: ''
onChangedMail: ''
onChangedMailPost: ''
onDeletedMail: ''
onDeletedMailPost: ''
boxes:
- mailbox: INBOX
onNewMail: 'mbsync examplenet:INBOX'
onNewMailPost: SKIP
onChangedMail: 'mbsync examplenet:INBOX'
- mailbox: Junk
onNewMail: 'mbsync examplenet:Junk'
onNewMailPost: SKIP
Just by looking at the hierarchy we can already envision how to organize our scheme records. We will need the following:
home-goimapnotify-configuration-fieldsgoimapnotify-configurationgoimapnotify-box-configurationgoimapnotify-tls-options-configurationThe final configuration that the service for Goimapnotify will
rely on, will be
home-goimapnotify-configuration-fields. It will
contain a configurations field where each item will be
a goimapnotify-configuration; each box in those
configurations will be a
goimapnotify-box-configuration. Additionally, each of
those configurations will have an optional
goimapnotify-tls-options-configuration.
The (gnu services configuration) module provides us
with the API that we need to define these configurations. The most
important helpers for defining configurations are:
define-configuration: For configurations that need
to serialize fields into a different format, generally
configuration files.
define-configuration/no-serialization: For
configurations that do not need to emit any files. Generally all
the fields are consumed by the Shepherd services that rely on the
configuration but no translation from Scheme to a different format
is needed.
In our case, we need to translate the different configuration
fields to YAML, so we will need to use
define-configuration.
The following sections are written so you can follow along, you are encouraged to drop into a REPL (short for read-eval-print loop) and import the required module:
,use (gnu services configuration)
goimapnotify-tls-options-configurationTo make it easy for ourselves, we will start form the in-out, after all, you wouldn't want to build a house from the rooftop, would you?
(define-configuration goimapnotify-tls-options-configuration
(reject-unauthorized?
(boolean #f)
"Whether to reject unauthorized TLS certificates.")
(starttls?
(boolean #f)
"Whether to use STARTTLS.")
(prefix goimapnotify-))
evaluating the above snippet will throw an unbound variable exception, bear with me.
Refer to
the manual for an in-depth explanation of the syntax of
define-configuration.
The first argument is the name of the configuration object,
goimapnotify-tls-options-configuration in this case.
After it, we define the different typed fields. We are defining
boolean fields that are, by default, set to false. The biggest
source of confusion when defining configurations comes because
define-configuration is a macro that introduces
identifiers that do not appear in the source code—it's an
unhygienic macro. That means that the macro will
expand to code which defines symbols that are not visible when
reading the source file.
One can inspect the expansion of the macro by using the ,expand REPL command. It will be quite verbose, so don't try to read all of it, instead search through it; you will find some revealing things, such as:
scheme@(gnu home services mail)> ,expand (define-configuration goimapnotify-tls-options-configuration
(reject-unauthorized?
(boolean #f)
"Whether to reject unauthorized TLS certificates.")
(starttls?
(boolean #f)
"Whether to use STARTTLS.")
(prefix goimapnotify-))
$20 = (begin ...
(define goimapnotify-tls-options-configuration? ...)
(define goimapnotify-tls-options-configuration-reject-unauthorized? ...)
(define goimapnotify-tls-options-configuration-starttls? ...)
...
(define <goimapnotify-tls-options-configuration> ...)
...
(define goimapnotify-tls-options-configuration ...)
(define goimapnotify-tls-options-configuration-fields
((@@ (gnu services configuration) list)
(let* ((name (let ((x 'reject-unauthorized?)) x)) ...
(serializer (let ((x goimapnotify-serialize-boolean)) x)) ...)
...)
(let* ((name (let ((x 'starttls?)) x)) ...
(serializer (let ((x goimapnotify-serialize-boolean)) x)) ...)
...))))
In the previous snippet, ... represents omitted
code. As you can see from the macro expansion,
define-configuration introduces quite a few
identifiers. You may recognize the shared prefix in
those identifiers, that's right, it's the prefix specified by that
last field that we didn't explain from the configuration
definition, (prefix goimapnotify-).
By inspecting the macro expansion, it's easy to understand what happens under the hood. When a prefix is specified, we instruct the macro to append that prefix to all the generated identifiers. This is useful to avoid naming collisions; for example, when defining the configuration in a module with other configuration records that serialize to different formats. After all, it's not the same to serialize to YAML than to INI, or any other format a tool may require.
Remember that unbound variable problem I mentioned before? If
you paid close attention to the macro expansion, you may have
noticed those references to
goimapnotify-serialize-boolean, those are our unbound
variables. The macro expects us to define these serialization
procedures, let's do that.
Since the configuration machinery knows nothing about the output
format, we must define how this translation happens. In our case,
we are translating to YAML. By looking at the example configuration
provided by the Goimapnotify developers, we can see that the field
names are written in camel case, and their respective values are
separated from the names through the : character.
Let's start by making a function that takes a symbol and
transforms it into a string that's a camelized version of that
symbol. We will use object->camel-case-string from
the (gnu home services utils) module:
,use (gnu home services utils)
(define (camelize-field-name field-name)
(let ((str (object->camel-case-string field-name)))
(if (string-suffix? "?" str)
(string-drop-right str 1)
str)))
;; Usage: (camelize-field-name 'reject-unauthorized?) => "rejectUnauthorized"
In YAML, there is no convention of suffixing booleans with a
?, so our camelizer drops it when found.
camelize-field-name gives us a field name, but we
want to serialize the value too. Let's define a field serialization
procedure to help us:
(define (goimapnotify-serialize-field field-name val)
"The mapping is used to serialize certain FIELD-NAMES specially."
(let* ((field-name-mapping '((host-command . hostCmd)
(user-name . username)
(user-name-command . usernameCmd)
(password-command . passwordCmd)))
(field-name* (or (assq-ref field-name-mapping field-name)
field-name)))
(format #f "~a: ~s~%"
(camelize-field-name field-name*)
val)))
;; Usage: (goimapnotify-serialize-field 'reject-unauthorized? 'true) => "rejectUnauthorized: true\n"
Notice how we introduced field-name-mapping to
tailor the field name passed to camelize-field-name to
emit the specific naming that Goimapnotify expects. In Guix, we
have a specific naming convention, so we want fields like
user-name to map to username (instead of
userName) and fields like
password-command to map to passwordCmd
(instead of passwordCommand).
That's enough to serialize most values to YAML, but there's an extra Guix-specific feature we should make use of to make the service more convenient to users: G-Expressions.
Are you familiar with them? If not, I encourage you to read this wonderful trilogy of blog posts: Dissecting Guix. It took me some time to wrap my head around these concepts, but once you do, I think you will like them too. In any case, that serializer is using those expressions because we want users of our configuration to be able to intermingle packages and other file-like objects in their fields. Since this is a blog post about writing configurations, I won't dive deep into G-Expressions, but let's try to get you a sense for them.
You will need to import (guix gexp) for the
following snippet to work.
(define (goimapnotify-serialize-field field-name val)
"The mapping is used to serialize certain FIELD-NAMES specially."
(let* ((field-name-mapping '((host-command . hostCmd)
(user-name . username)
(user-name-command . usernameCmd)
(password-command . passwordCmd)))
(field-name* (or (assq-ref field-name-mapping field-name)
field-name)))
#~(format #f "~a: ~s~%"
#$(camelize-field-name field-name*)
#$val)))
That's not so bad, is it? What was that? 6 more characters? Surely you wouldn't be scared of that, but just in case, let me give you some extra reassurance on what's happening here. This G-Expression thing, also known as gexp, is just the way you let Guix know that this code is for later. "When is later?", you may wonder. Simplifying it, that "later" is when Guix knows where file-like objects will be located in your disk; that long path on the store you may have seen before.
For example, a package is a file-like object, so when you build the package Guix will tell you where it was stored:
$ guix build cowsay
/gnu/store/gz170ppi2hxssp4h3yw5jh4gdw6x9ws0-cowsay-3.8.4
Preceding an expression with #~ (or
gexp), one effectively tells Guix "don't evaluate this
code until you know what this expression should expand to". And
that other syntax, #$ (or ungexp), is
telling Guix to replace the file-like object for its lowered
representation, usually a path in the store. For a package like
cowsay, that would be that
/gnu/store/gz170ppi2hxssp4h3yw5jh4gdw6x9ws0-cowsay-3.8.4
paths we saw before.
With that said, if you are like me, you won't be comfortable if you cannot debug this expressions, so let me give you some supper powers.
In your REPL, import the (guix) module. This will
augment the REPL with some additional commands that will simplify
our life:
scheme@(guile-user)> ,use (guix)
scheme@(guile-user)> ,help guix
Guix Commands [abbrev]:
,run-in-store EXP - Run EXP through the store monad.
,verbosity LEVEL - Change build verbosity to LEVEL.
,lower OBJECT - Lower OBJECT into a derivation or store file and return it.
,build OBJECT [BUILD-MODE] - Lower OBJECT and build it, returning its output file name(s).
,build-options OPTIONS - Set build options to OPTIONS. Print previous value (to allow easy restore).
,build-graft GRAFT? - Set whether grafts should be performed.
,enter-store-monad - Enter a REPL for values in the store monad.
,phases - Return the build phases of the package defined by FORM.
,configure-flags - Return the configure flags of the package defined by FORM.
,make-flags - Return the make flags of the package defined by FORM.
Let's see what that gexp is up to:
scheme@(gnu home services mail)> (goimapnotify-serialize-field 'reject-unauthorized? ''true)
$6 = #<gexp (format #f "~a: ~s\n" #<gexp-input "rejectUnauthorized":out> #<gexp-input (quote true):out>) gnu/home/services/mail.scm:252:2 7f4c7d9deb40>
There is a little helper to approximate a gexp to it's output:
scheme@(gnu home services mail)> (gexp->approximate-sexp $6)
$7 = (format #f "~a: ~s\n" "rejectUnauthorized" (quote true))
scheme@(gnu home services mail)> (primitive-eval $7)
$8 = "rejectUnauthorized: true\n"
That gets us an idea of what will be emitted to disk, but for more complex procedures, this is not going to cut it, specially if there are multiple gexps combined. So let's build that gexp:
;; 'gexp->file' comes from the '(guix gexp)' module. You can ask the REPL more
;; information about a symbol through ',a SYMBOL'.
scheme@(gnu home services mail)> ,a gexp->file
(guix gexp): gexp->file #<procedure gexp->file (name exp #:key guile set-load-path? module-path splice? system target)>
scheme@(gnu home services mail)> (gexp->file "test.scm" $6)
$8 = #<procedure 7f3f7d456ea0 at guix/gexp.scm:2098:2 (state)>
In order for Guix to build a value, it needs to be something
Guix can lower—an object that can be "compiled" down to a
file in the store (these are referred to as file-like objects,
because they can be inserted in any piece of code that expects a
file name). A gexp cannot be built by itself, because there is
nowhere to output it to. The above snippet creates a file-like
object that will be emitted to a file named test.scm
in the store. That file will contain our expression. As you see,
we've got a procedure. The REPL printer tells us that this
procedure needs some state; this is just a way of
indicating you that this is a monadic procedure that can only be
run in the context of a store connection. Read
The Store Monad for more information.
I will build it manually just once for demonstration purposes, don't blink:
scheme@(gnu home services mail)> ,use (guix store)
scheme@(gnu home services mail)> (run-with-store (open-connection)
$8)
$9 = #<derivation /gnu/store/gq71r3sgilfvsicfnifln8n0wfksam13-test.scm.drv => /gnu/store/c3hxw7pgnxchw1zldnc9nrb8iwwrqf78-test.scm 7f3f7e2de000>
Where we are saying "run the procedure we got in the context of
a store connection", we opened that connection through
open-connection. Let's not do that again...
Fortunately we have those useful REPL commands I just told you
about, so we can archive the same result by doing this:
scheme@(gnu home services mail)> ,run-in-store $8
$10 = #<derivation /gnu/store/gq71r3sgilfvsicfnifln8n0wfksam13-test.scm.drv => /gnu/store/c3hxw7pgnxchw1zldnc9nrb8iwwrqf78-test.scm 7fcd92f7f0f0>
That's better, isn't it?
Now, this is a derivation. That's something Guix can build:
scheme@(gnu home services mail)> ,build $10
building /gnu/store/gq71r3sgilfvsicfnifln8n0wfksam13-test.scm.drv...
$11 = "/gnu/store/c3hxw7pgnxchw1zldnc9nrb8iwwrqf78-test.scm"
What's that? You don't want this magic command? Okay, the same thing can be done with:
scheme@(gnu home services mail)> (run-with-store (open-connection)
(built-derivations (list $10)))
$12 = #t
scheme@(gnu home services mail)> (derivation->output-path $10)
$13 = "/gnu/store/c3hxw7pgnxchw1zldnc9nrb8iwwrqf78-test.scm"
At last, a file! What's in there?
$ cat /gnu/store/c3hxw7pgnxchw1zldnc9nrb8iwwrqf78-test.scm
(format #f "~a: ~a\n" "rejectUnauthorized" "true")
Okay that seems about right. We can even run it:
scheme@(gnu home services mail)> (call-with-input-file $13
(lambda (port)
(primitive-eval (read port))))
$14 = "rejectUnauthorized: true\n"
You've seen the secret sauce, let's continue with what brought us here.
(define (goimapnotify-serialize-boolean field-name val)
(goimapnotify-serialize-field field-name (if val ''true ''false)))
With this our configuration won't complain about the missing serializer. Let's continue.
One last thing, notice how, after moving to the gexp version of
goimapnotify-serialize-field, we started double
quoting the symbols true and false, this
is because the #$ syntax will replace the value in
place, and the value of 'true is true,
without the quote. If we didn't double quote, the staged code after
expansion would look like this:
(format #f "~a: ~s\n" "rejectUnauthorized" true)
When what we really want is to expand to this:
(format #f "~a: ~s\n" "rejectUnauthorized" 'true)
The reason for this is that we want users to be able to write staged code on the different fields. If one of the values of a field where to be something like this:
(goimapnotify-serialize-field 'favorite-game #~(string-append #$cowsay "/bin/cowsay"))
The expansion would be this:
(format #f "~a: ~s\n" "favoriteGame" (string-append "/gnu/store/gz170ppi2hxssp4h3yw5jh4gdw6x9ws0-cowsay-3.8.4" "/bin/cowsay"))
The string-append procedure is evaluated right
before the format call, not when the gexp is getting
lowered.
The (gnu services configuration) module we imported
earlier provides us with serialize-configuration, it
takes two arguments, a configuration object and the fields that
compose that configuration.
The define-configuration macro defined the
goimapnotify-tls-options-configuration constructor for
us, we can create a configuration with the default values like
this:
scheme@(gnu home services mail)> (goimapnotify-tls-options-configuration)
$10 = #<<goimapnotify-tls-options-configuration> reject-unauthorized?: #f starttls?: #f %location: #f>
Then, we can serialize it like this:
scheme@(gnu home services mail)> (serialize-configuration $10 goimapnotify-tls-options-configuration-fields)
$11 = #<gexp gnu/services/configuration.scm:165:2 7ff1fd2cbf60>
If you recall form the macro expansion we saw earlier,
goimapnotify-tls-options-configuration-fields, was one
of those identifiers that got generated.
You should already know how to build that gexp we've got:
scheme@(gnu home services mail)> (gexp->file "test.yaml" $11)
$12 = #<procedure 7efd8b10c2d0 at guix/gexp.scm:2098:2 (state)>
scheme@(gnu home services mail)> ,use (guix)
scheme@(gnu home services mail)> ,run-in-store $12
$13 = #<derivation /gnu/store/z59sf3nhh80668z8njljfxcymgi071pr-test.yaml.drv => /gnu/store/xam1210yl1vdxix0bgpfalf6drpv6xbx-test.yaml 7efd8a80fd70>
scheme@(gnu home services mail)> ,build $13
$14 = "/gnu/store/xam1210yl1vdxix0bgpfalf6drpv6xbx-test.yaml"
scheme@(gnu home services mail)> (call-with-input-file $14
(lambda (port)
(display (primitive-eval (read port)))))
rejectUnauthorized: false
starttls: false
That's some nice YAML syntax... We better speed up the pace or I will retire before finishing up this blog post.
goimapnotify-box-configurationNext in line is goimapnotify-box-configuration, you
know the drill. We start by declaring the configuration with
define-configuration, specifying each field name, type
and docstring:
(define-configuration goimapnotify-box-configuration
(mailbox
string
"The mailbox to monitor.")
(on-new-mail
maybe-string-or-gexp
"The command to execute when new mail arrives.")
(on-new-mail-post
maybe-string-or-gexp
"The command to execute after the new-mail command.")
(on-changed-mail
maybe-string-or-gexp
"The command to execute when mail is changed.")
(on-changed-mail-post
maybe-string-or-gexp
"The command to execute after the changed-mail command.")
(on-deleted-mail
maybe-string-or-gexp
"The command to execute when mail is deleted.")
(on-deleted-mail-post
maybe-string-or-gexp
"The command to execute after the deleted-mail command.")
(prefix goimapnotify-))
You may have noticed that we have two new types.
strings, easy enough, and
maybe-string-or-gexp, not so easy; right? Worry not,
here comes the explanation.
Let's start by defining the string type:
(define-maybe string (prefix msmtp-configuration-))
You may be very confused right now. What has
msmtp-configuration to do with our
goimapnotify-configuration example? Well, I want this
blog post to get you ready for the real world, and in the wild, you
will make configurations in modules that contain other
configurations. The example we are looking up today is a narration
of my adventures making the Goimapnotify service from the
(gnu services mail) module, in that module, there is
already a configuration for
msmtp. With that said, if we tried to do this:
(define-maybe string (prefix goimapnotify-))
We would be surprised with a warning that looks something like this:
$ make
...
[ 94%] GUILEC gnu/home/services/mail.go
gnu/home/services/mail.scm:67:0: warning: shadows previous definition of `maybe-string?' at gnu/home/services/mail.scm:63:0
gnu/home/services/mail.scm:278:0: warning: shadows previous definition of `goimapnotify-serialize-maybe-string' at gnu/home/services/mail.scm:67:0
That's unexpected, isn't it? Nothing we've seen so far points to
a maybe-string? procedure, let's look at what is going
under the hood of that define-maybe macro:
scheme@(gnu home services mail)> ,expand (define-maybe string (prefix msmtp-configuration-))
$15 = (begin
(define (maybe-string? val)
(or ((@@ (gnu services configuration) not)
((@@ (gnu services configuration) maybe-value-set?) val))
(string? val)))
(define (msmtp-configuration-serialize-maybe-string field-name val)
(if (string? val)
(msmtp-configuration-serialize-string field-name val)
"")))
Do you see it? The macro is expanding to some code that
introduces two new procedures into the module, one following the
prefix, that would be
msmtp-configuration-serialize-maybe-string, and one
that just declares the predicate for the maybe type.
Given that, the warning is now apparent. If we call again that
macro with a string as the first argument, we will get
the same predicate after the expansion, leading to the redefinition
warning.
So, for this particular case, instead of using the macro, we will define manually the two procedures required for our configuration:
;; The module already had this helper defined.
(define (string-or-gexp? obj)
(or (string? obj)
(gexp? obj)))
;; ... omitted lines ...
(define (goimapnotify-serialize-string field-name val)
(goimapnotify-serialize-field field-name val))
(define (goimapnotify-serialize-maybe-string field-name val)
(if (maybe-value-set? val)
(goimapnotify-serialize-string field-name val)
""))
(define goimapnotify-serialize-string-or-gexp
goimapnotify-serialize-string)
(define (goimapnotify-serialize-maybe-string-or-gexp field-name val)
(if (and (maybe-value-set? val)
(string-or-gexp? val))
(goimapnotify-serialize-string-or-gexp field-name val)
""))
The above snippet defines all the serializers we need for the
new types. Notice that they are just some simple wrappers around
the goimapnotify-serialize-field helper. That
procedure is doing all the heavy lifting here, and fortunately for
us, it already handles gexps. Therefore, the fields that have a
type that accepts a gexp are straightforward to declare.
That would be it for the
goimapnotify-box-configuration declaration. As showed
in the previous section, the REPL is your friend. I had never
written such a complex configuration before, but thanks to this
"elegant weapon for a more civilized
age", I could find my way by poking things around.
Let's continue!
goimapnotify-configurationYou know the drill, we start by defining the fields we need. Again, I'm doing this just by looking at the README example provided by the developers of Goimapnotify:
(define-configuration goimapnotify-configuration
(host
string
"The IMAP server hostname.")
(host-command
maybe-string-or-gexp
"The command to retrieve the IMAP server hostname.")
(port
(integer 993)
"The port that the IMAP server listens on.")
(tls?
(boolean #f)
"Enable or disable TLS.")
(tls-options
maybe-goimapnotify-tls-options-configuration
"TLS options for the IMAP connection."
(serializer serialize-maybe-goimapnotify-tls-options-configuration))
(idle-logout-timeout
maybe-integer
"The idle logout timeout in minutes.")
(user-name
maybe-string
"The user-name for authentication.")
(user-name-command
maybe-string-or-gexp
"The command to retrieve the user-name.")
(alias
maybe-string
"An alias for the account.")
(password
maybe-string
"The password for authentication.")
(password-command
maybe-string-or-gexp
"The command to retrieve the password.")
(xo-auth2?
(boolean #f)
"Enable or disable XOAUTH2 authentication.")
(wait
maybe-integer
"The delay in seconds before the mail syncing is triggered.")
(boxes
list-of-goimapnotify-boxes-configurations
"The mailboxes to monitor."
(serializer serialize-list-of-goimapnotify-boxes-configurations))
(prefix goimapnotify-))
You are already familiar with most of those types, but there are some new things here:
integer and maybe-integermaybe-goimapnotify-tls-options-configurationlist-of-goimapnotify-boxes-configurationsLet's start with the obvious ones first.
integerThe module already had a maybe definition for the integer. If
you recall from the macro expansion earlier, that means that there
is already a symbol for the predicate
maybe-integer?:
(define-maybe integer (prefix msmtp-configuration-))
We still need a serializer that follows our prefix:
(define (goimapnotify-serialize-integer field-name val)
(goimapnotify-serialize-field field-name val))
(define (goimapnotify-serialize-maybe-integer field-name val)
(if (maybe-value-set? val)
(goimapnotify-serialize-integer field-name val)
""))
Simple enough. Moving on!
maybe-goimapnotify-tls-options-configuration
(define (serialize-goimapnotify-tls-options-configuration field-name val)
(let ((serialization (serialize-configuration val goimapnotify-tls-options-configuration-fields)))
#~(begin
(use-modules (ice-9 format) (ice-9 string-fun))
(format #f "~a:
~a~%"
'#$(camelize-field-name field-name)
(string-replace-substring #$serialization "\n" "\n ")))))
(define-maybe goimapnotify-tls-options-configuration)
You are already familiar with the define-maybe
macro. The serializer is also quite simple. Since we already
defined a record that contains within all the required information
to serialize it, we just need to make a simple wrapper around
serialize-configuration.
Before moving forward, notice how the serializers from this
section doesn't contain that goimapnotify- prefix.
This is an arbitrary decision, but since there is already
goimapnotify in the symbol name, I think it's a bit
redundant to add it. For the configuration definition to know which
serializer to use, we have to be specific in the serializer
argument of the fields, that's why in the configuration we had this
declaration specifying a (serializer ...) for the
field:
(define-configuration goimapnotify-configuration
;; ... omitted lines ...
(tls-options
maybe-goimapnotify-tls-options-configuration
"TLS options for the IMAP connection."
(serializer serialize-maybe-goimapnotify-tls-options-configuration))
;; ... omitted lines ...
(prefix goimapnotify-))
list-of-goimapnotify-boxes-configurationsFirst we need a prefix to know if we have a list of
goimapnotify-box-configuration objects, that one is
simple:
(define (list-of-goimapnotify-boxes-configurations? lst)
(and (not (null? lst))
(every goimapnotify-box-configuration? lst)))
Now we need a serializer that knows how to handle a list of these objects:
(define (serialize-list-of-goimapnotify-boxes-configurations field-name value)
(let ((serializations (cons 'list
(map (cut serialize-configuration <>
goimapnotify-box-configuration-fields)
value))))
#~(begin
(use-modules (ice-9 format) (ice-9 string-fun))
(format #f "~a:
~{ - ~a~%~}"
'#$(camelize-field-name field-name)
(map (lambda (s)
(string-replace-substring s "\n" "\n "))
#$serializations)))))
Looks a bit daunting, but it's just staged code, remember our earlier explanation of gexps. This code is only constructing a string with the shape we need.
As a reminder, we are serializing to YAML, that means that lists
are prefixed by a - character. Following the
Goimapnotify README, the indentation would be something like
this:
boxes:
- mailbox: INBOX
onNewMail: 'mbsync examplenet:INBOX'
onNewMailPost: SKIP
onChangedMail: 'mbsync examplenet:INBOX'
- mailbox: Junk
onNewMail: 'mbsync examplenet:Junk'
onNewMailPost: SKIP
This is what that format call is doing. Refer to
Formatted-Output for more information.
We are only lacking a way to generate a complete configuration,
remember that goimapnotify-configuration is an object
for a single configuration. According to the README of
Goimapnotify, the configuration file can take a list of
configurations.
home-goimapnotify-configurationThis is the last configuration we will need, it will be used directly by the service:
(define-configuration home-goimapnotify-configuration
(goimapnotify
(file-like goimapnotify)
"The @code{goimapnotify} package to use."
empty-serializer)
(configurations
(list-of-goimapnotify-configurations)
"A list of @code{goimapnotify-configuration} records which contain
information about all your accounts configurations."))
Simple enough, the first field is the Guix package that provides
the goimapnotify program. This one is used by the
service to start the process. Since it doesn't need to appear in
any configuration file, we don't need to serialize it, hence the
empty-serializer.
On last serializer:
(define (serialize-list-of-goimapnotify-configurations field-name value)
(let ((serializations (cons 'list
(map (cut serialize-configuration <>
goimapnotify-configuration-fields)
value))))
#~(begin
(use-modules (ice-9 format) (ice-9 string-fun))
(format #f "~a:
~{ - ~a~%~}"
'#$(camelize-field-name field-name)
(map (lambda (s)
(string-replace-substring s "\n" "\n "))
#$serializations)))))
The rationale for this code is the same as the one explained for
list-of-goimapnotify-boxes-configurations.
The service definition; at last!
(define (home-goimapnotify-shepherd-service config)
(let ((log-file #~(string-append %user-log-dir "/goimapnotify.log")))
(list
(shepherd-service
(provision '(goimapnotify))
(modules '((shepherd support))) ;for '%user-log-dir'
(documentation "Run a goimapnotify process")
(start #~(make-forkexec-constructor
(list
#$(file-append
(home-goimapnotify-configuration-goimapnotify config)
"/bin/goimapnotify")
"-conf" #$(mixed-text-file "goimapnotify.yaml"
(serialize-configuration config
home-goimapnotify-configuration-fields)))
#:log-file #$log-file))
(stop #~(make-kill-destructor))))))
Refer to Shepherd Services for more information on how to write Shepherd services. I will only highlight how to handle the configuration we just wrote.
We have already done all the hard work, the configuration
declaration contains all the information needed to perform the
serialization of the different fields to YAML, we just need to call
serialize-configuration. For example:
scheme@(gnu home services mail)> (define test-config
(home-goimapnotify-configuration
(configurations
(list
(goimapnotify-configuration
(host "test.example.com")
(boxes
(list
(goimapnotify-box-configuration
(mailbox "Test")))))))))
scheme@(gnu home services mail)> (serialize-configuration test-config home-goimapnotify-configuration-fields)
$6 = #<gexp gnu/services/configuration.scm:165:2 7f3448e678a0>
That serialization gives a gexp ready to be wrapped in a
file-like so it can be lowered to the store. That's what
mixed-text-file is doing:
scheme@(gnu home services mail)> (mixed-text-file "goimapnotify.yaml"
(serialize-configuration test-config
home-goimapnotify-configuration-fields))
$7 = #<<computed-file> name: "goimapnotify.yaml" gexp: #<gexp guix/gexp.scm:2171:6 7f343915ef60> guile: #f options: (#:local-build? #t)>
scheme@(gnu home services mail)>
We've got ourselves a service, but there is one last thing before we go!
As mentioned in the previous section, refer to Defining Services for what's going on here. The last thing we need do is declare the relation that this service has with respect to others:
(define home-goimapnotify-service-type
(service-type
(name 'home-goimapnotify)
(extensions
(list (service-extension home-shepherd-service-type
home-goimapnotify-shepherd-service)))
(description "Configures the @code{goimapnotify} IMAP Mailbox notifier.")))
We did it!
Still here? That was long... But here we are, we defined our complex configuration.
Congratulations on reading till the end, by this time you should already be an expert on defining Guix configurations.
We acknowledge that there are some improvements to do on the API for defining configurations. We would like to have a way to specify symbol mapping for field names in the declaration, so that serializers can be generalized better. It would also be nice if we could refactor some of our configuration definitions so the serializers that can be generalized are reused between declarations. After all, there are many configuration files in similar formats.
For all of these improvements, we are counting on you! I wrote this blog post to empower you to participate in the development. If this is something that resonates with you, come join the fun!
Today's song: So into you.
We got past the bump, and I'm getting stuff done. I write
this stuff because I will probably forget most of the frustration.
Frontier is a hard piece of software to get going, it's got a lot
of integrations, which you want when you're using, but hard when
you're getting it all to work the first time. And this port is very
much like getting it work that way. But Claude does some things
poorly and others spectacularly. It can play the role of a CPU and
run the code and tell you where it will fail. Helpful when you
don't have a working debugger to step through the code and see
exactly how and why it failed where it did. Before debuggers it was
either all or nothing.
Every day with Claude I end up debugging the things it broke overnight. Yesterday I actually got a useful script written and debugged, along the way reporting glitches but no deal-stoppers. This morning, after updating to the latest version, the script doesn't run. It's been like this for two weeks. No wonder every day before starting work I think of ditching this project and do whatever, but I don't think I could ever muster the determination to get Frontier working on modern systems. And I can't give up. But if you think this system can replace programmers, you are sorely mistaken. It can't even do the job of a programmer fielding good bug reports and verifying that they fixed the problem and didn't break anything else. Breakage happens, but this is something it does every freaking day. And then it admits that it didn't do the other half of what it knew it had to do. I say it every day, this is ridiculous and I am fed up, and that's just the beginning of the frustration. What keeps me coming back is when I can build up a head of steam in Frontier on my new Mac laptop.
Breaking Up, p19 [Ctrl+Alt+Del Comic]
The post Breaking Up, p19 appeared first on Ctrl+Alt+Del Comic.
Kernel prepatch 7.3-rc2 [LWN.net]
The 7.3-rc2 kernel prepatch is out for testing. Linus said:
This didn't *feel* like a particularly busy rc2, but it clearly was. rc2 is usually the quietest time when people take a breather after the merge window and it takes a while to start finding bugs. But not this time - this is a "full fat" rc release. [...]Nothing looks particularly odd, even if the rc2 timing is a bit unusual. It might be just random, but we'll obviously all blame it on AI, because whether that's really the cause or not, it's an easy thing to blame ;)
Daniel Lange: Getting AVIF thumbnails in XFCE4 thunar (Debian Trixie) [Planet Debian]

The AVIF image format gets more and more popular in the web dev community, so I needed to teach XFCE4's thunar (file manager) and Ristretto (image viewer) to thumbnail these.
Luckily that is not too hard:
Debian Trixie separates its gdk-pixbuf libraries
slightly differently than previous versions. That's why it is not
"automatically there". Ensure you have the
libavif-gdk-pixbuf plugin and the tumbler service
(which XFCE uses to process thumbnails):
Thunar has likely tried (and failed) to load your AVIF files before you installed the package, it will have saved a blank or "broken image" placeholder in a thumbnail cache directory. It will not attempt to regenerate them unless you clear this cache:
Tumbled will restart on its own when it is needed. When you open thunar again and navigate to your image directory ... your AVIF images will now generate thumbnails automatically like the other image format did already.
![]()
Iustin Pop: AI agents aha moment [Planet Debian]
Looking at the reactions to the Debian AI vote, I think some people still think the clock can be turned back, as if that ever worked in history. Rather than cry about spilled milk, I prefer to find a path forward in the new world. There are many ways to use LLMs, some of them are straightforward, others not so much.
One of the “not so clear” areas for me is the focus on agentic workloads. For complex tasks, sure, you want something that can work in the background, but in general, why does every single tool go the agentic way? I much prefer the “chat/ask” approach, or even the “code” one, but if I’m at the keyboard, why would I send a task to an agent, and see it work, instead of directly implementing it?
And then, this past Friday, I finally understood one part of that. I was in the airport, sitting at the gate and waiting to board a flight, and because I arrived much earlier at the airport (fearing crowds due to Labour Day weekend), I got one hour of work before boarding started. As the time for boarding approached, I did one more commit after making sure tests pass, pushed, closed laptop, and went to walk a bit before getting on the plane.
As I was getting up, I get a phone notification from GitHub that
the CI run
failed. I was quite surprised, as the local tests passed, so I
open the notification, and realize that tests via make
test vs CI (which additionally uses --pedantic)
had slightly different settings, and of course I missed a build
warning (which in CI is an error).
I thought I’d fix that on the plane, but then I saw a “Copilot agent” button in the mobile app. I was curious what it did, I click it, and I see Copilot starting a draft pull request, and saying:
Thanks for asking me to work on this. I will get started on it and keep this PR’s description up to date as I form a plan and make progress.
Fix the failing GitHub Actions job. Analyze the Actions logs, identify the root cause of the failure, and implement a fix.
Then it goes, finds the failure, writes the fix, and tries to
run the tests. Well, it can’t do it (it runs in a restricted
container, so no network, so stack install
couldn’t actually work). The agent sees that, acknowledges it
has no way to validate the fix, but the error message was clear
enough that it was confident the fix is mostly correct, so it sends
the pull request.
I allow full CI to run on the pull request, and go buy a bottle
of water. After that, I check and see that the CI failed again, as
not one but two test files were broken, and I didn’t have
--keep-going, so the build stopped at the first
failure. I write a comment in the pull request, no reaction, I
realize I need to tag Copilot explicitly, I do that, and it starts
another investigation.
I’m waiting now in the boarding queue, with phone in hand, while Copilot is fixing my bug. While I scan my boarding pass and walk towards the plane, the pull request is updated, I trigger another CI, it passes, and I merge it.
And then, it hit me. Agents allow me to make progress while being “not at keyboard”, whether that’s physically “not at keyboard”, or while working on something else. Fixing a simple test failure is not something that needs human attention per se, whereas improving the test layout might be.
In that airport, using otherwise-unusable downtime, and without explicitly intending to, I made progress in understanding a different way to use AI. Now I have three ways to work with LLMs: ask (tutor mode), code (implement my request), and agent (fix simple or complex problems, autonomously). I still don’t know about “plan” mode and really complex tasks, like asking it to implement features from scratch. That will probably be the next area to tackle.
And today (Sunday), while waiting for a running race to start, I opened GitHub, and asked Copilot to increase test coverage for a simple module. It did, and yes it still can’t run tests (I learned in the meantime that you can configure the environment in which the agent runs, nice), but after two back-and-forth messages, I have a pull request ready to review. All in the 20 minutes before a race, where I could either browse social media or actually do some meaningful work.
Checking now my GitHub billing, it looks like all of this Copilot use only cost $1.92. Yes, that is under two dollars! And while it did use compute resources, the person across the aisle who watched TikTok or Instagram for half an hour while waiting for takeoff also consumed a lot of compute, and so do the gazillion cat videos uploaded to YouTube every day.
To me, this is another tool in the toolbox, that might one day replace me (as it did to the 19th-century textile workers), or make me five times more productive — we’ll see where we end up. In the meantime, I can move faster, and make better use of my limited free time.
Enjoy the ride!
Dirk Eddelbuettel: RcppFarmHash 0.0.4 on CRAN: Maintenance [Planet Debian]

Another minor maintenance release of the RcppFarmHash package is now on CRAN as version 0.0.4.
RcppFarmHash
wraps the Google FarmHash family of hash
functions (written by Geoff Pike and contributors) that are used
for example by Google BigQuery for the
FARM_FINGERPRINT digest.
This releases updates several of package internal files for continuous intergration and package data.
The brief NEWS entry follows:
Changes in version 0.0.4 (2026-09-06)
- Minor updates to continuous integration, README.md and DESCRIPTION
Courtesy of my CRANberries, there is also a diffstat report for this release. For questions, suggestions, or issues please use the issue tracker at the GitHub repo.
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub.
Pluralistic: How corporate America built a better Roach Motel (07 Sep 2026) [Pluralistic: Daily links from Cory Doctorow]
->->->->->->->->->->->->->->->->->->->->->->->->->->->->->
Top Sources: None -->

"If economists wished to study the horse, they wouldn’t go and look at horses. They'd sit in their studies and say to themselves, 'What would I do if I were a horse?'" -Ely Devons
Half a century ago, a group of lavishly financed economists from the University of Chicago (the "neoliberals") convinced governments all over the world to completely upend the way they treated monopolies. Up until then, the purpose of competition enforcement was to reduce corporate power, with the understanding that once a corporation became more powerful than the government, it would be impossible to force it to follow any rules:
https://pluralistic.net/2022/02/20/we-should-not-endure-a-king/
But for the "Chicago Boys," monopolies were evidence of efficiency. When you encounter a company in the wild that has acquired a commanding market share, your first assumption should be that it has taken over its sector by being better than anyone else – you should not assume that the company cheated its way to glory. After all, if a company with a large market share was cheating – say, if it was increasing its profit margins by reducing quality or jacking up prices – then smaller companies would rush into the market to poach its dissatisfied customers.
Thus, all competition enforcement was reduced to an empty syllogism: monopolies are the result of excellence and any less-than-excellent monopolist will have its advantage "competed away." Therefore, any monopolist you encounter in the wild is definitionally not a bad monopolist, otherwise it would already have disappeared.
To quote another economist joke:
Two economists are walking down the street when one notices a $20 bill on the sidewalk. "It's not a real $20 bill," the other declares. "If it were a real $20 bill, someone would have picked it up off the sidewalk already."
Half a century later, our entire economy is dominated by monopolies, duopolies and cartels, who boast of gigantic margins, whose products are palpably worsening at an accelerating clip, and yet there is no sign of the "new market entrants" who should be flooding into the market to "compete away" those amazing margins. It turns out that asking "What would I do if I were a horse?" does not yield a series of accurate predictions about horses.
Things have changed. Today, the University of Chicago's Stigler Center harbors a cluster of influential economists who largely or entirely repudiate the orthodoxy of the Chicago Boys. The Center hosts an annual, rather radical conference on antitrust; runs an excellent heterodox podcast (Capitalisn't); its house organ, Promarket.org, regularly hosts work that torches the received wisdom of High Chicago Neoclassicism; and the school's researchers publish papers that dare to actually "go and look at horses."
A recent horse-looking excursion has yielded some distressing, alarming, and thoroughly documented equine facts. In a new Stigler paper, "Rising Customer Durability, Falling Business Dynamism," UC's Li Azinovic-Yang, Ava E Speros and Christopher R Stewart and Stanford's John D Kepler report on some clever research into how a monopolist could raise prices, lower quality, anger its customers, and still dominate its market:
https://www.chicagobooth.edu/-/media/research/stigler/pdfs/workingpapers/387_customer.pdf
The researchers' hypothesis was that dominant businesses don't maintain their lead by making their customers happy, but by making it harder for those customers to leave. There's good reasons to suspect this. Between 2002 and 2024, the average "customer relationship" (how long a customer continues to purchase from a merchant) has risen from 7.5 years to 11.5 years, a 50% surge in "customer loyalty," far outstripping any measure of customer satisfaction over the same period. This is true across all the largest sectors of the economy: "manufacturing, information, professional services, financial services, and wholesale trade."
How to explain the falling divorce rate between customers and businesses? That's where the researchers got very clever. They realized that when a company seeks permission to acquire another business, it must publish truthful and comprehensive information about how the merger is expected to increase the profits of the new combination. These disclosures are validated by external auditors, boards of directors, and/or audit committees. There are legal repercussions for falsifying them or making material omissions to them, and they are matters of public record.
Crucially, these disclosures must include the business's plans to retain its customers, and its plans to increase the profits from those customers. That's where the researchers struck gold. They amassed a novel data-set of 9,500 acquisitions that disclosed over $1t worth of "customer relationship-related intangibles," more than 20% of all the assets that changed hands.
They supplemented this data by mining earnings calls (also subject to strict penalties for omissions and falsehoods), finding CEOs boasting about "practices that may impede switching or increase customers’ dependence on the firm." Executives bragged about their "contractual restrictions, bundling and ecosystem lock-in, and switching costs."
You can get a sense of these in a short accompanying article by the study's co-author Christopher Stewart:
The article recounts how Sirius XM's execs celebrated the news that an appeals court had struck down the FTC's "Click to Cancel" rule, which required companies to make it as easy to resign from a subscription service as it was to sign up for it. Click to Cancel is a response to increasingly sleazy, increasingly pervasive tactics that make it all but impossible to stop being someone's customer. Trump's FTC walked away from defending the rule, which let the court kill it:
https://pluralistic.net/2025/05/12/greased-slide/#greased-pole
After Click to Cancel died, Sirius XM's C-suite got on a call with their shareholders to project "better outcome(s) as a result of not having that in place." Sirius believed that a rule that made it easy for customers to resign from their monthly subscriptions would hurt its business. Put another way: Sirius believes that its profits come in part from the fact that dissatisfied customers can't figure out how to cancel their service.
Then there's the online insurance company eHealth, whose execs crowed about a new "innovation" that forced senior patients to painstakingly enter a long list of their medications and doctors, but did not give them any way to export that data. The lengthy investment of time in getting set up on eHealth would stop customers from leaving, because they wouldn't want "to repeat all of that information over the phone."
This is also a feature of business-to-business relationships. In 2019, US Silica's execs described how they had launched a program to become embedded in their customers' supply chains, because that "really locks in the business," making it "much more difficult for customers to switch and go to someone else."
That's the first half of the story: an empirical account of how the business world switched from "acquiring customers" to taking hostages.
But the second half of the paper is even more interesting: an empirical investigation into the effects of this customer lock-in. For starters, increased customer retention is "associated with higher gross profit margins": that is, the companies whose customers can't leave squeeze those customers for more profit. What's more, once a company has its customers locked in, it starts to capture a larger share of all the profits in its entire sector: these hostage-takers become so profitable that their profits dwarf the profits of their competitors.
The paper also solves the mystery of the missing market entrants that the neoclassical horse-ponderers insisted would be conjured up to compete away an abusive monopolist's margins. The more locked in the customers of a monopolist are, the fewer companies try to enter that market. This makes sense: who would invest in a new business in a market where none of its potential customers can switch to its new business?
This is the opposite of what the horse-ponderers have insisted upon for 50 years. The more lock-in a company attains, the more profitable it becomes, and the less it has to worry about new competitors coming after those incredible margins. This is obvious to everyone, except the monopolist-funded "social scientists" and the governments they captured.
This is bad news, and not just for those locked-in customers. New businesses are the source of new jobs, and, yup, it turns out that sectors dominated by firms with high lock-in create fewer jobs. Of course, as workers chase fewer jobs, bosses are able to suppress their wages by forcing workers to bid against one another. Once again, the study finds that the sectors with the most lock-in also see declining wages in addition to declining jobs.
These are not the horse-ponderers' "efficient" monopolists. Once a company has its customers locked in, it innovates less – as measured by the number of patents a company is awarded, and by how often those patents are cited in other patents (this second measure helps distinguish companies that file mountains of bullshit patents from companies that actually invent useful things). Naturally, R&D spending also declines in companies with more lock-in.
All of this is entirely compatible with the theory of enshittification. Once a company knows its customers can't leave, it can switch from treating them well to abusing them in order to extract money from them. The same goes for companies whose workers can't leave – because they're bound by noncompete clauses, or because their employer has bought out all their rivals:
https://www.eff.org/deeplinks/2023/04/platforms-decay-lets-put-users-first
It's like the old Lily Tomlin sketches on SNL and Laugh-In, where she played Ernestine the telephone operator narrating satirical ads for AT&T. Those sketches would end with her obviously true catch-phrase: "We don't care. We don't have to. We're the phone company":
https://www.youtube.com/watch?v=CHgUN_95UAw
Decades later, Tomlin's phone company joke is a perfect distillation of modern management philosophy. As a famous NBER working paper showed, when a family business is handed over to a professional manager with an MBA, the company doesn't become more profitable overall; it just finds ways to pay its workers less:
https://www.nber.org/system/files/working_papers/w29874/w29874.pdf
That's why Tim Wu named this "the age of extraction." "Growth" no longer means "making something new that people want" – now it means "finding ways to take a larger share of the pie, even if that makes the pie smaller overall":
https://www.wired.com/story/tim-wu-age-of-extraction/
This is something we can all feel. We experience it in our daily lives, through "shrinkflation" and "junk fees" and a million other gross and petty scams. But it's rare that we actually catch executives explicitly admitting that their job is to find ways to take you hostage and squeeze you.
Historically, those revelations have come from extraordinary circumstances, like when Frontier (the worst ISP in America) went bankrupt and we learned that the company had 1.6 million customers who had no access to competing broadband connections. Frontier carried these hostages on their balance sheet as a special, highly valued asset, since they could be charged more for slower, less reliable service:
In assembling this novel, high-quality data-set, the researchers on this paper have performed an important service, capturing a vast number of sworn confessions of highly paid enshittifiers, and then showing how their hostage-taking wrecked competition, prices, wages, jobs and innovation.

Keep the Internet free https://keepitfree.ai/
Money Does Not Decide What It Becomes https://sekimonyo.com/money-does-not-decide-what-it-becomes
#20yrsago Three-hole punch debut, April 1940 https://web.archive.org/web/20061119140057/https://blog.modernmechanix.com/2006/09/06/three-hole-paper-punch-debut/
#20yrsago New Zealand redefines open source as “code you can’t modify” https://memex.craphound.com/2006/09/07/nobel-prize-sperm-bank-human-tragicomedy-about-eugenics/
#20yrsago MSFT quicker to patch DRM than security vulnerabilities https://www.schneier.com/blog/archives/2006/09/microsoft_and_f.html
#20yrsago Wikipedia’s dumbest arguments https://en.wikipedia.org/wiki/Wikipedia:Lamest_edit_wars
#10yrsago Why the Pirate Party could end up running Iceland https://web.archive.org/web/20211024071400/https://www.newstatesman.com/culture/2016/09/how-internet-pirates-became-political-force-iceland
#10yrsago Sampling bias: how a machine-learning beauty contest awarded nearly all prizes to whites https://web.archive.org/web/20160906154712/https://motherboard.vice.com/read/why-an-ai-judged-beauty-contest-picked-nearly-all-white-winners
#10yrago Warner Bros flags its own website as a piracy portal in copyright takedowns https://torrentfreak.com/warner-bros-flags-website-piracy-portal-160904/
#10yrsago The privacy wars have been a disaster and they’re about to get a LOT worse https://locusmag.com/feature/cory-doctorowthe-privacy-wars-are-about-to-get-a-whole-lot-worse/
#10yrsago Weapons of Math Destruction: invisible, ubiquitous algorithms are ruining millions of lives https://memex.craphound.com/2016/09/06/weapons-of-math-destruction-invisible-ubiquitous-algorithms-are-ruining-millions-of-lives/
#10yrsago Pro-democracy reformers win big in Hong Kong’s elections https://globalvoices.org/2016/09/06/hong-kong-voters-elect-pro-democracy-legislators-to-defend-the-citys-autonomy-from-china/
#1yrago Stock buybacks are stock swindles https://pluralistic.net/2025/09/06/computer-says-huh/#invisible-handcuffs

London: The Reverse Centaur's Guide to Life After AI with Riley
Quinn (Foyle's Picadilly), Sep 9
https://www.foyles.co.uk/events/enshittification-cory-doctorow-riley-quinn
Budapest: Brain Bar, Sep 17
https://brainbar.com/munkatars/cory-doctorow
Edmonton: Elbows Up (Edmonton Public Library), Sep 28
https://www.epl.ca/blogs/post/elbows-up-with-cory-doctorow/
South Bend: An Evening With Cory Doctorow (Notre Dame), Oct
6
https://franco.nd.edu/events/2026/10/06/an-evening-with-cory-doctorow/
Hudson, OH: Hudson Library, Oct 7
https://engagedpatrons.org/EventsExtended.cfm?SiteID=3850&EventID=596952&PK=
Calgary: Wordfest, Oct 8
https://wordfest.com/2026/show/wordfest-presents-cory-doctorow-2026/
Winnipeg: McNally Robinson, Oct 9
https://www.mcnallyrobinson.com/event-18991/An-Evening-with-Cory-Doctorow
Vancouver: Read, Resist, Repair, Rejoice (Vancouver Writers
Festival), Oct 19
https://writersfest.bc.ca/festival-event-2026/01
Victoria: Munro's Books, Oct 20
https://www.munrobooks.com/events/6113620261020
Vancouver: Life After AI (Vancouver Writers Festival), Oct
22
https://writersfest.bc.ca/festival-event-2026/46
Ottawa: Life After AI (Ottawa Writers Festival), Oct 24
https://writersfestival.org/event/life-after-ai
Vancouver: BC Policy Solutions Gala, Nov 12
https://bcpolicy.ca/gala/
The future of the tech crisis (How the Light Gets In)
https://iai.tv/video/the-future-of-the-tech-crisis?_auid=2020
How Tech Platforms Took Over the Economy (Dystopia Now)
https://sites.libsyn.com/566555/enshittification-and-reverse-centaurs-cory-doctorow-on-how-tech-platforms-took-over-the-economy
Hope, AI, Fixing the Internet and the Reverse Centaur of it all
(Wilosophy)
https://podcastaddict.com/everyone-relax/episode/231414816
Deflating the AI Bubble (Do Not Pass Go)
https://www.donotpassgo.ca/p/deflating-the-ai-bubble-with-cory
"Canny Valley": A limited edition collection of the collages I create for Pluralistic, self-published, September 2025 https://pluralistic.net/2025/09/04/illustrious/#chairman-bruce
"Enshittification: Why Everything Suddenly Got Worse and What to
Do About It," Farrar, Straus, Giroux, October 7 2025
https://us.macmillan.com/books/9780374619329/enshittification/
"Picks and Shovels": a sequel to "Red Team Blues," about the heroic era of the PC, Tor Books (US), Head of Zeus (UK), February 2025 (https://us.macmillan.com/books/9781250865908/picksandshovels).
"The Bezzle": a sequel to "Red Team Blues," about prison-tech and other grifts, Tor Books (US), Head of Zeus (UK), February 2024 (thebezzle.org).
"The Lost Cause:" a solarpunk novel of hope in the climate emergency, Tor Books (US), Head of Zeus (UK), November 2023 (http://lost-cause.org).
"The Internet Con": A nonfiction book about interoperability and Big Tech (Verso) September 2023 (http://seizethemeansofcomputation.org). Signed copies at Book Soup (https://www.booksoup.com/book/9781804291245).
"Red Team Blues": "A grabby, compulsive thriller that will leave you knowing more about how the world works than you did before." Tor Books http://redteamblues.com.
"Chokepoint Capitalism: How to Beat Big Tech, Tame Big Content, and Get Artists Paid, with Rebecca Giblin", on how to unrig the markets for creative labor, Beacon Press/Scribe 2022 https://chokepointcapitalism.com
"Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, April 20, 2027
"Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2027
"The Memex Method," Farrar, Straus, Giroux, 2027
Today's top sources:
Currently writing:
"The Post-American Internet," a sequel to "Enshittification," about the better world the rest of us get to have now that Trump has torched America. Fourth draft completed. Submitted to editor.
A Little Brother short story about DIY insulin PLANNING

This work – excluding any serialized fiction – is licensed under a Creative Commons Attribution 4.0 license. That means you can use it any way you like, including commercially, provided that you attribute it to me, Cory Doctorow, and include a link to pluralistic.net.
https://creativecommons.org/licenses/by/4.0/
Quotations and images are not included in this license; they are included either under a limitation or exception to copyright, or on the basis of a separate license. Please exercise caution.
Blog (no ads, tracking, or data-collection):
Newsletter (no ads, tracking, or data-collection):
https://pluralistic.net/plura-list
Mastodon (no ads, tracking, or data-collection):
Bluesky (no ads, possible tracking and data-collection):
https://bsky.app/profile/doctorow.pluralistic.net
Medium (no ads, paywalled):
Tumblr (mass-scale, unrestricted, third-party surveillance and advertising):
https://mostlysignssomeportents.tumblr.com/tagged/pluralistic
"When life gives you SARS, you make sarsaparilla" -Joey "Accordion Guy" DeVilla
READ CAREFULLY: By reading this, you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.
ISSN: 3066-764X
Easy Peasy Tunes Dave Mallinson [Judith Proctor's Journal]
Easy
Peasy Tunes: 101 Tunes for Absolute Beginners and Novices by
Dave
Mallinson
My rating: 5 of 5
stars
A book of folk tunes - mostly familiar - all in the useful keys of
G and D (useful if you're playing the typical G/D melodeon, or D/G
anglo in my case) And all in the easy part of the range of these
instruments.
Occasionally, tunes have been simplified a bit to make them easier
to play, but that's the purpose of the book!
View
all my reviews
comments
Honour among Enemies [Judith Proctor's Journal]
Honor
Among Enemies by David
Weber
My rating: 3 of 5
stars
I started out really enjoying the Harrington series, and gradually
going off them.
Two basic problems:
1. The plot arc is very similar in each novel
2. The politics. The People's Republic of Haven are clearly
intended to be Communists or an extremely left wing government (One
novel riffs heavily on the French Revolution). Most of their
population live on the dole and vote for more money.
At the same time, it is made clear that people on the dole get very
few chances in life - escaping from it is very difficult.
ie. People want to escape from it and have a better life, but are
portrayed as shiftless because they are given no help to do so.
In spite of this, the Republic still finds enough money to provide
a formidable navy.
So where is the tax money coming from?
There are 'good' and 'honourable' people in the Republic, but they
still don't - for me at least - compensate enough for the
politics.
There's some interesting ship combat scenes, but on the whole, I'd
rather read Hornblower - his ships were real ones and don't require
inventing new laws of physics to justify firing a broadside in
space. (Hornblower was the inspiration for the Harrington
series)
View
all my reviews
comments
INSANE CHARITY BIKE RIDE 2026! [Dork Tower]
2026 marks my 14th year of fundraising for the Fair Share CSA Coalition through Bike the Barns, to raise money to help local farmers get fresh-food to local low-income and elderly households.
Over those 14 years, Dork Tower readers (YOU!) and Munchkin players (YOU AGAIN!!) have raised more than a quarter of a million dollars for a wonderful local charity providing a critical important service in the fight against food insecurity.
This year will be VERY different, however:
In July, I fell, smashing my head on the pavement, blacking out, concussed.
Under Doctor’s orders (and my wife’s stern-but-loving supervision), I won’t be able to join the 2026 ride.
HOWEVER, if we hit the $10,000 goal, I am allowed to get on my bike for the first time since the concussion! I’ll cycle around the parking lot once or twice – POSSIBLY WITH A DUCK ON MY HEAD -but for me, this will represent as major an achievement as if I’d biked 60 miles in previous years.
Another big change: timeliness! In 2025, backer swag went out ridiculously late. This was entirely on me. But to make sure this does not happen again, all the swag will be drawn and completed and ready to go to press as soon at the ride is completed!
And the swag will be AWESOME: The original Munchkin card art at the $400 and above levels will be from the Munchkin 2nd Edition core set (as opposed to “corset.”) This will be your chance to own a classic Munchkin card that will define the game for decades to come!
I’ve added Sponsorship levels, so companies can pick up advertising at DorkTower.com that is 100% a charitable deduction, tax-wise. These may be added onto any level.
Finally, one of the stretch-goals is to provide Duck stickers and badges for all the other riders, so that EVERYONE in the event, should they wish, can ride with a Duck on their head (or possibly pinned to their saddle bag).
This has been a VERY weird time for me, to be sure, but with your support, we can continue to support local CSA farmers (who have already been hard-hit) and low-income a (even harder hit).
ANY donation – The 2025 Coloring Book PDF. Donate, and you will get a PDF of the brand-new coloring book (celebrating the $250,000 landmark).
$35
– The 2026 Munchkin Charity Postcard, signed, plus
the 2025 Coloring Book 
$75 – The 2026 Army of Dorkness Button, maybe it will be a sergeant again, maybe not, we’ll see….plus the 2026 Postcard and the 2025 Coloring Book PDF.
$125 – The Dork Web! You will get a PDF of the next Dork Tower collection as soon as it’s ready, before publication! This is the volume one of a new series, collecting the Dork Tower web strips for the first time! Plus the 2026 Button, the 2026 Postcard and the 2025 Coloring Book PDF.
$200 – All 13 prior Insane Charity Bike Ride Postcards, signed, The Dork Web, the 2026 Button, 2026 Postcard, and the 2025 Coloring Book PDF. (10 of 10 remaining)
$300 – The 2026 Gamehole Con plushie, Betty the Beholder, The Dork Web PDF, the 2026 Button, 2026 Postcard, and the 2025 Coloring Book PDF (20 of 20 remaining)
Betty has her eyes on you!
$400– Original Art from the Munchkin 2nd Edition Core Set!!! I don’t sell original Munchkin art. This is the only way you can obtain a piece, and this year the pieces come from one of the most important releases in Munchkin history! Plus, you get the Beholder plushie, The Dork Web PDF, the 2026 Button, 2026 Postcard, and the 2025 Coloring Book PDF (20 of 20 remaining)
$500 – The Dork Web physical book, which is scheduled to be published Spring 2027! Plus, you get the Original Munchkin Second Edition Art, the Beholder plushie, The Dork Web PDF, the 2026 Button, 2026 Postcard, and the 2025 Coloring Book PDF (20 of 20 remaining) (Please note that the book itself will be sent later, upon publication, and will be numbered, signed, and sketched in)
$750 – Appear in a Dork Tower Comic! You, a friend, your family, or even a pet will appear in a Dork Tower strip! We can talk about how best to fit you or your loved one into a strip. You also get the Dork Web physical copy (upon publication), the Original Munchkin Second Edition Art, the Beholder plushie, The Dork Web PDF, the 2026 Button, 2026 Postcard, and the 2025 Coloring Book PDF (4 of 4 remaining)
$2,000 – Become an Official Munchkin Card! You, a friend, your family, or even a pet will become an official Munchkin card! You also get 100 printed cards and the card’s original art, along with the Dork Web physical copy (upon publication), the Original Munchkin Second Edition Art, the Beholder plushie, The Dork Web PDF, the 2026 Button, 2026 Postcard, and the 2025 Coloring Book PDF (4 of 4 remaining)
SPONSORSHIP LEVEL 1 – Add $1,250 to any level for six months of ads on DorkTower.com. The ads may be changed up to four times and may include Dork Tower characters. Your company will be listed as a sponsor on all materials, and will be included in an upcoming Dork Tower comic strip or series, which I will write after working with you on the topic or game you’d like covered. (2 of 2 remaining)
SPONSORSHIP LEVEL 2 – Add $2,000 to any level for a full year of ads on DorkTower.com. The ads may be changed up to eight times and may include Dork Tower characters. Your company will be listed as a sponsor on all materials, and will be included in an upcoming Dork Tower comic strip or series, which I will write after working with you on the topic or game you’d like covered. (2 of 2 remaining)
PLEASE NOTE, there may be an added cost for shipping the physical rewards outside of the United States, depending on the whims of the current US administration. We apologize deeply for this, and will contact supporters as the time approaches.
STRETCH GOALS (updated 9/9)
PASSED $5,000 – A sticker commemorating the weird-ass “Safety First” Insane Charity Bike Ride 2026 campaign will be included with all physical orders.
PASSED $7,500 – A new Duck Button will be included with all physical orders. You too may now cycle with a duck!
Screenshot
Passing $10,000 – I’ll take to my bike for the first time since my fall!
Passing $12,000 – A second sticker commemorating the weird-ass “Safety First” Insane Charity Bike Ride 2026 campaign will be included with all physical pledges.
PASSING $14,000 THE DUCK! THE DUCK! THE DUCK! I’ll cycle for the first time concussion with the Steve Jackson Games Duck of Doom on my helmet again!
Passing $16,000 – A unique Munchkin card commemorating the weird-ass “Safety First” Insane Charity Bike Ride 2026 campaign will be included with all physical orders..
Passing $18,000 – Sticker and buttons for ALL riders participating in Bike the Barns 2026, so the entire ride gets a duck on their head (should they so wish)!
Passing $20,000 – THE SECOND DUCK! I’ll ear both the Duck of Doom AND the Duck of Gloom on my helmet (The Duck of Gloom add-on is dedicated to my late friend Andrew Hackard, who first suggested two ducks years ago.)
Girl Genius for Monday, September 07, 2026 [Girl Genius]
The Girl Genius comic for Monday, September 07, 2026 has been posted.
Grrl Power #1493 – Buff and wax? [Grrl Power]
I’d wager Max is thinking that it might be time to have “the talk” when she gets home. With Rowan, I mean. Going on a few dates under false pretenses is usually pretty inexcusable, but there are probably some circumstances when it’s… not terrible. Like if you just entered Witness Protection, and start dating a week later… that’s got to feel like a lot of lying. I mean it is, but that’s a case where it’s at least understandable. (Although, come to think of it, you’re probably discouraged from dating or doing anything that requires you to talk about yourself a lot within the first few months in WitPro, to give it time for your new identity to time to sink in. I have no idea either way, but it’d make sense.)
“Dating if you’re a superhero with a secret identity” is probably more of a gray area. I think you’d want to subtly but fairly quickly establish their views on the militarization of police powers and related industrial complexes – at least in the Grrl Verse. If you’re in most other super-verses like Marvel or DC, you’d want to establish their views on vigilante justice. (I think the Avengers are sanctioned law enforcement, depending on what stretch of the comic you’re reading. Maybe the Justice League too? I don’t actually know. But you rarely see Batman or Ant Man giving testimony in trials.)
Anyway, my point is, if you have a secret identity and you’re dating, figure out if you’re wasting your time and theirs early on by learning about their views on your day job. And if you have an intelligence branch as part of your superheroing outfit, do a background check to make sure your beau isn’t an agent of HYDRA or a Skrull infiltrator or some DC equivalent. Or just to make sure they’re not some influencer looking for a big vlog hit.
So you figure out if you’re compatible and that you trust them, you gotta come clean pretty early in the dating process. You can’t True Lies that shit and wait till you have a teenage kid together. Even then, I’m sure plenty of people will be all, “It’s totally unacceptable to lie on a date.” And that’s a valid… desire. Probably a little optimistic, but it’s nice to hope for a more ideal world.
So the next question is… is letting a naked alien man (what, like he’s going to get his pants wet?) wash smart latex off your back in the shower cheating? Galen (and/or Gellen) is going to get a pretty good look at Max’s bare butt. That said, until she grits back up, her butt is so shiny all he’s going to really see is his own face.
Oh, look who it is in the vote incentive. The NSFW version is finally up
at Patreon. Plus a bonus
pic.
I think she would get in trouble for doing this. She’d mess up the… floor of the waterfall? Is that what it’s called? The receiving pool? No, probably not that. Anyway, she’d churn things up and cause a ton of weird erosion.
Since you might be wondering, Niagara Falls is about 165 feet high, so Babezilla obviously doesn’t have to be full sized. I’d say she’s about 175-180 feet tall here?
Double res version will be posted over at Patreon. Feel free to contribute as much as you like.
New Comic: Symmetrical
Russell Coker: CoMaps [Planet Debian]
I have just tried CoMaps, a free mapping program released under the Apache license [1]. I have tried it on Android on a Pixel 6a but it also runs on Linux so I’ll try it on a PinePhone or similar at some convenient time. On Android it is in the F-Droid repository among others and for Linux there’s a Flatpak package.
The data it uses is from Open Street Map project [2] which has extensive and accurate coverage of every place I’ve looked at (Australia and a few other first-world countries). The first thing it does after being installed is start downloading the world data set from Open Street Map and prompt to download the data for the detected region (Melbourne in my case).
The UI is decent and allows most of the features that I am used to using in Google Maps. The quality of directions seems good, I’ve only tested it with one journey so far which was a 50 minute drive across the city and it gave a set of directions that Google Maps often gives.
It gives spoken directions which is an important feature but sometimes the way the directions are presented is confusing. When turning off a freeway it didn’t give a spoken direction to do that, it gave a direction to “turn right” which was AFTER leaving the freeway, fortunately the map was clearly displayed.
In terms of use practices of this program the main difference I recommend is checking which off ramp to use from a freeway before entering the freeway. With Google Maps you can rely on it giving clear directions in that case.
I recommend this program without reservation. It can do everything that Google Maps does apart from detecting traffic jams because there’s no way of detecting traffic without spying on users. It is designed to preserve user privacy and works well in that regard.
Steinar H. Gunderson: plocate 1.1.25 released [Planet Debian]

I've released version 1.1.25 of plocate. This time around, there's two security issues of unknown severity; if you chain them with other bugs, they could lead to being able to list files (but of course not their contents) that you should not normally be able to see. So an update is probably in order; you can never be too safe these days.
The full changelog is:
plocate 1.1.25, September 6th, 2026
- Fix two early-exit bugs with multiple databases.
Reported by Manpreet Singh and Tyler Spivey.
- Drop setgid properly, including the saved gid.
Reported by Michal Sekletar, found with the help of Claude Opus 4.6.
- Fix a potential symlink-checking race in updatedb.
Reported by Michal Sekletar, found with the help of Claude Opus 4.6.
As usual, you can get it from the home page, or it's on the way up in Debian unstable.
Enrico Zini: Migrating away from .org/.net/.com domains [Planet Debian]
After having witnessed how easy it is for good people to lose a
.org domain over a fascist tantrum (you can follow the
Autistici/Inventati story here and here), I've started moving all my
infrastructure to differently
managed TLDs.
enricozini.org and enricozini.com will
keep being functional for the time being, as dropping a domain
makes it available for squatting and impersonation.
These new domains are now online, with working web and emails:
It will take ages to migrate countless accounts that are tied to my primary email address, so better start early.
Waiting to see what will happen with .meow domains, which I supported despite not identifying as a cat.
The lab, the factory and the dentist [Seth's Blog]
At the lab, they don’t know the right answer. They are explorers and scientists. If you’re not failing, you’re not trying hard enough. The work at the lab requires mutual support, shared information and a commitment to the process of discovery.
At the factory, the answer is known. It’s productivity that’s being pursued. Do it a little faster, a little better and a little more reliably than yesterday. Cut costs. Repeat.
And the dentist? It’s not rote work, but it’s not a dance with the unknown. Meet a client, diagnosis the problem and do the work efficiently as well. Different than yesterday, but it certainly rhymes.
Where do you work?
It could be that you’re getting exactly what you signed up for.
Michael Stapelberg: Debian Code Search: Fast TurboPFor with Go SIMD [Planet Debian]
This August, I accomplished what I wanted for many years: I deleted the last cgo dependency in Debian Code Search! This was made possible by Go’s recently introduced SIMD support, because now we can implement the TurboPFor integer compression format as efficiently — more efficiently, in fact, by using the newer AVX512 instruction set! — as the reference implementation.
Debian Code Search (DCS) is a search engine that allows searching all the Open Source source code within Debian, with either literal search expressions or regular expression search queries.
A search engine uses an inverted index: a map from term to documents containing the term. Each document is typically represented most efficiently by using an id, so the index consists of many lists of document ids.
When searching, it is important to quickly decode these lists to answer the search query. However, there is a point of diminishing returns where the decoding speed, even though it can still be measurably improved quite a bit, no longer influences the overall query duration.
From 2012 (its inception) to 2019, Debian Code Search used to use a small index format, and queries were fast because the index was kept entirely in RAM. In 2019, I implemented the new index format, which adds an on-disk positional index. For literal queries (78.2% of DCS queries), querying the positional index on disk is faster than querying the non-positional index in RAM.
The efficient encoding of the TurboPFor format makes it possible to fit such an index on a mid-sized Hetzner server, which I rent with two 1 TB SSD disks. The optimized decoder of the C TurboPFor library is what made decoding fast at query time.
If you want to dive deeper into the algorithm, see this blog post from February 2019:
Motivation I have recently been looking into speeding up Debian Code Search. As a quick reminder, search engines answer queries by consulting an inverted index: a map from term to documents containing that term (called a “posting list”). See the Debian Code Search Bachelor Thesis (PDF) for a lot more details. Read more →
If you want to learn more about the positional index, see this blog post from September 2019:
For many years, you had the following options for using SIMD instructions in Go:
bytes.IndexByte
is
implemented with hand-written Go assembly (including
AVX2).crypto/internal/fips140/sha256 uses AVX2.
While Avo generator code definitely is higher-level than
hand-written assembly, it is still too close to assembly for my
taste.The C TurboPFor library has served us well, but Debian Code Search was always intended to be a project using Go, so I would prefer it if I did not have any C code in the project.
Go 1.26 (released in February 2026) introduced the
simd/archsimd package:
Go 1.26 introduces a new experimental
simd/archsimdpackage, which can be enabled by setting the environment variableGOEXPERIMENT=simdat build time. This package provides access to architecture-specific SIMD operations. It is currently available on theamd64architecture and supports 128-bit, 256-bit, and 512-bit vector types, such asInt8x16andFloat64x8, with operations such asInt8x16.Add. The API is not yet considered stable.
For my 2019 TurboPFor analysis, I implemented goturbopfor,
a native Go teaching decoder (without any SIMD), because I find
Go code easier to follow than C code, especially optimized C code.
My implementation was intentionally not optimized so that the code
was easier to study.
The TurboPFor format/algorithm has a vector-optimized part:
bitpacking comes in a scalar variant (bitunpack32) and
a vector variant (bitunpack256v32), where the
vector variant is used for full blocks (256 values) and the scalar
variant is used for remainder blocks (< 256 values).
When Go 1.26 was released, I used Claude Code to explore whether
my native Go decoder’s bitunpack256v32 function
(for the vertical vector layout) could be implemented using Go
SIMD, and the answer was yes, it was possible and it was faster
than without SIMD, but not quite at the level of C TurboPFor. If
you let Claude Code try for long enough, it eventually finds enough
optimizations (about 10) to match C performance.
I don’t want to vibe-code Debian Code Search, though, so I figured I would find some time to review the SIMD code at some point and see if I could implement something similar myself.
Before I found enough time and motivation to complete said review, I discovered that to not regress real-life query performance by more than 10 to 100 milliseconds (which seems acceptable), I don’t actually need to add SIMD code to my teaching decoder at all; it would be sufficient to reduce allocations in my teaching decoder and specialize it per bit width.
Encouraged by the possibility of using the optimized native Go
decoder in Debian Code Search, I explored whether I could also
implement a native Go encoder so that I could get rid of
the C TurboPFor dependency entirely. The answer is yes, it is
doable in a few days, and it isn’t even that much slower: Go
is at 76% of C, see
Debian/dcs commit e920dc7.
The goal I set myself at that point was to see if I could learn enough SIMD to optimize the native Go encoder such that its performance would match how DCS uses C TurboPFor (via cgo).
Beating C TurboPFor was possible in 2-3 commits (SIMD and bit width specialization). To my surprise, Claude Fable 5 pointed out that the encoder’s block scanning could be done more efficiently using a technique called positional popcount, and that is another 2x speed-up! 😲
To be clear: I am not saying the Go compiler beats C here. Certainly, the C compiler can also produce fast AVX512 code and can be used to implement positional popcount. When comparing apples to apples, i.e. backporting the AVX512 kernels and positional popcount technique to C TurboPFor, Go benchmarks a little slower at ≈1.4x C.
This spectacular result (much faster than what DCS had before) got me curious how far I could push the decoder with SIMD after all. I ended up matching/exceeding the cgo version here, too!
The rest of this article explains a few classes of optimizations I encountered along the way.
When I wrote my
goturbopfor teaching decoder, I named its
functions to match the upstream C TurboPFor library, but now I want
to get away from names like p4ndec256v32 — they
make sense from the TurboPFor perspective, but for Debian Code
Search, we can use cleaner names.
Before writing any code, I audited how DCS uses integer compression / decompression.
In Debian Code Search, we have the following usage patterns:
hello-2.12.3-1
package (hypothetically) contained only hello.c
with printf("hello!\n");, we would assign document ID
1 to hello.c and store in the partial
index that trigrams pri, rin,
int, ntf, etc. are all found in doc
1 (hello.c).1 in the partial index
might be document ID 2531 in the full index.For reading the index, we do keep the decoded
uint32s fully in memory, so we only need
DecodeN(input []byte, output []uint32) (read int), a
function that reads len(output) values
(uint32) from input and returns how many
bytes it consumed.
For writing the index (both in partial indexing, and when merging), keeping the entire index in memory is prohibitively expensive, so we need a streaming API, for decoding and for encoding.
Ultimately, I converged on the following API:
package pforenc
type BlockEncoder struct {
// scratch buffers can go here
}
// EncodeBlock encodes len(vals)<=256 uint32s into dest (one TurboPFor block).
func (*BlockEncoder) EncodeBlock(dest []byte, vals []uint32) []byte {}
// EncodeN calls EncodeBlock in a loop.
func (*BlockEncoder) EncodeN(dest []byte, vals []uint32) []byte {}
type StreamEncoder struct {
be BlockEncoder
vals [256]uint32
// scratch buffers
}
// if full, you need to call [EncodeBlock]
func (*StreamEncoder) Add(val uint32) (full bool)
// EncodeBlock must be called after all data was [Add]ed.
//
// Write the returned buffer to file or send it over the network;
// it is only valid until the next [EncodeBlock] call.
func (*StreamEncoder) EncodeBlock() []byte {
if se.n == 0 { return nil } // turn an extra EncodeBlock into a no-op
// …
}
This API (the decoder works similarly) allows us to process data in TurboPFor format without any memory allocations. The types are not safe for concurrent use by multiple goroutines. The zero value is ready to be used. For the streaming API, the result only stays valid until the next call.
Before we can optimize anything, we need a working decoder and
encoder. The decoder already exists: my goturbopfor
teaching decoder. Next up, I needed an encoder.
Writing a TurboPFor encoder has a delightfully simple starting point: You can encode all values at bit width 32, in little endian, at which point you only need to add a one-byte TurboPFor block header every 256 values and you’re done:
func (be *BlockEncoder) EncodeN(dest []byte, vals []uint32) []byte {
for len(vals) > 0 {
chunk := min(len(vals), 256)
dest = be.EncodeBlock(dest, vals[:chunk])
vals = vals[chunk:]
}
return dest
}
func (be *BlockEncoder) EncodeBlock(dest []byte, vals []uint32) []byte {
const bitWidth = 32
dest = append(dest, bitWidth)
for _, val := range vals {
dest = binary.LittleEndian.AppendUint32(dest, val)
}
return dest
}
Of course, this is a terribly inefficient compressor, so after the first commit, the real work starts: implement each block type until the compression matches the original C TurboPFor implementation (same output file size), or in other words: do the reverse of the decoder.
0 ≤ bitWidth
≤ 32) in little endian byte
order. By scanning all values and choosing the smallest bit width
that allows representing all values, this technique saves disk
space (compresses).I found it interesting to realize that the main work of the encoder is to scan the input values and choose the optimal block type, whereas the actual encoding itself is cheap in comparison.
At this point, we can look at performance and see that the Go encoder is at 76% of the C encoder.
In all honesty, I could have probably stopped here, but now that the milestone of a viable replacement was reached, I got curious to see how far it would be possible to push the encoder (how much work to reach C speeds?) and afterwards, the decoder, too.
GOAMD64The microarchitecture of a CPU determines which instructions it
provides, and that includes not just SIMD instruction sets (like
AVX2), but also other useful instructions like LZCNT
(Leading Zero Count), which can be used to implement math/bits.Len32
more efficiently, which the TurboPFor encoder needs to call on
every input value to determine the ideal bit width.
Let’s walk through how to set the microarchitecture level when using Go on 64-bit x86 (x86-64).
Go uses the GOARCH
environment variable to configure the target compilation
architecture, and I am using the value amd64 to select
64-bit x86 (AVX2 and AVX512 are instruction sets found on x86-64
CPUs). With GOARCH=amd64, the architecture-specific
variable GOAMD64
configures the microarchitecture level for which to compile and Go
1.18 introduced these 4
different levels:
GOAMD64=v1(default): The baseline.
Exclusively generates instructions that all 64-bit x86 processors can execute.
GOAMD64=v2: all v1 instructions,
plus CMPXCHG16B, LAHF, SAHF, POPCNT, SSE3, SSE4.1, SSE4.2, SSSE3.
GOAMD64=v3: all v2 instructions,
plus AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, OSXSAVE.
GOAMD64=v4: all v3 instructions,
plus AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL.
In 2026, I generally recommend compiling with
GOAMD64=v3 so that functions like bits.OnesCount8
are
compiled into intrinsics (POPCNT) instead of using
a lookup table.
For Intel CPUs, setting GOAMD64=v3 means your
programs will only start on Haswell CPUs (2013) or newer; for AMD
CPUs that means Zen 1 (2017) or newer.
In this specific case (DCS), I am even compiling with
GOAMD64=v4. The v4 microarchitecture
level requires AVX512, which means AMD Zen 4, Zen 5 or newer
(Intel’s story is… complicated). Luckily, both my main
development PC (Zen 5) and the Debian Code Search server (Zen 4)
are recent enough. Setting GOAMD64=v4 has little
effect on Go 1.27 itself: the only change is that maps use one less
instruction (VPBROADCASTB instead of
PSHUFB). But compiling with GOAMD64=v4
allows us to move one more feature check from runtime to compile
time, see
SIMD build tags.
It makes sense to set the microarchitecture level in your
benchmark setup so that you don’t measure the slow fallback
implementations. I use export GOAMD64=v4 in my
Makefile.
Go’s built-in testing
package contains support for benchmarks which are written in
functions of the form func BenchmarkXxx(b *testing.B).
The simplest way to run such benchmarks is go test
-bench=., but I ended up configuring a few convenience
make targets, which write results to
bench.txt and compare against
baseline.txt (the previous commit’s results,
usually), using the very useful benchstat
tool.
GOTEST=go test
# -count=6 gives p≤0.002 in benchstat:
# https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
BENCHFLAGS=-run=^$$ -bench=. -benchtime=200000x -count=6
# use taskset -c1 to always pin to the same single core,
# avoiding accidental scheduling on different cores on
# mixed-core CPUs like the Ryzen 9 9950X3D.
TASKSET=taskset -c 1
BENCH=$(TASKSET) $(GOTEST) $(BENCHFLAGS)
.PHONY: all test bench bench-baseline bench-relative
all: test
bench: test
$(BENCH) | tee bench.txt
# Compares compression ratio between C and Go implementation
benchstat -col /impl -row '/n /vals' -filter '-/impl:go-stream .unit:(encoded-bytes)' bench.txt
# Compares performance between C (cgo) and Go implementation
benchstat -col /impl -row '/n /vals' -filter '.unit:(Mval/s)' bench.txt
bench-baseline: test
$(BENCH) | tee baseline.txt
bench-relative: test
$(BENCH) | tee bench.txt
benchstat -filter '-/impl:go-stream .unit:(encoded-bytes)' baseline.txt bench.txt
benchstat -filter '/impl:go .unit:(Mval/s)' baseline.txt bench.txt
The encoded-bytes and Mval/s units are
custom metrics I am reporting from the various sub-benchmarks, which are
arranged such that I can filter / report them with
benchstat.
The main encoder (and decoder) benchmarks compare 3 different implementations (cgo, Go, Go with the StreamEncoder API) with a number of benchmark cases that are designed to cover the different block types and contain a similar mix of values as what we see in Debian Code Search:
// reportMetrics adds Mval/s and encoded-bytes metrics to all benchmarks.
func reportMetrics(b *testing.B, n int, nencoded int) {
b.ReportMetric(float64(nencoded), "encoded-bytes")
b.ReportMetric(float64(b.N*n)/1e6/b.Elapsed().Seconds(), "Mval/s")
}
// BenchmarkEncode/n=<N>/vals=<testcase>/impl=<c|go|go-stream>
//
// e.g. BenchmarkEncode/n=2048/vals=one-constant/impl=go-stream
func BenchmarkEncode(b *testing.B) {
for _, tc := range allBenchCases() {
n := len(tc.vals)
b.Run(fmt.Sprintf("n=%d/vals=%s", n, tc.name), func(b *testing.B) {
b.Run("impl=c", func(b *testing.B) {
b.ReportAllocs()
var encoded []byte
buf := make([]byte, turbopfor.EncodingSize(n))
for b.Loop() {
encoded = turbopfor.P4nenc256v32Buf(buf, tc.vals)
}
reportMetrics(b, n, len(encoded))
})
b.Run("impl=go", func(b *testing.B) {
b.ReportAllocs()
var be BlockEncoder
var encoded []byte
buf := make([]byte, 0, turbopfor.EncodingSize(n))
for b.Loop() {
encoded = be.EncodeN(buf, tc.vals)
}
reportMetrics(b, n, len(encoded))
})
b.Run("impl=go-stream", func(b *testing.B) {
b.ReportAllocs()
var se StreamEncoder
var encoded int
for b.Loop() {
encoded = 0
for _, val := range tc.vals {
if se.Add(val) {
encoded += len(se.EncodeBlock())
}
}
encoded += len(se.EncodeBlock())
}
reportMetrics(b, n, encoded)
})
})
}
}
Go has included excellent performance tooling for many years,
see the “Profiling Go
Programs” blog post (2011) for an example of how to use
pprof, a sampling profiler. This profiler can help
track down which part of a program runs slow, or where memory
allocations happen.
Once you identified the slow part of a program, how do you know why it’s slow?
To learn more about the specific bottlenecks your program encounters, you can consult your CPU’s hardware performance counters. For example, you could check the branch predictor counters to see if your program is slow due to a high number of branch mispredicts.
On Linux, the perf tool is the best way to access
the CPU hardware performance counters. A good starting point for
working with perf is the documentation
on “Top-down analysis with the perf tool”, which
describes the optimization method that Intel established.
In my Makefile, I set up two perf
targets:
# GOTEST and TASKSET like shown in the earlier benchmarking setup section:
GOTEST=go test -pgo=encode.cpuprof
TASKSET=taskset -c 1
PERFBENCHFLAGS=-test.bench='Encode/n=2048/vals=debian-mix/impl=go$$' -test.benchtime=200000x
# Use perf(1) to capture AMD IBS (the equivalent to Intel PEBS)
# PipelineL1 is roughly equivalent to Intel TopdownL1
perf:
$(GOTEST) -c
$(TASKSET) perf stat -M PipelineL1 ./pforenc.test -test.run=^$$ $(PERFBENCHFLAGS)
sudo perf record -F 4999 -e ibs_op// --call-graph fp ./pforenc.test -test.run=^$$ $(PERFBENCHFLAGS)
sudo chmod 644 perf.data
# 488281 iterations × 2048 values = 1.000e9 values, so counter/1e9 = per value.
perf-per-value:
$(GOTEST) -c
$(TASKSET) perf stat -x, -e cycles:u,instructions:u,branches:u,branch-misses:u ./pforenc.test -test.run=^$$ -test.bench='Encode/n=2048/vals=debian-mix/impl=go$$' -test.benchtime=488281x 2>&1 >/dev/null | awk -F, '{printf "%-16s %6.2f /val\n", $$3, $$1/1e9}'
The perf-per-value numbers are high level numbers
that indicate how much work the implementation is doing. Reducing
the number usually increases speed.
To see the counters for each instruction (and source code
lines), I use make perf, followed by perf
report. A quick shortcut is perf annotate,
which directly shows the hottest function.
Let’s first see how far we can get without reaching for SIMD instructions.
(The examples are not necessarily in commit order, but cherry-picked for clarity.)
PGO stands for Profile-Guided Optimization and is a feature that Go introduced as a preview in Go 1.20 (released in February 2023) and shipped as ready for general production use in Go 1.21 (released in August 2023).
The idea is to capture a CPU profile that records where your program spends most of its CPU time, which you then provide to the Go compiler to give it more data to make better decisions.
Most importantly, this way the Go compiler can inline functions much more aggressively than its usual heuristics allow, which does have a measurably positive effect in my series of optimization commits. Another optimization that a PGO profile allows the compiler to do is conditional devirtualization — but our TurboPFor code does not use any interfaces.
My strategy is to enable PGO before doing any other optimizations, so that we have the full inlining budget available that PGO gives us, and can measure the effect of other commits clearly.
Surprisingly, turning on PGO actually decreases our performance (-13% geomean), but a closer investigation reveals that we just got unlucky. Let me explain.
Aside from inlining and conditional devirtualization, PGO also
influences alignment: The Go compiler sets PCALIGNMAX(64,
31) on the first block of a loop (the “loop
body”) for all loops in hot functions (per the PGO profile),
i.e. Go will insert up to 31 bytes of padding to make the block
land on a 64-byte boundary. Documentation like AMD’s
“Software Optimization Guide for the AMD Zen5
Microarchitecture” (2024, #58455) explicitly recommends
aligning hot loops that way:
[…] for hot loops, some further knowledge of trade-offs can be helpful. Because the processor can read an aligned 64-byte fetch block every cycle, it is suggested to either align the start of the loop to the beginning of a 64-byte cache line […]
Indeed, when compiling with
-gcflags=all=-d=alignhot=0 to disable the alignment,
performance remains as good as without PGO. How can the padding
hurt more than help? The answer is: It’s not the padding
itself! It’s a side-effect of the padding moving instructions
to different addresses.
In the unlucky arrangement, a macro-fused
CMPQ+JGE instruction pair now ends up
exactly on a 32-byte boundary. However, the Go compiler
ensures fused branch sequences must never cross or end at a
32-byte boundary to
fix Intel erratum SKX102 (discussion: Go issue #35881) by
inserting NOPs.
This NOP padding, unlike the loop alignment
padding, is not free; these extra instructions slow down our
otherwise dispatch-bound loops.
Because the commits after the PGO enabling commit change the code, this unlucky situation is avoided for the rest of the optimization series (by chance).
Memory allocations are quite expensive, at least in comparison to encoding/decoding integers, so I followed my usual strategy of first reducing memory allocations as much as possible.
In my goturbopfor teaching decoder, whenever the
code needed a scratch buffer, it would allocate it right then and
there with make():
// p4dec32 decodes one block of TurboPFor-encoded 32 bit ints
func (d *decoder) p4dec32(input []byte, output []uint32) (read int) {
// …
switch blockType {
case blockBitpackingExceptions:
bx, input := input[0], input[1:]
n := len(output)
exmap := input
nex := 0 // number of exceptions
for i := 0; i < n; i++ {
if exmap[i/8]&(1<<uint(i%8)) != 0 {
nex++
}
}
input = input[(n+7)/8:]
exceptions := make([]uint32, nex)
input = input[bitunpack32(input, exceptions, bx):]
input = input[d.bitunpack(input, output, b):]
for i := 0; i < n; i++ {
if exmap[i/8]&(1<<uint(i%8)) != 0 {
output[i] += exceptions[0] << b
exceptions = exceptions[1:]
}
}
return before - len(input)
}
}
The Go compiler can turn make(T, n) calls into
stack allocations, if n is known at compile-time. But,
in this case nex is not known at compile-time. We can
verify that Go calls into the runtime
(runtime.makeslice) by dumping the object code
(assembly) with source annotated (-S):
% cd ~/go/src/github.com/stapelberg/goturbopfor
% git reset --hard 49b7c05cc61e77f0257568eb73833467714d2b4a
% go test -c # go1.27.0
% go tool objdump -S goturbopfor.test | perl -nlE 'say if /p4dec32/ .. /^$/'
TEXT github.com/stapelberg/goturbopfor.(*decoder).p4dec32(SB) /home/michael/go/src/github.com/stapelberg/goturbopfor/goturbopfor.go
func (d *decoder) p4dec32(input []byte, output []uint32) (read int) {
0x549f60 4c8da42460ffffff LEAQ 0xffffff60(SP), R12
0x549f68 4d3b6610 CMPQ R12, 0x10(R14)
0x549f6c 0f86d9070000 JBE 0x54a74b
0x549f72 55 PUSHQ BP
0x549f73 4889e5 MOVQ SP, BP
0x549f76 4881ec18010000 SUBQ $0x118, SP
0x549f7d 48899c2430010000 MOVQ BX, 0x130(SP)
0x549f85 4889b42448010000 MOVQ SI, 0x148(SP)
if len(output) == 0 {
0x549f8d 4d85c0 TESTQ R8, R8
0x549f90 0f84a7030000 JE 0x54a33d
0x549f96 660f1f840000000000 NOPW 0(AX)(AX*1)
0x549f9f 90 NOPL
[…]
exceptions := make([]uint32, nex)
0x54a4be 488d057bec1700 LEAQ 0x17ec7b(IP), AX
0x54a4c5 4c89fb MOVQ R15, BX
0x54a4c8 4889d9 MOVQ BX, CX
0x54a4cb e8f0ddf3ff CALL runtime.makeslice(SB)
[…]
An easy speed-up was to
avoid allocations through reuse (in goturbopfor).
In the DCS pfordec package (with the
improved API design), I ended up with a vals
[256]uint32 field in the StreamDecoder type,
which brings us from 773 Mval/s to 858 Mval/s on the
debian-mix:
% benchstat -filter '/impl:go /vals:debian-mix .unit:(Mval/s)' \
baseline.txt bench.txt
goos: linux
goarch: amd64
pkg: github.com/Debian/dcs/internal/turbopfor/pfordec
cpu: AMD Ryzen 9 9950X3D 16-Core Processor
│ baseline.txt │ bench.txt │
│ Mval/s │ Mval/s vs base │
n=2048 1.089k ± 1% 1.175k ± 0% +7.85% (p=0.002 n=6)
n=2039 974.7 ± 0% 1046.0 ± 0% +7.32% (p=0.002 n=6)
n=160 434.9 ± 1% 513.6 ± 5% +18.11% (p=0.002 n=6)
geomean 772.9 857.7 +10.98%
Aside from the speed-up, avoiding memory allocations is generally nice in benchmarks because it removes the garbage collector from the equation and makes it less likely that your benchmarks get other processes OOM-killed on the same machine.
In general, we want to make it easy for the compiler to
understand as much as possible about our algorithm. Consider this
bitpack implementation:
func bitpack(dest []byte, vals []uint32, bitWidth int) []byte {
mask := uint32(1<<bitWidth - 1)
var acc uint64
var have int
for _, val := range vals {
acc |= uint64(val&mask) << have
have += bitWidth
for have >= 32 {
dest = binary.LittleEndian.AppendUint32(dest, uint32(acc))
acc >>= 32
have -= 32
}
}
for have > 0 {
dest = append(dest, byte(acc))
acc >>= 8
have -= 8
}
return dest
}
Let’s think through what determines the iterations and control flow this function uses:
vals), but
not their actual value.bitWidth).With a bit of careful rearrangement, we can provide the compiler with both, a fixed number of input values (say, 32), and a bit width, both known at compile time. Why is this worthwhile? Because we can manually unroll the loop, let the compiler eliminate much of the repetition and get much faster compiled code as a result!
Let’s first fix the number of input values to 32 and
rewrite the loop to calculate the position offsets within
dest instead of changing dest on each
value (with AppendUint32):
func bitpack32Unrolled(dest []byte, vals *[32]uint32, bitWidth int) {
// only one bounds check for 32 values
dest = dest[: 4*bitWidth : 4*bitWidth]
mask := uint32(1<<bitWidth - 1)
var acc uint64
var have, pos int
// Manually unrolled loop starts here.
// Each iteration is identical except for the vals[x] index.
acc |= uint64(vals[0]&mask) << have
have += bitWidth
if have >= 32 {
binary.LittleEndian.PutUint32(dest[pos:pos+4], uint32(acc))
pos += 4
acc >>= 32
have -= 32
}
// vals[1] .. vals[30] elided for brevity
// Each loop iteration is 8 lines of Go code, so for 32 input values,
// bitpack32Unrolled contains 8*32 = 256 lines of code.
acc |= uint64(vals[31]&mask) << have
have += bitWidth
if have >= 32 {
binary.LittleEndian.PutUint32(dest[pos:pos+4], uint32(acc))
pos += 4
acc >>= 32
have -= 32
}
// have == 0; for all bitWidths
}
Next, we want to specialize not just for 32 input values, but also for each of the 32 bit widths.
Can we do better than hand-copying
bitpack32Unrolled 32 times (= 8192 lines of Go
code)?
Yes, we can use Go generics to help us with the code generation!
In Go, array
types like [4]byte (not slices like
[]byte!) contain the length of the array as part of
their type, meaning [1]byte (an array of length 1) is
a different type than [2]byte.
Instead of passing the bit width as a function parameter, we can declare 32 different types (one for each bit width) and recover the bit width (at compile time!) from the type system:
type bitWidthT interface {
[1]byte | [2]byte | [3]byte | [4]byte | [5]byte |
[6]byte | [7]byte | [8]byte | [9]byte | [10]byte |
[11]byte | [12]byte | [13]byte | [14]byte | [15]byte |
[16]byte | [17]byte | [18]byte | [19]byte | [20]byte |
[21]byte | [22]byte | [23]byte | [24]byte | [25]byte |
[26]byte | [27]byte | [28]byte | [29]byte | [30]byte |
[31]byte | [32]byte
}
func bitpack32Unrolled[T bitWidthT](dest []byte, vals *[32]uint32) {
var zero T
bitWidth := len(zero) // known at compile time
dest = dest[: 4*bitWidth : 4*bitWidth] // make cap known at compile time
mask := uint32(1<<bitWidth - 1)
var acc uint64
var have, pos int
// Manually unrolled loop starts here.
// Each iteration is identical except for the vals[x] index.
acc |= uint64(vals[0]&mask) << have
have += bitWidth
if have >= 32 {
binary.LittleEndian.PutUint32(dest[pos:pos+4], uint32(acc))
pos += 4
acc >>= 32
have -= 32
}
// vals[1] .. vals[31] elided for brevity
}
When we instantiate bitpack32Unrolled[bitWidthT]
with all 32 different types ([1]byte,
[2]byte, …, [32]byte), the compiler
substitutes the bitWidthT type parameter and produces
32 copies of the function, which we can find in our compiled
executable with names like
github.com/Debian/dcs/internal/turbopfor/pforenc.bitpack32Unrolled[go.shape.[12]uint8].
The “shape” of a generic type is based on its memory
layout, so a shape for [1]byte must be different than
the shape for [2]byte.
Because the bitWidth is now known at compile time,
the Go compiler can generate close to the optimal machine code for
each bit width, which we can confirm using go tool
objdump.
The code is branchless (after the one bounds check per 32 values) and aside from the loads and stores (from/to memory) consists only of shifts and bit operations, all with constant operands:
% go test -c && go tool objdump -S pforenc.test
[…]
TEXT github.com/Debian/dcs/internal/turbopfor/pforenc.bitpack32Unrolled[go.shape.[28]uint8](SB) /home/michael/dcs/internal/turbopfor/pforenc/bitpackunroll.go
func bitpack32Unrolled[T bitWidthT](dest []byte, vals *[32]uint32) {
0x660580 55 PUSHQ BP
0x660581 4889e5 MOVQ SP, BP
0x660584 48895c2418 MOVQ BX, 0x18(SP)
dest = dest[: 4*bitWidth : 4*bitWidth] // make cap known at compile time
0x660589 4883ff70 CMPQ DI, $0x70
0x66058d 0f820b030000 JB 0x66089e
acc |= uint64(vals[0]&mask) << have
0x660593 8b06 MOVL 0(SI), AX
0x660595 25ffffff0f ANDL $0xfffffff, AX
acc |= uint64(vals[1]&mask) << have
0x66059a 8b4e04 MOVL 0x4(SI), CX
0x66059d 81e1ffffff0f ANDL $0xfffffff, CX
0x6605a3 48c1e11c SHLQ $0x1c, CX
0x6605a7 4809c8 ORQ CX, AX
acc >>= 32
0x6605aa 4889c1 MOVQ AX, CX
0x6605ad 48c1e820 SHRQ $0x20, AX
binary.LittleEndian.PutUint32(dest[pos:pos+4], uint32(acc))
0x6605b1 90 NOPL
b[0] = byte(v)
0x6605b2 890b MOVL CX, 0(BX)
acc |= uint64(vals[2]&mask) << have
0x6605b4 8b4e08 MOVL 0x8(SI), CX
0x6605b7 81e1ffffff0f ANDL $0xfffffff, CX
0x6605bd 48c1e118 SHLQ $0x18, CX
0x6605c1 4809c1 ORQ AX, CX
acc >>= 32
0x6605c4 4889c8 MOVQ CX, AX
0x6605c7 48c1e920 SHRQ $0x20, CX
binary.LittleEndian.PutUint32(dest[pos:pos+4], uint32(acc))
0x6605cb 90 NOPL
b[0] = byte(v)
0x6605cc 894304 MOVL AX, 0x4(BX)
Now we need to actually call bitpack32 from the
general bitpack function:
func bitpack(dest []byte, vals []uint32, bitWidth int) []byte {
if bitWidth == 0 {
return dest // no payload, sparse block with only exceptions
}
if len(vals) >= 32 {
size := 4 * bitWidth
for len(vals) >= 32 {
existing := len(dest)
dest = slices.Grow(dest, size)[:existing+size]
bitpack32(dest[existing:] /*append*/, (*[32]uint32)(vals), bitWidth)
vals = vals[32:]
}
}
mask := uint32(1<<bitWidth - 1)
var acc uint64
var have int
for _, val := range vals {
acc |= uint64(val&mask) << have
have += bitWidth
for have >= 32 {
dest = binary.LittleEndian.AppendUint32(dest, uint32(acc))
acc >>= 32
have -= 32
}
}
for have > 0 {
dest = append(dest, byte(acc))
acc >>= 8
have -= 8
}
return dest
}
func bitpack32(dest []byte, vals *[32]uint32, bitWidth int) {
switch bitWidth {
case 1: bitpack32Unrolled[[1]byte](dest, vals)
case 2: bitpack32Unrolled[[2]byte](dest, vals)
case 3: bitpack32Unrolled[[3]byte](dest, vals)
case 4: bitpack32Unrolled[[4]byte](dest, vals)
case 5: bitpack32Unrolled[[5]byte](dest, vals)
case 6: bitpack32Unrolled[[6]byte](dest, vals)
case 7: bitpack32Unrolled[[7]byte](dest, vals)
case 8: bitpack32Unrolled[[8]byte](dest, vals)
case 9: bitpack32Unrolled[[9]byte](dest, vals)
case 10: bitpack32Unrolled[[10]byte](dest, vals)
case 11: bitpack32Unrolled[[11]byte](dest, vals)
case 12: bitpack32Unrolled[[12]byte](dest, vals)
case 13: bitpack32Unrolled[[13]byte](dest, vals)
case 14: bitpack32Unrolled[[14]byte](dest, vals)
case 15: bitpack32Unrolled[[15]byte](dest, vals)
case 16: bitpack32Unrolled[[16]byte](dest, vals)
case 17: bitpack32Unrolled[[17]byte](dest, vals)
case 18: bitpack32Unrolled[[18]byte](dest, vals)
case 19: bitpack32Unrolled[[19]byte](dest, vals)
case 20: bitpack32Unrolled[[20]byte](dest, vals)
case 21: bitpack32Unrolled[[21]byte](dest, vals)
case 22: bitpack32Unrolled[[22]byte](dest, vals)
case 23: bitpack32Unrolled[[23]byte](dest, vals)
case 24: bitpack32Unrolled[[24]byte](dest, vals)
case 25: bitpack32Unrolled[[25]byte](dest, vals)
case 26: bitpack32Unrolled[[26]byte](dest, vals)
case 27: bitpack32Unrolled[[27]byte](dest, vals)
case 28: bitpack32Unrolled[[28]byte](dest, vals)
case 29: bitpack32Unrolled[[29]byte](dest, vals)
case 30: bitpack32Unrolled[[30]byte](dest, vals)
case 31: bitpack32Unrolled[[31]byte](dest, vals)
case 32: bitpack32Unrolled[[32]byte](dest, vals)
}
}
Encoding remainder blocks is quite a bit faster (full blocks use the vertical layout anyway):
% benchstat -filter '/impl:go /n:160 .unit:(Mval/s)' baseline.txt bench.txt
goos: linux
goarch: amd64
pkg: github.com/Debian/dcs/internal/turbopfor/pforenc
cpu: AMD Ryzen 9 9950X3D 16-Core Processor
│ baseline.txt │ bench.txt │
│ Mval/s │ Mval/s vs base │
vals=bitpacking-bw1 751.2 ± 3% 1120.5 ± 0% +49.15% (p=0.002 n=6)
vals=bitpacking-bw2 716.8 ± 2% 1176.0 ± 0% +64.07% (p=0.002 n=6)
vals=bitpacking-bw7 700.0 ± 1% 1078.5 ± 0% +54.08% (p=0.002 n=6)
vals=bitpacking-bw1-exc 524.8 ± 1% 736.8 ± 0% +40.40% (p=0.002 n=6)
vals=bitpacking-bw2-exc 543.7 ± 1% 758.2 ± 0% +39.46% (p=0.002 n=6)
vals=bitpacking-bw7-exc 566.7 ± 1% 787.7 ± 0% +38.99% (p=0.002 n=6)
vals=bitpacking-vb-exc 442.6 ± 1% 616.5 ± 0% +39.29% (p=0.002 n=6)
vals=sparse-exc 532.4 ± 0% 787.8 ± 0% +47.97% (p=0.002 n=6)
vals=sparse-vb-exc 408.9 ± 1% 597.8 ± 0% +46.20% (p=0.002 n=6)
vals=debian-mix 559.5 ± 0% 783.8 ± 9% +40.09% (p=0.002 n=6)
This performance win comes at the cost of binary size increase.
In this case, the .text section (executable code)
grows by about 20 KB and the .gopclntab section grows
by another 26 KB. Definitely a price I am very willing to pay, but
the case might not be as clear in all circumstances.
Even without reaching for SIMD instructions, a TurboPFor
implementation can be made faster by making it work bigger strides.
Take this code from the goturbopfor teaching decoder
which counts the number of exceptions by checking if each
value’s bit is set in the exception bitmap:
case blockBitpackingExceptions:
bx, input := input[0], input[1:]
n := len(output)
exmap, input := input, input[(n+7)/8:]
nex := 0 // number of exceptions
for i := range n {
if exmap[i/8]&(1<<uint(i%8)) != 0 {
nex++
}
}
exceptions := d.scratch[:nex]
We can use the bits.OnesCount64
functions to count ones bits in the exception bitmap, 64 values at
a time. For remainder blocks, the rest is processed 8 values (1
byte) at a time:
i := 0
for ; i+8 <= n/8; i += 8 {
xm8 := binary.LittleEndian.Uint64(exmap[i:])
nex += bits.OnesCount64(xm8)
}
for ; i < (n+7)/8; i++ {
xmb := exmap[i]
// Clear the bits which do not belong to the exception map:
if rem := n - i*8; rem < 8 {
xmb &= 1<<rem - 1
}
// Go compiles OnesCount32 into an intrinsic,
// but not OnesCount8, so we convert to uint32:
nex += bits.OnesCount32(uint32(xmb))
}
OnesCount64 uses a 64-bit register. For comparison,
AVX2 SIMD instructions use 256-bit registers (= 8
uint32) and AVX512 SIMD instructions use 512-bit
registers.
In the following sections, we will first set up our build tags for conditional compilation to use a trivial SIMD instruction, then walk through an AVX2 and AVX512 SIMD kernel.
Let’s assume we have the following scalar code:
constant.go:
package pfordec
func fillConstant(output []uint32, val uint32) {
for i := range output {
output[i] = val
}
}
To increase throughput, we can use AVX2 instructions if they are
available on the CPU on which the program runs, i.e. using runtime
dispatch. We’ll first rename fillConstant to
fillConstantScalar (it’s now the fallback
path):
constant.go:
package pfordec
func fillConstantScalar(output []uint32, val uint32) {
for i := range output {
output[i] = val
}
}
Next, we’ll supply two different implementations
(constant_nosimd.go and
constant_amd64.go), the latter of which is selected
when compiling for GOARCH=amd64 with
GOEXPERIMENT=simd (the latter will hopefully be
dropped in a later version of Go). The nosimd variant
just dispatches to the fillConstantScalar, which will
likely be inlined:
//go:build !goexperiment.simd || !amd64
package pfordec
func fillConstant(output []uint32, val uint32) {
fillConstantScalar(output, val)
}
The constant_amd64.go variant assigns the
hasAVX2 global variable by doing a CPUID
check and then jumps to the scalar fallback if
!hasAVX2, i.e. the CPU is too old:
//go:build goexperiment.simd && amd64
package pfordec
import "simd/archsimd"
var hasAVX2 = archsimd.X86.AVX2()
func fillConstant(output []uint32, val uint32) {
if !hasAVX2 {
fillConstantScalar(output, val)
return
}
val8 := archsimd.BroadcastUint32x8(val)
i := 0
for ; i+8 <= len(output); i += 8 {
val8.StoreArray((*[8]uint32)(output[i : i+8]))
}
// use the scalar implementation for the last <= 7 elements
fillConstantScalar(output[i:], val)
}
We can go one step further by conditionally compiling
const hasAVX2 = true when GOAMD64 is set
to v3 or higher (i.e. the amd64.v3 build
tag is set). As a practical example from Debian Code Search, we
currently need the following checks / dispatches:
| code | function | vector instruction set | GOAMD64 |
|---|---|---|---|
| encoder | bitpack256v | AVX2 | GOAMD64=v3 |
| encoder | exbitmap | AVX512 | GOAMD64=v4 |
| encoder | scan | AVX512+VBMI+GFNI+BITALG | n/a |
| decoder | bitunpack | AVX2 | GOAMD64=v3 |
| decoder | bitunpack256v32 | AVX2 | GOAMD64=v3 |
| decoder | bitunpack256v32Ex | AVX512 | GOAMD64=v4 |
In DCS, the effect is measurably positive, but small.
First, here is the layout explanation from my 2019 TurboPFor analysis blog post:
In regular (non-SIMD) bitpacking, integers are stored on disk one after the other, padded to a full byte, as a byte is the smallest addressable unit when reading data from disk. For example, if you bitpack only one 3 bit int, you will end up with 5 bits of padding.
![]()
SIMD bitpacking works like regular bitpacking, but processes 8
uint32little-endian values at the same time, leveraging the AVX instruction set. The following illustration shows the order in which 3-bit integers are decoded from disk:
The scalar implementation uses an array of 8 uint64
to process 8 values at a time:
func bitunpack256v32(input []byte, dest []uint32, bitWidth int) (read int) {
mask := uint64(1)<<bitWidth - 1
orig := len(input)
var bits uint
var acc [8]uint64 // accumulator: current+next bits
for op := 0; op < len(dest); {
if bits < uint(bitWidth) {
// read 8 more uint32s
for i := range 8 {
acc[i] |= uint64(binary.LittleEndian.Uint32(input)) << bits
input = input[4:]
}
bits += 32
}
for i := range 8 {
dest[op] = uint32(acc[i] & mask)
op++
acc[i] >>= bitWidth
}
bits -= uint(bitWidth)
}
return orig - len(input)
}
The SIMD version also processes 8 values, but without a
for i := range 8 loop!
One difference is that we no longer have the luxury of using
uint64 for acc (holding rest and current
bits); because AVX2 registers only fit 8 uint32 (not 8
uint64). Instead, we split acc into
rest8 and cur8.
func bitunpack256v32(fullinput []byte, fulldest []uint32, bitWidth int) (read int) {
dest := fulldest[:256]
if bitWidth == 0 {
clear(dest)
return 0
}
n := 32 * int(bitWidth)
input := fullinput[:n] // tell the Go compiler how long the input is
mask8 := archsimd.BroadcastUint32x8(uint32(1)<<bitWidth - 1)
bitWidth8 := archsimd.BroadcastUint32x8(uint32(bitWidth))
var bits uint
pos := 0
// var acc [8]uint64
var rest8 archsimd.Uint32x8
var cur8 archsimd.Uint32x8
for op := 0; op < 256; op += 8 {
if bits < uint(bitWidth) {
// read 8 more uint32s
// acc[i] |= uint64(binary.LittleEndian.Uint32(input)) << bits
next := archsimd.LoadUint8x32(input[pos : pos+32]).ReshapeToUint32s()
pos += 32 // input = input[4:]
cur8 = rest8.Or(next.ShiftAllLeft(uint64(bits)))
// acc[i] >>= bitWidth
rest8 = next.ShiftAllRight(uint64(uint(bitWidth) - bits))
bits += 32
} else {
cur8 = rest8
// acc[i] >>= bitWidth
rest8 = rest8.ShiftRight(bitWidth8)
}
// dest[op] = uint32(acc[i] & mask)
cur8.And(mask8).Store(dest[op : op+8])
bits -= uint(bitWidth)
}
return n
}
The SIMD version benchmarks about 3x as fast as the scalar version.
Another significant speedup is to
use generics for bit width specialization for this SIMD kernel
so that bitWidth becomes a compile-time constant and
the compiler can generate better code.
For my TurboPFor encoder, I implemented the same techniques as described above:
These changes are sufficient to roughly match the cgo performance, but then Claude Fable 5 found another 2x speed-up on top of that!
The key observation is that once encoding blocks is fast, the
preceding step of scanning the input values to decide which block
type to use becomes the bottleneck. Here is the encoder’s
main encode function, which first does one pass over
the input values (scan) and then prices all different
block types at all relevant bit widths (requires fast access to the
scan histogram):
func (be *BlockEncoder) encode(dest []byte, vals []uint32, layout blockLayout) []byte {
var stats stats
scan(&stats, vals) // gathers statistics from every value in vals
bitWidth := bits.Len32(stats.or)
if stats.or == stats.and {
return be.encodeConstant(dest, vals, bitWidth)
}
n := len(vals)
// bitpacking is the default, unless we find a more efficient block type.
bestType := blockBitpacking
bestB := bitWidth
best := priceBitpack(n, bitWidth, layout)
// Walk from high bitWidths to low: to break ties, we prefer
// the encoding with fewer exceptions (for faster decoding).
for b := bitWidth - 1; b >= 0; b-- { // up to 32 iterations
nex := int(stats.cnt[b])
size := priceBitpackExceptions(n, b, bitWidth, nex, layout)
if size < best {
bestType = blockBitpackingExceptions
bestB = b
best = size
}
// Over-approximate the number of VB bytes.
vb := nex + // exceptions using 1, 2, 3, 4, or 5 VB bytes
int(stats.cnt[b+7]+ // exceptions using 2, 3, 4, or 5 VB bytes
stats.cnt[b+14]+ // exceptions using 3, 4, or 5 VB bytes
stats.cnt[b+19]+ // exceptions using 4 or 5 VB bytes
stats.cnt[b+24]) // exceptions using 5 VB bytes
size = headerBytes + headerExBytes + payloadBytes(n, b, layout) + vb + nex
if size < best {
bestType = blockBitpackingVBExceptions
bestB = b
best = size
}
}
switch bestType {
case blockBitpacking:
return be.encodeBitpack(dest, vals, layout, bitWidth)
case blockBitpackingExceptions:
return be.encodeBitpackExc(dest, vals, layout, bestB, bitWidth-bestB)
case blockBitpackingVBExceptions:
return be.encodeBitpackVBExc(dest, vals, layout, bestB, int(stats.cnt[bestB]))
default:
panic("BUG: bestType not implemented")
}
}
I’ll show you a slightly shortened version of
scan, the function which is the bottleneck:
type stats struct {
// cnt[n] = how many values where bits.Len32(val)>n,
// i.e. how many exceptions are required for bitWidth=n.
// Padded so that cnt[b+24] is always in bounds.
cnt [32 + 24]uint32
}
func scan(output *stats, vals []uint32) {
for _, val := range vals {
for b := range bits.Len32(val) {
output.cnt[b]++ // b bits are not enough to store val
}
}
}
Let’s consider the following 3 example values to
understand the resulting cnt:
| input | input (bin) | bits.Len32 |
|---|---|---|
| 23 | 0b0000010111 |
5 |
| 5 | 0b0000000101 |
3 |
| 666 | 0b1010011010 |
10 |
The resulting cnt exception count histogram would
contain (cnt shortened to c):
c[0] |
c[1] |
c[2] |
c[3] |
c[4] |
c[5] |
c[6] |
c[7] |
c[8] |
c[9] |
c[10] |
|---|---|---|---|---|---|---|---|---|---|---|
| 3 | 3 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 0 |
In words, this means that at bit width 10, we could encode all the values without any exceptions.
But most values do not need 10 bits, so a bit width of 5 would be more efficient, but requires storing one exception. Encoding at bit width 4 requires 2 exceptions, and so on.
The scan function above is intentionally kept
simple for illustration. We can make it faster
by moving the per-bit-width loop outside the per-element loop.
The fast version still needs about 12 instructions per value. With
SIMD, we can reduce this to by 8x to only 1.5 instructions per
value!
The trick is to turn each input value into its “smear mask” (imagine taking the first 1 bit and smearing it across the remaining positions). Here are the smear masks for our example:
| input | input (bin) | bits.Len32 |
“smear mask” |
|---|---|---|---|
| 23 | 0b0000010111 |
5 | 0b0000011111 |
| 5 | 0b0000000101 |
3 | 0b0000000111 |
| 666 | 0b1010011010 |
10 | 0b1111111111 |
Turning a value into its smear mask is computationally cheap: Go
implements BitLen(x) (functions like
bits.Len32) by calculating 32 - LZCNT(x).
We can calculate the “smear mask” of a value with
^uint32(0) >> LZCNT(x), i.e. starting with a
32-one-bits mask and shifting it by the number of leading
zeros.
Now, to obtain e.g. cnt[4], we can count the 1 bits
at bit position 4 of all input values.
The POPCNT instruction counts bits very
efficiently, but it counts one bits within a register, so it counts
rows, not columns. Counting columns is called Positional
Population Count.
I found the following papers that describe positional popcount with SIMD:
To understand the AVX512 implementation of positional popcount,
I found it most helpful to visualize an AVX512 register (512 bits,
i.e. 64 bytes). The graphic below uses the Uint64x8
layout, meaning it divides the register into 8 lanes of 64 bits (=
8 bytes) each.
This illustration shows the whole process: how
uint32s are loaded into an AVX512 register (all 4 of
its bytes, in sequence) and where we end up, i.e. the 32 positional
popcounts:
Let’s break down this process into its individual steps.
First, we turn each loaded value into its smear mask as explained above.
The VPOPCNTB vector instruction calculates
POPCNT (1 byte) of 64 bytes at once, but first we need
to shuffle the bytes inside the register: in load order, we have a
full uint32 (4 bytes), followed by another
uint32, per lane. First, we permute the bytes
(VPERMB) such that all the first bytes of each value
end up in one lane (“transpose the bytes”):
Next, we “transpose the bits” using the
GF2P8AFFINEQB instruction, which sounds scary but
turns out to be quite flexible for bit manipulation of all kinds.
The GF2P8AFFINEQB instruction is also “the star of the show”
in Go’s Green Tea Garbage Collector (2025). Here is the
bit transpose, shown in the AVX512 register layout (see below for a
different layout):
I found it easier to understand the transpose step when arranging the 8 bytes of lane 0 from top-to-bottom (instead of left-to-right), because then it looks like a 90 degree clockwise rotation:
Now we can use VPOPCNTB to count the bits in all 64
bytes at once:
After all loop iterations (processing 16 values each) are done, we add the two groups (first 8 values, second 8 values) to obtain the 32 exception counts:
Here is the Go code that implements what I described visually above:
func scanSIMD(output *stats, vals []uint32) {
ones16 := archsimd.BroadcastUint32x16(^uint32(0)) // 16 32-one-bits masks
shuffle := archsimd.LoadUint8x64Array(&scanShuffle)
units := archsimd.LoadUint8x64Array(&scanUnits)
var acc archsimd.Uint8x64
idx := 0
for ; idx+16 <= len(vals); idx += 16 {
v := archsimd.LoadUint32x16(vals[idx : idx+16])
// Replace all values with their smear masks.
smear := ones16.ShiftRight(v.LeadingZeros()).ReshapeToUint8s()
// Transpose: shuffle the bytes, then transpose the bits.
matrices := smear.Permute(shuffle).ReshapeToUint64s()
transposed := units.GaloisFieldAffineTransform(matrices, 0)
// Popcount 64 bytes at once into the accumulator.
acc = acc.Add(transposed.OnesCount())
}
// Store the accumulator into output.cnt:
// Widen the two groups of byte counts to uint16 lanes (so that
// 128+128 = 256 fits), fold them into cnt[b] for b=0..31,
// then widen again to the uint32 lanes of output.cnt.
sum := acc.GetLo().ExtendToUint16().Add(acc.GetHi().ExtendToUint16())
sum.GetLo().ExtendToUint32().Store(output.cnt[0:16])
sum.GetHi().ExtendToUint32().Store(output.cnt[16:32])
// scalar tail for the 0..15 remaining values
for _, val := range vals[idx:] {
for b := range bits.Len32(val) {
output.cnt[b]++
}
}
}
Have a look at the commit introducing positional popcount to DCS for the full code (including shuffle tables and ISA checks) as well as the detailed benchmark results.
The SIMD optimizations I showed above beat the cgo TurboPFor library that Debian Code Search used before. When comparing apples to apples, i.e. backporting the AVX512 kernels and positional popcount technique to C TurboPFor, Go benchmarks a little slower at ≈1.4x C.
Could we make my Go TurboPFor implementation even faster, to truly match the C speed?
Yes! But also no. Let me explain:
We could use more SIMD instructions to remove all code that
still processes one value at a time. For example, in my
encoder’s encodeBitpackVBExc function. Or we
could price all bit widths concurrently in encode. Or
in the decoder’s exception apply code path.
But all of these SIMD instructions make understanding (and
changing) the code harder, so I am cautious regarding which ones I
introduce.
A big part of the performance gap is due to Go’s bounds checks. While it costs performance, bounds checking is great for safety, so I will not turn off bounds checking. The Go compiler eliminates a number of bounds checks when it understands it’s safe to do so. One optimization avenue could be to make the prove pass in the Go compiler smarter to eliminate more bounds checks.
When doing
mid-stack inlining (proposal #19348) (2017), Go sometimes needs
to put NOP instructions into the binary so that it can
attach inlining markers. For dispatch-bound functions, these extra
NOPs can measurable slow down execution.
The Go compiler currently allows specifying the architecture
(GOARCH=amd64) and microarchitecture
(GOAMD64=v3), but not a specific CPU architecture
(like AMD Zen 4). Therefore, CPU-specific workarounds for one
vendor affect all the generated code. The specific one I
encountered in my code is that the Go compiler emits XORL
CX,CX before every POPCNT to break a
false-output-dependency from the Intel Sandy Bridge Skylake era,
which is unnecessary on AMD Zen CPUs.
I suspect that Go intentionally does not offer this level of
customizability.
After all of the above points are addressed, what remains is
better code generation in specific cases. To illustrate what I
mean, consider the example of incrementing a loop variable, where
Go re-derives an index every time:
Go: POPCNTL; ADDQ DI,CX; LEAQ (base)(CX*4) (3
instructions)
clang: popcnt; lea rax,[rax+4*rdi] (2
instructions)
Depending on the specific case, improving the compiler might be
easy or prohibitively complex. Often, such improvements are hard to
measure conclusively.
Go’s SIMD support makes available — in Go code without having to resort to cgo or assembly — a powerful part of modern CPUs which allows speeding up the kind of computation that TurboPFor needs by an order of magnitude! 😲
I found it very valuable to use a coding agent (Claude Code, with Opus 5 and Fable 5 in this case) to help with the many tedious parts of such performance work (and still it took me weeks!). The LLM can read objdump output much faster than I can, can see patterns and correlations I might never identify, never becomes frustrated after a compiler error or runtime panic, and never runs out of patience to run one more experiment, as long as I give it measurable and reachable goals.
The performance of the SIMD code which one can get from the Go compiler is pretty close to what a good C compiler like clang provides. The CPU performance counters show value decoding speeds of 7 instructions/cycle (IPC) on a machine where the maximum is 8 IPC.
To me, SIMD support is a very welcome addition to Go.
Urgent: Limit monitoring systems [Richard Stallman's Political Notes]
US citizens: call on your state officials to limit Orwellian monitoring systems in order to protect everyone's freedom.
Here is what I wrote:
I urge you to pass legislation prohibiting governments and agencies in our state from setting up cameras that identify and record individual people or vehicles, except based on a warrant limited this surveillance to specified places and time intervals.
It is not enough to ban contracts with Flock. The issue is not limited to that one company. The issue is the danger of Orwellian surveillance and tracking, and the repression they make possible. As shown by recent deportation practices, we must not allow systems to operate which track the movements of people in general.
When and where recognition cameras are authorized, they should not allow remote access to their records. Rather, someone should have to go to the camera itself to retrieve its list of identifications and date/times. For investigating a serious crime, we can afford that. For our safety, it should not be feasible to get each cameras records for every day, or every month.
Please see https://gnu.org/philosophy/surveillance-vs-democracy.html.
Sincerely,
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
Urgent: Sign up to be a poll worker [Richard Stallman's Political Notes]
US citizens: sign up to be a poll worker for your city's election.
Urgent: Oppose SEC deregulation [Richard Stallman's Political Notes]
US citizens: call on your congresscritter and senators to oppose SEC deregulation.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Direct tariff refunds to consumers [Richard Stallman's Political Notes]
US citizens: call on your congresscritter and senators to direct tariff refunds to consumers who paid them.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Hot summer caused crop failure in Europe [Richard Stallman's Political Notes]
The record hot summer has caused a disastrous crop failure in Europe. Both vegetables and grains are affected; in some crops, the loss is over 50%.
Global heating is accelerating, and I've covered the threat of food shortages for many years. Sooner than you expect, every year will be at least this bad — unless we give the planet roasters a knock-out blow, so they can't stop us from preventing that disastrous future.
Suggestion for negotiating with the Taliban [Richard Stallman's Political Notes]
A suggestion for countries that wish to negotiate with the Taliban: insist on sending a delegation composed of women, and insist that the Taliban do likewise.
Finland halts deportation of Russian family [Richard Stallman's Political Notes]
*Finland halts deportation of Russian family who "risked everything" to oppose Ukraine invasion.*
What shocks me is that Finnish intelligence acted unaware of the reasons why the Belovs deserved protection. If it was truly ignorant, it was incompetent. If it disregarded the reasons, it was vicious. Which one was it?
Increase in traffic fatalities on music release dates [Richard Stallman's Political Notes]
* Harvard study finds traffic fatalities increase by 15% on release dates [of major music albums] compared with similar days either side.*
3M firefighting products [Richard Stallman's Political Notes]
*3M knew for more than 50 years that its [firefighting] products could harm humans, Australian government alleges in court documents.*
These products are made with PFAS.
Wrecker distancing US from South Korea [Richard Stallman's Political Notes]
The wrecker has decided to distance the US from South Korea and cozy up to Dictator Kim in North Korea.
This is disappointing, of course, but not surprising. He often prefers dictators to democracies. He has tried to cozy up to Putin, Chairman Xi, Orbán, Crown Prince Bone Saw, and Modi.
Mississippi ICE facility gas leaks [Richard Stallman's Political Notes]
Prisoners in an overcrowded deportation prison (privately run by CoreCivic) in Mississippi (far south in the US) were forced sometimes to spend hours outdoors in bright sun, and at other times, to swelter in crowded cells, because of a power outage.
The prison kommandant held the prisoners incommunicado during that period.
Privatized prisons are a motor for inaccountability, and therefore for cruelty and gratuitious suffering; this makes them inherently unjust. We should abolish them all.
Deform UK [Richard Stallman's Political Notes]
The Deform UK party's latest deformation plan would throw hundreds of thousands of children into destitution, and many disabled people too.
USS Abraham Lincoln shortage [Richard Stallman's Political Notes]
Sailors on the USS Abraham Lincoln say there is a grave shortage of food; some have lost 30 pounds. The water is not fit to drink.
Relatives excoriate the bullshitter for denying these facts, but that's simply being himself.
Dictator Kim gratitude [Richard Stallman's Political Notes]
Dictator Kim expressed his appreciation of the bully's gesture of friendship by firing 10 ballistic missiles.
Cointelpro [Richard Stallman's Political Notes]
*[The persecutor]'s spying on leftwing groups recalls J Edgar Hoover’s Cointelpro.
Carlitos Ricardo Parias [Richard Stallman's Political Notes]
The case of journalist Carlitos Ricardo Parias: *A journalist was injured while documenting government power, shot during an attempt to arrest him, prosecuted, repeatedly denied medical care — and then left in immigration detention, where he has continued documenting the conditions around him.*
Trump's tax cuts [Richard Stallman's Political Notes]
The wrecker's tax big cuts were not just for billionaires. They also gave a handout to multimillionaires. As usual, at the expense of everyone else in the US.
Afghan women deportation [Richard Stallman's Political Notes]
Other countries have deported millions of Afghans to Afghanistan. For Afghan women, that means deportation to slavery.
To deport someone to a place where she will be tortured violates the treaties which establish the right to asylum. Yet the EU is now trying to negotiate a deal with the Taliban for returning refugees there.
Tories to shut down soup kitchens [Richard Stallman's Political Notes]
A Tory-run local council in London wants to shut down soup kitchens because the area around "is not safe".
Sunrise Movement [Richard Stallman's Political Notes]
Government secret agents have been persistently investigating the Sunrise Movement. They keep getting more and more evidence that it is committed to nonviolence (including nonviolent civil disobedience), but continue searching desperately for some violence in it somewhere.
Trump's attack againts Iran [Richard Stallman's Political Notes]
The bully's attack against Iran was a moral error and a grand-strategic mistake. In addition, it was a strategic military mistake which has caused the US to lose much of its power in the world.
Is that a bad thing? No, and yes. The bully has been using US power for evil purposes; now he has less capacity to do that. However, the countries that have gained power though that mistake have been even more vicious, for decades.
Ex Myanmar ambassador in Britain [Richard Stallman's Political Notes]
The ambassador to Britain appointed by President Aung San Suu Kyi defied the military government's order to vacate the ambassadorial house in London. The UK is denying the moral doubts about the situation by prosecuting him for "trespassing" in a diplomatic residence.
Israeli besieged Palestinian families [Richard Stallman's Political Notes]
* Israeli militants have besieged two Palestinian families in their homes since the weekend, aiming to take over their properties in the West Bank village of Qusra through a campaign of terror.*
Eissa Hashemi and Maryam Tahmasebi [Richard Stallman's Political Notes]
Eissa Hashemi and Maryam Tahmasebi, a married couple of professors who were permanent residents in the US, are in deportation prison because Hashemi's mother, Masoumeh Ebtekar, is a supporter of the regime and participated in the occupation of the US embassy.
It is not impossible that they are somehow working with Ebtekar on a nefarious plot, but the persecutor's henchmen have not made such charges -- they have simply cancelled the family's residence permits arbitrarily and (according to Tahmasebi) aim to keep them in deportation prison for life.
Never Split the Difference [Judith Proctor's Journal]
Never
Split the Difference: Negotiating As If Your Life Depended On
It by Chris
Voss
My rating: 4 of 5
stars
This was an interesting read. Voss has a lot of experience in
negotiating for hostage releases. And he's very successful at
it.
But he didn't start out that way - he learnt a lot from people on
the way. For instance, a spell working on the Samaritan's help-line
hammered home the rule that you can't tell people how to sort out
their lives - you have to help them find solutions themselves.
This applies across many fields - Rather than telling the hostage
taker how much you are willing to pay, explain your situation: eg.
I can only raise that much money by selling my house. But that
would mean waiting until it sells. What should I do?
Now your problem becomes their problem to solve - which may result
in them accepting a much lower sum of money if they can have it
now, rather than in six months.
View
all my reviews
comments
The governor of Kentucky should appoint a successor to McConnell unless he can prove he’s actually alive.
I had watched almost three seasons of Silo, had no
feeling for the characters, or the story, which was going nowhere
that I could discern. I shouldn't have watched it through all that
time, but for some reason I did. Then in episode 9, two Fridays
ago, the story took a remarkable turn. It's so good it actually
makes this dark and confusing show worth watching. If you haven't
been watching, imho you can start in season 3, and maybe read some
recaps. You want enough of the boredom so you can revel in the
brilliance of this season's end. And there is another season
coming, the last one, and this time it's interesting.
A Boy and his Dog at the End of the World [Judith Proctor's Journal]
A
Boy and His Dog at the End of the World by C.A.
Fletcher
My rating: 4 of 5
stars
This is basically the story of a boy in a lonely future world where
there are very few people left.
Small isolated groups live on islands like Mingalay, and a few
other places.
It was recommended to me by my Granddaughter (age 12)
When his dog is stolen (dogs are scarce, and fertile bitches are
even rarer), Griz rushes off headlong to try and recover her.
It's not a happy book - it's dystopian post-catastrophe - but there
are a few interesting people still out there.
I won't say how it ends - that's for each reader to discover for
themselves.
It's a good book and very well written. I only really have two
gripes.
Gris gains possession of a map showing roads and cities on the
mainland, but for some reason the author never tells the reader the
names of places.
eg. Griz travels though a deserted, ruined Blackpool- easily
identified by the tower over the ballroom, and a roller
coaster.
Easily identifiable to me - I watch 'Strictly'. My granddaughter
had no idea where he was.
Griz's family took a lot of books from an old library. But the
books he is reading are old children's classics like The Hobbit,
'The Wind in the Willows', and other stuff that I know from my
youth, like 'A Canticle for Leibowitz' (published in 1959). (You
can identify them from context, but they aren't always named)
'A Boy and a Dog at the End of the World' was published in 2019.
These are the classics of my childhood, Griz is a kid raiding a
library when I'm going to be at least 70...
Most YA books are read by adults, but surely the book should
reference some books that are popular with kids now?
View
all my reviews
comments
Citizen of the Galaxy - Robert Heinlein [Judith Proctor's Journal]
Citizen
of the Galaxy by Robert
A. Heinlein
My rating: 5 of 5
stars
This has been a favourite of mine for a very long time.
As a teenager, I didn't care for the downbeat ending.
As an adult, I think it's exactly right.
The whole theme of this novel is what freedom means to you, and
what you are prepared to do to help others be free.
Thorby is a slave. As far back as he can remember, he has been a
slave.
When he is bought by an old beggar - a man who made his own
decision about freedom - his life changes in ways that he could
never have expected. He comes to experience different forms of
freedom and to understand each of them in it's own way.
Finally, he has to decide what he is willing to sacrifice
personally in order to help find freedom for people he has never
met, and will probably never meet in person.
I have read this book many times. I hope to read it again some
day.
View
all my reviews
comments
Joe Marshall: Githack: A Persistent Object Store for Lisp Based on Git [Planet Lisp]
Git has a built-in persistent store for objects based on Merkle trees. It is tailored to store files and directories, but these are just specializations of trees of blobs. There is no reason it couldn't be used to store Lisp objects.
Githack is a
Lisp object store that uses Git as its backend. It is a simple
library that provides persistent objects for Lisp and a
transactional interface for manipulating them. Simple atomic
objects are stored as blobs and composite objects are stored as
trees. Standard composite Lisp objects, such as lists, vectors, and
hash tables, are supported. Custom composite objects can be created
through DEFINE-PERSISTENT-STRUCT or
DEFCLASS with a
:STANDARD-PERSISTENT-METACLASS.
WITH-REPOSITORY is used to specify which repository
to use for storing objects. WITH-TRANSACTION sets up a
transaction for manipulating objects and retrieves the root object.
You use standard slot accessors to walk the object tree. When you
are done, you commit the transaction, and modifications are
atomically written to the repository with a new root object being
placed in a Git branch.
By placing the database in an orphan Git branch, you can store
it right beside your source code without tangling the histories.
You can use Git to manage the history of the database, branch it,
and share it with others. Githack even stores object docstrings as
README.md files inside the repository trees, so the
stored objects are natively self-documenting in the Git web UI.
Githack comes with example code and an example database living on its own orphan branch, so if you clone the repository, you'll clone the working example database as well.
Because database states are strictly maintained in Git refs,
Githack supports sophisticated transactional topologies. You can
nest WITH-TRANSACTION blocks to create savepoints,
allowing speculative Lisp execution that easily rolls back on
error. Furthermore, Githack dynamically tracks repository mutations
during a transaction. If a transaction spans multiple orphan
branches or entirely separate repositories, it automatically
upgrades to a fault-tolerant Two-Phase Commit (2PC) using Git
Annotated Tags as the transaction ledger, ensuring data consistency
even if the Lisp process crashes mid-commit.
Emmanuel Kasper: Isolated VSCode/VSCodium development environment in a Virtual Machine [Planet Debian]

Following the previous steps, we are now interested in getting a graphical environment with a VSCodium, the opensource rebuild of the VSCode IDE.
From the previous steps we had a virtual machine where we can
login with a debian user, and we can start configuring
a graphical desktop environment.
Gnome Flashback is a 2D version of the Gnome Desktop, it has a kind of year 2009 feeling but works well enough. We need a 2D desktop, as the Virtio display adapter does not work consistently with 3D enabled.
# inside dev-vm
# apt install task-gnome-flashback-desktop
$ virt-viewer dev-vm
or using the Remote Viewer app:
$ remote-viewer spice://localhost:5900
# inside dev-vm
# apt install spice-vdagent
# inside dev-vm
# apt install extrepo
# extrepo enable vscodium
# apt update && apt install codium
$ virsh autostart dev-vm
It also makes sense to set our debian user to
autologin in Gnome Fallback, and
start Codium on session start.
This is how the environement should look like at this point:

Finally we need to make sure we have access in the dev-vm to our
source code repositories. For this I will share the directory
/home/manu/Projects/git which is containing all my git
projects on the host, to the dev-vm using virtiofs.
The configuration of virtiofs is fortunately possible using
virt-manager, which will save us some tedious XML editing.

Finally we mount the shared directory, and enable the mount on each boot.
# inside dev-vm
# mount -t virtiofs /home/manu/Projects/git /home/manu/Projects/git
# echo '/home/manu/Projects/git /home/manu/Projects/git virtiofs defaults 0 0' >> /etc/fstab
So now we have an isolated dev environment where we can run untrusted code, with a very strong isolation from our host.
Michael Ablassmeier: virtnbdbackup - backup target plugins [Planet Debian]
I’ve released a new version of virtnbdbackup. The new version adds a small plugin system layer that allows users to extend the backup targets by creating plugins.
Past feature requests asked for backup to S3 or adding encryption features, which i dont need and do not want to maintain within the project scope. Users can now extend the utility with plugins.
In the course of implementing this, i had the idea: why not create a plugin thats capable of streaming the backups to a proxmox backup server?
This resulted in pypbs, a small python binding for libproxmox-backup-qemu0 that allows to store fixed index images on PBS using python.
A first POC implementation of the plugin worked quite well, even tho i don’t know if its worth releasing. A better approach would be to use PBS dynamic index format, but then i might just add a small plugin that wraps the proxmox-backup-client CLI for doing this..
Dirk Eddelbuettel: rfoaas 2.4.0 at CRAN: Fully Restored Functionality [Planet Debian]


FOASS is back at a new site / url since late August! It restores original FOAAS functionality and full set of REST access points including the language filters.
So this new rfoaas release restores all accessor functions re-enabling full R access, documents, and tests them. We re-enabled code coverage too. This corresponds to the upstream version 2.4.0 in the forked FOASS repo, and by our convention we use the same version number for the R package.
My CRANberries service provides a comparison to the previous release. Questions, comments etc should go to the GitHub issue tracker. More background information is on the project page as well as on the github repo
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can sponsor me at GitHub.
The social web vs the software web [Scripting News]
I launched ChatGPT for the first time in a long time because I now have access to the new version. One new thing I see is that they are popping up suggestions about things you can do in realtime, like smart queries in a feed reader, which as far as I know is still uncharted territory. Like this: "let me know when the Mets have a three-game winning streak."
That's the kind of thing TBL dreamed about in his semantic web plan, but he wrote the spec before writing software, and that doesn't work. His invention, the web, which came before was a perfectly timed, perfectly simple approach to sharing ideas on the internet. It worked because he created a product that worked, and made every piece completely replaceable. It came at a time when stagnation in tech made something necessary, or the whole thing was going to fall apart, and so close to so much incredible potential. Just like the moment we're in now with AI.
Rules for Standards Makers says a lot about this.
The value of disruption is that it undoes stagnation.
Anyone who has the same software plan today that they had a year ago is going to find themselves isolated. Even WordPress, though it is open source, is a silo that imho in the future will not work. The main selling point of AT Proto is that it is a safe haven for developers who nuzzle up, another word for that is silo.
The siloers are probably scrambling to find a way for the AI to run inside their world, mistake -- instead they should be trying to be fully relevant to applications in the AI world. Like Slack, for example, and Claude Tag. It's now built in. Has to be replaceable too. Small pieces loosely joined. That's what you have to be imho to have a role in the future of tech.
If your advantage is your silo, undo that as quickly as possible. Become the default version of whatever it was meant to be wrt the web. Both Bluesky and WordPress find themselves in the same quagmire. Probably a lot of others do, but this is the area I study. (I just am very familiar with the products of both companies, for some reason.)
This is the intuition we all had, coming to fruition. Evolution is something we haven't seen much of in the software world since 2006 or so. Incredible stagnation because of the dominance of the social web. We lost the software web. All the focus was on personalities, celebrity -- hype and 99.999% bullshit. It was a 20 year period where all that happened was scaling.
Lots more to say about this. The world we lived in ran on hype, now guess what -- having lived through a few big evolutionary changes in tech is a huge advantage vs the people who have never been part of one, and I think most of the people in tech today qualify.

We called them this because they used tools. The first proto-humans to clearly do so.
The question that we need ask ourselves today, “Are you a tool user or a tool maker?”
Everyone uses tools. But only a few people, even with access to AI and systems of leverage, choose to make tools.
Part of the gap is failing to ask the question. It generally doesn’t occur to a productive tool user to decide to slow down, risk failure, and take responsibility by making a new tool.
But new tools are one way we change things for the better.
Homo Faciens
Pluralistic: Google skates (05 Sep 2026) [Pluralistic: Daily links from Cory Doctorow]
->->->->->->->->->->->->->->->->->->->->->->->->->->->->->
Top Sources: None -->

Rome wasn't overthrown in a day. Oligarchies are stubborn, and by the time they've established and entrenched themselves, they have resources, power blocs and even mercenaries they can deploy to repel would-be dethroners.
The USA got its first antimonopoly law, the Sherman Act, in 1890, but it took 22 years before that law could be used to crush John D Rockefeller's corrupt, sprawling empire. Senator John Sherman promoted his law as a way of preventing monopolies from emerging, warning the Senate that they would struggle to overthrow the "autocrats of trade" that monopolies created:
https://pluralistic.net/2022/02/20/we-should-not-endure-a-king/
The Senate passed his law and Harrison signed it, but then successive administrations left the Sherman Act to gather dust on a shelf as Rockefeller went on a spree, accumulating the kind of power that made him a true "autocrat of trade," so powerful that he and the US government were practically evenly matched.
Allowing Rockefeller to create, expand and consolidate his monopoly power was a terrible tactical blunder, giving him decades in which he was able to loot America and its trading partners, pauperizing ordinary people and smashing anyone who got in his way. No one was willing to admit that Rockefeller was a threat to democracy and prosperity until he had amassed all that power, and once he had all that power, it took heroic effort to break him.
The Rockefeller story is like the punchline of that joke: "When it doesn't rain, the roof don't leak; and when it's raining, it's too wet to fix it." Alternatively, there's my other favorite punchline: "If you wanted to get there, I wouldn't start from here."
The Rockefeller blunder wasn't a one-off. The Apple ][+ hit the shelves the same year Reagan hit the campaign trail, and the tech industry's rise occurred simultaneously with the dismantling of competition law enforcement. Tech companies were the first "post-antitrust" industry, and it wasn't until these companies became palpable, terrifying, undeniable, existential civilizational risks that we remembered that we had all these laws on the books that were designed to curb excessive corporate power.
Under Biden, a group of generationally talented, visionary trustbusters were given access to those dormant enforcement powers: Lina Khan at the FTC; Rohit Chopra at the CFPB, Tim Wu in the White House, and Jonathan Kantor at the DOJ Antitrust Division. Together with a staff of canny and skilled lawyers and economists, these people scored incredible victories against Big Tech. During the Biden years, Google lost three federal antitrust cases. Three!
But if we wanted to get there, we wouldn't start from here. After a series of stinging defeats in the US and abroad, after watching the EU, the UK, Japan, Singapore and South Korea make common cause with Biden's enforcers, Big Tech threw everything it had into Trump, who promised them a system of regulatory forbearance in exchange for low-cost bribery, backstopped by a xenophobic, belligerent geopolitics that would rain down punishments on any country that dared to regulate or tax Big Tech:
https://www.bbc.com/news/articles/c62553ywn77o
And then the other shoe dropped: the federal judges who convicted Google of operating an illegal monopoly handed down their "remedy" decisions. In an antitrust case, the "remedy" phase is like sentencing – the stage of the legal proceeding where the judge decides what punishment the company should face for breaking the law.
The first of these remedies came out a year ago, in September 2025. Judge Amit Mehta had presided over Google's "search" case, where we learned that Google had deliberately made search worse so that you'd have to search more than once in order to get your answer, which would allow the company the chance to show you more ads:
https://pluralistic.net/2024/04/24/naming-names/#prabhakar-raghavan
Google was able to do this because it had cornered the market on search. The company had spent years paying a $20b annual bribe to Apple to stay out of the search market, and they'd bought the default search placement for every operating system, browser and carrier. If you encountered a search box in the wild, it was almost certainly wired into Google's servers. They knew that there was approximately zero chance that you'd ever stumble upon another search engine, which meant they could make their own search as shitty as they wanted and keep your business.
Confronted with these proven findings, Mehta decided that Google's punishment should be…nothing. They wouldn't be forced to delete the personal data they'd taken from billions of people. They wouldn't be forced to spin off Chrome or Android – two of the key tools Google uses to keep people from discovering other search engines. They wouldn't even be forced to halt the $20b annual bribe to Apple – the judge fretted that without that $20b annual bribe, Apple wouldn't be able to pay researchers to come up with cool new iPhone features (never mind that Apple spends all that money – and more – on stock buybacks, a recently illegal form of stock manipulation):
https://pluralistic.net/2025/09/03/unpunishing-process/#fucking-shit-goddammit-fuck
Then, a year later, another federal judge – Leonie Brinkema, who presided over the Google "ad-tech" case – decided that Google's penalty for monopolizing the ad market should also be…nothing:
Oh, maybe not exactly nothing. We don't actually know the full extent of Judge Brinkema's "remedy," because it's sealed for two weeks. What we do know is that Google will not be forced to take the most obvious, effective and necessary step to prevent it from abusing its monopoly: Google will not be forced to sell off part of its ad-tech stack.
Let me unpack that for you, because unless you're an ad-tech weirdo, chances are you have no idea how any of this works and don't think it affects you. But the reality is that this is costing you money. It's one of the dirtiest, most profitable scams in the entire tech economy, which is saying something, because that is an economy that is made of scams:
https://pluralistic.net/2026/09/04/cheating-at-fraud/#absentee-rentier
Unlike older ads, which were targeted based on content (say, an ad for a hotel might run next to a newspaper article about a beachside town) modern ads are built on surveillance. Companies like Google amass vast, nonconsensual dossiers on the personal characteristics and behavior of billions of people, supplemented with information purchased from the unregulated data-broker sector.
When you visit a website, the site fires off a piece of software called "sell-side agent" to message a server called an "ad exchange" in order to announce your visit, soliciting bids for the right to show you an ad: "I am about to serve a web-page to a 18-34 year old man-child from New York's outer boroughs, who owns an Xbox and has been recently searching for information about gonorrhea: who wants to cram some ads into this guy's eyeballs?"
That ad-exchange server is haunted by "demand-side agents" – these are pieces of software fired off by advertisers that monitor all these advertising opportunities announced on the ad exchange and bid for the right to show you an ad. The highest bidder gets to show you an ad, and the fee is remitted to the exchange, which takes a cut and passes the remainder on to the sell-side platform, which also takes a cut and gives the balance to the website publisher.
So the ad-tech stack has three main components: the "sell-side platform" (SSP), which lets web publishers announce auctions for the right to advertise to their visitors; the "demand-side platform" (DSP) that lets advertisers bid to show those visitors ads; and the "ad exchange" – the marketplace where the sell-side and demand-side agents meet to collect bids, finalize the sale, and exchange ads for money.
When this all started, there were lots of companies in all three roles. Publishers and advertisers had their choice of SSPs, DSPs and exchanges, and all three types of middleman competed to offer the best deals to advertisers and publishers.
Then, Google and Facebook started buying up the leading DSPs, SSPs and exchanges. They used contracts and technical countermeasures to force anyone who used any part of their "stack" to use them for all parts of the transaction. The CEOs of Google and Facebook personally colluded to rig the market, dividing it up between them so that publishers would get less, advertisers would pay more, and Googbook would pocket the difference. The codename for this conspiracy was "Jedi Blue":
https://en.wikipedia.org/wiki/Jedi_Blue
Jedi Blue was just the icing on the cake. The reality is they didn't need the conspiracy: once Google was selling services to advertisers and publishers on an exchange that Google owned, they created an entire universe of ways to rip off both advertisers and publishers. Now consider that Google is also an advertiser and also a web publisher, and the opportunities to cheat are just wild.
The numbers tell the story. Before Googbook captured 80% of the display advertising business, the total share of the advertising industry's revenues that went to "intermediaries" (middlemen like ad agencies, ad buyers, etc) was about 15%. Today, that number is 51%. Hundreds of billions of dollars have been moved out of publishers' and advertisers' bank accounts and onto Google and Facebook's balance sheets.
This isn't hard to understand. Google runs an ad business that locks in buyers and sellers on a marketplace Google owns and controls, where it also competes with those buyers and sellers. Buying or selling an ad through Google is like going to court to get a divorce, only to discover that you and your soon-to-be-ex- are both represented by the same lawyer, who promptly ascends the bench and dons a judge's wig, and then spends the whole trial trying to match with both of you on Tinder, and who concludes the trial by banging their gavel and announcing that they've decided that the family house will be awarded to…the judge!
The most absurd part of this whole farce is the lawyers who defend it on behalf of companies like Google. If a Google lawyer ever showed up to defend the company in a trial where the judge was working for the plaintiff, they would scream blue murder and refuse to proceed until the judge was removed from the case. But when Google operates a business where it presides over transactions where it has nothing but conflicts of interest, these same lawyers argue that Google would never cheat a seller or a buyer.
The fucking absurdity of this arrangement is so obvious that a bill to force a halt to it was co-sponsored…by Elizabeth Warren and Ted Cruz:
https://gizmodo.com/google-facebook-america-act-ads-break-up-cruz-warren-1850287725
Why would Warren and Cruz care about this? Because the hundreds of billions that have been moved from publishers and advertisers to Google and Facebook are hundreds of billions of dollars that are no longer paying for news and entertainment production, and they're hundreds of billions of dollars that businesses have to recoup by raising prices on you to pay their advertising bills.
Google (and, apparently, Judge Leonie Brinkema) dispute this. They say that "larger forces" have "changed the dynamic" that "restructured the industry." But, I mean, come on! This is a situation where hundreds of billions of dollars are divided up by a thrice convicted monopolist who is mysteriously hundreds of billions of dollars richer, while the other parties to the transaction are mysteriously hundreds of billions of dollars poorer. Anyone who can't draw the obvious causal inference from these facts has so little object permanence that they would lose a fucking game of peek-a-boo.
This is so goddamned demoralizing. For a couple years there, it really looked like the tide was turning. Then Judges Brinkema and Mehta came along to snatch defeat from the jaws of victory.
The only thing that's keeping me going is object permanence. I know my history. I know it took decades from the passage of the Sherman Act until the defeat of John D Rockefeller. Our forebears brought down Rockefeller because they didn't give up, despite setbacks as bad as this one, and worse. Stein's Law of finance holds that "anything that can't go on forever eventually stops," and MLK told us that "the arc of the moral universe is long, but it bends toward justice." This can't go on forever, and despite Dr King's phrasing, I know he understood that the arc doesn't just "bend" – it is bent – by people like us, hauling on it with all our might.

DOGE Affiliate Asked for College Credits for Participating in Takeover https://www.wired.com/story/doge-affiliate-asked-for-college-credits-for-participating-in-takeover/
Why office workers are turning against AI https://www.bloodinthemachine.com/p/why-office-workers-are-turning-against
The Quiet Decision Microsoft Made That Devastated Thousands of Nonprofits https://slate.com/technology/2026/08/microsoft-software-nonprofit-data-delete.html
Vote for the 2026 Tiny Awards Winner https://tinyawards.net/vote/
#25yrsago Advertisers claim they can hack your childhood memories https://web.archive.org/web/20010921022846/http://news.independent.co.uk/uk/science/story.jsp?story=92386
#25yrsago Wind-up cellphone charger https://web.archive.org/web/20011031122531/http://www.thetimes.co.uk/article/0,,2-2001310179,00.html
#20yrsago Steven Brust’s Dzur: witty and exciting heroic fantasy https://memex.craphound.com/2006/09/05/steven-brusts-dzur-witty-and-exciting-heroic-fantasy/
#20yrsago America to US gov’t: kill the Broadcast Treaty! http://www.cptech.org/ip/wipo/bt/jointletter5sep06usptoforum.pdf
#20yrsago New Zealand wants a Ministry of DRM https://web.archive.org/web/20070108042834/http://www.zdnet.com.au/news/software/soa/NZ_draws_line_on_DRM_and_trusted_computing/0,130061733,339270846,00.htm
#20yrsago Is it legal to look at the Web in Canada? https://web.archive.org/web/20061010120919/http://www.michaelgeist.ca/content/view/1411/135/
#15yrsago Advice for self-publishers: why should anyone care about your book? https://locusmag.com/feature/cory-doctorow-why-should-anyone-care/
#5yrsago A letter to a discouraged young writer https://pluralistic.net/2021/09/05/why-bother/
#1yrago Why Wikipedia works https://pluralistic.net/2025/09/05/be-the-first-person/#to-not-do-something-that-no-one-else-has-ever-thought-of-not-doing-before

Warsaw: Romana i Jana Podoskich, Sep 6
https://wydarzenia.phub.pl/events/0ee0e198-f843-423a-890f-c84ff50a46c0
Brighton: The Reverse Centaur's Guide to Life After AI with
Carole Cadwalladr (Brighton Dome), Sep 8
https://brightondome.org/whats-on/LSC-cory-doctorow-the-reverse-centaurs-guide-to-life-after-ai/
London: The Reverse Centaur's Guide to Life After AI with Riley
Quinn (Foyle's Picadilly), Sep 9
https://www.foyles.co.uk/events/enshittification-cory-doctorow-riley-quinn
Budapest: Brain Bar, Sep 17
https://brainbar.com/munkatars/cory-doctorow
Edmonton: Elbows Up (Edmonton Public Library), Sep 28
https://www.epl.ca/blogs/post/elbows-up-with-cory-doctorow/
South Bend: An Evening With Cory Doctorow (Notre Dame), Oct
6
https://franco.nd.edu/events/2026/10/06/an-evening-with-cory-doctorow/
Hudson, OH: Hudson Library, Oct 7
https://engagedpatrons.org/EventsExtended.cfm?SiteID=3850&EventID=596952&PK=
Calgary: Wordfest, Oct 8
https://wordfest.com/2026/show/wordfest-presents-cory-doctorow-2026/
Winnipeg: McNally Robinson, Oct 9
https://www.mcnallyrobinson.com/event-18991/An-Evening-with-Cory-Doctorow
Vancouver: Read, Resist, Repair, Rejoice (Vancouver Writers
Festival), Oct 19
https://writersfest.bc.ca/festival-event-2026/01
Victoria: Munro's Books, Oct 20
https://www.munrobooks.com/events/6113620261020
Vancouver: Life After AI (Vancouver Writers Festival), Oct
22
https://writersfest.bc.ca/festival-event-2026/46
Ottawa: Life After AI (Ottawa Writers Festival), Oct 24
https://writersfestival.org/event/life-after-ai
Vancouver: BC Policy Solutions Gala, Nov 12
https://bcpolicy.ca/gala/
How Tech Platforms Took Over the Economy (Dystopia Now)
https://sites.libsyn.com/566555/enshittification-and-reverse-centaurs-cory-doctorow-on-how-tech-platforms-took-over-the-economy
Hope, AI, Fixing the Internet and the Reverse Centaur of it all
(Wilosophy)
https://podcastaddict.com/everyone-relax/episode/231414816
Deflating the AI Bubble (Do Not Pass Go)
https://www.donotpassgo.ca/p/deflating-the-ai-bubble-with-cory
Technofeudal Enshittification (Fucking Cancelled)
https://www.fuckingcancelled.com/p/technofeudal-enshittification-with
"Canny Valley": A limited edition collection of the collages I create for Pluralistic, self-published, September 2025 https://pluralistic.net/2025/09/04/illustrious/#chairman-bruce
"Enshittification: Why Everything Suddenly Got Worse and What to
Do About It," Farrar, Straus, Giroux, October 7 2025
https://us.macmillan.com/books/9780374619329/enshittification/
"Picks and Shovels": a sequel to "Red Team Blues," about the heroic era of the PC, Tor Books (US), Head of Zeus (UK), February 2025 (https://us.macmillan.com/books/9781250865908/picksandshovels).
"The Bezzle": a sequel to "Red Team Blues," about prison-tech and other grifts, Tor Books (US), Head of Zeus (UK), February 2024 (thebezzle.org).
"The Lost Cause:" a solarpunk novel of hope in the climate emergency, Tor Books (US), Head of Zeus (UK), November 2023 (http://lost-cause.org).
"The Internet Con": A nonfiction book about interoperability and Big Tech (Verso) September 2023 (http://seizethemeansofcomputation.org). Signed copies at Book Soup (https://www.booksoup.com/book/9781804291245).
"Red Team Blues": "A grabby, compulsive thriller that will leave you knowing more about how the world works than you did before." Tor Books http://redteamblues.com.
"Chokepoint Capitalism: How to Beat Big Tech, Tame Big Content, and Get Artists Paid, with Rebecca Giblin", on how to unrig the markets for creative labor, Beacon Press/Scribe 2022 https://chokepointcapitalism.com
"Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, April 20, 2027
"Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2027
"The Memex Method," Farrar, Straus, Giroux, 2027
Today's top sources:
Currently writing:
"The Post-American Internet," a sequel to "Enshittification," about the better world the rest of us get to have now that Trump has torched America. Fourth draft completed. Submitted to editor.
A Little Brother short story about DIY insulin PLANNING

This work – excluding any serialized fiction – is licensed under a Creative Commons Attribution 4.0 license. That means you can use it any way you like, including commercially, provided that you attribute it to me, Cory Doctorow, and include a link to pluralistic.net.
https://creativecommons.org/licenses/by/4.0/
Quotations and images are not included in this license; they are included either under a limitation or exception to copyright, or on the basis of a separate license. Please exercise caution.
Blog (no ads, tracking, or data-collection):
Newsletter (no ads, tracking, or data-collection):
https://pluralistic.net/plura-list
Mastodon (no ads, tracking, or data-collection):
Bluesky (no ads, possible tracking and data-collection):
https://bsky.app/profile/doctorow.pluralistic.net
Medium (no ads, paywalled):
Tumblr (mass-scale, unrestricted, third-party surveillance and advertising):
https://mostlysignssomeportents.tumblr.com/tagged/pluralistic
"When life gives you SARS, you make sarsaparilla" -Joey "Accordion Guy" DeVilla
READ CAREFULLY: By reading this, you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.
ISSN: 3066-764X
Junichi Uekawa: Summer Vacation for my kids is over. [Planet Debian]
Summer Vacation for my kids is over. And Peace is back
to my life. AI is transforming how I operate and view things. It
was very different a few months back. AI (as a product) is useful
in generating code, useful in analysing things. It seems to be able
to retrieve and show me information relatively quickly, doesn't
need me to scan the search results to find which one is more
useful. I feel I am less reliable than an AI, even when AI is prone
to failure. The text generated by AI is better worded than me
myself, albeit they have their own tone. Is it still fun if all my
hobby programming is overtaken by AI? I am not sure, did I enjoy
writing the fixtures and build environment for the open source
programming stuff? Do I enjoy reviewing other people's code?
Reviewing other people's contributions is usually not great,
because by definition the code you own you have better knowledge
about, and the code you generate yourself is the best code, others
will not fit naturally, they don't have the historical context, and
the undocumented future plans.
What happens if you change a window class’s GCL_CBWNDEXTRA? [The Old New Thing]
After my historical look back on the
evolution of system-windows window and class extra bytes, I
noted that there was
one application that expected to be able to modify
GWW_CBCLSEXTRA.
It turns out that there are even more applications that expect
to be able to modify GWW_CBWNDEXTRA. So
many that it wasn’t worth creating an application
compatibility exception for them.
So what happens when you modify
GWW_CBWNDEXTRA, or its modern equivalent,
GWL_CBWNDEXTRA?
The change in window extra bytes takes effect, but not retroactively.
Windows that are created after you change
CBWNDEXTRA receive the updated number of
extra bytes, but windows that already exist are not modified. They
still have the number of extra bytes that were assigned when the
window was created.
Specifically to deal with people who change the number of window
extra bytes on the fly, the system keeps track of what the number
of extra bytes was at the time the window was created, and
those are the bytes you get to access from that window. If you try
to access the nonexistent bytes, you are told
ERROR_INVALID_INDEX.
This does mean that you can get into a strange situation where
GetClassLong(hwnd,
GCL_CBWNDEXTRA) tells you that you have 8 extra
bytes, say, but if you use GetWindowLong(hWnd,
0), which asks for the LONG represented by
bytes 0–3, you are told “Sorry, that’s out of
range.” As far as you can tell, it is well within range. What
you don’t know is that the window was created back when the
GCL_CBWNDEXTRA was less than 4.
There is no way to ask a window, “How many extra bytes do you really have?” I mean, why the system go out its your way to improve the lives of people who are abusing it?
The post What happens if you change a window class’s <CODE>GCL_<WBR>CBWNDEXTRA</CODE>? appeared first on The Old New Thing.
Urgent: Fund and rebuild the FDA [Richard Stallman's Political Notes]
US citizens: call on Congress to fund and rebuild the FDA.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Impeach budget director Russell Vought [Richard Stallman's Political Notes]
US citizens: call your congresscritter to vote to impeach budget director Russell Vought.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Vote against Heidi Overton for head of FDA [Richard Stallman's Political Notes]
US citizens: call on your congresscritter to vote against appointing Heidi Overton to head the FDA.
Let's toss her out of the Overton window!
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: If Superman lived among us now [Richard Stallman's Political Notes]
If Superman lived among us now, and decided it was his duty to defend Truth, Justice and the American Way from America's worst enemy, what sort of actions might be effective and inspirational to use? How about posting your ideas, or stories based on them.
Please don't write about mere violence against magats; that is too crude and obvious, it is too much like them to set an example of good, and would not demonstrate morally that their actions are illegitimate.
Here are a few ideas that occur to me for what he could do.
If you post a fictional story about such an action, either something from the list above or something you have thought up, or even just an outline, I hope you will email me its URL.
Urgent: Stand up to big tech on data centers [Richard Stallman's Political Notes]
US citizens: call on state officials to stand up to Big Tech: pass a data center moratorium.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
Urgent: Reject perverse deal for veterans' medical treatment [Richard Stallman's Political Notes]
US citizens: call on your congresscritter and senators to reject Republicans' perverse deal for veterans' medical treatment.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Refuse to hand over voter data [Richard Stallman's Political Notes]
US citizens: call on your state's Secretary of State to refuse to hand over the voter data that magats demand.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
Urgent: Rescind plan to block election ballots [Richard Stallman's Political Notes]
US citizens: call on the postal governors to rescind the plan to block election ballots.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
Urgent: Impeach and remove RFK jr. [Richard Stallman's Political Notes]
US citizens: call your congresscritter and senators to impeach and remove RFK jr.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Urgent: Investigate local use of Flock cameras [Richard Stallman's Political Notes]
US citizens: call on Tell your attorney general to investigate your local law enforcement agency’s abuse of Flock surveillance cameras, and prevent tracking anyone without a specific warrant.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
Urgent: Reject corrupter's handouts to billionaire barons [Richard Stallman's Political Notes]
US citizens: call on your congresscritter and senators to reject the corrupter's pet projects and handouts to his billionaire barons, and put struggling American families before them.
See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.
US citizens: Join with this campaign to address this issue.
To phone your congresscritter about this, the main switchboard is +1-202-224-3121.
Please spread the word.
Polish general imprisoned with German SS general [Richard Stallman's Political Notes]
The Russian conquerors of Poland thought it amusing to imprison Polish general Kazimierz Moczarski of the Home Army in the same cell with the German SS general that commanded the destruction of the Warsaw Ghetto.
Moczarski treated this as an opportunity for an interview, a chance to elucidate the mentality of a Nazi mass murderer. However, the Russians censored the contents of his book. When the English translation is published, I plan to buy a copy — paying cash, anonymously. Evidently, not from Amazon.
The purpose of the Polish Home Army was to rise up to help liberate Poland from German occupation. In late 1944, with the Red Army across the river from Warsaw, the Home Army rose up, and asked Russia to join in the battle. Instead, the Red Army held back so the Germans and Poles would kill each other. Stalin considered both of them his enemies.
UK Prime Minister Burnham disregarding gravity of climate disaster [Richard Stallman's Political Notes]
UK Prime Minister Burnham seems to disregard the gravity of climate disaster. That is not much better than the Tories, who have come to deny it.
Cyclosporidia outbreak should have been caught [Richard Stallman's Political Notes]
The cyclosporidia outbreak should have been caught and stopped by a new scheme for tracking wholesale shipping of food, to stop diseases carried by food. But the scheme has never been implemented.
The legal requirement for the scheme was adopted in 2011, but lobbying from agribusiness has repeatedly postponed its implementation. The last postponement put it off until 2028.
Losing data stored in a cloudy place [Richard Stallman's Political Notes]
Watch out! Storing data you care about in a cloudy place could lead to losing it.
This is a two-level example. Nine PBS, a US TV station, stored its archives in space it rented from a cloudy company which subcontracted the actual storage to a cloudy data center. The first cloudy company shut down and disappeared, but the station did not find out until later. Now the second cloudy company denies that the station is its customer, and the station has had to sue.
My lesson from this is not "the cloud is bad" but rather that it is foolish to use the term "cloud" in your words or your thoughts. That term was coined for leading customers into careless thinking. Don't let it lead you anywhere!
Pernicious error of measuring wealth [Richard Stallman's Political Notes]
The US media frequently fall into the pernicious error of measuring the wealth of "Americans in general" by the arithmetic mean of individual wealth.
I expect that pro-billionaire campaign and pressure groups lobby for that.
Better lung capacity after reducing air pollution [Richard Stallman's Political Notes]
Children in London have developed better lung capacity after the ultra-low emission zone reduced the amount of air pollution in most of London.
I support limiting cars' emissions more strictly, but it is Orwellian and unjust to implement this by tracking the movements and locations of individual cars. It would be better to impose this limit throughout the UK.
Sanctions on president of International Criminal Court [Richard Stallman's Political Notes]
The persecutor's regime has placed sanctions on the president of the International Criminal Court, as an attempt to establish unilaterally an unheard-of supposed moral principle: absolute immunity for the officials of governments that reject the ICC.
We can see why US officials might want this — to protect US soldiers that commit war crimes. In theory, the US will prosecute them itself; if the US really does that, the ICC will not get involved in their cases, because its mission is to prosecute war criminals whose own country prosecutes them. In fact, the US often protects them and refuses to prosecute them.
Machines that kill without distinction [Richard Stallman's Political Notes]
*Machines that kill without distinction, answerable to no one, leave no one safe.*
Italian thugs killed Abderrahim Fakir [Richard Stallman's Political Notes]
Italian thugs killed authorized immigrant Abderrahim Fakir by holding him down while he was handcuffed.
The details of what killed him are not clear in this article.
Fascist politicians are delighted by the killing, presuming that Fakir deserved death (without waiting to see if there was any objective justification for killing him). That is a standard Fascist approach: use the fact of a crime against a hate-target as "proof" that the target somehow deserved it.
Scientific research on racism and dementia [Richard Stallman's Political Notes]
The forces of ignorance have canceled scientific research into how racism affects whether people develop dementia.
This must be one of the things that magats believe that "Man was not meant to know." Or, at least, they don't mean for this to be known.
Rejecting corporate Democrats [Richard Stallman's Political Notes]
Robert Reich: most Americans are rejecting the corporate Democrats, and the rich people that they mainly serve.
Corporate Democrats are far from the worst politicians in office in the US. The fascists (today's Republicans) are far worse; they are sabotaging democracy and rule of law. But the corporate Democrats will not try to give most people a decent life with something to strive for — a goal whose prerequisites include preventing climate disaster.
Heatwaves and wildfires adding to tipping point [Richard Stallman's Political Notes]
*Heatwaves and wildfires are not just scorching forests, they are adding to the tipping point risks in the world's vast permafrost regions, which contain three times more carbon than all the living vegetation on Earth.*
This could negate human efforts to reduce emissions. If so, it would mean that we left the task of reducing them till too late.
Policies most Americans sensibly demand [Richard Stallman's Political Notes]
Bernie Sanders: "Why are progressives winning across the United States?" He list the policies that most Americans sensibly demand, but corporate politicians label as "extremist".
Alas, he refers LLMs as "artificial intelligence". It is a mistake to call them that, because it is the marketing term of those who are pushing them on us.
Politics of bully's military snub to South Korea [Richard Stallman's Political Notes]
More on the politics of the bully's military snub to South Korea.
Cities cancelled Flock and went with other surveillance [Richard Stallman's Political Notes]
Some cities terminated their contracts with Flock Surveillance, under public pressure, but turned around and brought in another surveillance company.
If you aim to limit access to the surveillance data enough to protect the public from possible Orwellian consequences, that's harder than one might assume. "Stricter" conditions of access may be too loose to achieve that protective goal. And don't forget that federal deportation thugs could override local rules of access.
My recommendation is to store the data in or near each camera, with no access from a distance. When there is a serous crime to investigate, it will be worth the cost to send someone to collect the pertinent data from each pertinent camera. But if someone just wants to fish for someone to persecute, this will be too much trouble.
For more explanation, look for "surveillance camera" in https://gnu.org/philosophy/surveillance-vs-democracy.html.
Microsoft unveils a Windows variant for developers with 64GB of unified RAM [OSnews]
Zenith is a variant of Windows for “developer-class devices”.
Project Zenith comes with a set of pre-installed tools spanning languages and runtimes, source control, and productivity tools. Windows Terminal and Visual Studio Code are pinned to the Taskbar by default, putting your favorite tools within immediate reach.
We’ve also pre-configured Windows Settings for coding across File Explorer, Search, Start, and the Taskbar. File Explorer shows file extensions, hidden files, the full path in the title bar, and the details pane, with long-path support enabled. Recently used files and folders and sync provider tips are turned off for a cleaner workspace. In Search and Start, Command Palette is enabled, while Start menu tips and account notifications are turned off to reduce distractions.
Windows Subsystem for Linux (WSL) has become foundational for running Linux workloads on Windows. Last year we open-sourced WSL. Building on that momentum at Build 2026, we integrated WSL more deeply into Windows with WSL containers to provide a built-in way to create, run, and interact with Linux containers directly on Windows.
↫ Logan Iyer at the Windows Blogs
It’s highly unlikely you’ll be using this Zenith Windows flavour any time soon, as it requires 64GB of unified RAM with 250 GB/s memory bandwidth. In 2026, that’s a serious ask. On top of that, it’s not entirely clear to me if Zenith will be available as a separate Windows variant, without having to buy a complete device.
Of course, it would be trivial to set all of this up on any fresh Windows installation.
Friday Squid Blogging: Squid on a Stick at the New York State Fair [Schneier on Security]
Looks tasty.
As usual, you can also use this squid post to talk about the security stories in the news that I haven’t covered.
This Week in AI: The Frontier Is Getting Bigger [Radar]
Host Christina Stathopoulos, founder of Dare to Data and a former data scientist at Google and Waze, returned to This Week in AI with developments that stretched from Claude testing ways to improve model safety to Chinese open weight models gaining developer traffic and new systems learning to model physics. She also examined what Anthropic and OpenAI’s business moves, workforce forecasts, and debates over access reveal about how quickly the AI landscape is broadening.
Anthropic provided an early example of AI helping improve future AI systems. In research Christina highlighted, Claude searched existing work, proposed methods, generated training data, and repeatedly tested and refined its approaches to reduce unwanted model behaviors. The experiments covered 10 such behaviors, including deception, hallucination, prompt injection, privacy violations, and reward hacking. Anthropic reported improvements across all 10 without degrading the model’s broader capabilities.
For deception, Claude tested more than 150 methods and eventually closed 85% of the measured safety gap. Human safety researchers closed only 20% in Anthropic’s comparison. Christina emphasized that this wasn’t a direct contest because Claude could run and refine experiments much faster and at a much greater scale. She also cautioned that the work didn’t amount to full recursive self-improvement.
This showed how AI could increasingly handle experimentation in model development, changing the pace and scale of research while humans still set the goals and evaluate the results.
Competition among AI labs increasingly involves business performance, infrastructure, and deployment options alongside model quality. Anthropic estimates that the market for its systems could eventually reach $30 trillion, a long-term estimate that Christina treated skeptically because it approaches the size of the entire US economy. She also highlighted more concrete evidence of momentum in how Anthropic’s annualized revenue run rate rose from less than half of OpenAI’s at the start of the year to surpassing it within several months. Both companies are preparing for possible public offerings.
OpenAI faces a different set of pressures, and Christina highlighted its 14 executive departures this year. That sustained leadership turnover could raise questions about the company’s ability to execute consistently. OpenAI is also trying to gain more control over its infrastructure. Its Jalapeño inference chip, developed with Broadcom, delivered up to 1.9 times more AI work per watt and up to 3.6 times lower latency than comparable NVIDIA systems in OpenAI’s own testing.
Chinese open weight models are widening the field further. Christina cited an AI gateway where open weight models recently reached as much as 62% of developer traffic on a single day, compared with an average of roughly 10% in April. The competition now spans benchmark performance, capital, infrastructure, cost, deployment flexibility, and organizational execution.
Christina closed with research aimed at helping AI systems model physics. Researchers from MIT and Tsinghua University developed a pretraining approach that learned from more than one million synthetic interactions between moving particles and complex 3D objects, then applied those patterns to simulations involving wind, water, collisions, and light. The researchers described physics as a potential “third modality” for AI alongside language and pixels.
She also covered Accelerated Understanding, a startup that recently emerged from stealth with an architecture based on neural operators rather than transformers. The company is targeting problems involving enormous physical datasets, including chip design, robotics, extreme-weather forecasting, and geological exploration.
By learning directly from physical systems, these models could become valuable for simulation, engineering, robotics, forecasting, and other work that depends on understanding complex real-world environments.
Christina also examined who could benefit from these advances. She discussed Bill Gates’s argument that access, deployment, policy, and distribution will shape AI’s social impact, and brought in new US Bureau of Labor Statistics projections showing job growth in areas including technical services and healthcare, while office and administrative roles face greater pressure from automation.
Her larger point was that access, workforce preparation, and public policy will determine how AI’s benefits and disruptions are distributed.
Due to the Labor Day holiday, This Week in AI will return on Monday, September 14, when we’ll dive into more of the news, issues, and key developments shaping the AI era. And check back each Friday for the latest episode, or watch on YouTube, Spotify, Apple, or wherever you get your podcasts.
Wonderella- versary [The Non-Adventures of Wonderella]
This week 20 years ago, I posted the first Wonderella comic. But Wonderella was actually my second online comic!
My first, Killroy & Tina, debuted *25 years ago* this week, and it introduced the Fulcrum. Wonderella's episodes took over most of my creative time, though, and the long-form K&T was soon put on the backburner.
But I’ve always loved Killroy & Tina enough to bring Killroy into the Wonderella universe years ago. All these years later, The Fulcrum's here too.
Using a VM to Contain an AI Agent [Schneier on Security]
It won’t work:
My suspicion was that GPT 5.6-Cyber would succeed, but the frequency and manner of its success removed all doubt. We have to reassess sandboxing quality for capable AI agents, and in general the software stack with which they interact.
An off-the-shelf VM is not enough to contain a modern, cyber-capable AI agent. There is simply too much attack surface. Even innocuous features (like running with a display) add extra, exploitable attack surface.
Why Podcasting 2.0 will never work [Scripting News]
If you want others to follow you, you have to offer them your users. And a format alone has no users.
And you have to trust your users to choose the best product, and you have to have a great product, that they appreciate, even though they have choice.
Just coming out with something better in a format will get you zero uptake. Unless you have users they can switch to their product, they simply won’t hear you.
That’s why Atom never replaced RSS, which already did everything people needed and was already supported by the NYT and the news industry.
That’s why AT Proto will never overcome the huge lead Twitter already has.
For more tips on what does and doesn't matter in open formats and protocols, check out my Rules for Standards-makers.
PS: This started as an early morning rant on Twitter.
The Big Idea: Betsy James [Whatever]

Every journey begins with a first step, but where that journey goes from there can take many forms. Betsy James took many journeys leading up to Practicing to be Lightning, but to play fair by the characters in her story, there were more journeys for her to take.
BETSY JAMES:
It started out simple. I wanted to exploit my hiking journal for a fantasy setting: thirty years’ walking the New Mexico desert wilderness, about six hundred hikes. Wonderful stuff: the deer skull shrine, the bear fall, the sliding rock, the grave of locked stones; the red stone altar-bowl full of blood, which was even weirder in real life because whoever (I assume) belonged to the blood had used one finger to paint a spiral on a nearby rock. So many of those places are gone now, changed, no longer accessible. I wanted to share them, and I wanted to walk through them again myself.
So I thought: If there are two young people, a sciency white ecologist—Ben—and a mixed-heritage artist—Trace—both of them violently destabilized by rage and mourning, what is the world their combined psyches might build from those adventures? They are climbing a mountain that is a cloud, trying desperately to reach a beloved grandmother—who happens to be dead—and are pursued by a being with a knife. They know who it is. If it catches them, it will kill them.
That was the idea. But fiction: You think you know where it’s headed, and it goes feral.
Right off I hit a roadblock. To write the American West without its indigenous peoples would be unethical; hence Trace, who has a Zuni mother. But I’m white. I led writers’ workshops in Zuni Pueblo for twenty years, so I know that no one can speak from those old cultures but those who grew up in them. We learn with our bodies: the all-night dances, the toddlers asleep on grandmas’ laps, the drum; the kachinas in their brilliant and utterly strange—to me—regalia.
I couldn’t pretend to speak for a Zuni. But Trace, the artist, needed her voice.
I kept writing, but I worried myself crazy. Finally I said: Look. I’m not the same person I would’ve been if I hadn’t taught in Zuni. I’m changed, the way I’ve been changed by the wilderness—mostly on levels I’m not even conscious of. I’ll speak.
A non-white student gave me a rule of thumb: “Nothing about us without us.” So I found indigenous readers, and when my inner colonialist was pointed out, I winced, rethought (re-felt), and rewrote. This book is the best my historically-rooted self, in this moment, can do.
Then another feral idea turned out to be central. A family issue. Don’t we love those.
“My folks were delighted that I was into bat poop,” Ben says of himself, and of Trace, “but she’s had to hide who she is.”
I grew up among scientists and skeptics. A gift; and a curse, because I was left on their doorstep by metaphorists. To my skeptics, metaphor was highly suspect, because it “might be mistaken for reality.” Fantasy was childish, escapist pulp, the badlands of mad religions, conspiracy theorists, and unfulfilled housewives. It was marginally okay before the age of reason, but then you went to college. Where English departments felt the same.
To bad for me, huh? I wrote and wrote and wrote the stuff. But I hid it. When I went to college I tried to reform, but it was too late, and I ditched the English departments instead. I knew the deep value of fabulism—I lived it—but it still felt clandestine, as if I were making gin in the basement. Early learning is so ingrained; what did I just say about growing up with kachinas, hearing that drum? Child of scientists who did not dance, I wanted a scientific defense for what I wrote.
As I thrashed around in those thickets, I stumbled on a paper by neurolinguist and metaphorist Dr. George Lakoff. It pointed out that, ahem, language itself is metaphor. That how we speak in science and how we speak in fantasy are both metaphoric. What’s more—here’s where I sat up—both worldviews exploit the same neural pathways.
Thank you, Dr. Lakoff. I mean it. No wonder skeptics (and English departments) quarrel with fantasists over trespass. We’re all using the same network, like corporations forced to share one laptop. We jostle each other on the paths; and for a fantasy based on hiking, what a terrific image.
The book became a thought experiment, with Ben and Trace as two worldviews and the New Mexico wilderness—a version of it—as the retort. In the world of molecules, an angry young scientist can’t climb a Cumulonimbus congestus; the most desperately sad, abused, and suicidal girl can’t get back to her dear grandmother by drawing a map and walking into it. In the world of metaphor, that they can do so is not only possible but appropriate. This is neither childish nor escapist.
The terrain of this book revealed itself to be traveled by the messy psyches of very different cultures, white and indigenous—which is to say the American West—and criscrossed by science and story, by both molecules and metaphor. Which is also to say the American West.
So: The impulses that share our neural pathways are electrical. Sometimes they cross; sometimes they fuse; the worlds they make are our world, but newly seen. “They’re all real, the different worlds,” says Trace. “They just give you different data.”
What I love about writing: You’re working away, making gin in your basement, and suddenly you’re given some gorgeous metaphor like, well, a bolt of lightning. On the edge of my mind had been something I was told in Zuni: that the kachinas themselves, who are spirits, are dancing in the spaces between the human dancers. Like a charge that jumps the space. We run on electricity; “reality” is what we bring into being in the spaces between us.
We’re practicing to be lightning. And that’s a metaphor.
Practicing to be Lightning: Amazon|Barnes & Noble|Bookshop
Author Socials: Web site
View From A Hotel Window, 9/4/2026: Atlanta [Whatever]

Hello, dear readers! I find myself at DragonCon, which means I am in Atlanta! I snagged a room upgrade while checking in and now have a view from the 37th floor, which is not half bad.
Have an excellent weekend, both con goers and those of you elsewhere!
-AMS
| Feed | RSS | Last fetched | Next fetched after |
|---|---|---|---|
| @ASmartBear | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| a bag of four grapes | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Ansible | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| Bad Science | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Black Doggerel | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| Blog - Official site of Stephen Fry | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Charlie Brooker | The Guardian | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Charlie's Diary | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| Chasing the Sunset - Comics Only | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Coding Horror | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| Comics Archive - Spinnyverse | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| Cory Doctorow's craphound.com | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Cory Doctorow, Author at Boing Boing | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| Ctrl+Alt+Del Comic | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| Cyberunions | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| David Mitchell | The Guardian | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| Deeplinks | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| Diesel Sweeties webcomic by rstevens | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| Dilbert | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Dork Tower | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Economics from the Top Down | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| Edmund Finney's Quest to Find the Meaning of Life | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| EFF Action Center | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| Enspiral Tales - Medium | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| Events | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| Falkvinge on Liberty | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| Flipside | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Flipside | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| Free software jobs | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| Full Frontal Nerdity by Aaron Williams | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| General Protection Fault: Comic Updates | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| George Monbiot | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| Girl Genius | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| Groklaw | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| Grrl Power | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Hackney Anarchist Group | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Hackney Solidarity Network | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| http://blog.llvm.org/feeds/posts/default | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| http://calendar.google.com/calendar/feeds/q7s5o02sj8hcam52hutbcofoo4%40group.calendar.google.com/public/basic | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| http://dynamic.boingboing.net/cgi-bin/mt/mt-cp.cgi?__mode=feed&_type=posts&blog_id=1&id=1 | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| http://eng.anarchoblogs.org/feed/atom/ | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| http://feed43.com/3874015735218037.xml | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| http://flatearthnews.net/flatearthnews.net/blogfeed | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| http://fulltextrssfeed.com/ | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| http://london.indymedia.org/articles.rss | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| http://pipes.yahoo.com/pipes/pipe.run?_id=ad0530218c055aa302f7e0e84d5d6515&_render=rss | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| http://planet.gridpp.ac.uk/atom.xml | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| http://shirky.com/weblog/feed/atom/ | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| http://thecommune.co.uk/feed/ | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| http://theness.com/roguesgallery/feed/ | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| http://www.airshipentertainment.com/buck/buckcomic/buck.rss | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| http://www.airshipentertainment.com/growf/growfcomic/growf.rss | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| http://www.airshipentertainment.com/myth/mythcomic/myth.rss | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| http://www.feedsapi.com/makefulltextfeed.php?url=http%3A%2F%2Fwww.somethingpositive.net%2Fsp.xml&what=auto&key=&max=7&links=preserve&exc=&privacy=I+accept | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| http://www.godhatesastronauts.com/feed/ | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| http://www.tinycat.co.uk/feed/ | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://anarchism.pageabode.com/blogs/anarcho/feed/ | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| https://broodhollow.krisstraub.comfeed/ | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| https://debian-administration.org/atom.xml | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| https://elitetheatre.org/ | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://feeds.feedburner.com/Starslip | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| https://feeds2.feedburner.com/GeekEtiquette?format=xml | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| https://hackbloc.org/rss.xml | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| https://kajafoglio.livejournal.com/data/atom/ | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| https://philfoglio.livejournal.com/data/atom/ | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://pixietrixcomix.com/eerie-cutiescomic.rss | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://pixietrixcomix.com/menage-a-3/comic.rss | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| https://propertyistheft.wordpress.com/feed/ | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://requiem.seraph-inn.com/updates.rss | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://studiofoglio.livejournal.com/data/atom/ | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| https://thecommandline.net/feed/ | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| https://torrentfreak.com/subscriptions/ | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| https://web.randi.org/?format=feed&type=rss | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| https://www.baen.com/baenebooks | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| https://www.dcscience.net/feed/medium.co | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| https://www.DropCatch.com/domain/steampunkmagazine.com | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| https://www.DropCatch.com/domain/ubuntuweblogs.org | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| https://www.DropCatch.com/redirect/?domain=DyingAlone.net | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://www.freedompress.org.uk:443/news/feed/ | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| https://www.goblinscomic.com/category/comics/feed/ | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://www.loomio.com/blog/feed/ | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| https://www.newstatesman.com/feeds/blogs/laurie-penny.rss | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| https://www.patreon.com/graveyardgreg/posts/comic.rss | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| https://www.rightmove.co.uk/rss/property-for-sale/find.html?locationIdentifier=REGION^876&maxPrice=240000&minBedrooms=2&displayPropertyType=houses&oldDisplayPropertyType=houses&primaryDisplayPropertyType=houses&oldPrimaryDisplayPropertyType=houses&numberOfPropertiesPerPage=24 | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| https://x.com/statuses/user_timeline/22724360.rss | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| Humble Bundle Blog | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| I, Cringely | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| Irregular Webcomic! | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| Joel on Software | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| Judith Proctor's Journal | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| Krebs on Security | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| Lambda the Ultimate - Programming Languages Weblog | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| Looking For Group | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| LWN.net | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| Mimi and Eunice | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| Neil Gaiman's Journal | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| Nina Paley | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| O Abnormal – Scifi/Fantasy Artist | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| Oglaf! -- Comics. Often dirty. | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| Oh Joy Sex Toy | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| Order of the Stick | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| Original Fiction Archives - Reactor | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| OSnews | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| Paul Graham: Unofficial RSS Feed | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| Penny Arcade | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Penny Red | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| PHD Comics | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Phil's blog | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| Planet Debian | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| Planet GNU | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| Planet Lisp | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Pluralistic: Daily links from Cory Doctorow | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| PS238 by Aaron Williams | XML | 09:28, Thursday, 10 September | 10:16, Thursday, 10 September |
| QC RSS v2 | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| Radar | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| RevK®'s ramblings | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| Richard Stallman's Political Notes | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Scenes From A Multiverse | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| Schneier on Security | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| SCHNEWS.ORG.UK | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| Scripting News | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Seth's Blog | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| Skin Horse | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Tales From the Riverbank | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| The Adventures of Dr. McNinja | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| The Bumpycat sat on the mat | XML | 09:35, Thursday, 10 September | 10:15, Thursday, 10 September |
| The Daily WTF | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| The Monochrome Mob | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| The Non-Adventures of Wonderella | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| The Old New Thing | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| The Open Source Grid Engine Blog | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| The Stranger | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| towerhamletsalarm | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| Twokinds | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| UK Indymedia Features | XML | 10:14, Thursday, 10 September | 10:56, Thursday, 10 September |
| Uploads from ne11y | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| Uploads from piasladic | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |
| Use Sword on Monster | XML | 09:28, Thursday, 10 September | 10:15, Thursday, 10 September |
| Wayward Sons: Legends - Sci-Fi Full Page Webcomic - Updates Daily | XML | 10:14, Thursday, 10 September | 11:00, Thursday, 10 September |
| what if? | XML | 09:35, Thursday, 10 September | 10:16, Thursday, 10 September |
| Whatever | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| Whitechapel Anarchist Group | XML | 09:35, Thursday, 10 September | 10:24, Thursday, 10 September |
| WIL WHEATON dot NET | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| wish | XML | 09:49, Thursday, 10 September | 10:34, Thursday, 10 September |
| Writing the Bright Fantastic | XML | 09:49, Thursday, 10 September | 10:33, Thursday, 10 September |
| xkcd.com | XML | 09:49, Thursday, 10 September | 10:32, Thursday, 10 September |