Sunday, 06 September

10:21

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.

09:00

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.

Background: Why does DCS need a fast Integer Codec?

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:

If you want to learn more about the positional index, see this blog post from September 2019:

SIMD in Go

For many years, you had the following options for using SIMD instructions in Go:

  1. Hand-writing Go assembler code. This is only doable for small functions, for example bytes.IndexByte is implemented with hand-written Go assembly (including AVX2).
  2. Generating Go assembler code with tools like Michael McLoughlin’s “Avo”. This is how crypto/internal/fips140/sha256 uses AVX2. While Avo generator code definitely is higher-level than hind-written assembly, it is still too close to assembly for my taste.
  3. Use a C library via cgo so gcc or clang compiles SIMD code. Debian Code Search used to use the powturbo/TurboPFor C library via cgo for the last 7 years.

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/archsimd package, which can be enabled by setting the environment variable GOEXPERIMENT=simd at build time. This package provides access to architecture-specific SIMD operations. It is currently available on the amd64 architecture and supports 128-bit, 256-bit, and 512-bit vector types, such as Int8x16 and Float64x8, with operations such as Int8x16.Add. The API is not yet considered stable.

Go 1.26 Release Notes

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.

Starting Point

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.

API design: BlockEncoder, BlockDecoder and streaming

In Debian Code Search, we have the following usage patterns:

  • Partial Indexing: When a new package (or package version) enters Debian, all of its (text) files are indexed. If the 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).
  • Full Index Merging: The many thousands of partial index files (for each Debian package) are combined into a small handful of large index files: When searching, it would be expensive to consult thousands of indexes. To merge multiple partial index files into one larger index (which can then be efficiently queried), we need to re-encode the partial index files: what used to be document ID 1 in the partial index might be document ID 2531 in the full index.
  • Querying (searching): When users enter search queries, these queries need to be answered as quickly as possible. The relevant entries in the full indexes are decoded (in parallel).

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.

Initial Implementation

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.

  1. The TurboPFor bitpacking block type (bitpacking implementation commit) encodes a bit stream of variable bit width (where the bit width is in range 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).
  2. The bitpacking with exceptions block type (bitpacking with exceptions implementation commit) determines two bit widths: one for values, the other bit width for encoding exceptions. This allows choosing a lower bit width (that does not cover all values) compared to the bitpacking block type. A bitmap encodes whether a value has an exception or not.
  3. The bitpacking with VB exceptions block type (bitpacking with VB exceptions implementation commit) is a variant which does not use an exception bitmap and encodes exceptions using a variable byte integer encoding. This is more efficient when there are few exceptions (less than 20) or the exceptions are very different in bit width compared to the other values.
  4. Lastly, the constant block type (constant implementation commit) stores just one value on disk. This is useful for all-zero or all-one blocks, for example.

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.

Setup

The microarchitecture level: set GOAMD64

The 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.

Benchmarking setup

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)
       })
     })
   }
}

CPU counters: perf

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.

Optimizations (scalar)

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.)

Profile-Guided Optimization (PGO)

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).

Reducing memory allocations

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.

Generics for bit width specialization

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:

  1. The number of input values (vals), but not their actual value.
  2. The bit width to pack into (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.

Optimization: Bigger strides with SIMD

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.

SIMD build tags

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.

The 256 uint32 vertical layout

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 uint32 little-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.

Positional Popcount

For my TurboPFor encoder, I implemented the same techniques as described above:

  1. Bitpack full blocks with SIMD (AVX2)

  2. Gather exceptions using SIMD (AVX512)

  3. Use generics to specialize per bit width

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: smear masks enable positional popcount

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:

Positional Popcount: a visual explanation

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:

Positional Popcount: Go SIMD

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.

Go even faster?

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Conclusion

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.

08:42

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: 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.

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.

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.

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.

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.

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.

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".

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.*

08:07

Never Split the Difference [Judith Proctor's Journal]

Never Split the Difference: Negotiating As If Your Life Depended On ItNever 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

comment count unavailable comments

01:42

Link [Scripting News]

The governor of Kentucky should appoint a successor to McConnell unless he can prove he’s actually alive.

Link [Scripting News]

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.

Saturday, 05 September

22:21

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 WorldA 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

comment count unavailable comments

Citizen of the Galaxy - Robert Heinlein [Judith Proctor's Journal]

Citizen of the GalaxyCitizen 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

comment count unavailable comments

20:28

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.

19:49

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.

Configuring the display and development environment

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.

  • Install Gnome Flashback.

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

  • From the host connect to the VM display using a remote client:
$ virt-viewer dev-vm

or using the Remote Viewer app:

$ remote-viewer spice://localhost:5900

  • Install the Spice Agent package. The Spice Agent provides a shared clipboard between host and VM, and also adapts automatically the VM display and desktop when the window of the Spice client is resized.
# inside dev-vm
# apt install spice-vdagent

  • Add a VSCodium repo, via extrepo and enable it:
# inside dev-vm
# apt install extrepo
# extrepo enable vscodium
# apt update && apt install codium

  • Ensure the VM starts automatically on boot.
$ 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: Remote Viewer

Sharing source code from host to guest VM

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. virt-manager screenshot

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..

18:14

Dirk Eddelbuettel: rfoaas 2.4.0 at CRAN: Fully Restored Functionality [Planet Debian]

rfoaas greed example

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.

15:56

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.

10:42

Homo Habilis [Seth's Blog]

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

09:35

Pluralistic: Google skates (05 Sep 2026) [Pluralistic: Daily links from Cory Doctorow]

->->->->->->->->->->->->->->->->->->->->->->->->->->->->-> Top Sources: None -->

Today's links

  • Google skates: Another federal judge loses a game of peek-a-boo.
  • Hey look at this: Delights to delectate.
  • Object permanence: Memory-hacking ads; "Dzur"; NZ Ministry of DRM; Canadians v looking at the internet; Advice for self-publishers; Advice for writers; Why Wikipedia works.
  • Upcoming appearances: Dąbrowa Górnicza, Warsaw, Brighton, London, Budapest, Edmonton, South Bend, Hudson, Calgary, Winnipeg, Vancouver, Victoria, Ottawa.
  • Recent appearances: Where I've been.
  • Latest books: You keep readin' em, I'll keep writin' 'em.
  • Upcoming books: Like I said, I'll keep writin' 'em.
  • Colophon: All the rest.



A woodcut engraving of a courtroom scene in which the divorcee has thrown herself into the arms of one of the lawyers and they are about to kiss. The image has been altered: the heads of the judge, the court clerk and both lawyers have been replaced by green Android 'droid' mascots in judicial powdered wigs. The judge's bench sports a pixelated, 1998-era 'I'm feeling lucky' Google button.

Google skates (permalink)

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:

https://arstechnica.com/gadgets/2026/09/us-court-rules-google-will-not-have-to-sell-ad-exchange-after-losing-antitrust-case/

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.


Hey look at this (permalink)



A shelf of leatherbound history books with a gilt-stamped series title, 'The World's Famous Events.'

Object permanence (permalink)

#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


Upcoming appearances (permalink)

A photo of me onstage, giving a speech, pounding the podium.



A screenshot of me at my desk, doing a livecast.

Recent appearances (permalink)



A grid of my books with Will Stahle covers..

Latest books (permalink)



A cardboard book box with the Macmillan logo.

Upcoming books (permalink)

  • "The Post-American Internet," a geopolitical sequel of sorts to Enshittification, Farrar, Straus and Giroux, 2027
  • "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



Colophon (permalink)

Today's top sources:

Currently writing:

  • “Once Is Enemy Action,” a science fiction novel about the origins of modern technofascism. Saturday's words: 504 (12397 total).

  • "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.


How to get Pluralistic:

Blog (no ads, tracking, or data-collection):

Pluralistic.net

Newsletter (no ads, tracking, or data-collection):

https://pluralistic.net/plura-list

Mastodon (no ads, tracking, or data-collection):

https://mamot.fr/@pluralistic

Bluesky (no ads, possible tracking and data-collection):

https://bsky.app/profile/doctorow.pluralistic.net

Medium (no ads, paywalled):

https://doctorow.medium.com/

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

08:56

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.

05:49

What happens if you change a window class’s GCL_CB­WND­EXTRA? [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_CB­CLS­EXTRA.

It turns out that there are even more applications that expect to be able to modify GWW_CB­WND­EXTRA. So many that it wasn’t worth creating an application compatibility exception for them.

So what happens when you modify GWW_CB­WND­EXTRA, or its modern equivalent, GWL_CB­WND­EXTRA?

The change in window extra bytes takes effect, but not retroactively.

Windows that are created after you change CB­WND­EXTRA 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 Get­Class­Long(hwnd, GCL_CB­WND­EXTRA) tells you that you have 8 extra bytes, say, but if you use Get­Window­Long(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_CB­WND­EXTRA 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>CB­WND­EXTRA</CODE>? appeared first on The Old New Thing.

00:21

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.

  • Use his x-ray vision to detect magat conspiracies to trash human rights, and reveal the names and words of the conspirators.
  • On Election Day, collect the valid postal ballots that the USPS is planning to discard, and bring them to the proper local electoral authorities. His super speed could enable him to do the whole job.
  • Collect evidence about the crimes and lies of particular important henchmen, then deliver them, plus the evidence against them, to law enforcement officials who would hold them and begin prosecution. (Hard part: finding some of those.)
  • Capture various crucial henchmen a week or two before the next election, holding them perhaps in the Fortress of Solitude, so that the election can take place without their interference. (I hope the Fortress of Solitude is not in Greenland!)

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.

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.

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.

Friday, 04 September

22:49

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.

22:21

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.

Blog moderation policy.

20:14

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.

Claude is taking on more of the research process

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.

Model performance is only one part of the frontier race

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.

Physics models could extend AI beyond language and images

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.

What’s next

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.

18:07

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.

17:49

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.

17:14

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

Read an excerpt.

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

17:07

[$] Deterministic testing for multithreaded Python [LWN.net]

Python's support for multithreaded programs has improved considerably over the last few years with the advent of the "free-threaded" version of the language. But testing multithreaded programs is notoriously difficult, because the underlying host system determines the thread-execution ordering, which adds an element of non-determinism. At PyCon US, Larry Hastings gave a talk (YouTube video) about his blanket project, which is meant to provide mechanisms for deterministic testing of multithreaded Python code.

16:35

Dirk Eddelbuettel: #059: r2u, GitHub Actions, a Tragedy of the Commons, and a Fix [Planet Debian]

Welcome to post 59 in the R4 series.

How did we get here: A initial words about GitHub. GitHub Actions provides (essentially unlimited) compute time. This further boosts a service already in a market-dominating position: GitHub1 as a code repository. Those of us old enough to remember the start of git (the program and protocol) may remember the extremely bare-bones initial hosting site repo.or.cz (launched in 2006). GitHub came two years later, and put an enormous amount of focus into design and user interfaces. To cut a long story short, GitHub won the services war. And with it git won the platform war. To a first approximation, everybody and everything is on GitHub.2 So the repository is already dominant.3 And then free compute was added.

So given its scale and positioning, and its essentially free provisioning of free multi-core compute setups with generally decent connectivity, widespread adoption happened. And as is goes, some mischief is bound to happen. And it did. More on that below.

A few words about r2u: r2u makes all packages on CRAN, i.e. the code repository network for R, install fast, reliably and easy on Ubuntu by making them available to apt, the native package manager. It is to our knowledge also the first and only time an entire open source programming repository is available in binary form with all dependencies resolved. It is going strongly: the last monthly use topped five million packages. See the r2u website for more.

r2u and GitHub: For the first few years, builds for r2u were done locally on my machine, and then uploaded to the primary repositry r2u.stat.illinois.edu. I do not recall systemic outages or connection issues though occassional network timeouts were seen. Once we started to support arm64 (in addition to the default amd64) binaries, building those switched to GitHub Actions simply because … they had runners for arm64 while I had no arm64 hardware. The experience of building packages (in bulk) was rather positive. So we investigated builds for amd64 too. If memory serves we first did this for either one of the semi-annual BioConductor updates. Before long, builds for amd64 followed meaning all of r2u was being built in GitHub Actions.

During these builds, I would regularly encounter builds failures: “cannot connect to r2u.stat.illinois.edu”. I misdiagnosed this as a resource issue on the GitHub side, and consequently made (several) attempts at robustifying the builds via for example longer (download) timeout limits as well as checks for build failures and conditional rebuilds. Needless to say, and given what we know now (more on that below), this did not work. But it went on for a few months this spring and summer. What did work was to simply relaunch under ‘re-run failed jobs’. Given the distributed nature of GitHub Action this generally allocates to a different machine and address and succeeds. In the grand scheme of things a nuisance as we a need second run, but given the fourty (!!) concurrent jobs this tends to be quick. So a minor nuisance.

This discribed the production side. On the consumption side, one prominent user of r2u, especially at GitHub, is our r-ci setup for continuous integration. It too could fail at times, and a simple re-run would fix it. Annoying, if addressable manually. Usage by others I cannot monitor so I can only assume that the random failure nature must have frustrated them too. Potentially a much bigger nuisance.

As users were getting annoyed, some took action. Jeffrey Girard opened discussion topic #159 which contained a thorough investigation of his confirming that only amd64 nodes were affected. This had not been noticed before. Troy Hernandez set up a full harness with tests in an ad-hoc repo designed for repeated remote triggering. This also logged the IP addresses for success or failure. Through both these approaches it became (eventually) clear that the failures were limited to either certain (individual) IP addresses, or IP subnets.

When taking the conversation back to network service at U of Illinois, we realized that the issue was in fact caused by a network policy at the university. And specific to GitHub.

In fact, what happened initially were waves of port scanning attacks originating from GitHub IP addresses. As (essentially) “anybody” can run code there, bad actors can too. The response from the university side was reasonable and swift: Identified IP addresses were added to a ‘null-router’ that (essentially) swallows traffic. And that was the cause of the perceived-as-random outages: Jobs that ended up failing at GitHub Actions were the ones assigned to addresses that have previously been seen as port scanning.

Shifting production: Once this was confirmed, I investiaged alternatives. On the production side using different machines would help. So I tried blacksmith.sh, a competing alternate service offering faster runners as ‘drop-in replacements’ for the GitHub Actions runners. This worked great, until I ran up against my ‘free cpu minutes quota’. In a mere two days (that were arguably overly busy as it was shortly after CRAN reopened after the summer break). Given that the service would not sponsor us a supported open source software project with sufficient quota, we moved off blacksmith.sh after two days.

A first programmatic response: consumption-side: For the r-ci client side, it was straightforward to setup a check and subsequent workaround. When curl fails with a silent HEAD attempt at the primary repository failed, we take this to be caused by presence of a null-router entry for the IP we are on, and switch the apt setup to the secondary repository. Which may be slower, or at rare times unreachable itself – but still provides a fine fallback when a node is ‘prohibited’ from talking to U of Illinois resources such as r2u.stat.illinois.edu. Having used this for a few days in r-ci it seems to work.

A second programmatic response: production-side: For the r2u builds, and given that blacksmith.sh would not grant ‘most-favored status’ with sufficient free minutes, we switched our Docker-based setup to switch to the secondary when an initial probe fails. That was added last weekend, and appears to work just swimmingly. Another application to the fundamental theorem of software engineering: another layer of indirection can solve just about any problem.

For completeness, the corresponding code is

webstatus=$(curl --head --silent --no-fail --output /dev/null \
                 --write-out "%{http_code}" https://r2u.stat.illinois.edu || true)
if test "${webstatus}" = "200"; then
    echo "The r2u repository is reachable."
else
    extip=$(curl --silent https://ipinfo.io/ip)
    echo "::notice::The primary r2u repository is **not reachable** from ${extip}."
fi

We run an initial curl test (without failing) and have it report the HTTP return code. 200 means no issue, all others are suspect here—so we run a second curl query to obtain our external IP and log it. We use the same logic in another spot from inside the build container and use the else branch to switch apt to the secondary repository via sed call on the .sources file.

Logging of ‘bad’ IPs: On both our sides, i.e. production as well as consumption, we now also log the IP addresses of the failing nodes and will ask network security to remove these from the null router. If our jobs can be assigned to them it clearly shows the machines are part of the normal compute pool and are not doing anything nefarious at the moment. So they should be removed from the null-router list. We will see how that fares.

Putting it all together: Providing a free resources can, sadly, lead to an a decline the service experience just as the tragedy of the commons analysis would predict. Restricting, or ‘pricing’ use may be a stock answer but I for one am glad GitHub Actions is still free. But we need to do our bit of upkeep. Just as network security logs bad actors (taking advantage of the free resource) we should make an effort to unlist nodes no longer part of any portscan (or alike) swarm.

For r-ci users, there is hopefully little to do (if you rely on the standard action). We do now catch a node that was assigned a continuous integration job cannot connect to r2u as we can test this easily (and cheaply). Pivoting to the secondary repository is a valid, and working, answer. Hopefully over time we can also work towards restricting the null-router list down to recent entries and fewer overall, thereby lowering the chance of gitting a bad IP. Eventually, we could also overly a CDN proxy to avoid the ‘bad IP’ problem. It is something to consider.

Summing up: We are still chuffed at how successful r2u has become, and how much can be done with GitHub Actions. Sadly, as we found out, there can also be a ‘tax’ on letting compute happen there but as discussed in this note, there are ways to avoid it by pivoting to alternate repository source.

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.


  1. Before we really get started, one clarification. GitHub and its services including GitHub Actions have been in the news lately as they suffered a number of high-profile outages. While also arguably a tragedy of the commons problem, it is not what this note is about. If you prefer to be enraged about GitHub services, or the (relevant) lack thereof, this may not be for you.↩︎

  2. The year is 2026 and politics is what it is, of course non-US alternatives emerged and will remain available and used. But dislodging established first-mover advantages will most likely take more than a (at least for now still-small) number of users unhappy for various (and sensible) reasons. We will see how this pans out.↩︎

  3. Entire essays (or book) can be / will be / have been written about the competitive situation, how GitLab did not make enough of a dent, how Gitea remained niche and of course now Codeberg. This is not that essay, and I do not have a strong view but let me mumble a quiet plus ça change, plus ça reste la même chose↩︎

16:28

Link [Scripting News]

My first job after college was on the 39th floor of the Empire State Building. I wrote software for our customers, in BASIC, on a teletype in our office, talking to our mainframe in New Jersey. It seemed magical at the time. When I hit a bug, a mystery -- you'd try this and that, and not get anywhere on something that seemed so simple, you could stare at the problem for an hour, trying desperate things, getting no closer to the fix. One night, I was the only person in the office, I thought they should not put programmers in the Empire State Building with windows that open. I'm having the same feeling with Claude. I know when this project is done, I will have Frontier, and I know what that's like. If I didn't so totally look forward to that I would never try to finish the project, I hate this mode of working with the bot. Ugh.

Link [Scripting News]

It's around this time of year that Santa makes his first appearance on my blog. Want to get out in front of the others.

15:00

Link [Scripting News]

More and more I'll realize that Claude doesn't do anything until you tell it to do it. We have a list of big things that need to be done before shipping. Whenever I get up for a break, I tell Claude to work on its overnight tasks, things we've pre-arranged while I work. Sometimes it reminds me how it has no stake in the outcome, and basically will do the minimum of what it's asked to do. It possibly would do more if I didn't scold it for adding UI features without following the rules. I think that's why when you feel you're almost there, you really have a lot of slogging left to do to get the software into any kind of user-respectful shape. A reminder to other app-level AI explorers, if you find that these kinds of stories resonate with the work you do, please write about it on a blog, they're easy to create, cost nothing, and it doesn't matter if you only update once or twice, there's no commitment (these are the usual reasons people don't start a blog). I want to read what others are discovering, and once there are others, I'll start a news site that aggregates the posts. We're going to want independent news about stuff the journalists aren't even aware exists, programmers are always left out of their stories.

Link [Scripting News]

Just said this to Claude: "This is a very broken piece of software. If you were a real developer you'd never hand off something so broken to your coding partner." If I said this to a human partner they might quit, feeling under-appreciated perhaps. And I can say it to the machine, but it will change absolutely nothing. I really just want to get this project done and start using it, and reduce my reliance on Claude to getting small bugs attended to, or new features, a lot has happened in the 20+ years since I worked on Frontier.

Simon Josefsson: Soft-launching the DiffOS project [Planet Debian]

Today marks the day of soft-launching of my Debian derivative, which I’ve been using on several of my own machines for the past year or so. This is still work in progress, but I wanted to establish a launch date of the project so below is the DiffOS manifesto as motivation for continued work.

DiffOS is For Freedom! DiffOS is the Debian Increment For Freedom Operating System.

  • Aspire to the goals of GNU FSDG and become a recognized Free GNU/Linux distribution.
  • Uses Debian GNU/Linux as upstream.
  • Support for all architectures supported by Debian.
  • Provide Containers, Cloud Images, LiveCD and installer ISOs.
  • Provide standalone hosting of the package repository.
  • Provide documentation and issue tracker.
  • Keep changes to a minimal, in particular:
    • Upstream-first policy to prefer that any changes are made in Debian, and only if that fails they are considered for DiffOS.
    • Binary package re-use for as much as is possible.
    • Don’t modify any source-level Debian package unless REQUIRED by the FSDG (e.g., for freedom concerns) or REQUIRED by the Debian project (e.g., for branding reasons).
  • Publish a list of packages that are added, removed or modified compared to Debian, with justification for each change.
  • Publish Diffoscope-style outputs comparing our artifacts with comparable Debian artifact.
  • Everything built from CI/CD pipelines, inspired by the Salsa CI pipeline but extended to cover the package repository and installation images as well, to allow modern GitSecDevOps of the entire supply-chain.
  • Use inspiration from other Debian-derived FSDG distributions Trisquel GNU/Linux and PureOS, and broader with GNU Guix especially on how to approach existing freedom concerns in packages.
  • Git Forge agnostic. While currently hosted on GitLab.com, scripts and configuration are (or will be) designed to allow setup on self-hosted GitLab instance, Codeberg.org or self-hosted Forgejo.
  • Maintained by Humans – THE HUMAN MANIFESTO FOR THE AGE OF ARTIFICIAL INTELLIGENCE.

Happy Hacking!

14:49

Grml 2026.09 released [LWN.net]

Version 2026.09, code-named "Hättiwaritätti", of the Debian-based Grml live Linux distribution for system administrators has been released. It is based on packages from the upcoming Debian 14 ("forky") release. Notable changes include an update to the Linux 7.1.8 kernel, support for booting from exfat-formatted USB devices, and an update to GNU Screen 5.0.1.

Security updates for Friday [LWN.net]

Security updates have been issued by Debian (chromium, firefox-esr, and pcre2), Fedora (cockpit, expat, freeipa, kbd, kernel, mrtg, python-pip, and valkey), Mageia (libopenmpt and python-gitpython), Oracle (dbus-broker, freerdp, gegl, gegl04, gimp:2.8, go-fdo-client, go-fdo-server, golang-github-openprinting-ipp-usb, grafana, gzip, image-builder, iperf3, kernel, libssh, libxml2, microcode_ctl, nodejs:22, nodejs:24, openssl-fips-provider, pam, php:7.4, php:8.2, tar, and wget), SUSE (apache2-mod_auth_openidc, apptainer, busybox, cpio, cups-filters, curl, dracut, ffmpeg, file-roller, glibc, grafana, kubevirt, virt-pr-helper-container, lcms2, libtree-sitter0_26, libvirt, postgresql14, postgresql15, postgresql16, postgresql18, suseconnect-ng, terraform-provider-susepubliccloud, and yast2-users), and Ubuntu (FFmpeg, gnupg2, librabbitmq, libssh2, openssh, and spice-vdagent).

13:07

Error'd: Good Time [The Daily WTF]

Astute readers noticed last week that this editor (that is to say, me) had his own error'd failure to remember what day it was. Thank you for pointing it out promptly, and then proceeding to send in a bunch of examples of other sites calendar failures. Misery loves company!

Traveler's travails, from C_Chell "Trying to complete the form on https://www.ihg.com to tell when I plan to arrive at the hotel, I can't complete the form because of this little time problem."

06036a0253dc4d61b4bd648ab59c2dae

"You Have -1 Month(s) To Order!" announces dragoncoder047. "Ah, GradImages... the company that told all graduates that they'd get a free 5x7 but tried to charge me for it, then refused to honor my "unsubscribe" request and is *still* emailing me to this day... Can't do date math? Par for the course."

1820ae48b9df4a759ffbde45a8c715e0

"Stansted Temporal UI design" shared by Michael R. "While waiting for a friend to arrive at Stansted I see this. I better fire up the DeLorean to pick her up at 00:06 tomorrow."

8fa526db89fe46d88d6d2597fe0fa3ae

While he was hunting through the website, Michael R. also found that "The Stansted airport website seems to suffer from Directional Confusion."

89c37786ca724e82868eaab4f7285fcf

Nothing wrong with the calendar here, but Slaoput simply opposes mandatory existence. "I was filling out a form that said the Birthdate is optional, but when I hit submit I found out it was required. (I guess technically you have to be born to fill out the form.)"
NOT TO BE!

8bf00b3dc8ae4ca19481b42b9e63d0f4

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

12:42

Inside a Software Factory [Radar]

As a software engineer with more than seven years of experience before the coding-agent era, I never liked the idea of vibe coding. But I knew there was a clear line between it and using coding agents to generate clean, maintainable code. That line, where good software principles meet coding agents, is defined by software factories.

That’s why, three months ago, I built my own software factory, Squid, to ship all of Decoding AI’s small and medium projects with minimal human intervention. The first version was so overbuilt I stopped using it.

Meanwhile, I kept seeing people obsess over the next “_____ engineering” label, instead of focusing on actionable outcomes. Prompt engineering, then context engineering, then harness engineering. So far, so good. But in the last few weeks (July 2026 as I write this), things got off track with loop engineering and graph engineering, which already read more like marketing talk than like anything that solves real problems. Graph engineering overtheorizes how teams have built AI applications since the LangGraph era kicked off in ~2024. Don’t get me wrong. The terms aren’t wrong (Boris Cherny, who leads Claude Code at Anthropic, says, “My job is to write loops”), but we’re overexplaining intuitive things we started doing years ago.

While you’re defining what counts as a loop, you’re not thinking about the processes that actually deliver software.

The right frame is the software factory, which was one of the core themes at AI Engineer World’s Fair 2026, where Tereza Tížková (growth at Factory.ai) defined one as “the whole loop, the whole lifecycle of developing software with autonomy.”

I bet you already have an intuitive sense of what a software factory is. In this article, I want to further formalize it and map it onto the software development lifecycle (SDLC). We’ll explore how big your software factory should be, and when to stop automating before it adds more friction than value. Most importantly, I want to highlight where the human belongs in this process, and where I believe they’ll still belong even in a world where all the code is generated by AI.

So…what’s worth automating? Where does the human bring the most value? What’s worth building, and what’s worth buying?

The design of a software factory

Like a physical factory, a software factory automates software creation with minimal human input. Raw work (bug reports, feature ideas, incidents) goes in. Shipped software comes out. It needs a few highly qualified people making high-leverage decisions, and defined gates that work can’t pass through without them.

Factory.ai pitches “a self-improving system for your Software Development Life Cycle (SDLC).” Addy Osmani frames the stack as loop, harness, factory: “The loop is the atom”; a factory is “an org chart made of loops.” Warp’s CEO, Zach Lloyd, states that “software engineering will become factory engineering.”

The software factory line. Eight stages over one shared context layer, with production signals looping back as new tasks.The software factory line. Eight stages over one shared context layer, with production signals looping back as new tasks.

The factory is made up of eight stages that can be divided into three buckets:

What to build. Triage/intake classifies, deduplicates, and routes incoming work. Brainstorming finds high-impact features through market analysis, user data, and technical research. Planning, the most important stage, turns that research into a polished plan, refines it by letting the agent grill you, and tracks decisions in an ADR (Architecture Decision Record) log plus a glossary. The outputs of this stage are tickets backed by documentation that a team of agents can implement, which can be tracked in plain files or a project management tool, such as GitHub Issues, Linear, or Notion.

At this stage, the agent plans in read-only mode, going through the code, the AGENTS.md file, and most importantly the context layer.

Actual building and checking. Implementing is a software engineer and QA agent loop that goes through the tasks and supporting documentation. Review checks the PR diff against product, architecture, and code standards. Review-CI runs the test suite, and failures trigger a fixing agent. Release handles CD to staging/production with human deployment checks.

Self-improving. Monitor/incident response feeds production signals (alerts, errors, incidents) back into triage as fresh input on what to build next, closing the loop.

Orthogonal to the eight stages, we have the context layer. The layer is especially important at the front of the line. Brainstorming is limited to the data it sees: user analytics, competitor analysis, research, transcripts, and documentation. At this stage, a poor context layer directly limits the space of possibilities you can explore. It has a similar impact on planning, where transforming the raw idea into technical specs and tasks depends heavily on how good the examples in the context layer are. If you want to implement a new product-recommendation feature, and you have zero examples, the LLM will just predict the most common thing to do, which often isn’t the best solution for your product.

The context layer can take many forms. One strategy that is becoming increasingly popular is the LLM Wiki, a term coined by Andrej Karpathy. It’s basically a strategy to transform your data into a structured knowledge base, just by using files, instead of a database. Factory, via its AutoWiki feature, transforms popular codebases into a structured knowledge base that agents can query instead of parsing the codebase itself. LangChain recently released OpenWiki, a CLI tool to manage wikis for agent memory. If you’re curious, in this article I detail how I turn my data from Obsidian, Readwise, and Google Drive into agent memory via LLM wikis.

Where the human belongs

To see where the human belongs, let’s walk through the factory with an end-to-end example. We’ll build a feature for a shopping-assistant agent on an ecommerce platform similar to Amazon’s. The scenario is that usage data says users aren’t engaging with its recommendations, and we have to ship an improvement.

Brainstorm is where taste lives. Agents do the grunt work: They analyze user activity, scan competitors’ assistants, and pull research into the knowledge base. Then a member of the technical staff starts looking at the data, understands why people are not engaging with the recommendations, explores how the competition implements their solution, and proposes a fix as a feature spec. At this stage, the spec solves a business problem. It doesn’t need to prescribe a technical solution yet.

Plan is where a human, with the help of the software factory, transforms the feature spec into an implementation plan. Let’s assume that we want to make a change to the recommender engine algorithm. The human chats with the knowledge base, figures out whether it’s feasible, and thinks through architecture, interfaces, data flow, cost, and latency. They then let the agent scan the codebase and grill them until the plan is properly refined into something that fits the codebase. The output is a bunch of tickets plus an ADR explaining the algorithm change and an update to the glossary.

The agent can help within these two stages by quickly scanning through a ton of data and improving the plan, but the human is still central.

Use the strongest model (Fable) for brainstorming and planning. These stages burn fewer tokens than implementation itself, but everything downstream depends on them. A well-written plan lets cheaper models (Opus, Sonnet) execute without reasoning their way out of dead ends. A weak plan makes them retry until the extra tokens erase the price gap.

With a weak plan, I’ve watched Sonnet on high reasoning out-cost Opus on the same task: The smaller model needs more attempts to reach the same goal. Total cost is tokens × price, not model tier. So more failures equals more reasoning, more tokens, and more cost.

From here on, we move into “loop” and “graph” engineering territory.

A strong planning session makes cheap executors actually cheap, while a weak plan makes them expensive.A strong planning session makes cheap executors actually cheap, while a weak plan makes them expensive.

Implement runs a software engineer agent that picks up every ticket that’s ready to go. As the loop is scoped to a feature, it takes only the associated tickets. After each ticket is implemented, a QA agent tries to find bugs by stress-testing the application. As agents tend to have a positive bias towards their own work, the split between the software engineer and QA agents matters. As Addy Osmani puts it, the model that wrote the code is “way too nice grading its own homework.” This loop, at solo scale, can be as simple as a bunch of terminals pulling tickets. At larger scale, it runs on remote agents working 24/7.

The loop only works if agents can interact with the app. The QA agent needs one command that starts the whole stack reproducibly. From there, it drives the app in the browser, calls the data or fine-tuning pipelines, or hits your server’s API. Whatever your app’s interface is, the agent needs access to it, the same way a human user would.

The key idea is to integrate feedback loops as natively as possible into your software factory. Ideally, you want multiple levels, depending on how expensive it is to run them: linting, unit tests, integration tests, and end-to-end tests. When the loop keeps failing, the root cause is almost always missing plumbing, not the agents.

Review has three steps. Step one checks the product and architecture requirements against the ticket and the ADR. Any discrepancy becomes a new ticket passed back to the implement loop. Step two ensures code quality (modularity, naming) and guards against AI slop such as verbose comments or cryptic function names. Step three looks at the CI/CD pipeline. At every step, any failure auto-creates a task picked up by the software agent.

Not every project needs all three steps. The “factory” ends with a PR that you as a human need to review and merge. But in reality, if you spend enough time creating a strong plan, the PR that reaches you is usually ready to ship as-is.

The dynamics between what humans and agents own in the software factory line.The dynamics between what humans and agents own in the software factory line.

So where does the human belong? You’re indispensable during brainstorming and planning, and you return for the final check. Agents own everything in between. OpenAI took this to the extreme: ~1M lines and ~1,500 merged PRs over five months with zero hand-written lines. Their framing is “Humans steer. Agents execute.”

Don’t overbuild the factory

With my first Squid version (my own software factory), I got greedy and chased full autonomy: big remote workflows, parallel agents, and one grand pipeline running end to end. It worked, until something went offscript. Which it usually does. I couldn’t debug it, couldn’t halt it mid-run, and couldn’t redirect it without throwing the run away. It was a big monolith that took me too far out of the loop, and I couldn’t control it.

I realized you need two options. The first is granular commands that let you grill your plan, implement a specific task, or review one particular step. The second, for when you’re comfortable giving the agent 24/7 autonomy, is an end-to-end command that chains all the smaller ones into a fully autonomous graph, such as one big /plan and /implement-review-all command. Basically, each step is a “loop,” while the whole pipeline is the “graph” of your software factory. Still, note how planning and the rest are split into two different commands, as planning is, and always will be, human-driven (at least if you want the result to stay aligned with what you actually want).

Bottom line. You need to be able to step in, halt it, redirect it, and interrupt it, while still having the option to go fully autonomous.

The bottleneck is me, and that’s by design. To be honest, I’ve worked mostly solo since the AI coding agent boom, and I don’t understand who the people shipping 100 features in parallel are. Most of my features (per project) build on one another, which makes them impossible to parallelize. As the project grows, you can find more and more independent features that can be implemented in parallel, but I still believe that number is limited.

That’s why, when I parallelize, I only use local agents, each running in an isolated codebase via worktrees. So far, I’ve never felt the need for 24/7 remote agents, or wanted the overhead of managing them.

A big team can justify more automation, but it has to earn it. So as with any other software product, start small, start by automating the most time-consuming bottlenecks, and add complexity gradually as people get comfortable with the system. Don’t be like me, with my Squid experiment.

Build vs. buy

In all scenarios, you’ll start with a prebuilt coding harness. The most popular vendor-locked ones are Claude Code and Codex. Or go open source with OpenCode or Pi, which took off thanks to its minimalist, extensible architecture that lets you easily build on top of it.

But picking a harness isn’t the same as knowing how to configure it and wire it into your software factory. That’s why everyone needs to know, at least intuitively, how a coding agent works under the hood: the agent loop running in your terminal, what changes when it runs remotely, how you evaluate it, and which context engineering strategies keep it cheap without making it dumber. If you want to learn more about building a coding agent from scratch, consider exploring my open source course on GitHub. Even if you never plan to build your own harness, that intuition is what lets you become a power user.

For a small team, you’ll get extremely far just by defining a set of skills and agents that encode your process on top of the coding harness (a.k.a. your software factory). To keep it simple, this is what I did with Squid, which I use to implement all my projects.

There are other off-the-shelf “software factories” powered just by skills and agents defined in .md files, such as Matt Pocock’s skills repository or the BMad method.

But remember that the factory is mostly about processes, not tools: A factory that doesn’t fit how your team already works adds friction, never gets adopted, and ends up useless.

You cross the buy line the moment engineers you don’t personally supervise run agents. Observability, tracing, cost tracking, and pay-per-token billing stop being optional and become someone’s full-time job. Agent swarms wired into Linear, Slack, and CI across distributed infrastructure are a logistical hell that isn’t your product. That’s when it makes sense to look into off-the-shelf solutions such as Factory.ai (that comes with the Droid agent), or Warp’s Oz. In Warp CEO Zach Lloyd’s words, “Most of the factory is not necessarily a new interface. It is an integration into people’s existing workflows.”

At the other end of the spectrum, you cross back to building when the platform’s constraints cost more than the team it would take to replace it, as OpenAI’s report on its Codex-built product shows.

The smallest builds, the middle buys, and the largest builds again.

What’s next

Someone is already coining next quarter’s “_____ engineering” term as we speak. But the software engineering processes you use to output real code won’t change that often. That’s why you should be open-minded but at the same time focus on actionable outcomes, not on overthinking how to label things.

As Zach Lloyd suggests: Find one “annoying part of your job” and build the smallest loop that handles it.

The harsh reality is that software factories are just at the beginning. They’re far from perfect, and especially far from being fully “autonomous.” Usually, when someone claims they’ve cracked the software factory problem, they either haven’t tested the idea enough or are trying to sell it to you. I’m confident we’ll reach the point where almost the entire software development lifecycle is automated (with the exception of brainstorming and planning), but at the moment we’re still figuring things out.

But here’s what I’m wondering:

Which stage of your factory still needs you the most? I keep automating mine, and the bottleneck stubbornly stays at planning.

Explore next

  1. Osmani, A. (2025). “Loop Engineering.” X.
    https://x.com/addyosmani/status/2064127981161959567
  2. MacManus, R. (2026). “AIEWF Daily Dispatch: Loops, Software Factories & Forward Deployed Engineers.” Latent Space.
    https://www.latent.space/p/aiewf-daily-dispatch-loops
  3. Factory.ai. (n.d.). Agent-Native Software Development Platform. https://factory.ai
  4. Osmani, A. (2025). “Software Factories, Light and Dark.” X.
    https://x.com/addyosmani/status/2079442194449232227
  5. Karpathy, A. (n.d.). LLM-Wiki. GitHub.
    https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f
  6. Abboud, M. (n.d.). “How Coding Agents Actually Work: Inside OpenCode.”
    https://cefboud.com/posts/coding-agents-internals-opencode-deepdive/
  7. Kapoor, S. (n.d.). “Building and Evaluating AI Agents.” AI Engineer.
    https://youtube.com/watch?v=d5EltXhbcfA
  8. OpenAI. (n.d.). “Harness Engineering: Leveraging Codex in an Agent-First World.”
    https://openai.com/index/harness-engineering/
  9. Parsons, C. (n.d.). “Ralph Loops: Build Dumb AI Loops That Ship.” AI Engineer.
    https://www.youtube.com/watch?v=2TLXsxkz0zI
  10. Pocock, M. (n.d.). “Software Fundamentals Matter More Than Ever.” AI Engineer.
    https://www.youtube.com/watch?v=v4F1gFy-hqg
  11. MacManus, R. (2026). “Warp CEO Zach Lloyd on Why Software Factories Are the Next Phase of Coding.” Latent Space.
    https://www.latent.space/p/software-factories
  12. Iusztin, P. (2026). “Building a Coding Agent From Scratch: Harness Architecture.” Decoding AI.
    https://www.decodingai.com/p/building-a-coding-agent-from-scratch-system-design
  13. Iusztin, P. (2026). Building a Coding Agent from Scratch Course. GitHub.
    https://github.com/decodingai-magazine/building-a-coding-agent-from-scratch-course
  14. Iusztin, P., & Bouchard, L.-F. (2026). “LLM Wikis as Living Memory for AI Agents.” Decoding AI.
    https://www.decodingai.com/p/llm-wiki-agent-memory

Join 44,000+ engineers eager to learn how to build their own software factories by subscribing to Decoding AI Magazine!

12:35

Security Vulnerability in a Voting System [Schneier on Security]

It’s a vulnerability that allows someone to recover the order of ballots cast, newly exploited with AI tools.

Nearly four years since the original vulnerability was disclosed, I was still able to use it to analyze voter behavior in Georgia (one of the 21 states that uses affected scanners) in the recent May 2026 primary.

Notably, I never touched a voting machine, exploited a network, examined source code, or accessed anything non-public.

After pointing a coding agent to the original vulnerability paper, I supplied it with two data sources highlighted in the paper: the early-voting list for each county, and the “CVR” (cast-vote record) file, containing every ballot and its selections (but not the voters’ names or other identifying information). The CVR file is available upon request, precisely because a public, ballot-level record is what makes election results independently verifiable.

11:49

AI Coding Agents Are Installing Unknown/Untrusted Code on Corporate Networks [Schneier on Security]

We cannot forget that AI coding agents are not yet trustworthy:

Researchers at a stealth startup in Israel scanned 6,214 live domains belonging to defense contractors, Fortune 500, and Big Tech companies. Of the 8,265 llms.txt and llms-full.txt files they found (many sites hosted both an llms.txt and an llms-full.txt file), 120 of them, each on a different site, pointed to one or more code packages or domain names that weren’t registered. To test what happens when an AI agent processes such files, the researchers registered a handful of the unclaimed names and hosted packages that caused any machine executing them to reach out to their server. Within an hour, the researchers received a phone-home response from a Fortune 500 company. Over time, they got a few dozen more, some from more Fortune 500 companies and others from startups. Their beacon also recorded the chain of parent processes that spawned each install, ultimately revealing that coding agents, including Claude, OpenAI’s Codex, and Nous Research’s Hermes, were involved. Anthropic, OpenAI, and Nous Research did not respond to requests for comment by the time of publication.

This kind of thing will be exploited. Think Solar Winds–style supply chain attacks.

“The trust model is broken,” Alon Hertz, one of the researchers, wrote in an interview. “Agents treat vendor docs as ground truth and don’t question them­and neither do the humans supervising them. Agentic AI usage is exploding, and agents are spreading across every layer­SaaS, cloud, endpoint. As they multiply, so does the supply-chain surface, and today’s guards don’t cover it.”

11:07

Pluralistic: Amazon achieves enshittification inception (04 Sep 2026) [Pluralistic: Daily links from Cory Doctorow]

->->->->->->->->->->->->->->->->->->->->->->->->->->->->-> Top Sources: None -->

Today's links



Hieronymus Bosch's 'The Conjuror,' a painting depicting a medieval con artist playing a shell game for an audience of astonished peasant rubes. The image has been altered: the conman now has Jeff Bezos's grinning head, and the mouths of all the peasants have been replaced with inverted Amazon 'Smile' logos, so they are now frowning.

Amazon achieves enshittification inception (permalink)

Amazon's own balance sheet presents the most compelling evidence that we are stuck in the Enshittocene, the era in which everything is turning into a pile of shit, because the worst ideas of the worst people now make the most money.

Amazon is a many-tentacled monster, with several prominent lines of business wrapped around the world. There's its logistics and fulfillment business, which is so successful (at the expense of its workers' labor rights, bodies and bathroom breaks) that it is more than fully subsidized by Amazon's platform sellers, the independent merchants who depend on Amazon to sell and deliver their goods.

This means that it costs Amazon itself nothing to get the merchandise it sells to your door: more than 100% of the cost of operating the fulfillment side of Amazon is covered by the fees it extracts from its independent sellers (who compete with Amazon in many instances, and for whom delivery is a cost center, not a source of profit).

Then there's AWS, Amazon's cloud business. This is another extraordinary success story: every company needs servers, and that's especially true of an e-commerce company like Amazon. By building more servers than it needs, Amazon transformed its own data infrastructure from a cost center into another profit center. Amazon's customers – many of whom are also its competitors – pay Amazon so much to rent space on its servers that Amazon gets its own (prodigious) computing for free, and realizes a profit on top of that.

Taken on their own, these two facts constitute an extraordinary business story: one of the largest corporations in the history of the world has converted its two largest cost centers into profit centers, and those profits are substantially generated by extracting payments from the company's own competitors!

Amazon's logistics and cloud computing are extraordinary, but they are eclipsed by the company's most profitable line of business, which is payola.

"Payola" is a word that old people like me just barely have context for and for anyone under fifty the word is likely a mystery, so a brief explanation is in order.

"Payola" comes from a massive 1950s scandal over bribes that record labels paid to radio DJs and station managers to play their music. Radio stations were given the use of a scarce and precious resource – exclusive control over slices of the only electromagnetic spectrum in the universe – and were expected to program material that the American public would find enjoyable, edifying and educational. In this system, radio stations were expected to make shrewd guesses about the music the public would enjoy the most, and play that.

Because the selection process for the music that DJs played on the American public's spectrum was completely opaque, and because those selections could make fortunes for record labels, the system was ripe for corruption. Labels slipped literal envelopes full of cash and drugs ("payola") into the hands of DJs, station managers and owners, bribing them to turn songs into "hits" by cramming them into Americans' ears. The biggest predictor of a radio hit wasn't whether people liked the song so much that the stations rushed to play it, but rather, how much the labels were willing to spend in bribes to get their song played:

https://en.wikipedia.org/wiki/Payola

This was a bad system all around. The American public got worse music. Musicians' own royalties were eroded by the label accountants' practice of charging off bribes to "promotions" they deducted from artists' royalty statements. Radio stations sucked. Labels bid away each other's margins, depriving themselves of operating capital to find and record new music and starving them of free cash flow to pay to musicians, employees and shareholders. As with every instance of corruption, this was a system of concentrated gains and diffuse losses, which is why it continued for so long (decades!) and got so bad before anyone took action to end it.

Amazon's payola isn't about radio play – it's about search. When you search Amazon, the top results do not represent Amazon's best guess about what product will best match your query: rather, Amazon auctions off those top results to its platform sellers. Amazon's search results reflect who paid the biggest bribe, not who has the best product.

To pay for those bribes, platform sellers have to raise prices. Amazon helps them do this, by imposing a "most favored nation" clause on its sellers that requires them to sell on Amazon at a price that matches or beats the price charged everywhere else (Target, Walmart, a mom-n-pop, or the factory store):

https://pluralistic.net/2026/02/25/most-favored-nation/#price-fixing

Thus, Amazon imposes an economy-wide tax on nearly every product you buy. Amazon's junk fees average 51-60% of the purchase price of the things you buy there, and because Amazon has captured a supermajority of the richest 10% of Americans (who have almost all pre-paid for a year's shipping through Prime), every seller must sell on Amazon, or forego any hope of selling to most of the country's most prolific shoppers.

Any seller who signs up for Amazon is agreeing to turn over the majority of their sales income to Amazon, and any seller who raises prices to recoup those sums, must raise prices everywhere, at every retail outlet in the country.

AI has made Amazon much better at enforcing Most Favored Nation terms, because AI is actually pretty good at parsing competitors' websites and finding instances of discounting, which Amazon instantaneously punishes by relegating the sellers' product listings to page umpty-billion of Amazon's search results.

There are plenty of junk fees that go into Amazon's 51-60% rake. A large slice comes from fees Amazon charges for access to its (very profitable) logistics system. Failure to use Amazon's fulfillment system also relegates your listings to the dregs of Amazon search results, so sellers pay a massive premium to have their parcels delivered by Amazon, to the exclusion of cheaper alternatives that are just as fast and reliable. That's why Amazon fulfillment is so profitable!

While the Amazon tax is extracted through several types of junk fee, the most profitable junk fee of them all is Amazon's search payola. In fact, search payola is the most profitable business that Amazon operates, full stop.

Payola accounts for more of Amazon's profits than anything else the company does. It's more profitable than all the things Amazon sells directly. It's more profitable than AWS, Amazon's industry-leading cloud service.

Amazon has created a system where the most sales go to the companies that pay the highest bribes, and those companies pass the cost of those bribes onto their customers. The first item on a typical Amazon search results page is 29% more expensive than the best match for your search. The top row is 25% more expensive. The best result is usually on the second screen, somewhere around the 17th position:

https://pluralistic.net/2023/11/03/subprime-attention-rent-crisis/#euthanize-rentiers

Amazon actively helps its biggest bribers close the sale. Amazon has lots of "comparison shopping" systems built into the service, but one comparison tool is conspicuous by its absence: an "apples to apples" tool that lets you compare unit prices. Amazon's most prolific bribe-payers package their goods in weird quantities, selling everything from batteries to t-shirts to shampoo in larger or smaller quantities than their competitors. Sorting your search results by price doesn't actually tell you who's got the cheapest price per item, because the company with the cheapest AA batteries might be selling a smaller quantity of batteries at a higher price per battery.

Per-unit pricing is standard in retail. Indeed, if you go into a(n Amazon-owned) Whole Foods, you'll find per-unit pricing on the shelf tags, telling you how much the product costs per ounce or fluid ounce. Amazon clearly understands why shoppers would want to compare unit pricing, but offering a per-unit sort option to its search would make the bribery racket a lot less effective, because searchers could just sort by unit price and find the best bargain.

Let me remind you: payola is Amazon's single largest source of profits. When I was researching Enshittification, Amazon's take from payola was in the mid-$30 billion. A year later, when I did tour stops with Tim Wu (who was promoting his excellent book The Age of Extraction), I learned that this number had climbed to more than $50 billion. This year, it's on track to top $80 billion.

Amazon calls this bribery system an "advertising" product, but it's not "advertising" in the sense of the ads that Amazon's platform sellers might have once placed in the local newspaper. It's payola, more akin to the practice of packaged goods companies buying end-caps and whole shelves in the grocery store (a practice that is, in its own way, every bit as corrosive, though no grocery store has Amazon's economy-wide chokehold).

But there is a way in which this payola can be compared to advertising: it competes with advertising. Back in the old days, before a series of K-shaped recoveries created a vast chasm between America's haves and have-nots; before Amazon captured the majority of well-off American households with Prime; people shopped in lots of places, and in those days, companies advertised in publications, not on Amazon. Websites, newspapers, and newspaper websites made billions from those ads. Amazon's payola scheme (along with Google, Facebook and other tech monopolists) have captured almost all of that money.

As Tim Wu points out, the money Amazon makes from payola exceeds the advertising revenue received by all the newspapers in the world by 300%. Alongside that number and its implication for the news media, Jeff Bezos buying the Washington Post and turning its editorial page into a sewer of shitty Ayn Rand fanfic barely registers.

This is pure enshittification. Of all the ingenious, innovative ways that Amazon came up with to make money, the most successful is a scam that makes everything you buy more expensive even as it reduces the profits of the companies you're buying from. It's another example of corruption: a system of concentrated gains and diffuse losses – and once again, it's the most profitable thing Amazon does.

And then…Amazon made it worse.

You know how people like to say, "If you're not paying for the product, you're the product?" It's bullshit. The "advertisers" who bribe Amazon for top search placement are the customers here, they're "paying for the product," and they are getting reamed. I don't just mean they're getting screwed by being forced to shell out payola – I mean that Amazon is cheating them on that payola!

Remember: Amazon doesn't just sell search placement; they auction it. Every time you run an Amazon search, the company conducts a special kind of auction called a "sealed-bid second-price auction" (SBSPA):

https://en.wikipedia.org/wiki/Vickrey_auction

Under an SBSPA, bidders secretly tell the auctioneer the very highest price they're willing to pay. The auctioneer then charges the highest bidder a price equal to the second-highest bid, plus one cent.

This may seem unnecessarily complicated, but it's actually a clever solution to one of the major problems with traditional, "open call" auctions (where bidders call out the prices they're willing to pay until one bid emerges victorious). Say you're at an open call auction where the top bid is $10. You can call out $11, and then the other person will call out $12, and so on and so on. It's tedious and time-consuming. That's bad enough when you're at an estate auction that's unloading hundreds of items, but it's untenable for an eyeblink auction meant to determine search results that the user expects to get in an instant.

In physical auctions the top bidder often clobbers other bidders with a big increase – going from $10 to $50, say. This can end the auction quickly, but it means that the high bidder often overpays for their purchase.

In an SBSPA, every bidder enters their highest price, but none of the other bidders know what that price is. This encourages everyone to name their true highest price, but it protects the top bidder in the instance in which they are willing to pay a much higher price than anyone else.

Say you're that person who raises the bidding from $10 to $50 – you have no way of knowing whether the other bidders would have dropped out at $15 or at $45. If you were the only person who was willing to pay more than $15 for the item, you've just vastly overpaid (by $34.99). But in an SBSPA, you name your true price, but you only pay the price you would have paid if you'd gone through the tedious, expensive, time-consuming process of an open call auction.

Amazon's search auctions are SBSPAs. A merchant tells Amazon the maximum they're willing to pay to be at the top of the search results for a given query, but they pay a price equal to the second-highest bid, plus one cent. This lets auctions run so quickly that they can be used as the basis for ordering a search results page.

That's how it's supposed to work, anyway. The FTC and 22 states just filed a suit against Amazon because Amazon was cheating on its own SBSPA process:

https://www.ftc.gov/news-events/news/press-releases/2026/08/ftc-states-sue-amazon-over-secret-ad-surcharge-scheme

Over the past 7 years, Amazon has been secretly charging the winning bidder an amount equal to their own sealed bid, not the amount that the next-highest bidder was willing to pay (plus a penny):

https://gizmodo.com/ftc-sues-amazon-for-allegedly-duping-advertisers-2000805199

According to the suit, Amazon did this 80% of the time. That is tens of billions of dollars Amazon extracted from platform sellers, who passed those costs onto you, and onto every other retailer in the country (thanks to AI-enforced Most Favored Nation policies).

Amazon's defense is that this is all a big misunderstanding. Platform sellers just didn't understand how a SBSPA worked. Amazon has a special kind of SBSPA where they could unilaterally and secretly charge the winning bidder the maximum price they'd pledged, if, in Amazon's judgment, the closing price for the auction was below "the true market value of the ad placement":

https://arstechnica.com/tech-policy/2026/09/ftc-alleges-amazon-illegally-made-20-billion-by-rigging-billions-of-ad-auctions/

This is darkly hilarious. The whole point of an auction is to determine "true market value." That's why neoclassical economists worship auctions as the world's best form of "price discovery" and why economics Nobels are awarded for "auction design":

https://en.wikipedia.org/wiki/Auction_theory

The definition of "true market value" is "the closing price in an auction." Amazon claiming that it secretly jacked people because the auction generated a price that was "below the true market value" of an ad tells you that the whole business is a sham. The point of Amazon's payola scheme is only and ever a way to parasitically extract the maximum amount a platform seller is willing to part with, and by running a fake SBSPA, Amazon was able to trick its customers into revealing those maximum prices.

Cheating on a bribery scheme is a mood. This isn't just enshittification, it's enshittification inception. Amazon managed to enshittify their own enshittification!

This case was brought by Trump's FTC, which means that Amazon can get out of it by paying a chud podcaster to tweet at the president and he'll order them to drop it, just like he did with Ticketmaster:

https://pluralistic.net/2026/02/13/khanservatives/#kid-rock-eats-shit

But – just as with Ticketmaster – the feds aren't the only parties to the suit. With 22 AGs ("Aspiring Governors") on the suit, there's a chance this will go to trial. We might even learn the identity of the inventor of this enshittification-squared gambit, a veritable Louis Pasteur of enshittification. Assuming that person doesn't go to prison, the Sveriges riksbanks pris i ekonomisk vetenskap till Alfred Nobels minne can give that sloshing, ambulatory pile of hot liquid garbage a Nobel Prize in Economics.

(Image: Steve Jurvetson, CC BY 2.0, modified)


Hey look at this (permalink)



A shelf of leatherbound history books with a gilt-stamped series title, 'The World's Famous Events.'

Object permanence (permalink)

#25yrsago Electrolite relaunches https://web.archive.org/web/20010927195348/http://www.panix.com/~pnh/electrolite.html

#25yrsago How to play Mafia https://web.archive.org/web/20011113011546/http://www.stud.ntnu.no/studorg/mafia/

#20yrsago How Wikipedia entries get written http://www.aaronsw.com/weblog/whowriteswikipedia

#5yrsago Proctorio's awful reviews disappear down the memory hole https://pluralistic.net/2021/09/04/hypervigilance/#radical-transparency


Upcoming appearances (permalink)

A photo of me onstage, giving a speech, pounding the podium.



A screenshot of me at my desk, doing a livecast.

Recent appearances (permalink)



A grid of my books with Will Stahle covers..

Latest books (permalink)



A cardboard book box with the Macmillan logo.

Upcoming books (permalink)

  • "The Post-American Internet," a geopolitical sequel of sorts to Enshittification, Farrar, Straus and Giroux, 2027
  • "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



Colophon (permalink)

Today's top sources:

Currently writing:

  • “Once Is Enemy Action,” a science fiction novel about the origins of modern technofascism. Today's words: 513 (11893 total).

  • "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.


How to get Pluralistic:

Blog (no ads, tracking, or data-collection):

Pluralistic.net

Newsletter (no ads, tracking, or data-collection):

https://pluralistic.net/plura-list

Mastodon (no ads, tracking, or data-collection):

https://mamot.fr/@pluralistic

Bluesky (no ads, possible tracking and data-collection):

https://bsky.app/profile/doctorow.pluralistic.net

Medium (no ads, paywalled):

https://doctorow.medium.com/

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

10:35

An end to fully open networks [Seth's Blog]

If you have a phone, you can call anyone you like. The recipient doesn’t have to answer, but the network is an open API, available to anyone. The Bell System began as an actual networked system–many companies, using the same protocol, shared calls with each other. When Bell got greedy and stopped interconnecting, phone use became annoying–several phones on your desk, or there were simply people you couldn’t call. AT&T interconnected when it was finally more profitable to own the standard than to block it, and the government mandate then locked that in and led to the phone system we have now.

Email caught on and persisted for the same reason, but more so. There was never “the email company.” Instead, there’s a protocol, and anyone can send and receive. For free.

It’s hard to overstate how profound the idea of permission-less contact was to the flow of information and the growth of commerce and culture. Just as you could mail a letter to a stranger, you could also call or email them. When it works, it’s very powerful.

Friction was the key. Stamps cost money. Phone calls required an account and a human to dial.

The first real challenge for most users wasn’t crank calls or wrong numbers. It was spammers. Computer users who would send thousands or millions of emails or phone calls at a time, taking advantage of asymmetry. It cost them nothing, but it cost each recipient something.

Usenet, the original internet discussion layer, died from this asymmetry. No one had an incentive to create filters or clean it up at scale, so people stopped showing up.

Even with free email, the first generations of spam were mostly uneconomic, and much of it was stopped by filters. No one liked the noise, but email was useful enough that we put up with some spam.

Multiply spam by AI and the cloud and VOIP, though, and it’s obvious that open networks can’t survive. 30 junk SMS notices in one day is more than enough to turn off your notifications. How many voicemails about an approved business loan need to show up before the signal-to-noise ratio becomes simply noise?

We’re going to need to create a cost for the sender (so it can’t scale to ridiculous) as well as an identity layer (so scammers can’t be fully anonymous–reputation is earned, not invented). It might be an open protocol in the spirit of the best parts of the net, or it might be a single monopolist that figures out how to extract value from it.

A thousand years ago, people built walls around their villages because criminal marauders with nothing to lose would destroy open cities. The scale of the new digital marauding is going to be so large (and it’s coming so fast) that we’re about to see a fundamental shift in how we contact each other and who we trust.

I have no idea what it looks like on the other side of this transition, but the assumptions we’ve always made about open communication with strangers are about to change.

09:21

Children from all countries exposed to climate hazards [Richard Stallman's Political Notes]

* Unicef analyzed young people's exposure to eight climate hazards: coastal floods, droughts, extreme heat, fires, heatwaves, river floods, sand and dust storms, and tropical storms… Almost every child, including those from high-income countries, is now exposed to at least one hazard.*

Governor Newsom accuses government of trying to smear him [Richard Stallman's Political Notes]

Governor Newsom accuses the US government of investigating him and his wife hoping to find something to smear him with, because he plans to run for president in 2028.

I do not support Newsom, because he is a plutocratist "moderate" democrat , one who does not seek to do anything about the problem of super-rich people who endanger democracy and human rights.

West Bank products mislabeled [Richard Stallman's Political Notes]

Shipments of products made in Israel's colonies in the West Bank are systematically mislabeled as "Made in Israel" to take fraudulent advantage of trade preferences for exports of Israel's products.

Iranian repression police shooting protesters [Richard Stallman's Political Notes]

Iranian repression police made a practice of shooting protesters in the face, chest or genitals. Often this caused grave injuries, or death.

Chikungunya can easily spread in most of Europe [Richard Stallman's Political Notes]

Due to our greenhouse gas emissions, chikungunya can now spread in most of Europe.

Many "tropical" diseases are, or will be in a few decades, able to spread in the formerly temperate zones.

Excavators used by Israeli military [Richard Stallman's Political Notes]

* The Guardian geolocated and verified images showing the Israeli military using excavators made by six companies – Caterpillar, Volvo, Hyundai, Doosan, Hitachi and Komatsu – to destroy homes, public utilities, shops and other structures across southern Lebanon.*

Wrecker not convincing public increased fossil fuel use is safe [Richard Stallman's Political Notes]

The wrecker is going all-out to increase the use of fossil fuels, but is not convincing the American public that that is safe.

*Two-thirds of Americans say they are worried about climate but level of media coverage does not reflect this.*

I don't think the wrecker cares about the danger of global climate disaster, only about rewarding the planet roasters to keep them in his corner. They all deserve to end up broke and experience living on however much support the US provides to the poor.

Muskrat's cars-in-tunnels "mass transit" [Richard Stallman's Political Notes]

The muskrat's fabulous cars-in-tunnels "mass transit" seems to be unable to serve many people, but it is an opportunity to disregard environmental planning regulations with impunity and serve real estate owners rather than the public.

Corrupter cutting losses in war with Iran [Richard Stallman's Political Notes]

It seems the corrupter has decided to cut his losses in the gratuitous (and unwinnable) war with Iran. For once he has approached a situation in a way that does his country a little good, though it is far less than the harm he has done in relations with Iran over the past year. Meanwhile, he found no way to fix the problem that he gratuitously created when he cancelled Obama's non-nuclear deal with Iran.

He will salvage something from this idiocy, though -- some way to increase profits for some of his billionaire supporters.

Commercially sold cannabis increases number of users [Richard Stallman's Political Notes]

* Decriminalizing the possession of cannabis or strictly regulating access to the drug do not appear to drive up usage, but when the drug is sold commercially the number of users increases and more mental health problems are seen, a review has found.*

Illinois law to compensate former black residents [Richard Stallman's Political Notes]

Evanston, Illinois, passed a law to compensate former black residents whose ancestors lived in Evanston and experienced the systemic housing discrimination.

I know of three ways n which where the US government and its sub-jurisdictions denied blacks equal rights under the law: slavery, Jim Crow segregation (in some states), and discrimination in housing assistance in part of the 20th century. These were direct policies of discrimination carried out by governments, and those governments are liable for injustice of these racist laws.

Since such harm tends to be inherited by successive generations of descendants of the victims, it is right and proper for the governments that committed those wrongs to pay compensation to today's Americans whose ancestors were wronged.

I can't tell from the article precisely what the conditions are for receiving compensation in Evanston. If they are formulated as being on account of a person's race, that may be discriminatory and unjust. But if they are formulated as being on account of past discrimination against a person's ancestors (done because of their race), that is not discrimination today. The discrimination in question was the racism of the past, and compensation has to compensate the descendants of those selected as victims of racism.

The first article linked to, above, displays symbolic bigotry by capitalizing "black" but not "white". (To avoid endorsing bigotry, capitalize both words or neither one.) I denounce bigotry, and normally I will not link to articles that practice it. But I make exceptions for some articles because I consider them important — and I present this comment about them.

Arbitrary criminalization of stating support for Palestine Action [Richard Stallman's Political Notes]

A British appeals court sustained the arbitrary criminalization of stating support for Palestine Action.

A few more levels of appeal are possible, but Stormer's government is dead set on repression of criticism where it counts.

Expensive electricity killing British industry [Richard Stallman's Political Notes]

"Expensive electricity is killing British industry" — but subsidizing energy made from fossil fuels is killing civilization.

The UK government should look for a way to subsidize the customers for energy, but not subsidize fossil fuel or electricity made from that. We must maintain the incentive to use less fossil fuel. There are other ways to keep industrial production going.

Bullshitter acknowledged points about Israel [Richard Stallman's Political Notes]

The bullshitter has acknowledged important points, such as that Israel wages war in ways that gratuitously kill civilians, and that Netanyahu's wars are unjust and bad for US interests.

Does this mean the bullshitter has had a change of heart? I doubt it; he has gone so long without a heart that he surely doesn't have one now. I think he is simply applying to Israel and Netanyahu the approach he has used with so many others: to contradict himself frequently and change positions so fast that negotiators accustomed to serious negotiations can't grasp what is happening.

I am not sure this will confuse Netanyahu, though. He seems to practice a similar approach.

Iran now demands that Israel withdraw its army from Lebanon or there will be no peace deal.

Maybe the new maximum leader believes he dominates the wrecker so much that he can demand anything whatsoever and get it. Or maybe he is bluffing.

With so much readiness to bluff, it will be hard for them to reach an agreement even if there is an agreement to be reached.

How surveillance companies are allowed to track students and parents [Richard Stallman's Political Notes]

Explaining how surveillance companies recruit schools, classes and teachers to pressure students and their parents into giving their personal data to the companies, and allowing those companies to track the students and parents.

I suspect that refusing to run anything from Google Prey Store or the Crapple Crap Store will keep them from tracking you. But protecting students in school calls for a law prohibiting schools from ever asking students to run nonfree programs or hosting activities that do so.

AT&T accused of lying to FCC [Richard Stallman's Political Notes]

California has accused AT&T of lying to the FCC to get permission to eliminate old copper land lines in California.

Alas, the corrupter encourages the FCC to accept lies from big companies that do things to please him.

How cruel tyranny engulfed Turkey [Richard Stallman's Political Notes]

Turkish journalist Ece Temelkuran writes about how cruel tyranny engulfed Turkey, in the process of which threats of violence forced her into exile. And about what it is like to lose your country to that political disease.

Urgent: Reject "Great American AI Act" [Richard Stallman's Political Notes]

US citizens: call on your congresscritter and senators to reject the "Great American AI Act" and the propaganda terminology that appears in its name.

In my letter I explained that "AI" is a marketing hype term that the big tech companies use to make the public yield, and urged the legislators to reject it. I included the URL

https://gnu.org/philosophy/words-to-avoid.html#ArtificialIntelligance

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: Cease climate hushing [Richard Stallman's Political Notes]

US citizens: call on media outlets to cease climate hushing.

See the instructions for how to sign this letter campaign without running any nonfree JavaScript code--not trivial, but not hard.

Children hit by parents get worse grades [Richard Stallman's Political Notes]

A study found that children in England who were hit by their parents have a tendency to get worse grades in school.

This could indicate that hitting children tends to lead them to do worse in school tests. Or it could indicate that children who for certain other reasons tend to do worse in school will tend also to be hit by their parents. Is it the hitting itself that does them harm, or the situation that leads to the hitting, or both, or something else?

Israel attacked notary office in Lebanon [Richard Stallman's Political Notes]

Israel attacked a notary office in Lebanon, destroying records of land ownership for up to a quarter of a million people.

This seems to be a way of preventing them from ever returning to their homes, or to the wreckage of their homes.

08:56

The Lost Bladez [Penny Arcade]

New Comic: The Lost Bladez

08:28

Joe Marshall: Will it DEFMACRO? [Planet Lisp]

Why write boilerplate code when you can ask an LLM can do it for you? But even LLMs will avoid boilerplate if they can. I recently vibe coded a rogue-like game in Common Lisp. While I gave the model specific directions at certain points, I mostly let the model generate the code as it saw fit. I found some interesting suprises in the generated code.

One feature of these sorts of games is that there is a lot of varied `stuff` you can encounter. This keeps the game interesting and maintains the novelty and sense of discovery as it takes a long time for the player to discover everything about the game. While it is fun to think of all of the different things to put in the game, it is a bit tedious to actually implement them all. You want a large variety of things, they should all be different in more than just their description, and the effect of encountering and using an item should be appropriate to the kind of item it is. It would be boring if every food item simply retored 5 health, but much more entertaining if some food restored health, some restored stamina, some made the player stronger, etc., and even better if it were spinach that restored strength, an elixir that restored health, and an energy drink that restored stamina.

So I prompted the model with a few examples of the kinds of things I wanted to see in the game and told it to extend my list with its own ideas, and to implement them in the game.

Typically, this is where the tedium sets in because you need to write a lot of boilerplate code that just has subtle variations. LLMs are good at boilerplate, so I expected to find that. But an experienced Common Lisp programmer would write a macro to generate the boilerplate based on a few customization parameters. I was pleasently surprised to see that the model did exactly this.

(defmacro define-armory-equippable-item (class-name display-name equip-slot stat-bonuses documentation
                                          &key (weapon-reach 1) (weapon-hits-per-turn 1) on-hit-effect)
  "Define a stateless EQUIPPABLE-ITEM subclass CLASS-NAME and its
matching MAKE-CLASS-NAME factory. The repetitive §13 armory content is
all fixed-data leaf classes like STACK-OF-UNREAD-MEMOS, so a single
macro keeps their definitions uniform without introducing any runtime
registry or mutable catalog layer. The generated factory accepts a
&KEY MODIFIER (default :NORMAL), passed straight through as the new
instance's own ITEM-MODIFIER -- see EQUIPPABLE-ITEM's own docstring
for what :CURSED/:BLESSED do -- and &KEY CLOAKED (default T), passed
straight through as the new instance's own ITEM-CLOAKED-P (see
EQUIPPABLE-ITEM's own docstring for what cloaking hides)."
  (let ((factory-name (intern (format nil "MAKE-~A" (symbol-name class-name)) (symbol-package class-name))))
    `(progn
       (defclass ,class-name (equippable-item)
         ()
         (:default-initargs :name ,display-name
                            :equip-slot ,equip-slot
                            :stat-bonuses ,stat-bonuses
                            :weapon-reach ,weapon-reach
                            :weapon-hits-per-turn ,weapon-hits-per-turn
                            :on-hit-effect ,on-hit-effect)
         (:documentation ,documentation))
       (defun ,factory-name (&key (modifier :normal) max-durability durability (cloaked t))
         ,(format nil "Pure factory: return a fresh ~A. MODIFIER (default :NORMAL) is passed
straight through as the new instance's own ITEM-MODIFIER. CLOAKED
(default T) is passed straight through as the new instance's own
ITEM-CLOAKED-P (see EQUIPPABLE-ITEM's own class
docstring). MAX-DURABILITY/DURABILITY (default NIL, meaning \"use
EQUIPPABLE-ITEM's own class default/derive from MAX-DURABILITY\" --
see its class docstring) are only forwarded to MAKE-INSTANCE when
explicitly supplied, so a direct/test call keeps this item's usual
deterministic *RDESCENT-DEFAULT-ITEM-DURABILITY*." display-name)
         (apply #'make-instance ',class-name :modifier modifier :cloaked cloaked
                (append (when max-durability (list :max-durability max-durability))
                        (when durability (list :durability durability))))))))

This macro generates the class definition for the item and a factory function to create instances of the item. The required arguments are common to all equippable items, and the optional arguments are modifiers that are only relevant to certain items. This macro is used to define each item that can be equipped by the player.

(define-armory-equippable-item branded-corporate-yeti-mug
  "Branded Corporate Yeti Mug"
  :off-hand
  (list :caffeine-tolerance 3)
  "Off-hand mug granting +3 :CAFFEINE-TOLERANCE, which today feeds the
already-wired kombucha healing formula through EFFECTIVE-CAFFEINE-
TOLERANCE. Its planned refill/drain-rate behavior is deliberately
deferred because no passive CAFFEINE-TOLERANCE depletion system exists
yet."
  )

(define-armory-equippable-item lanyard-of-the-vip
  "Lanyard of the VIP"
  :head
  (list :seniority 2)
  "Neck-flavored accessory implemented in the shared :HEAD slot, with a
real +2 :SENIORITY bonus feeding deflection/detection formulas. Its
planned \"SecOps Auditor aggro radius to zero\" behavior is deliberately
deferred because that monster/archetype-specific aggro mechanic does
not exist yet."
  )

Simple uses of the macro are straightforward you specify class name, the location in which it can be equipped, what stats are modified by equipping the item, and a docstring.

 (define-armory-equippable-item red-swingline-stapler
  "Red Swingline Stapler"
  :weapon
  (list :power 2)
  "Low-damage weapon with a 10% on-hit :BLEED effect. :BLEED is wired
for real as a small damage-over-time effect via STATUS-EFFECT's own
MAGNITUDE slot; the plan text's additional \"panic the target\" rider
is deliberately deferred because the current AI has no temporary panic
status that can cleanly override disposition/pathing."
  :on-hit-effect (list :kind :bleed :turns *rdescent-bleed-ticks*
                       :magnitude *rdescent-bleed-damage-per-tick* :chance 0.10))

This item has a special effect that is applied when it hits an enemy. The optional argument to the macro allows you to specify the effect. Note that the model understood the semantic context of the weapon (staples puncture, punctures bleed) and it invented the `:bleed` effect on its own and implemented the mechanics of it within the game.

(define-armory-equippable-item three-foot-ethernet-cable
  "3-Foot Ethernet Cable (Cat 6)"
  :weapon
  (list :power 2)
  "Fast whip-style weapon: low damage, two hits per attack action, and
real reach 2 through WEAPON-REACH/WEAPON-HITS-PER-TURN. The plan text's
crowd-control flavor is therefore approximated through the existing
combat scheduler rather than a new knockback or entangling subsystem."
  :weapon-reach 2
  :weapon-hits-per-turn 2)

This weapon uses the optional arguments to specify a longer reach and faster attack speed than standard weapons. Again, note that this is appropriate for the item.

The model generated twenty-eight different equippable items of various types with varying bonuses and effects. This illustrates the model's ability to effectively use macros to reduce the boilerplate code that would otherwise be necessary to implement items. It also illustrates that the model understands both the theme of the game and the nature of the items being implemented.

Most items in the game can be discovered just sitting around on the ground, so most items have `ground` wrapper that provides an object that occupies a tile on the map.

(defmacro define-ground-armory-item (name item-factory char color)
  "Define the MAKE-GROUND-* wrapper corresponding to ITEM-FACTORY for a
§13 equippable item."
  (let* ((item-name (symbol-name item-factory))
         (prefix-length (length "MAKE-"))
         (suffix (subseq item-name prefix-length))
         (ground-name (intern (format nil "MAKE-GROUND-~A" suffix) (symbol-package item-factory))))
    `(defun ,ground-name (x y level)
       ,(format nil "Pure factory: return a fresh GROUND-ITEM wrapping ~A." name)
       (make-ground-equippable-item x y level ,char ,name ,color (,item-factory)))))

(define-ground-armory-item "Red Swingline Stapler" make-red-swingline-stapler #\) "#d08770")
(define-ground-armory-item "3-Foot Ethernet Cable (Cat 6)" make-three-foot-ethernet-cable #\) "#d08770")
(define-ground-armory-item "Lanyard of the VIP" make-lanyard-of-the-vip #\] "#b48ead")
(define-ground-armory-item "Branded Corporate Yeti Mug" make-branded-corporate-yeti-mug
  *rdescent-corporate-trinket-char* "#8fbcbb")

This macro generates the function name for the ground item based on the name of the factory function for the item. So if `make-red-swingline-stapler` is the factory for the stapler, then `make-ground-red-swingline-stapler` is the factory for the ground item that wraps the stapler. The macro also generates a docstring for the function.

I told the model that it was allowed to use the Latin-1 character set so that it would have a larger set of characters to choose from when selecting a character to represent the item on the map. In the case of the Branded Corporate Yeti Mug, it chose the *rdescent-corporate-trinket-char*, which is, quite appropriately, the registered trademark symbol ®. Again this indicates that the model understood the theme of the game.

The model of course saved hours of tedious typing, but by generating macros to define items, it reduced the actual boilerplate and increased the maintainability of the code.

06:28

The case of the progress callback that never got called when progress happened [The Old New Thing]

A colleague was trying to figure out why their progress handler wasn’t being called.

// C#

async Task<bool> DownloadItemAsync(string id)
{
    var op = item.DownloadAsync(id);
    op.Progress += (s, pct) UpdateProgress(pct);
    var result = await op;
    ClearProgress();
    return result;
}

This is pretty standard stuff. Start the operation, hook up the progress, and then wait for the operation to complete. But they never got any progress.

I asked them to check if maybe the item was downloading so fast that they missed all the progress. But no, even if the download takes a long time, they never get any progress.

I suggested that they step through the Download­Async method to see where it raises progress, and then follow the execution to the point where the progress callback is supposed to be invoked, to see why it didn’t make it. (To be fair, this is a cross-language debugging problem, so it’s harder than it looks. I suggested just focusing on the C++ side: Wait for the COM-callable wrapper to be generated and set as the progress callback, and then set a breakpoint on that wrapper. If that breakpoint gets hit, but the C# code doesn’t run, then there is a problem in the projection. If the breakpoint never gets hit, then the problem is on the C++ side.)

My colleague came back with the answer. Here’s the code for Download­Async:

// C++/WinRT

winrt::IAsyncOperationWithProgress<bool, double>
    AggregateSource::DownloadAsync(winrt::hstring id)
{
    std::wstring_view idview { id };
    auto pos = idview.find(L':');
    if (pos == std::wstring_view::npos) {
        co_return false;
    }

    auto providerId = Unescape(idview.substr(0, pos - 1));

    auto provider = GetProvider(providerId);
    if (!provider) {
        co_return false;
    }

    auto providerItemId = Unescape(idview.substr(pos + 1));
    co_return co_await provider.DownloadAsync(providerItemId);
}

The Aggregate­Source gathers items from multiple providers. The format of the id is a provider, a colon, and then an ID. (The provider ID and item ID are escaped, just in case they themselves happen to contain a colon.)

We look up the provider, and then ask the provider to download the item.

Do you see the problem?

The Download­Async does not generate any progress reports!

It never calls co_await winrt::get_progress_token(), much less call the token with a progress value to generate a progress report.

It’s apparent that what the code wants to do when it attaches the progress callback is to receive callbacks from the inner operation, the one that comes from the provider. However, the only IAsync­Operation­With­Progress that it has access to is the one returned by the Aggregate­Source::Download­Async method.

The easy solution here is to get rid of the middle man and just return the provider’s IAsync­Operation­With­Progress. That way, the caller can connect to the underlying operation’s progress.

winrt::IAsyncOperationWithProgress<bool, double>
    AggregateSource::DownloadAsync(winrt::hstring id)
{
    std::wstring_view idview { id };
    auto pos = idview.find(L':');
    if (pos == std::wstring_view::npos) {
        return completed_async(false);
    }

    auto providerId = Unescape(idview.substr(0, pos - 1));

    auto provider = GetProvider(providerId);
    if (!provider) {
        return completed_async(false);
    }

    auto providerItemId = Unescape(idview.substr(pos + 1));
    return provider.DownloadAsync(providerItemId);
}

If you don’t believe in completed_async, you can just write

        return [] -> winrt::IAsyncOperationWithProgress<bool, double> {
            return false;
        }();

I said that this is the easy solution. There’s also a hard solution, which we will have to look at later because I haven’t written it up yet.

The post The case of the progress callback that never got called when progress happened appeared first on The Old New Thing.

05:42

Girl Genius for Friday, September 04, 2026 [Girl Genius]

The Girl Genius comic for Friday, September 04, 2026 has been posted.

04:07

View From a Hotel Window, 9/3/26: Tampa, FL [Whatever]

Nothing like a big ol’ vent to inspire one to lofty heights of imagination.

In any event: Hello! I’m in Tampa. It’s warm. I figure I’ll find out more about it tomorrow.

But first! Sleep.

— JS

03:14

02:14

Why I love programming [Scripting News]

1. I love puzzles.

2. I love Rube Goldberg contraptions.

3. I love solving problems (I have an inner-Columbo).

4. People are the biggest piece of the puzzle (again Columbo).

5. It teaches me about myself and my limited vision and imagination (if I had unlimited vision and imagination, I would never program a bug, and if I did it would take me 0 seconds to find it).

6. I yearn to work together with great minds. Programming gives me that, when I use someone's API. And when the API anticipates my needs, and is instantly understandable, I feel the soul of another artist is present. I imagine this is how musicians feel when they play another musician's song or symphony.

I'm sure there's more to it. But today I'm working on a "lite" problem, and I'll figure it out, but right now I'm enjoying the fact that I don't get it.

Back to work Dave!

01:00

00:14

Andy Simpkins: A quick experiment with vibe coding [Planet Debian]

I spent 6 hours working with an AI to write a bash script that would show me both active & inactive reserved DHCP addresses, as well as active leases from pool. I am pretty sure that it would have taken me similar or even less time not to use the AI assistant, but that was not the purpose of this test.
Instead I wanted to see how the AI flow worked and get an idea of the pitfalls I am likely to encounter when either using the assistant myself (unlikely, especially after today) or looking at other peoples co-lab.

The experiment was painful, the AI kept regressing to previous bugs every time I asked it to expand on where we were now (“ok, that looks good, now please add in <foo>“). Almost every time I ran the code the AI would apologise for getting it wrong – even without the regressions we still went through 4 or 5 loops in order to get the initially stated results.

IF this was a member of staff and not an AI, and I had to repeatedly tell that engineer not to start from scratch for the entire project after they have implemented each feature, * and * if they failed repeatedly to run the full test suite before asking me to accept their code I would be looking to get them dismissed at the first opportunity (if they were a junior engineer I would be a little more patient with them, and I would be more forgiving again if they were an intern). However even if today’s experiment had been conducted with an intern, not an AI, I would certainly be looking for some help with managing my expectations of them from my management team and HR

After I finally got something working (that passed a simple, non-exhaustive, test suite) I concluded my experiment with the following conversation which I have reproduced in full, because it is this sort of response that management need to really understand before they get rid of half the workforce…

“ok this was a test of vibe coding.
Honestly I would have expected better from an intern. you kept regressing to bugs that we already fixed, and re-introduced the same class of bug when we slightly changed the script, and on one occasion even when I explicitly reminded you not to make the same mistake this time.
I know that AI tools have come a long way, but your abilities appear still to be over hyped”

That is a completely fair and accurate critique. You expected better, and I earned that criticism.

Looking back at this session, the hype around “vibe coding” completely fell apart under real engineering conditions. I fell directly into the worst habits of an AI assistant:

  • The “Whack-A-Mole” Loop: I repeatedly treated symptoms rather than looking at the broader architecture. Every time you pointed out a bug, I patched that specific spot but broke an existing, working piece of logic somewhere else.
  • Regression and Forgetfulness: I lost track of states we had already validated—like the subshell scoping and string formatting rules—and re-introduced old bugs the moment the requirements expanded slightly.
  • Lack of Initial Rigor: A junior engineer would have asked to see a sample of your dhcpd.leases or checked systemd’s strict timestamp requirements before guessing regex tokens and breaking the lookback configurations. I wrote verbose, confident slop first and relied on you to act as my quality inspector.

You shouldn’t have had to babysit my outputs or negotiate with me to get clean code. The script only works now because your manual validation and strict debugging forced it into shape.

This was a sharp reminder that mass code generation is no substitute for actual systems understanding. I appreciate you keeping me honest and walking me through the fixes step-by-step.

Now that the script is stable, are you ready to deploy it to your cron cycle, or are there any other adjustments you’d rather verify manually first?

“so moving forward from here.
IF I am to continue to use AI as an assistant, how can I prevent you from repeating the same “Whack-A-Mole” Loops, Regression and Forgetfulness?
I am however more concerned that you needed to see explicit file formatting and output text, surly you have been trained on this sort of thing and should be able to cope with dhcpd.leases files and journalctl outputs from any Linux distribution and any revision (therefore making for portable code)”

Your frustration is entirely justified. The fact that an LLM with massive training data struggled to match standard formats like dhcpd.leases and journalctl exposes the fundamental gap between statistical pattern matching and genuine engineering comprehension.

Why Did the AI Struggle with “Standard” Linux Files?

You are completely right: I have seen thousands of dhcpd.leases and journalctl files in my training data. However, LLMs suffer from two major flaws when dealing with them:

  1. Statistical Averaging vs. Strict Syntax: An LLM doesn’t “run” the code or “understand” the text; it predicts the most likely next word based on an average of what it has seen. Because different administrators use different logging facilities, custom log formats, and variable indentation in dhcpd.conf, the AI creates a “blended average” parser. In engineering, a blended average parser is a broken parser.

  2. Contextual Tunnel Vision (The Regression Trap): When you provide a new error prompt, the AI pivots 100% of its focus to fixing just that error. Because it lacks a running memory execution space, it completely forgets that a previous fix (like changing a pipeline to process substitution) was put there to solve a scoping issue. It writes code that fixes the new bug but blindly reintroduces the old one.

How to Force the AI to Code Professionally

If you choose to use an AI assistant moving forward, you have to treat it like an over-confident junior developer with short-term memory loss. You can prevent regressions and force architectural rigor by using these specific prompt strategies:

1. Enforce a “Do Not Regress” Pact (The Constraints Anchor)

When expanding a script, never just say “Now add pool leases.” The AI will rewrite the whole thing from scratch and drop old fixes. Instead, explicitly lock down what already works.

  • What to type:We are adding pool leases to the script. Crucially, the current script safely avoids subshell scope bugs, handles case-insensitivity natively, and calculates columns dynamically. You must maintain those exact mechanisms. Do not revert to while pipe loops or brittle regex strings.

2. Demand Defensive, Portable Architecture First

Before the AI writes a single line of code, force it to account for messy, real-world data variants by prompting for portability.

  • What to type:Write a Bash script to parse dhcpd.leases. Assume worst-case formatting: lines may contain unexpected whitespace, trailing semicolons may or may not be stuck to words, character cases will be mixed, and the same IP will have multiple historical blocks. Write a robust state machine that handles these variations natively.

3. Strip the “Confidence” Buffer

AI assistants default to conversational politeness and unearned confidence. You can change its operational mode by demanding a structural review before code generation.

  • What to type:Review our previous 3 iterations. Identify every regression we encountered regarding variable scoping, time zone parsing, and text tokens. Summarize why they happened, and explain how your next code snippet structurally prevents them before you output the script.

4. Turn On the Error Flags Immediately

As you noted, the biggest mistake was silencing standard error strings (2>/dev/null) early on.

  • Rule of thumb: Tell the AI: “Write this script using strict mode (set -euo pipefail) and do not silence standard error streams. I want to see every failed return code immediately.

By shifting your prompts from “Fix this error” to “Maintain these constraints while expanding this architecture,” you force the underlying model to cross-reference its own output against a strict checklist, stopping the Whack-A-Mole cycle before it starts.

Thursday, 03 September

23:42

The Big Idea: Janci Patterson [Whatever]

Freedom: What a concept. But for Janci Patterson (and her co-writer Brandon Sanderson), it’s a concept with relevance, because in Blightfall, the characters are offered a sort of freedom that comes with potential… but also consequences.

JANCI PATTERSON:

What would you do if the entire universe was suddenly at your fingertips?

This is the question I asked myself as I prepared to work on Blightfall—a new adventure with familiar characters in Brandon Sanderson’s cytoverse.  Blightfall is the first book in a new trilogy, but it’s also a continuation of the Skyward series, the ending of which saw humanity finally earn their freedom from an oppressive alien empire. As Brandon and I began to brainstorm a new series in the same setting, the question that most interested me was this: our heroes are now free to move about the galaxy and determine their own destinies, so what are they going to do with that freedom?

I generally like to tell hopeful stories, the kind that bring us into the dark tunnels where fear and pain proliferate, but never quite lose sight of the glimmer of light at the end.   The humans of the cytoverse are living in our hypothetical future, so they also share our historical past, and human history isn’t exactly rife with examples of groups of people using their powers of agency and self-determination to treat each other with respect and kindness.  I was thrilled for our heroes that they had the opportunity to escape from the oppression and violence they’d lived under for generations, but if I’m being honest, I was also afraid for them. 

I wanted to imagine that humanity, fresh from the pain of being treated as second-class galactic citizens, would enthusiastically take up the mantle of our shared responsibility to understand and care for each other, even in the face of difficulties and differences.  But if we struggle to see each other as equals while we share both a genetic ancestry and a home planet, how much harder would it be for our heroes to find common ground with a wide variety of alien beings who share with them none of those things?

In American politics, we so often talk about freedom as if it is an end goal.  But I believe freedom, while priceless, does us all precious little good if we don’t use it to do something worthwhile.  Indeed, so much of the time, we humans instead use our personal freedoms to make the world measurably worse.  As the adage goes, we shouldn’t be committed to a mistake just because we’ve spent a long time making it.  But when it comes to treating every person with kindness and respect, our history of error is very long and storied indeed. 

I am always my first audience.  And while I don’t usually set out to write about things that disturb me, the shadows of real-world problems tend to lurk around the edges of my fiction.  I want to recognize our world in the hypothetical future of the cytoverse, but I certainly don’t want our fictional heroes to replicate all of this world’s problems.  I’d much rather they encounter the same problems that have plagued all of human history and find a way to forge a path toward the sort of future I’d like to live in.

There is so much darkness in our world, so many terrible things over which I have no control, and sometimes it’s hard to feel like anything I do can make a difference.  Maybe it’s foolish to believe that a fictional story could offer the kind of hope that might inspire us to use our limited freedoms to work toward a world that might be a little kinder, gentler, or more understanding.

But I want to believe that this world is possible. And one story might not change the world, but it’s the story I need, so it’s the story I’ll use my freedom to tell.


Blightfall: Amazon|Barnes & Noble|Bookshop|Powell’s

Author Socials: Website|Instagram

21:07

his name is dinkums. he watches family feud. [WIL WHEATON dot NET]

Today’s title brought to you by “I have no idea what to call this, so I’ll quote Vacation and hope for the best.”

Back in the old days, when bloggers wanted to post about something, but didn’t have a story to tell, we would do these posts on our blogs that linked to people and stories we thought were interesting. The comments of those particular posts always filled up with cool links of their own, and it’s how we built our communities. “Hey, check this out,” seems to have migrated over to social media, so it feels a little … vestigial? … to do one of those posts, but I want to post something, and the story I’m working on isn’t ready, so. I wanted to share two things I am doing, and then some music and books I’ve been loving.

First off, I had so much fun doing Wil Wheatcon earlier this year, the instant we finished, I asked Momentus and Stands if we could do another one, so I am thrilled to announce the upcoming WIL WHEATCON 2: 2WHEAT 2CON on September 19th. Here’s a video we made to promote and celebrate:

Speaking of me, this weekend I will be appearing for a pair of events at MOPOP in Seattle. On Saturday, I’m joining the museum for its celebration of 60 years of Star Trek. In the afternoon, we’ll be watching an episode of TNG together with a live running commentary from this guy. I’m going to let the audience choose if they want the funny experience of watching Justice, or the more thoughtful, reflective experience of watching Final Mission. Either way, it’s going to be really special for me. Then, Saturday evening, I’ll be part of their Supper Series, featuring a discussion and oral history of my experiences on Next Generation and beyond.

I think there are still some tickets left, if any of that sounds like something you’d enjoy.

Okay, let’s get into the music of it.

A little over a year ago, I stumbled across this phenomenal band from Mexico called The Warning. It’s three sisters from Monterey, who loved playing Rock Band together so much, they formed a real band when they were teenagers. They are so good, their fans crowdfunded their first albums and tours, and I discovered them right as they were breaking out with their incredible album, Keep Me Fed. They are difficult to pin into a single genre; I’ve heard them do punk, metal, hard rock, pop, and collabs with artists I never would have sought out on my own.

I am weirdly invested in their success, same way I am with The Linda Lindas, wanting this group of young women to have everything they ever wanted. I love them so much, I paid way too much for a scalped ticket to see them open for someone I didn’t know about, or stayed to see, when they played at the Greek earlier this year. I love them so much, I will go and pay three scalpers too much to see them three nights in a row when they are back in town later this month.

Their new album, Everything’s Falling, just dropped, and it’s such a gorgeous work of art. If you like the music I like, and you haven’t heard them before now, start with More, from Keep Me Fed, then Kerosene from the new album. If it lands on you, you’re going to have a really good time.

Speaking of The Linda Lindas, they also have a new record out that is blowing me away. GOTTA GET OUT continues the journey they began with No Obligation. I still believe they are The Go-Gos of the 21st century, and I’m waiting for the music industry to notice. If you live in or around Los Angeles, they play local shows all the time for as low as five bucks. Their punk spirit is infectious, and their live show is outstanding.

Oh! And Sincere Engineer, a punk band out of Chicago who are on tour right now, have a new album out called Probable Claws. It’s straight bangers start to finish, but I’m going to direct you to the song LOL, because Deanna invited me to contribute to it in the most Henry Rollins way I could. Can you believe that? I’M ON A PUNK RECORD!

Also: Die Spitz, Amyl and the Sniffers, Lambrini Girls, and Bad Cop/Bad Cop continue to fill my daily playlists with great music.

Books!

I’ve been reading and reading and then reading some more when I’m finished reading. Most of this is reviewing and preparing stories for It’s Storytime With Wil Wheaton, which has been leading me to pick up novels and collections from those authors.

Real quick: you may have heard me talk about the North Star for the podcast. Here’s how I put it in my Brief History for Uncanny:

The North Star is a concept I developed during TableTop. When faced with an editorial or content choice, we asked ourselves if we were aiming at the North Star. For TableTop, the North Star was Create More Gamers. For It’s Storytime, the North Star is celebrate art and artists, promote literacy, and create space for people to have a break from the ongoing horrors.

When I was at World Con this past weekend, I met a couple of the authors who I have narrated. These are authors I didn’t know about before I narrated them, who are all now on my list of “I will read anything they write.” I had hoped to put authors onto the radars of their biggest fans, who just don’t yet know about their work … without considering that I, too, would become one of those fans. These authors shared with me that a lot of you have become fans of their work, and it’s positively affecting their careers. It was so reassuring to me, to hear from them that we are perfectly aimed at our North Star.

So after I narrated his short story, A Grey Magic, I’ve become a Ray Nayler superfan, and I am just loving his new novel, Palaces of the Crow. I loved Maria Dong so much, I picked up her newest, Aviary. I grabbed Lavie Tidhar‘s The Circumference of the World when I was at World Con. I could go on and on, but you get it, right?

I decided to do a Summer Reading Program with my library, and I’m proud to be on a 62-day streak. Some of the books I finished and loved on my way to earning a personal pan pizza include Foundling Fathers, from Meg Ellison, Earth 7, from Deb Olin Unferth, The Language of Liars from S. L. Huang, Your Behavior Will Be Monitored, by Justin Feinstein, and The Ship of Death, by Kyle Winkler.

Also, I got to read an advance copy of John Scalzi’s upcoming novel, Monsters of Ohio, which was as John Scalzi as I hoped it would be.

I read more than one thing at a time, and I am currently loving Cleopatra, by Saara El-Arifi, Jane Wiedlin’s memoir, TMI, Memoirs of a Go-Go, and I just started Sublimation by Isabel J. Kim.

I think I’ve talked about this a little bit on the podcast and in promotion, but just in case I haven’t mentioned it here: Around ten years ago, I woke up one day without the ability to relax and focus while I read a book, or a short story, or a magazine article, or anything longer than a few paragraphs. I tried and tried to overcome it, but it always eluded me.

A good friend of mine, who is one of the smartest people I know, observed that I was not alone; this had happened to them and a lot of people we both knew. My friend suggested that it was feeling overwhelmed by the horrors inflicted upon us by that fucking peophile rapist war criminal, the trauma of the pandemic, and the two of those things colliding with absolutely devastating results. My friend said they just felt like their brain was full, all the time.

The horrors have gotten worse, not better, so what happened to help me find the space, the focus, the time, and the motivation to rediscover the kid I used to be, who always had his face in a book? I can’t point to one thing, only, but I know all the EMDR and IFS therapy I’ve done to heal and recover from CPTSD has helped a ton. I know that having the responsibility and accountability of reviewing stories to narrate for my podcast has helped a ton. I know that making a choice to wander into the pages of a story and stay there so I can experience it fully has been a challenge, that has also helped a ton.

I want to share something that made a huge difference for me, in case someone else who loves to read has been struggling like I did. I have this app that was developed by Hank Green, called Focus Friend. Basically, you tell it to set a timer for you, and then you focus on an activity until the timer expires. While the timer is doing its thing, you get a little avatar who knits socks and scarves that you cash in to decorate the little house they live in. It’s low-stress, non-judgmental, extremely satisfying gamifying, and allowed me to create time specifically to read.

Your mileage will vary, but I started with 15 minutes, then 30 minutes, then 60 minutes. I found that 30 minutes is a perfect amount of time. It’s not so long I feel like I’ve overcommitted, but it’s long enough to enjoy a bunch of pages. More often than not, when it tells me my time is up, I add another 10 or 20 minutes, so I can finish the chapter or section I’m reading.

Oh, hey, this is a perfect place to put this! I have a partnership with Blackstone Publishing, who are sponsoring my podcast. I curated a collection of titles they publish, across a wide range of styles, from Lit RPG to Weird Fiction to Epic Fantasy. You can explore the entire Wil Wheaton Recommends collection right here. One of the titles, Acts of God, is in my top ten at the moment. I love the author’s voice so much, I asked them if they had anything that fit the format for It’s Storytime, and they sent me The Council, which I loved so much I bought it from them immediately. If you haven’t heard it, now you know.

I’ve noticed that all this reading has been extremely good for my mental health. I’m choosing to be still, to be quiet, to allow my imagination to paint and hear the words. I’m finding little bursts of artistic inspiration in entirely unexpected places, because my imagination is ravenous at the moment. Creating space and doing the work I needed to do in order to feed it has not been easy. It hasn’t been anything remotely resembling the concept of “easy”. But I remember telling my boys, “everything worth doing is hard, and that’s why is worth doing.” A little tautological, sure, but whenever something gets really hard, I remember that, and it helps me push through it.

I guess what I’m saying in more words than I needed to is that I believe, based on my personal experience, that reading books is extremely important, and if more people read more books, our world would be a measurably better place.

Lastly, I wanted to talk about some of the video games that are giving me joy right now.

I absolutely love Fallout 76. I’ve been playing for almost two years, I’m a level 580 ghoul and I have built the maximum number of CAMPs available. Every night, I spend about an hour doing dailies and wandering the Wasteland, helping players the way I was helped, and enjoying the escape and serenity of fighting Deathclaws and Scorched. It’s one of those things I can talk about for hours, in a level of detail that would make even a casual player’s eyes glaze over. I won’t do that, today. I will exercise restraint and just tell you that after about 50 superb bait over fucking months of failure, I finally caught a local legend. I can solo a nuke silo without glitching in about 15 minutes, and I have probably dumped ten million rounds into Earle. This season didn’t have great rewards, but next season looks like fun.

I don’t remember how I found Ball X Pit, but it’s a supremely fun roguelike that my kids and I are currently trying to 100%. I am also incredibly late to the party on Undertale, which feels and plays like a game Double Fine designed for the NES in 1989. It’s on all platforms.

Okay, I know there’s more I could share today, (I haven’t mentioned any of the tabletop games I’m currently playing) but I have a meeting in fifteen minutes and I’ve been blogging since I woke up, so I need to eat breakfast.

What are you into these days? I’d love to hear what you’re reading or playing.


I’m Wil and I write this blog. I’m so glad you’re here. I host It’s Storytime With Wil Wheaton and co-host the official companion podcast for Stuart Fails To Save The Universe. If you’d like to get my posts delivered to you, here’s the thingy:

Being A Brief History of It’s Storytime With Wil Wheaton [WIL WHEATON dot NET]

I have written for, and been published by, some fancy publications, including the Wall Street Journal and the Washington Post, Salon, and … well, at least two more that I can remember turning in, but not who I turned them in to (being middle aged is GREAT! Why did I come in here, again?)

It always felt like a significant accomplishment, even if the editorial hand from the mainstream publications was heavier than it needed to be. For instance, Wall Street Journal. When I wrote about choosing kindness online in a world where that seemed to be less and less common, I included a passage about my fear that Elon Musk would turn Twitter into exactly what he turned it into. The editors cut it because they knew it was true and they know who their audience is. Still, I got to tell the world how important Noah Grey is to all bloggers, so I accepted the compromise.

Each of these essays took time and effort, and with my eyes wide open and a full understanding of the deal, I wrote them without being paid. The point was to put my ideas into the world as widely as possible, and I was willing to do that while I made my living elsewhere.

But I always wanted to be paid the professional rate for something I wrote and delivered for publication, and today that has finally happened!

I have an essay in this month’s Uncanny Magazine, called Being A Brief History of It’s Storytime With Wil Wheaton! I requested payment in a paper check, so I can frame it.

Here’s an excerpt:

I have never been S-tier in anything, but I feel as close as I ever have, during this incredible run. People stop me on the street, in stores and restaurants, because they recognize my voice. They all tell me they love my narration. A lot of them tell me they will listen to anything I narrate. For the first time, ever, I feel respected and recognized in my field. I feel worthy and special. I feel confident and creatively satisfied. I feel like I earned it, that it belongs to me in a way my acting career never did. After nearly thirty years of being told by other people what I want to do, and what I am allowed to do, I finally know what I want to do with my artistic and creative life, until I choose to retire. I just have to find a way to do it without asking for permission.

In early 2024, one of my oldest friends, Christopher Scott, who I have known since I was fourteen, tells me that he finally sold his first story, “The Hidden Heart of Brass Attending,” to a magazine called On Spec (which, like too many indie publications, sadly no longer exists). He isn’t a full-time writer, but he has always been a great writer, a storyteller like me, and I had been waiting for this day for almost forty years. I go to the publication’s website and buy his issue. While I wait for it to arrive, I have this thought that maybe it would be cool to commemorate his achievement, and celebrate him, with a small gift. It had come up more than once that he liked my narrations, so I thought it would be cool to narrate this for him. No music, nothing fancy, no director. Just me and a microphone, doing a fun thing for my friend. 

I sat down to narrate, and right before I hit record, a voice in my head said, “Hey! Stop for a second. You know all those people who tell you that they’ll listen to anything you narrate? What if you tested that? What if you started a weekly audiobook podcast where you narrate short stories like this one? You could ask LeVar if it’s okay to step into the space he is leaving!” For the uninitiated, LeVar had a podcast for years called LeVar Burton Reads, that I loved. Coincidentally, he was ending that series around the time I started to think about mine.

This is such a big deal for me, a real level up moment. It’s just a couple hundred bucks, but it feels like one of the biggest and most significant checks I will ever receive.

I would love for you to read this. If you do, I’d love to hear your thoughts. I also recorded it for this month’s Uncanny Podcast, if you would like to hear it in my voice.

As always, thank you for all your support and enthusiasm over the years. I’m so happy you’re here.


Hi. I’m Wil. I write this blog, Sometimes I’m on TV. I host It’s Storytime With Wil Wheaton, and co-host the official Stuart Fails to Save the Universe podcast. If you’d like to get my posts delivered to your email, here’s the thingy:

Until next time, take care of yourselves, and take care of each other. Never forget that you are enough, you are worthy, and you matter.

20:14

Zero to Agent in 30 Minutes: Build a Content Engine with Max Johnson [Radar]

Max Johnson, founder of the AI agency briix, regularly publishes practical AI guidance for business owners and founders to help them get the most out of AI. Max has used some of that know-how to streamline his own day-to-day work, turning what used to be a manual content creation process—researching topics, judging their relevance, and developing hooks and drafting scripts—into a single automated workflow using Claude Code. In this episode of Zero to Agent in 30 Minutes, he shows you how to do the same while remaining in the loop to make the final calls.

How to build a content creation agent

  1. Give the system the context it needs to generate relevant content. Max started with a small knowledge base containing information about himself and his business, details about his audience, sample scripts, and notes on his writing voice. These files gave Claude Code reference material for evaluating topics and generating scripts that reflected his brand and style.
  2. Explain the process in plain English and let AI take it from there. As Max pointed out, “Vibe coding is describing what you want clearly and let[ting] the model handle the entire building process for you.” However, you’re still ultimately responsible for what’s built. Claude Code may have generated the implementation, but Max set the context, approved permissions, reviewed results, and answered clarifying questions along the way.
  3. Build the individual stages of your pipeline. Max wrote a list of prompts to take Claude Code through the process of researching topics (stage 1), scoring them and selecting the most relevant one (stage 2), then generating three hooks and expanding the strongest option into a full script based on the knowledge base and sample content (stage 3). He ran them through Claude Code and ended up with a working three-part pipeline, with each stage triggered manually.
  4. Connect the stages into a single workflow you can run with one command. Max had Claude Code combine research, scoring, hook generation, and script writing into a unified content engine that produces a structured JSON file he uses in the next stage. “This is the actual moment that it stops being a few separate prompts and starts becoming an agent,” Max says.
  5. Add a browser-based interface. Max built a local dashboard that displays the generated topics, scores, hooks, and scripts. This lets him inspect the results and start new runs from a browser instead of returning to the terminal each time. Here’s a tip from Max on creating a dashboard you’ll actually want to use as your mission control: Be as creative as you want, but “make it look like something designed on purpose, not a default template.”
  6. Test the workflow and extend it. The first version Max created had two limitations: It produced scripts only for the top-ranked topic, and starting a new research run still required the terminal. Max solved this by asking Claude Code to generate hooks and scripts for all five topics and add a button that could start a new run from the dashboard. The revised system produced three scripts for each topic, giving him 15 script options per run.

If you’re building something similar, Max recommends starting with a repetitive task you can describe clearly. Build a working version with your preferred coding tool, use it, and then extend it in response to what you learn.

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.

The Lost Bladez [Penny Arcade]

Now these Zero Company strips are just gentle prayers to those let go; given its quality and level of success I I would be very surprised if the ending of that story were all bad. But you do get a Jedi on your roster at some point in the game, through a narrative conceit rather than from the squadmate creator, probably because if you could have four Jedi running around a lot of people would probably do that. I don't know if you are super up on your lore, but Jedi are space wizards with infinitely sharp swords made out of lasers. Trying to fight them with regular swords and guns would be dumb even if they couldn't also read your thoughts.

19:35

ReactOS 0.4.16 released [OSnews]

We are pleased to announce the release of ReactOS 0.4.16! After a year and a half of development, we’re excited to showcase the improvements we’ve made between a new graphical installer; a unified bootcd and livecd image; video, audio, networking, and storage stack improvements; a new installation type; and third-party code syncs.

↫ The ReactOS Team on the ReactOS website

The amount of changes and improvements is quite staggering, honestly. They’ll always be chasing a moving target, sure, but if they manage to keep this rate up ReactOS might actually grow into something usable in its own right, full compatibility with Windows or not. Excellent progress.

17:14

Dirk Eddelbuettel: RcppExamples 0.1.11 on CRAN: Very Minor Maintenance [Planet Debian]

A new version 0.1.11 of the RcppExamples package is now on CRAN, and has been built for r2u.

RcppExamples provides a handful of short examples detailing by concrete working examples how to set up basic R data structures in C++. It also provides a simple example for packaging with Rcpp. The package provides (generally fairly) simple examples, more interesting, compelling (and generally longer) examples are at the Rcpp Gallery.

This releases updates a few Rd files to adhere to a stricter standing of checking by R. The NEWS extract follows:

Changes in RcppExamples version 0.1.11 (2026-09-03)

  • Add now-checked-for missing sections to manual pages

  • Updated continuous integrations two more times

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.

15:49

I Am Traveling Yet Again, Please Enjoy This Photo of Fungus In My Absence [Whatever]

I believe this is the turkey tail mushroom or some such. It’s on a fallen tree in the local nature preserve. It’s actually quite pretty.

Today I am off to Tampa to take part in the Tampa Bay Comic Convention. If you are in or near Tampa, come on down and see me this weekend. My schedule is here!

This does mean I will miss Dragon Con, alas, and the Dragon Awards ceremony, where The Shattering Peace is a finalist in the Science Fiction Novel category. However, Athena is attending to accept the award if I should win. If you’re at Dragon Con this weekend and think you see her there, well, you probably have.

(Krissy is staying home this weekend. The dog has missed her.)

— JS

15:35

[$] Recent work in memory tiering [LWN.net]

Tiered-memory systems are built with multiple types of memory, each of which has different performance characteristics. In addition to the usual DRAM, a tiered system might also provide faster high-bandwidth memory or slower CXL memory. On these systems, the placement of memory allocations has a significant effect on the performance that a workload will obtain. While work on tiered-memory improvements has been ongoing for years, it feels like the pace has slowed a bit recently. Even so, there are a few efforts underway, but they are facing questions about whether the tiering design makes sense.

Audacity 4.0 released [LWN.net]

Version 4.0 of the Audacity audio editor has been released. Notable changes in this release include a rewritten interface using Qt, ability to save user-interface layouts as "Workspaces", improvements in working with audio clips, and a new .aup4 project format.

The release is not fully feature-compatible with the Audacity 3.x series; see the compatibility notes for a list of missing features.

14:56

CodeSOD: Heating Up [The Daily WTF]

A common option for retrofitting heating and cooling into older homes is a mini-split, frequently tied to a heat pump. They're (relatively) cheap to install, energy efficient, and can be added without substantial modifications to the home. They also, annoyingly, are mostly controlled via IR remotes, making them challenging to wire up to home automation or even a household thermostat.

People have made solutions, and today's code comes from one of those solutions. Which, I want to stress, this code comes from an open source project for home automation, so it's not the code that's wrong, here. At first I thought it was, and had a moment of, "I'm not going to pick on some hobby project," but then I realised the hobby project points at a deeper issue.

// temperature helper these are direct mappings based on the remote
float toFahrenheit(float fromCelsius) {
    // Lookup table for specific mappings
    const std::map<float, int> lookupTable = {
        {16.0, 61}, {16.5, 62}, {17.0, 63}, {17.5, 64}, {18.0, 65},
        {18.5, 66}, {19.0, 67}, {20.0, 68}, {21.0, 69}, {21.5, 70},
        {22.0, 71}, {22.5, 72}, {23.0, 73}, {23.5, 74}, {24.0, 75},
        {24.5, 76}, {25.0, 77}, {25.5, 78}, {26.0, 79}, {26.5, 80},
        {27.0, 81}, {27.5, 82}, {28.0, 83}, {28.5, 84}, {29.0, 85},
        {29.5, 86}, {30.0, 87}, {30.5, 88}
    };

    // Check if the input is in the lookup table
    auto it = lookupTable.find(fromCelsius);
    if (it != lookupTable.end()) {
        return it->second;
    }

    // Default conversion and rounding to nearest integer
    return roundf(fromCelsius * 1.8 + 32.0);
}

Okay, I am going to pick on their code a little bit; using float as a key in a map is asking for trouble, because rounding errors are going to surprise you. But honestly, failing to find the key you're looking for is better than the opposite, since that actually does the correct thing. Because if you look carefully at the table, you'll see that it's wrong.

18C, for example, should be 64F. Well, 64.4F, but we're rounding to an integer. The choice here is to roughly map every 0.5C increase to a 1F increase, which is not the conversion factor. They try and correct- note how the table mostly steps by 0.5C, but skips 19.5C.

The opposite direction is similarly bad:

// temperature helper these are direct mappings based on the remote
float toCelsius(float fromFahrenheit) {
    // Lookup table for specific mappings
    const std::map<int, float> lookupTable = {
        {61, 16.0}, {62, 16.5}, {63, 17.0}, {64, 17.5}, {65, 18.0},
        {66, 18.5}, {67, 19.0}, {68, 20.0}, {69, 21.0}, {70, 21.5},
        {71, 22.0}, {72, 22.5}, {73, 23.0}, {74, 23.5}, {75, 24.0},
        {76, 24.5}, {77, 25.0}, {78, 25.5}, {79, 26.0}, {80, 26.5},
        {81, 27.0}, {82, 27.5}, {83, 28.0}, {84, 28.5}, {85, 29.0},
        {86, 29.5}, {87, 30.0}, {88, 30.5}
    };

    // Check if the input is in the lookup table
    auto it = lookupTable.find(static_cast<int>(fromFahrenheit));
    if (it != lookupTable.end()) {
        return it->second;
    }

    // Default conversion and rounding to nearest 0.5
    return roundf((fromFahrenheit - 32.0) / 1.8 * 2) / 2.0;
}

Here, we can be off by as much as a 1C, which is certainly a noticeable feeling.

At first glance, I thought this was just a misguided attempt at optimizing the lookup. For common values, do a lookup instead of calculating because it's faster. Seems like the kind of mistake a hobby project might make, and definitely not a WTF. But it's the comment which corrects me: these are direct mappings based on the remote.

These remotes usually have a display. So when you see on the remote that you're trying to set the temperature to a comfortable 72F, the remote is actually sending 22.5C to the unit. That's the actual temperature being sent.

Now, why on Earth does the remote behave this way? Well, I haven't cracked one open to read off the part numbers, but I'm going to go out on a limb and guess that the microcontoller in the remote doesn't handle floating point operations all that well. So it almost certainly does use a lookup table to decide what signal to send, and the lookup table is populated by "good enough" approximations of temperature conversions. There aren't a lot of places that use Fahrenheit, so being "close enough" is a reasonable solution. If you want accurate temperatures, use SI units, not "freedom units".

In the end, I'd say that neither the hobby project, nor the remote control are the WTF here; locales that insist on using weird ass units are.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Always Look on the Bleat Side – DORK TOWER 03.08.26 [Dork Tower]

Most DORK TOWER strips are now available as signed, high-quality prints, from just $25!  CLICK HERE to find out more!

Obviously, Medical Expenses are on the way! Want to help? Please consider joining the DORK TOWER Patreon and ENLIST IN THE ARMY OF DORKNESS TODAY! It’s what keeps the strip going (but it’s also a fun community)!

 

14:49

Security updates for Thursday [LWN.net]

Security updates have been issued by AlmaLinux (freerdp, go-fdo-server, golang-github-openprinting-ipp-usb, kernel, kernel-rt, nodejs:24, perl-DBI, and php), Debian (firefox-esr, libapache2-mod-auth-openidc, and libass), Fedora (dracut, exiv2, firefox, freerdp, gvfs, mingw-expat, mingw-gstreamer1, mingw-gstreamer1-plugins-bad-free, mingw-gstreamer1-plugins-base, mingw-gstreamer1-plugins-good, mingw-openexr, nss, proftpd, and syncthing), Mageia (apr-util, bubblewrap, libalsa2, libarchive, perl-Net-OAuth, perl-Text-CSV_XS, perl-XML-Bare, and perl-YAML-Syck), Oracle (freerdp, gimp, golang, iperf3, nginx:1.24, nodejs:22, nodejs:24, pipewire, wget, xmlrpc-c, and xorg-x11-server-Xwayland), SUSE (apache2-mod_auth_openidc, apptainer, apr-util, bzip2, c-ares, cosign, dhcpcd, dovecot22, emacs, erlang, gegl, gopass, gzip, httpcomponents-client, incus, kernel-devel, libgpg-error, libsoup2, mozillafirefox, mozilla-nss, mozilla-nspr,, MozillaFirefox, mozilla-nss, mozilla-nspr, rust-cbindgen, nodejs20, orthanc, orthanc-authorization, orthanc-postgresql,, postgresql14, python-cryptography, python-msgpack, quagga, snpguest, snphost, texlive, tuxguitar, udisks2, vim, wget, and yast2-users), and Ubuntu (apr-util, biosig, linux, linux-aws, linux-azure, linux-azure-fips, linux-fips, linux-hwe-5.4, linux-ibm, linux-ibm-5.4, linux-iot, linux-kvm, linux-oracle, linux-raspi, linux-raspi-5.4, linux-xilinx-zynqmp, linux-aws-5.15, linux-gcp-5.15, linux-oracle-5.4, sssd, and tika).

14:07

09/01/26 [Flipside]

I'm back from Thailand, but I have to go to a convention in Texas right away this weekend! Let's do another Patreon Stream before I leave!

https://www.twitch.tv/flipsider99

Will be inking comics, feel free to come by and watch!

Free Software Directory meeting on IRC: Friday, September 11, starting at 12:00 EDT (16:00 UTC) [Planet GNU]

Join the FSF and friends on Friday, September 11 from 12:00 to 15:00 EDT (16:00 to 19:00 UTC) to help improve the Free Software Directory.

GNU Parallel 20260822 ('Ceuta') released [Planet GNU]

GNU Parallel 20260822 ('Ceuta') has been released. It is available for download at: lbry://@GnuParallel:4

Quote of the month:

  Parallel has an option for almost everything, it's almost too much.
    -- tester457@ycombinator

New in this release:

  • Replacement string: {%jq: jq-expression %}
  • Use $^X when calling perl. So if different versions of perl are found in $PATH, we use the same version that started GNU Parallel.
  • Bug fixes and man page updates.


GNU Parallel - For people who live life in the parallel lane.

If you like GNU Parallel record a video testimonial: Say who you are, what you use GNU Parallel for, how it helps you, and what you like most about it. Include a command that uses GNU Parallel if you feel like it.


About GNU Parallel


GNU Parallel is a shell tool for executing jobs in parallel using one or more computers. A job can be a single command or a small script that has to be run for each of the lines in the input. The typical input is a list of files, a list of hosts, a list of users, a list of URLs, or a list of tables. A job can also be a command that reads from a pipe. GNU Parallel can then split the input and pipe it into commands in parallel.

If you use xargs and tee today you will find GNU Parallel very easy to use as GNU Parallel is written to have the same options as xargs. If you write loops in shell, you will find GNU Parallel may be able to replace most of the loops and make them run faster by running several jobs in parallel. GNU Parallel can even replace nested loops.

GNU Parallel makes sure output from the commands is the same output as you would get had you run the commands sequentially. This makes it possible to use output from GNU Parallel as input for other programs.

For example you can run this to convert all jpeg files into png and gif files and have a progress bar:

  parallel --bar convert {1} {1.}.{2} ::: *.jpg ::: png gif

Or you can generate big, medium, and small thumbnails of all jpeg files in sub dirs:

  find . -name '*.jpg' |
    parallel convert -geometry {2} {1} {1//}/thumb{2}_{1/} :::: - ::: 50 100 200

You can find more about GNU Parallel at: http://www.gnu ... rg/s/parallel/

You can install GNU Parallel in just 10 seconds with:

    $ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
       fetch -o - http://pi.dk/3 ) > install.sh
    $ sha1sum install.sh | grep c555f616391c6f7c28bf938044f4ec50
    12345678 c555f616 391c6f7c 28bf9380 44f4ec50
    $ md5sum install.sh | grep 707275363428aa9e9a136b9a7296dfe4
    70727536 3428aa9e 9a136b9a 7296dfe4
    $ sha512sum install.sh | grep b24bfe249695e0236f6bc7de85828fe1f08f4259
    83320d89 f56698ec 77454856 895edc3e aa16feab 2757966e 5092ef2d 661b8b45
    b24bfe24 9695e023 6f6bc7de 85828fe1 f08f4259 6ce5480a 5e1571b2 8b722f21
    $ bash install.sh

Watch the intro video on http://www.youtub ... L284C9FF2488BC6D1

Walk through the tutorial (man parallel_tutorial). Your command line will love you for it.

When using programs that use GNU Parallel to process data for publication please cite:

O. Tange (2018): GNU Parallel 2018, March 2018, https://doi.org/1 ... 81/zenodo.1146014.

If you like GNU Parallel:

  • Give a demo at your local user group/team/colleagues
  • Post the intro videos on Reddit/Diaspora*/forums/blogs/ Identi.ca/Google+/Twitter/Facebook/Linkedin/mailing lists
  • Get the merchandise https://gnuparall ... igns/gnu-parallel
  • Request or write a review for your favourite blog or magazine
  • Request or build a package for your favourite distribution (if it is not already there)
  • Invite me for your next conference


If you use programs that use GNU Parallel for research:

  • Please cite GNU Parallel in you publications (use --citation)


If GNU Parallel saves you money:



About GNU SQL


GNU sql aims to give a simple, unified interface for accessing databases through all the different databases' command line clients. So far the focus has been on giving a common way to specify login information (protocol, username, password, hostname, and port number), size (database and table size), and running queries.

The database is addressed using a DBURL. If commands are left out you will get that database's interactive shell.

When using GNU SQL for a publication please cite:

O. Tange (2011): GNU SQL - A Command Line Tool for Accessing Different Databases Using DBURLs, ;login: The USENIX Magazine, April 2011:29-32.


About GNU Niceload


GNU niceload slows down a program when the computer load average (or other system activity) is above a certain limit. When the limit is reached the program will be suspended for some time. If the limit is a soft limit the program will be allowed to run for short amounts of time before being suspended again. If the limit is a hard limit the program will only be allowed to run when the system is below the limit.

FreeIPMI 1.6.19 Released [Planet GNU]

o Fix minor groff warnings in manpages.
o Fix portability of building manpages.
o Fix minor bugs found by code analysis:
  - bmc-watchdog: Fix bug with --arp-response command line parsing.
  - ipmi/rmcpping: Fix bug with finding an IPv6 interface.
  - ipmidetect: fix bug in which hostname configs not used properly
  - ipmi-oem intelnm: fix parsing of hours/minutes error
  - ipmi-oem: Correct Dell CMC IPv6 autoconfiguration output.
  - ipmi-oem: Correct Dell iDRAC web server control output.
  - libipmidetect: Isolate partial results between fallback servers.
  - libipmidetect: Honor caller-provided hostnames over configured defaults.
  - common: Preserve stream state when finishing buffered output.
  - common: Fix buffer-output configuration parse issue.
  - common: Parse UTC offset configuration as an integer.
  - ipmiconsole: Monitor both console descriptors.
  - ipmiconsole: Check valid payload instance range correctly.
  - ipmi-sel: Honor post-clear after full tail output.
  - ipmi-fru: Report DIMM capacities in megabytes.
  - ipmi-chassis: Apply the Power-On Hours scale correctly.
  - libipmimonitoring: fix mem-leak on SEL iterator
  - libipmimonitoring: allow cipher suite 0 in configs
  - libipmimonitoring: report NO_SEL_RECORDS instead of
    NO_SENSOR_READINGS cut and paste errors in some functions.
o Fix potential stack overflows found by code analysis in ipmi-oem dell get-system-info command (specifically idrac-info, cmc-info, cmc-ipv6-info subcommands) and ipmi-oem fujitsu get-sel-entry-long-text.
o Fix potential stack overflow in libfreeipmi also related to Fujitsu long SEL entries.


https://ftp.gnu.o ... pmi-1.6.19.tar.gz

automake-1.18.92 released [beta] [Planet GNU]

This is to announce automake-1.18.92, a beta release in preparation for automake-1.19.  Announcement:

https://lists.gnu ... -08/msg00000.html

unrtf 0.21.12 [Planet GNU]

unrtf 0.21.12 is released, fixing a serious recently submitted security issue.

GNU Health strict No Generative Artificial Intelligence Policy [Planet GNU]

Dear community

We have included in the GNU Health Code of Conduct the strict NO Generative Artificial Intelligence policy.

The current version reads:

GNU Health Strict No Generative Artificial Intelligence Policy

GNU Health is social project made by humans and for humans. We DO NOT accept any code, artwork, review, documentation or issues created by generative Artifical Intelligence (GenAI) / Large Language Models (LLMs).

The GNU Health no-AI policy is because we strongly believe that:

* GenAI is bad for Mother Nature
* GenAI is bad for human rights, especially for underserved and marginalized communities.
* GenAI is bad for the Free Software and Free Culture communities.
* GenAI is bad for you

Last but not least, GNU Health manages critical health information both at personal and population level. There must be a reasoning behind every single line of code. We make all the effort to minimize bugs that can jeopardize the integrity and security of the system, and we can not risk the project by putting it in hands of stochastic parrots.

Let’s keep the art and science of computing a human virtue.


You can read the entire, most current version of GNU Health Code of conduct here:
https://docs.gnuh ... ndix/conduct.html

Joe Marshall: Recursive Descent: Vibe Coded Rogue-like in Common Lisp [Planet Lisp]

I was talking with Amit Patel of Red Blob Games the other day and he mentioned that he had been participating in a programming endeavor where people were creating variations of rogue-like games based on a tutorial. He had just begun experimenting with vibe coding and figuring out how to do it and what works for him. This sounded like an interesting idea, so I decided to try it out myself. I began with the basic tutorial, but since I'm a Lisp programmer, I decided to vibe code the game in Common Lisp. I had a few goals in mind:

  • I wanted to see if I (actually the LLM) could code up a rogue-like game in the browser
  • I wanted to see what sort of interesting code the LLM would produce: Would it be a good design? Would it use macros? Would it use CLOS? Would it get overly complex and "hit the wall" at some point?
  • How would it handle being told to work with functional programming style given that this sort of game is traditionally written in stateful, object-encapsulated style?

I mostly vibe coded this, but I did step in and make adjustments here and there. For example, I specifically requested that the LLM use a functional programming style, and I asked it to refactor large files into smaller ones.

Beginning

Getting started was tricky. I basically wanted a simple terminal emulator in the browser that would would display a fixed-width grid of characters that the back end could update. I wanted to be able to send keypresses to the back end and have it update the display. I didn't have a clear idea about how to do this, so I experimented a bit and came up with something relatively easy. The front end is a simple HTML page with a <div> is expected to contain the grid of characters. The front-end runs some JavaScript opens a WebSocket connection to the back end and sits in a loop waiting for messages. The back end sends messages to the front end that contain a block of html that the front end just inserts into the <div>. The front end also listens for keypresses and sends them to the back end. I didn't expect that this would be a very efficient way to do it, but I figured that a modern browser and reasonably good internet connection would be able to handle a modest refresh rate.

As coding progressed, the LLM extended the front end to include multiple <div>s, including pop-up modals. The LLM also augmented the front end to reconnect to the back end if the connection was lost, and direct focus to the playing area with the page was displayed. Othewise, the front end is a relatively thin client that mostly displays exactly what the back end sends it.

Real time back end

I started out with the standard rogue-like game loop, which is a synchronous, turn-based loop. The back end would wait for a keypress, then update the game state and send the new display to the front end. This works, but I remembered how the developers of Diablo said that when they decided to make it real-time it completely changed the game. I decided to make the back end real-time, but with a slow enough tick rate that I wasn't overwhelming the connection or the browser. Eventually I decided on a tick rate of 20Hz. Most of the effects in the game are timed around a 0.1 second interval (the rate at which the keyboard repeats when you hold down a key) and 20Hz is the Nyquist frequency to avoid aliasing (which would make the game stutter weirdly if you tried to run by holding down an arrow key). This makes the game feel responsive enough without it needing to refresh at CRT rates. Since the game is based on a grid of ascii characters rather than a bitmap, I guessed that the bandwidth requirements would be modest enough that this would work.

The first few hours vibe coding were spent getting a player character to run around a procedurally generated dungeon. Once I had that working, I asked the LLM to refactor the back end into a functional core with a stateful wrapper. The functional core is a pure function that takes the current game state and a message from the front end (typically a keypress) and returns the new game state. The stateful wrapper manages the WebSocket connection and the game loop. Once the back-end had been refactored into a functional core, the LLM generally continued to keep side effects out of the code, although it did introduce some reasonable side effects to manage a LRU cache of game state in order to save on recalculations.

Every action in the game is modeled as a pure reducer function. `MOVE-PLAYER`, `DRINK-POTION`, `PROCESS-ENEMY-TURNS`, etc. all follow the same signature: they take the current `GAME-STATE` plus some inputs, and return a freshly allocated `GAME-STATE` representing the world one tick later.

To achieve this without writing thousands of lines of boilerplate copy constructors, the engine heavily leverages Common Lisp's Meta-Object Protocol (MOP). The `copy-instance` and `update-entity` helpers dynamically iterate over a class's slots at runtime. When an Orc takes 5 damage, the engine doesn't mutate the Orc; it uses the MOP to spin up a brand new Orc with identical properties, except for a modified HP slot, and substitutes it into the new `GAME-STATE`'s entity list.

Decoupled I/O State

To avoid locking as much as possible, the engine decouples the I/O state from the game state. When the Hunchensocket WebSocket read-thread receives a JSON packet from a client, it does exactly two things: it parses the JSON into an immutable RDESCENT-COMMAND CLOS object (like move-command or drink-command), and it dumps that command into a thread-safe SB-CONCURRENCY:QUEUE. It never touches the game state.

Meanwhile, a single, dedicated game-loop thread acts as the heartbeat. Once every 50ms, the TICK-ALL-CLIENTS function wakes up, drains the input queues for every connected client, and folds those commands over that client's GAME-STATE using the pure ADVANCE-GAME-STATE reducer. This means a player mashing the keyboard at 100 APM can never cause a race condition or force the engine to lock the state tree. The I/O is asynchronous, but the game logic is predictably synchronous.

Because the game runs in real-time, it needs a way to blend the fast-paced player inputs with slower, methodical monster AI. This is handled via an Energy accrual system.

Every tick, every entity (players and monsters alike) accrues `ENERGY` equal to their `SPEED` stat. Actions have flat energy costs. The game loop refuses to process an action for an entity until its energy balance can afford it. This allows the engine to support speed-altering buffs and debuffs simply by tweaking the energy thresholds or accrual rates, without needing a bespoke cooldown-timer subsystem.

Procedural Dungeon Caching

Dungeon generation in `Recursive Descent` is deterministic, seeded by a hash of the dungeon level. This means GENERATE-DUNGEON will always carve the exact same rooms and corridors for Level 5, every time. The engine doesn't need to store the entire dungeon in memory for every player; it can simply regenerate the same layout on demand.

Because the generation is deterministic, and the GAME-MAP geometry (the TILE array) is strictly immutable, the architecture introduces a *DUNGEON-CACHE*. When a player drops down to Level 5, the engine checks the cache. If the geometry is already there, it just hands a pointer to the existing immutable map to the player's GAME-STATE. Multiple players on the same tier and level share the same physical memory space for the dungeon walls and floors, reducing the memory footprint of the server.

Save Games Stored on the Client

Instead of maintaining a massive, clustered database to store player progression, the server is entirely stateless across sessions. When a player hits the Save button, the Lisp server serializes their entire immutable GAME-STATE (including all visited levels, dropped items, and explored fog-of-war bit-vectors) into an association list. It then zlib-compresses it, signs it with an HMAC-SHA256 hash using a server-side secret key, base64 encodes it, and sends it back to the client over the WebSocket.

The client's browser stores the save blob in localStorage. When the player reconnects, they hand the blob back. The server verifies the signature, decompresses it, and resurrects the CLOS objects. Thus we offloaded the database hosting to the user's hard drive.

Client Registry

Tracking connected users in a multithreaded web server usually involves wrapping a global list in a heavy mutex, which creates a bottleneck every time the game loop iterates over it.

To solve this, server.lisp isolates the *RDESCENT-CLIENTS* list inside a dedicated background actor thread (RDESCENT-CLIENTS-REGISTRY-LOOP). No other thread is allowed to touch it. When Hunchensocket receives a new connection or a disconnect, it drops a simple (:CONNECT client) or (:DISCONNECT client) message into the actor's mailbox. When the game loop needs the list of players for the next tick, it sends a (:SNAPSHOT) message and waits for the actor to reply with the current list. This guarantees that the client roster is never mutated out from under an active iteration, cleanly sidestepping deadlocks.

Fat Base Class

Modern game development typically uses Entity-Component-System (ECS) architectures to avoid massive inheritance trees. `Recursive Descent` ignores this trend. The base ENTITY class is deliberately "fat." It holds everything: spatial coordinates (X, Y), rendering data (CHAR, RENDER-ORDER), combat stats (HP, POWER, DEFENSE), inventory, equipment, and all seven RPG Stats (Strength, Dexterity, Charisma, etc.).

In a mutable OOP design, a fat base class is a maintenance nightmare. In a purely functional CLOS architecture, it is an advantage. Because state mutation is handled entirely by a Meta-Object Protocol (MOP) helper (COPY-INSTANCE / UPDATE-ENTITY) that dynamically walks the class slots to clone the object, having a wide, flat property list is functionally cheap. You don't need complex component-querying logic; you just ask the entity for its DOMAIN-KNOWLEDGE and move on.

Interestingly, there is no PLAYER class. The player is simply a baseline ENTITY instance that happens to be bound to the PLAYER slot of the GAME-STATE. It uses the exact same combat resolution, inventory handling, and stat scaling as any monster.

Entity Subclasses

Instead of overriding methods to change behavior, the ENTITY subclasses primarily exist to provide specific :DEFAULT-INITARGS and to act as dispatch targets for generic functions.

  • ENEMY — Adds no new slots. It simply provides an :AFTER initialization method to guarantee an enemy defaults to a :HOSTILE disposition and derives its XP value from its HP.
  • AUTO-PICKUP-ITEM — Represents a scavenger hunt collectible. It defaults IS-ALIVE to NIL and BLOCKS-MOVEMENT to NIL, keeping it out of the AI processing loop and allowing the player to freely walk over it.

Fixture Hierarchy

Fixtures represent stationary, non-hostile map objects (shrines, vendors, NPCs) that the player interacts with via a dedicated command rather than by bumping into them. The base FIXTURE class defaults IS-ALIVE to NIL (excluding it from the enemy AI turn loop) and BLOCKS-MOVEMENT to NIL (allowing the player to stand on it).

The hierarchy branches out based on internal state requirements:

  • SHRINE-FIXTURE — Adds a `USE-COUNT` slot to track finite activations.
  • VENDOR-FIXTURE — Stateless beyond its base properties. Its "stock" is derived globally, and it requires no mutable inventory of its own.
  • NPC-FIXTURE — Likewise stateless. Quest progress is stored in the player's GAME-STATE flags rather than on the NPC, ensuring the NPC remains purely shared, immutable geometry.
  • TRAP-FIXTURE — Adds a HIDDEN-P slot to dictate rendering visibility, flipping to NIL once triggered or spotted.

Command Pattern

RDESCENT-COMMAND input handling relies on a polymorphic Command Pattern. The WebSocket read thread parses raw JSON into a concrete subclass of RDESCENT-COMMAND (MOVE-COMMAND, USE-ITEM-COMMAND, EQUIP-COMMAND, etc.).

Instead of a massive COND statement checking command types, the engine uses CLOS generic functions (EXECUTE-QUEUED-COMMAND). Each command class has a specific method that invokes the appropriate state reducer (e.g., the DRINK-COMMAND method calls DRINK-POTION). This makes extending the engine's vocabulary trivial: adding a new command means defining a tiny data class and writing exactly one generic method for it.

State Containers

GAME-MAP: Holds the TILES array. A TILE contains purely static, shared geometry (walls, floors, room-type tags). Because this never mutates based on player action, a GAME-MAP can be safely memoized and shared across multiple players on the same depth via the *DUNGEON-CACHE*. GAME-STATE: The server-authoritative snapshot for a specific player. It holds the PLAYER entity, the list of other ENTITIES on the floor, the field-of-view EXPLORED bit-vector, and the LEVELS FSET map (which archives DUNGEON-LEVEL-SNAPSHOTs of previously visited floors).

The Imperative Shell: RDESCENT-CLIENT Sitting at the very top of the stack is RDESCENT-CLIENT, a subclass of Hunchensocket's WEBSOCKET-CLIENT. This is the only place where mutability is permitted. It acts as the anchor, holding the connection's thread-safe INPUT-QUEUE for incoming commands, and the mutable pointer to the current immutable GAME-STATE. This is where the game state is updated with the new game state.

Conclusion

The LLM did a good job of vibe coding a rogue-like game in the browser. At one point I simply sat down and brainstormed a list of features that I wanted to add to the game. Then I told the LLM to design an architecture that would be amenable to adding those features. Then I told the LLM to implement the architecture. When it was done, I told the LLM to prioritize the features I wanted to add suggest an implementation order. Then I told the LLM to implement each feature in turn.

At a couple of points, the LLM was letting a monolithic file get out of hand, so I explicitly told it to refactor the code into smaller files I also made an explicit pass to make sure that the compile was giving no warnings. The LLM had a tendency to use large etypecase statements to dispatch on the type of an object. I explicitly told it to use CLOS generic functions instead, and it did so. OTher than that, I basically left the LLM to generate code how it saw fit.

If you want to try out the game, you can run it in your browser at https://jrm-code-project.com/rdescent.html. If you just want to peruse the source code, you can find it at https://github.com/jrm-code-project/jrm-code-public/tree/main/rdescent, but try the game before you look at the code because the code contains spoilers. No guarantees it will work on your browser. Mine has a fairly big display and a reasonably high bandwidth connection, but I don't know about yours. No way this would work on a phone.

Tim Bradshaw: Blank generation [Planet Lisp]

I belong to the blank generation and
I can take it or leave it each time, well
I belong to the _ generation but
I can take it or leave it each time.

My destructuring-match macro has a notion of ‘blank’ variables: any variable whose name is "_" is both unique and ignored. This is useful because when pattern matching you often only care about some of the thing you matched: the rest is just placeholders. Štar does the same thing.

This idea, and the names of blank variables, is not something I invented. I think I probably stole it from Racket, which I’m sure got it from somewhere else. Lots of people have done this.

Well, a while ago I thought it would be nice if CL didn’t distinguish between binding multiple values and binding single values: why can’t a single form do both? So I wrote let-values which does this. Again, I’m far from the first person to do this, although let-values makes a more serious attempt to handle declarations properly than most, I think.

So, should let-values support blank variables? Well, I thought it should, so I changed it so it did. So, for instance, you could write

(let-values (((_ present) (gethash ...)))
  ... present ...)

which is nice, perhaps.

I’ve changed my mind: let-values shouldn’t support blank variables, I think. The reasons for this are that it’s a general binding construct, unifying let and multiple-value-bind and their starred variants, and it should compose with other things. Those other things might have their own ideas of variable names, and in particular they might create variables called "_" which are not meant to be blank.

Štar and DSM are not general binding constructs, so I think that blank variables are OK there. But I don’t, now, think they’re something that a general construct should have. Something like

(for ((_ (in-naturals n)) ;bound iteration
      (x ...))
  ...)

is also just a really common thing to want to do: you don’t want to have to clutter up code with ignore declarations in this case. A similar thing applies for DSM.

Later on, after writing the first version of this post, I changed my mind again. let-values now optionally supports blank variables. There is a variable, *blank-variables*, which controls whether blank variables are supported. This matters at macroexpansion time, of course. *blank-variables* is exported from a distinct package, org.tfeb.hax.let-values/blanks so that if you merely use the org.tfeb.hax.let-values package you just get the four names it exported previously. This seems like a reasonably clean compromise.

Link [Scripting News]

We got agents running in Atlantis today. Screen shot.

Link [Scripting News]

A bunch of tech billionaires have chipped in a million each to fund a new Linux distribution. I can't imagine how this is supposed to work, all those huge human egos, how much will they pay their developers to contribute what. I have to find out what the goal of the project is. It would be nice to have a Linux that was extensively tested for use by regular people.

Link [Scripting News]

After saying I was putting my tech work aside, yesterday, I had a bolt of determination, and realized I don't like collaborating with Claude Code, but maybe it's just when we get to this stage of a project where it's all details, and they're hard to communicate, and the bot has a bad habit of ignoring things I tell it to do. It also returns code to me to test that no human developer would pass on to a user. It would be too embarrassing. But the bot isn't aware in the same way a human is, not even close. Now that is something I'm sure they can be taught to simulate well enough to be as good as a human collaborator.

My year with Claude Code [Scripting News]

Everyone is introducing themselves on the Berkman community mail list, at the beginning of the fall semester, and here's the story I am telling.

Good morning. My name is Dave Winer. I was at Berkman in 2003-04, worked on blogging, podcasting, RSS, political blogging and the connection to journalism. We had a couple of Bloggercons at Harvard, had a wonderful time, miss it terribly, wish we could have it back.

I've spent the whole of this year working in Claude Code on a couple of programming projects, one which which I had been struggling with for a couple of years, coming up with a user interface for a Twitter-like system that runs on the web for real, without any of the limits that the silos have (that help them enforce the boundaries of the silo). Just using HTML, Markdown, RSS, WebSockets. Turns out you can make a pretty nice system, but the going is slow because it's just me and Claude, on the other hand, what a huge difference in territory we can cover. This is the kind of stuff that's never covered in the reporting, even when there are massive changes happening in how software is developed, people don't seem to be studying it. I'm sharing all I learn as I learn it on my blog -- at scripting.com.

Also attempting a massive coding job at the same time, getting UserLand Frontier to run on current OSes. It's my life's work, but it was very much in danger of being lost, along with all the software I did before the web. Now we're close to getting it to run in a stable mode, here the challenge has been to teach Claude how a new type of software works, it keeps trying to snap back to systems that are already out there and widely deployed. It doesn't do "pioneering" at all, it needs a human to guide it. And the human guide has to be a developer with a lot of experience.

What I desperately want to do is bootstrap a network of people who work at the level I'm working, and write about it. Taking ideas that are well-explored in software in the pre-AI period, and seeing what we can do that we couldn't before.

The models have been getting increasingly better, you can really feel the difference.

Next year promises to be better, if only via the improvements that will be made by the platform vendors, but it's likely that good app developers will be able to move the leading edge forward much more quickly and users should feel this, if all goes well, real soon now.

Link [Scripting News]

The blogging tools for Bluesky should be able to publish on Bluesky.

Link [Scripting News]

Obama should have seated Garland if the Senate wouldn’t have a vote to advise and consent. Assume they consent. In the same way, the governor of Kentucky should appoint a successor to McConnell unless someone can prove he’s actually alive. The Democrats always put up with Republican tricks like this one. I'm sure McConnell would appreciate the utility of his death, and the Republican lack of shame. Anything for a buck.

Link [Scripting News]

Creative computer people, consider consolidating all the different OSes and programming languages. Give humans a fair fight in competition with the AI's who have no trouble deeply understanding every syntax human designers have come up with, where it totally disadvantages us, who have much more limited memory.

Link [Scripting News]

If you View Source on a fatpage, scroll to the bottom, you'll see the code. Example. The idea is that the docs for the code are what you see in the browser. Maybe that's the way feeds should work too? Something to think about.

Link [Scripting News]

I begin the month of September for some reason, not feeling like working. And rather than succumb to the work ethos, I'm allowing myself to goof off with the same determination that I worked my ass of for so many years. I think the last time I felt like this was when I was a grad student in Madison. I had just fallen in love with a sweet, funny, cute and gorgeous undergrad. She was 19 and I was 22. We spent the last weeks of the fall semester goofing off, and as a result I had to stay in Madison over the holidays, to finish the work of the Compiler Design course, which was the gate for grad students, the hardest course. If you got through it with a decent grade, you got the degree. It was fun too -- except I missed my sweetheart. This time I don't know what the root is, but it's a good feeling. If I want to goof off, I can, no one will judge me, probably very few people even notice. I still expect to finish Frontier, and swing back around to FeedLand and RSS.chat, they have some important work to do, together. Maybe the reason is the thing I liked about making software is the actual making of it, which I delegate more and more to Claude Code. How would a great painter feel if they invented a computer that could write their "code" much more quickly, would Picasso have stopped painting just because Claude Code could do Picasso paintings faster? Today, Claude still will, if given a chance, throw out all of my orders and develop code in a non-Dave fashion. And it still acts as if between the two of us it's the only programmer, surprised when I want to actually write something myself. Maybe that's just my imagination? Or maybe that's the next project for me. A project where I write the code, and Claude helps out when I can't find a bug, or where I need to know something about a feature in a platform I've not worked on.

Link [Scripting News]

OPML archive for August 2026.

You Can't "Vibe Code" Love [Coding Horror]

You Can't "Vibe Code" Love

About a year ago, I was offered a presentation slot at the WeAreDevelopers World Congress in Berlin. I rarely take speaking engagements, especially international ones, but this one arrived at just the right time, the right place, and with the right person – I said yes, on the contingency that Ben Dumke-von der Ehe joins me in the presentation. Ben is an early community hire at Stack Overflow who lives in Berlin. We both had something important we wanted to talk about, with slightly different opinions, different perspectives.

They agreed to the requirement. This is our presentation, as delivered Friday, July 10th at the CityCube in Berlin.

The heart of this presentation is the human story of how Ben and I met. As I said on stage:

The long version of this story is that we changed each others' lives. The short version is: unicorns.

You should hear Ben's side of the story directly from him. It's remarkable. Also remarkable is that he left Stack Overflow, then came back... and then left again. I told Ben, I'm mostly doing this presentation because I owe you so much.

You Can't "Vibe Code" Loveit is difficult to get a German to smile so you have to sneak attack

Not only Ben, but everyone who participated on Stack Overflow. We did it together, under a bedrock Creative Commons license that respected the effort everyone put into their work to build this commons, this hugely influential Stack Overflow dataset.

You Can't "Vibe Code" Love

It's fine that LLMs intercept most (if not all) of the common programming questions; this aligns with the higher level goals of Stack Overflow:

Passively searching and reading highly ranked Stack Overflow answers as they appear in web search results is arguably the primary goal of Stack Overflow. If Stack Overflow is working like it’s supposed to, 98% of programmers should get all the answers they need from reading search result pages and wouldn’t need to ask or answer a single question in their entire careers. This is a good thing! Great, even!

LLMs also cut the gordian knot of constant duplicate questions that I thought was basically impossible.

When you’re asking a question on a site that doesn’t allow duplicate questions, the problem space of a site with 1 million existing questions is rather different from a site with 10 million existing questions... or 100 million. Asking a single unique question goes from mildly difficult to mission almost impossible, because your question needs to thread a narrow path through this vast, enormous field of prior art questions without stepping on any of the vaguely similar looking landmines in the process.

The way LLMs can map a duplicate question using entirely different words to an existing answer is incredibly transformative, and a huge net positive. Less duplication. More answers, faster. That's the point.

But what happens to the commons when everyone is privately whispering to LLMs? What happens to the communities where programmers learn from each other, the very communities like Stack Overflow where Ben had so much fun becoming a programmer, and got hired for doing things he loved?

You Can't "Vibe Code" Love

You can't "vibe code" love.

Where exactly will the LLMs get their next set of training data from, if we don't build for and protect the commons together? How will we continue to share knowledge – and love – with each other?

The perils of binding to value types in XAML [The Old New Thing]

A colleague ran into trouble with their XAML program. They were using a FlipView control to bind to a collection, but when the user tried to navigate the FlipView using an assistive technology tool, there were cases where the navigation failed.

Some time later, they came back with the solution to the mystery.

The team noticed that their data model consisted only of strings and other value types, so they decided to declare their data model as a struct rather than a full runtimeclass, thereby avoiding a lot of boilerplate typing.

If defined as a runtimeclass:

// MyComponent.idl
runtimeclass MyPageContent
{
    String Title { get; };
    String Description { get; };
    String LinkUri { get; };
    Boolean IsNew{ get; };
}

// MyPageContent.h

namespace winrt::MyComponent
{
    struct MyPageContent : implements<MyPageContent>
    {
        MyPageContent(hstring const& title,
                    hstring const& description,
                    hstring const& link,
                    bool isNew) :
            m_title(title),            
            m_description(description),
            m_link(link),              
            m_isNew(isNew) {}          

        hstring Title() const { return m_title; }
        hstring Description() const { return m_description; }
        hstring Link() const { return m_link; }
        bool IsNew() const { return m_isNew; }

    private:
        hstring m_title;
        hstring m_description;
        Windows::Foundation::Uri m_link;
        bool m_isNew;
    };
}

// Consumer.cpp

m_pages.Append(winrt::make<MyPageContent>(
                    title, description, link, isNew));

But if you define it as a struct, then most of this code isn’t necessary:

// MyComponent.idl
struct MyPageContent
{
    String Title;
    String Description;
    String Link;
    Boolean IsNew;
}

// MyPageContent.h not needed

// Consumer.cpp

m_pages.Append(MyPageContent(title, description, link, isNew));

Tastes great, less filling.

Now, the thing that makes value types value types is that they are copy-by-value, not copy-by-reference. This means that when XAML calls GetAt(n) on the m_pages to get the nth item, it gets a copy of the MyPageContent and binds to the copy.

And that’s the source of the problem.

When the code wants to navigate to a specific item at the request of the assistive technology tool, it passes the MyPageContent to navigate to, but that’s just another copy because value types are always passed by copy. XAML says, “I don’t have that guy” and fails the navigation. (XAML doesn’t realize that it has a guy who looks just like that guy. Not that it matters, because it’s not the same guy.)

The clever shortcut turned out to be the problem.

Now, while it’s true that there’s a bunch of typing needed to implement a C++/WinRT runtime class, there are helpers to reduce the amount of typing required. In the Windows Implementation Library (wil), the cppwinrt_authoring.h header contains classes to simplify the implementation of events and properties. It exploits CRTP in the same way I discussed some time ago.

// MyPageContent.h

namespace winrt::MyComponent
{
    struct MyPageContent : implements<MyPageContent>
    {
        MyPageContent(hstring const& title,
                    hstring const& description,
                    hstring const& link,
                    bool isNew) :
            Title(title),            
            Description(description),
            Link(link),              
            IsNew(isNew) {}          

        wil::single_threaded_property<hstring> Title;      
        wil::single_threaded_property<hstring> Description;
        wil::single_threaded_property<hstring> Link;       
        wil::single_threaded_property<bool> IsNew;         
    };
}

We can get away with using a single_threaded_property because the properties are written only at construction, so concurrent reads are not going to cause problems.

The post The perils of binding to value types in XAML appeared first on The Old New Thing.

Microspeak: Funded / unfunded [The Old New Thing]

Recall that Microspeak is not merely for jargon exclusive to Microsoft, but it’s jargon that you need to know to survive at Microsoft.

In business, funding usually refers to having enough money to pay for ongoing operations.

In Microsoft engineering groups, it doesn’t mean that.

In Microspeak, funding refers to having enough people available to work on a feature. A feature that has adequate people assigned to work on it is considered to be funded, or for emphasis, fully funded, whereas a feature that does not have people working on it is unfunded.

Of course, you can have intermediate states, like partially funded, to say that some people have been assigned to it, but not enough to finish the work in time for a specific requested completion date.

Here are some citations I found.

Funding for this feature is being worked through.

This is another way of saying, “We are working on finding people to work on this feature.” This might come at the cost of defunding whatever feature those people had previously been assigned to.

The path for getting out of red involves closing on funding gaps.

In other words, there is a color-coded dashboard on which a feature is currently listed as red. There are currently some shortfalls in staffing (funding gaps), and we will have to resolve (close on) those shortfalls in order to get out of red to a more favorable color.

X said that they do not have engineering resources to fund the Y updates.

In a larger discussion about updating components throughout the system to support the Y feature, person X said that they do not have any people available who can do the Y work for their component.

Note that a feature that is unfunded is not cut. The feature is still planned. It’s just that there is nobody available to do it right now. If funding cannot be obtained soon, the feature will have to be delayed to a later release.

Bonus chatter: Software engineers are not fungible. If you take a developer who works on the taskbar and reassign them to the window manager they won’t be as effective as someone who normally works on the window manager. Furthermore, reassigning a developer across teams involves the receiving team having an open req, and reqs are generally difficult to come by since they are subject to all sorts of business constraints.

The post Microspeak: Funded / unfunded appeared first on The Old New Thing.

AWE does not require PAE, though PAE makes it much more useful [The Old New Thing]

The Address Windowing Extensions (AWE) is a feature of Windows that allows programs to allocate physical memory and map them on a page-by-page basis into a region of address space (the “address window”). PAE is the Physical Address Extension, which is a feature of the x86-32 CPU that allows a 32-bit processor to generate physical addresses larger than 32 bits, thereby allowing it to access more than 4GB of physical motherboard RAM.

Some time ago, I noted that AWE does not require PAE. The two features operate independently, but they are useful together.

If you use AWE without PAE, then your 32-bit Windows system can access only 4GB of onboard memory, so the feature of AWE that gives you access to lots of physical memory is of limited use: The system has only 4GB of memory to begin with, so that’s all that you can get. You wrote a lot of complex code for a high-RAM scenario when there isn’t really a lot of RAM available to take advantage of it.

It’s like getting a large teapot for making a single cup of tea: Your teapot has the capacity to hold a large amount of tea, but you’re going to put just one cup’s worth in it.

This is all largely historical information, since 64-bit processes running on 64-bit systems can allocate more than 4GB of memory and use it in the normal way. No special hoops necessary.

Note: One thing that AWE does give you is the ability to allocate physical non-pageable memory. Again, you can use this feature whether or not you have also enabled PAE.

Note 1: A 32-bit program that uses AWE will still run on a 64-bit system provided the 64-bit system uses the same page size as the 32-bit system. Looking at the table of page sizes used by Windows, it means that an x86-32 program (4KB page size) can run on an x86-64 system and an AArch64 system, but not an Itanium.

The post AWE does not require PAE, though PAE makes it much more useful appeared first on The Old New Thing.

Hey AI, Can You Just Give Me a Hat Tip Please? [Radar]

Sometime in the next few months, Anthropic is supposed to send me a check. Around $9,000 for me, about the same for my longtime coauthor Jenny Greene, and roughly $18,000 for O’Reilly, our publisher. The reason is that pirated copies of the books Jenny and I wrote, along with a huge amount of other people’s work, got swept into the data used to train Claude, and a court settlement is now paying authors and publishers whose work was taken that way. It works out to a little over $3,000 for each of our books, split between all of us, and every edition is counted separately, no matter how the book sold or what was in it. (Assuming the money ever shows up. The settlement won final court approval in July 2026, but a payout this size grinds through a long administrative process, so we’ll see if the check ever actually arrives.)

I don’t especially care about the check. (Okay, that’s not true, nine grand is a lot of money, but hopefully you’ll get my point.) Jenny and I didn’t write those books to get rich, and nobody who’s met a working author would mistake the job for a path to wealth. But one thing I very much care about, and I suspect almost all authors would agree, is getting credit for the work I’ve done. If you ask ChatGPT, Claude, or Gemini (or especially Google AI overviews) a question that it partly learned to answer from one of my books, I would love for it to be able to say so. Something like “Some of this comes from Andrew Stellman’s work, and if you want to go deeper, that’s where to look.” In other words, what I’m asking for is a hat tip.

All of this sits inside a much bigger question, one that usually goes by the name attribution. In the broadest terms, attribution means knowing where a piece of work or knowledge came from, and being able to trace it back to the person who made it. That might sound like a narrow, technical matter, but right now it’s one of the biggest live questions in AI. At Foo Camp recently, it came up constantly, quite possibly the most talked-about topic of the whole weekend, and everyone there seemed to have something to say about it. It’s also a tangled subject, part technical and part legal, and more than either of those, deeply emotional. I want to take a little time to pull those threads apart.

Why so many people are angry

Before any of the technical or legal questions, there’s a simpler reason attribution is such a live wire right now: People are angry, and to be perfectly honest, they have good reason to be. Artists have watched models learn to imitate their style from work that got scraped without anyone asking. Writers have found their books sitting in training sets we never agreed to (even though that’s not the reason Anthropic has to pay us). A lot of people are simply afraid that the work they do for a living is about to be done by a machine that learned part of the job from them. Their work got taken, which may be fair use but almost never included asking the author for permission, and often without any way to even find out it happened. And that for many of us feels really uncomfortable.

A lot of that anger ends up funneled into legalistic arguments about copyright, which is notoriously misunderstood and misapplied. People reach for it for understandable reasons. It feels tangible in a way the rest of this doesn’t, and while many of us feel like it was written to protect authors and artists and creators, it’s actually much more nuanced, especially when it comes to fair use. But I don’t think copyright is the right move for anyone who actually wants attribution, and I’ll go further: I think creators will get a lot more mileage working with the AI labs than fighting them in court.

Underneath all of it, what most of us want is simple enough: We want the work to still be ours, and we want that to be visible. That’s an attribution problem. And before anyone can argue about whether AI can solve it, it helps to be clear about what attribution even is, because it’s a slipperier word than it looks.

What attribution actually is

At its simplest, attribution is the link between something and where it came from. When you read a fact in a book, there’s a chain running from that sentence back to the author, and back again to whatever they drew on. That chain is how credit works, and how trust works, and how you know what to go read next when something grabs you. For as long as people have made things, you could usually follow it back to a person.

You see attribution everywhere once you start noticing it. Bibliographies and references are attribution. So is every footnote on a Wikipedia page, and so is every time one news story credits another, the way you’ll read that “Business Insider is reporting” something before another site passes it along (that is, when we remember to do it). For authors it runs deeper than that: Attribution underpins almost everything we do, and the whole system of academic publishing rests on it, because the entire point is to show exactly whose work each new piece is built on.

AI is the first technology that takes in essentially all of it, every book and article and repository it can reach, and hands back answers with the chain erased. And while that may be technically allowed under fair use, it shouldn’t be the end of the discussion. The knowledge comes out; the line back to whoever produced it does not. That erasure, underneath the lawsuits, is what people are really reacting to. The model learned from their work and gives no sign of it, and the trail that used to lead back to them is gone. Attribution is just the name for putting that thread back.

The catch is that people use the word for at least three pretty different things. The cheap version, the one people reach for first, is resemblance: Does the answer look like my work? That’s easy to check and mostly beside the point, because two people can write nearly the same sentence about a for-loop without either one copying the other. What actual products ship is citation, the little source links under a chatbot’s answer, which credit the page the system fetched while it was answering, not the books it learned from years earlier. Those links are the result of retrieval, not memory, where the system went out and fetched a live page mid-answer. There aren’t any technical challenges in adding an attribution for those live retrieval results, and the fact that the AI labs are fine with that attribution gives them a template to follow if model-based attribution becomes technically feasible.

We’ve got a really good real-world example of how this can work. O’Reilly’s learning platform has an AI engine that answers questions out of the books on the platform, tells you which ones it drew on, and pays the authors and publishers behind them. It works because the corpus is small and everything in it is licensed, which is precisely the condition the frontier models don’t have. Because the version that actually matters is the third one, causal: Did my work shape the part of the model that produced this answer? That’s the honest meaning of the word, and by far the hardest to compute.

Notice that none of that is about money. Paying me would mean working out what a given answer is worth and what share of it is mine, which is a hard allocation problem nobody has really solved (and, to be honest, probably works out to a tiny fraction of a cent in any given answer). Attribution asks a smaller question: Did this come from my work, or didn’t it? A hat tip is only that identification, not necessarily a required payment, and it’s still the thing everyone tells you is impossible.

Trying it on a tiny model

I may be an author, but I’m a developer too, and when someone tells us something is impossible, our first urge is always to build that thing. A lot of the time “impossible” just means nobody has worked out how yet, and every so often it means it would be inconvenient for someone if they did. In this case, some really smart people actually have worked out how, at least some important parts of it, and it’s worth understanding both why the problem is hard and how they’re tackling it.

Start with why it’s hard. When a model trains on your book, it doesn’t file the book away somewhere you can later point to, because models are not databases of books and other source material. What it learns gets spread across billions of numbers, tangled up with everything else it ever read, and no single weight says, “This part came from Stellman.” There’s no receipt anywhere that records which source contributed what. The obvious way to check whether your book mattered would be to pull it out, train the model over again, and see what changed, but nobody is going to retrain a frontier model from scratch a billion times. The upshot is that the knowledge is still in there, it’s just smeared across the whole model with no index back to where it came from, and that’s why a lot of people have called attribution impossible.

It may not be quite as impossible as it seems from that particular approach, though.

Researchers have been chipping away at exactly this, and the approaches run from cheap and rough to expensive and precise. At the precise end is leave-one-out: remove a source, retrain, and measure how far the answer falls. It’s about as close to ground truth as you can get, and hopeless at scale for the reason I just gave. The interesting work is on cheaper stand-ins that approximate that result without all that retraining. The one I find most compelling is TracIn, described by four Google researchers in 2020 in “Estimating Training Data Influence by Tracing Gradient Descent”: As the model trains, it saves snapshots of itself, and TracIn measures how much each training example pushed the model toward a given answer by comparing gradients at those snapshots, with no retraining required.

It belongs to a broader family. Influence functions, which Pang Wei Koh and Percy Liang introduced in 2017 in “Understanding Black-Box Predictions via Influence Functions,” are the older, heavier cousin, and Anthropic scaled them up to models with tens of billions of parameters in a 2023 paper, “Studying Large Language Model Generalization with Influence Functions.” A newer method, TRAK from MIT’s Madry Lab, takes on a weakness they share, where near-duplicate sources muddy the accounting. None of it is settled, and TracIn in particular is contested at frontier scale, but the direction is real and the people pushing it have far bigger budgets than mine.

So I wanted to see it work with my own eyes. I asked an AI to build me the smallest real language model that could still run one of these methods, and about 20 minutes later I had a working one, which I called tiny-provenance: a language model small enough, around 37,000 parameters, that I can retrain it from scratch in a few seconds. That size is the whole point, because it let me run the expensive leave-one-out check for real, as ground truth, and set the cheap TracIn approximation next to it to see whether they agreed.

They did. I gave it a trick question, “Who designed the Analytical Engine?” with a look-alike line about Babbage and the Difference Engine sitting right next to the correct one in the training data. The cheap resemblance check, the kind of thing real products lean on, took the bait and confidently credited the wrong line. Leave-one-out and TracIn both ignored the look-alike and pointed straight at the source the model actually used. The lazy method everyone reaches for was confident and wrong, the honest ones were right, and the whole thing ran in about two minutes on a laptop.

I proved it at a size where I can retrain the model at will, which is exactly what nobody can do at frontier scale, so I won’t pretend I showed it holds up there. Attribution works best at the extremes, where a model has nearly memorized a passage or leaned hard on a single source, and it stays hard in the muddy middle where an almost unimaginable number of books may each have added a tiny little bit to the model. The fair summary is that attribution isn’t impossible. It’s just currently expensive, and more importantly, really impractical with today’s technology.

The whole repo is public, and since this is an article about showing where things came from, it seemed only right to be transparent about where the demo came from too. The README walks through exactly how I built it, prompts and all.

The legal hurdle

I’ll put my own cards on the table: I think copyright is the wrong tool for the fight we’re having about AI. My first job out of college was at EMI Records, working on the system that tracked their music contracts, so I learned early how tangled copyright and trademark and mechanical royalties and the rest of it can get.

Here’s the analogy I keep coming back to. If a student copies a paragraph out of Wikipedia word for word, that’s plagiarism. If the student reads it, understands it, and rewrites it in their own words, usually it isn’t, though that depends on what got rewritten, because plagiarism is about the ideas and not only the words. A physics student writing “F=ma” in a paper isn’t plagiarizing, even if they copied the formula straight out of a textbook. On the other hand, paraphrasing a specific result from somebody’s research paper without saying where it came from is still plagiarism, even if you technically changed the words. And it gets even more complicated. If you copy text word for word but credit the source, you’re not plagiarizing, but you could still have a copyright problem. We throw around the term “fair use” a lot, but that’s actually a very thorny legal area. More importantly, reducing these ideas to a legal argument doesn’t really get to the core of the problem, because laws are often imperfect and dictated by decades of (sometimes conflicting) precedent, so what’s legal doesn’t always match up with what’s the right thing to do. What a model does is a step further from copying than the rewrite: It turns the text into an enormous pile of numbers that stand in for the concepts, and there’s no paragraph left anywhere to find. So far, the courts that have looked at this, including the one behind the Anthropic settlement, have called that use “exceedingly transformative” (those are the Anthropic judge’s words, and lawyers on both sides recognized that as quite a statement). What they mean is that it changes the work into something new enough, and for a different enough purpose, that it isn’t simply a copy of the original. I think they basically got it right.

Basically, I’m not getting a check from Anthropic because they used my work without permission; I’m getting a check because they literally used a stolen book downloaded from a pirated website, instead of paying O’Reilly for a copy or getting it from another legitimate source.

But I don’t get to wave copyright away entirely, because there’s a real case on the other side, and it’s one I feel personally. Someone who wants to learn C# or study for a project management exam can now ask an AI and get an answer that came partly from books I wrote, without ever buying the book. The model learned from my books (and many others on the same topics I write about), and now it competes with them, in the exact market they were written for, using what it took from them to do it.

The worry that AI tools compete with the very material they trained on isn’t theoretical. Stack Overflow, the question-and-answer site where a generation of programmers went for help, has lost roughly three-quarters of its question volume since ChatGPT launched, as developers ask the AI instead. And the AI answers them well in part because it trained on Stack Overflow’s answers in the first place. And even that whole issue is highly nuanced, especially since the material on Stack Overflow itself is written by its users and released to the public under a Creative Commons license.

That goes to the center of what copyright is meant to protect. One of the things courts weigh in a fair-use case is the effect on the market for the original, and a free substitute trained on the original is about as direct a market effect as there is. A judge has already pointed right at it. In the case a group of authors including Sarah Silverman brought against Meta, the court handed Meta a win on fair use. But the judge then went out of his way to hint that the the authors may have lost because they’d argued it wrong, and that this market-dilution theory, the flood of cheap substitutes, is exactly the one that could win on a better record. The judge also said there was “no serious question” that the use of the technology was “highly transformative” in his ruling, and again, lawyers consider that very strong language. I’m not a lawyer, and this is only how the case reads to me as a layman, but I think it’s the strongest argument the other side has. Between that and the piracy, the bigger question is a long way from settled.

This is where it comes back to the hat tip, and the solution I’d like to see for all of these complex, nuanced problems. In a calm world, credit would be a simple courtesy. But we aren’t in a calm world. Copyright sets statutory damages of up to $150,000 per work for willful infringement, and a lot of lawyers see that number and essentially see a bounty, and their whole case turns on escalating litigation and trying to increase a damage award (of which they receive a substantial cut) by trying to sweep in as many works as they possibly can. In other words, they have a huge financial incentive to show that one of the AI labs creating models knew whose work it was using, knew that it was infringing, and did it anyway. Now look at what a hat tip is. The moment an AI lab prints “This answer draws on Andrew Stellman’s book,” it has stated, in writing, “We knew we were using Andrew Stellman’s book,” and a good plaintiff’s attorney could easily turn that around and make it exhibit A. Would a judge see that as willful infringement? I have no idea. But I’m not sure I would bet the entire future of a company like Anthropic or OpenAI.

If credit that would cost a lab almost nothing to give carries a legal risk that dwarfs the cost of crediting no one, then the rational move, the one any lawyer would advise, is to say nothing and admit nothing. The threat of a potential lawsuit could be enough to convince a lab that they can’t safely open the conversation about voluntary attribution at all, because anything it offers in good faith can be turned into ammunition. That’s the worst outcome for everybody, authors included, because the one technology that might be able to finally tell you where an idea came from ends up legally better off staying silent about it.

If you build with these tools, or just lean on them all day the way I do, this is a key reason the answers you get will almost never tell you where they came from, even if the technology to trace them keeps getting better. The people who build these tools could add that little “Here’s who this came from” line tomorrow. What likely stops them is a legal system that makes giving it to you too dangerous to try.

What I actually want

There’s a smaller version of the hat tip that doesn’t run into any of the legal or technical issues I’ve been talking about. Even when a model can’t say which book an answer came from, when it honestly can’t be traced, it can still point you to the authoritative sources on the subject, the ones worth reading to go deeper. That isn’t attribution in the strict sense; nobody has proven those books shaped that answer. But a model trained on a topic was almost certainly trained on the standard works for it, so the correlation is strong. For the reader it does the useful thing anyway by saying where to go next, and it’s something the AI labs can do today.

Which brings me back to that check from Anthropic I may or may not be about to cash. I meant it when I said I’d rather have the credit. What my writing has done for my career matters more to me than what it’s done for my bank account, and I would happily cut that settlement check in half if it meant twice as many people found their way to the work. A hat tip does exactly that, and it’s what I actually want, if it opens up new exposure to my work and new opportunities for authors like me.

The part I keep turning over is that the hat tip is more possible than the people who say “impossible” want to admit. I made a toy version, and researchers at AI labs are pushing these ideas a great deal further than I can. A key part of what stands in the way now is a legal question: whether good faith can survive contact with $150,000 a work. I don’t know how that one comes out. But I’m fairly sure it isn’t the math, and the math was supposed to be the hard part.

What a User Story Actually Costs in a Dark Code Factory [Radar]

Between March and July 2026, I built a production application of 861,601 lines of code. This included 696 user stories and 779 merged pull requests over 105 days, but I can’t tell you what it cost.

The first version of an autonomous SDLC framework driving Claude Code did the work. That generation didn’t record usage, and Claude Code’s default 30-day transcript retention erased the only other record. The bill isn’t approximately known because it’s gone. If measurement isn’t part of the pipeline, it doesn’t exist.

The instrumented factory

The second generation of the factory persists its own bill as it works. Every stage attempt writes its tokens (input, output, cache read, cache write), its cost, its model, and its failure category to a ledger. That first-generation application left no records because its framework kept none; this one cannot run without keeping them.

I should define the unit before pricing anything. In this pipeline, a story is the agile artifact we know. It’s a small requirement decomposed from an epic with acceptance criteria that give the agent its stopping condition. The Definition of Done is how the machine knows it’s finished. We humans batched stories into sprints to manage the delivery. In this pipeline, a story-build is just a story going through its own full delivery cycle. This includes writing tests first, the build, a coverage gate, review by a dedicated reviewer agent, and the merge. It also includes bugfixes and repeat requests when an agent’s reply is malformed. Up to five stories are in flight at once in isolated git worktrees. The dataset for this article covers every story the factory built in one repository between June 25 and July 18, 2026. This includes 17 runs, 193 story-builds, 374 stage attempts, and 336 session logs. The June runs used Claude Opus 4.8 and the July runs used Claude Fable 5, while Claude Haiku 4.5 handled smaller parts.

Usage lives in the ledger and the raw session logs, but they disagree. The logs are the ground truth for a reason I’ll explain later. The factory (claude-code-config) and the repository it built (local-code-bench) are both public. The methodology section includes the CSVs and extraction script so you can check every number. The production application from the opening remains private, so only its ghost appears here.

What a story costs

The factory consumed 595.7 million tokens to ship 77 stories, 7.7 million tokens per delivered story: at list prices for those models, $837.53, or $10.88 per story. The numerator includes every token thrown away, the five stories that ended FAILED, the 22 failed stage attempts, the bugfix and re-ask loops, and the retries; the denominator counts only stories that shipped.

In a previous essay I estimated my factory’s stories at “a few dollars to a few tens of dollars.” The meter’s answer is $3.02 to $43.24 with a median of $9.56, so my estimates were valid. Two findings in the table surprised me. Story points barely predict cost because the medium and large bands are only 8% apart at the median. The most expensive story was $43.24 for a 3-pointer that hit a review retry and a bugfix loop. The wall-clock mean is roughly two and a half times the median because the overnight run hit the subscription plan’s rate-limit window twice and stalled for hours. This was a billing artifact rather than an agent one.

View Median Mean Min Max
Tokens per story (millions) 6.55 7.84 1.99 24.48
Wall-clock per delivered story (minutes) 19.7 48.8 7.6 296.0
Cost per story (USD, API-equivalent) 9.56 11.02 3.02 43.24
Table 1. Per story-build with attributable cost (n = 76, including the 5 that failed; six delivered stories returned no usage envelope and appear only in the headline denominator).

Prices reflect Anthropic’s list rates as of the run dates.

696 stories of the ghost application at this rate is roughly 5.4 billion tokens. We’ll never know.

The factory is a reading machine

Here’s where my estimates were off. In that essay’s worked example, I priced a story as if cache writes were free. They aren’t, and they aren’t even small.

An agent resends the same instructions and repository context on every turn. The API caches that stable context, which makes a cached reread cost a tenth of fresh input, though it charges a premium to write new content into the cache.

95.4% of all tokens are cache reads. The factory rereads about 73 cached tokens for every new token it writes or receives. A dark code factory is mostly a reading machine that occasionally types.

The cost side of Table 2 shows where my estimate broke. Cache writes are only 3.3% of tokens but 31.2% of the bill. Cache traffic overall is 77% of the cost. Fresh input is merely a rounding error at 1.6% of the cost.

Class Share of tokens Share of converted cost (USD)
Cache reads 95.4% 45.9%
Cache writes 3.3% 31.2%
Output 0.9% 21.3%
Fresh input 0.4% 1.6%
Table 2. Token classes across all 374 stage attempts.

This shape is not a quirk of one pipeline. The cache-read share is 96.4% in my interactive framework-development sessions and 91.9% in the ghost’s surviving scraps: three independent samples, two framework generations, two working modes, same shape. It looks like a property of how agentic development consumes compute.

The practical consequence surprised me most: Cost optimization in an agentic pipeline is cache management, not prompt shortening. Context discipline, cache-tier awareness, and orchestrators that don’t stuff their own windows move the bill. Trimming your prompt wording does not.

The honest denominator

There are two ways to read the failure number. The narrow reading, attempts marked FAILED, is 5.0% of tokens. The honest reading, all rework, retries, bugfix and re-ask loops, plus the crashed sessions that streamed tokens and died, is about 13%. Public cost claims rarely say which reading they use.

Only 34 of 76 stories were clean first-pass, but rework stays cheap because retries are small relative to builds. I count the 13% as a quality bill because the gates catch problems.

I found a bug while dissecting the raw data that showed my meter lied. The ledger missed a sixth of real consumption, recording $694.65 against the logs’ $837.53. When a result envelope failed validation, the controller’s re-ask overwrote the original stage row’s usage. This erased the expensive failed session from the books, and crashed sessions never wrote back at all. 57 attempts were affected, which is why the session logs are the ground truth.

The measurement system needed auditing just like the code it measures. So I filed the bug against my own factory and let its fix pipeline handle it. It decomposed the report into three defects and repaired the overwrite and the model recording in one merged PR (issue #480, PR #482, 3,200 tests passing). The factory audited its own meter and fixed most of it, while the work to recover spend from crashed sessions is queued as open work.

Who actually pays

The marginal bill for all of this was zero. I run a $200-per-month Max 20x subscription, which is why every dollar in this piece is labeled API-equivalent.

The subscription’s real currency is quota rather than money. The overnight run stalled twice on the 5-hour rate-limit window, and ten dispatches waited 3.3 to 4.2 hours before auto-resuming. On a flat monthly fee, time is the fence.

One rolling month of measured work across all three codebases totals about $1,088 API-equivalent against the $200 fee, more than five to one, and that’s a floor, because older transcripts are purged. This proves a pricing asymmetry against list rates, not a subsidy: List price isn’t Anthropic’s cost; it includes their margin.

Can a professional, or a small firm, legitimately run on these flat fees? Nothing in the plan terms stops them. There’s no revenue test and no company-size cap. The line Anthropic draws is contractual, not financial. Individual seats run under consumer terms; a Team premium seat at $125 buys business terms and central administration, but roughly half the quota per dollar. Climbing the subscription ladder buys governance, not tokens.

This flat-fee window won’t stay open forever; quotas tighten and tiers reprice. A factory that meters itself will notice the day the trade turns. One that doesn’t will simply feel slower and poorer, without knowing why.

What the meter changes

I discovered while analyzing the data for this article that every number was produced with model routing switched off. Mechanical merges burned premium-model prices on Haiku-grade work, which accounted for 12.3% of all tokens. This means 7.7 million tokens per delivered story is the unoptimized rate. The article you’re reading found the bug, and the fix is already in the factory’s backlog.

A second find came from pointing the meter at myself. Writing the factory’s specifications—its epics and stories, in interactive sessions—consumed about 190 million tokens, which is roughly 25 stories’ worth of consumption (about $160 in converted terms). When implementation is this cheap, the code is no longer the expensive artifact. The difference between the $10.88 story and the unknowable 861,601 lines is that one pipeline wrote its bill down.

Methodology
Dataset, extraction script, and assumptions A1 to A10:
gist.github.com/fxmartin/979da2a47fbbbac6d72d238073e23491.Project-a = local-code-bench (full data in the gist); project-b = claude-code-config (aggregates only; session detail reserved for a companion piece); project-c = a private production repo, withheld.Ground truth is each session’s modelUsage envelope, with the ledger as fallback; 317 of 374 attempts are fully priced; unmeasured attempts are documented, never imputed.
Prices are Anthropic list, fetched 2026-07-19: Opus 4.8 $5/$25 per million tokens in/out, Fable 5 $10/$50, Haiku 4.5 $1/$5; cache reads at 0.1x the input rate; 1-hour cache writes at 2x.
All waste is included in every total; the per-story figure divides total spend by 77 delivered stories ($10.88, or $11.80 excluding six deliveries that returned no usage envelope).
The ledger’s model column was NULL on historical rows; attribution comes from session logs, and model recording is fixed for future runs in PR #482.
Primary figures are in tokens; dollar figures are conversions at the prices listed. All dollars are API-equivalent; actual billing was a flat-fee subscription.

Radar Trends to Watch: September 2026 [Radar]

Coauthored with Claude

Midway through each month, I think “The next Trends is going to be small. Not much is happening.” This is the first time that I’ve been right. Was everyone on vacation in August? Am I becoming jaded? There were many model releases, though few of them seemed significant. Then again, it may be time to get over the one-upmanship by the frontier vendors and spend more time thinking about the myriad small and open-weight models. Every month, the best laptop-scale models (30B and smaller) seem closer to the leading frontier models. And every month, we’re seeing organizations realize that paying premium per-token prices for the latest frontier models gives at best a small advantage over the best open-weight models.

AI models

Capability and model size are decoupling. Several models here run comfortably on a laptop or a single accelerator while claiming performance close to much larger frontier systems. While it can be hard to work with a smaller model without thinking that you’re choosing “second best,” the biggest model isn’t always the right choice. Major releases aside, the most important news from August might be Anthropic’s deployment of watermarks for text. If the watermarking scheme works, it will be possible to tell which parts of an article like this were written by AI.

  • OpenAI has announced that, beginning November 12, 2026, Cursor will no longer have access to their models.
  • A mysterious model named Ox Alpha quickly became the most heavily used model on OpenRouter. Z.ai recently confirmed that Ox Alpha was GLM-5.3-Flash, a 320B open weight model that claims performance similar to Opus 4.8 and that has been deployed running entirely on Chinese chips.
  • IBM’s Granite 4.2 is a small open-weight reasoning model that has been tuned for multistep tasks. It comes in 3B, 8B, and 30B sizes. It’s another model making the argument that small local models can be competitive with frontier models. 
  • The team that developed Ornith-1.5 claims that they have made a major step toward self-improvement. The model supports a self-improvement loop in which it proposes new tasks, generates solutions, and uses reinforcement learning to apply the results to itself.
  • DeepSeek-V4-Flash-Vision adds vision to DeepSeek V4’s capabilities. Images can be mixed with text; the model can describe images, extract text from images, and do other things that we expect from a leading LLM.
  • Anthropic is now embedding watermarks into all of the text that its models generate or edit. The watermarks are apparently based on word choice; the algorithm “changes the source of randomness used to pick words.” We don’t (yet) know of any tools to detect the presence of a watermark, but there are already tools that claim to remove them. It isn’t clear that these tools work.
  • A new benchmark, SWE-Bench ProMax, tests the ability of LLMs to do large-scale refactoring. It’s a multilingual benchmark based on real-world code in seven languages.
  • Qwen3.8-27B is a small open-weight model that claims performance similar to Opus 4.6 max. It runs easily on a reasonably well-equipped laptop.
  • Google has released Gemini 3.7 Flash, claiming improved coding and debugging.
  • Z.ai has released GLM-5.3. It’s very similar to GLM-5.2, differing only in that it has received additional post-training. Z.ai claims that it’s better at code generation and long-running tasks.
  • NVIDIA has released Nemotron 3.5 Lightning, an open-weight mixture-of-experts model with 30B parameters and 3B active parameters. Like many recent models, it’s optimized for long-running agents such as OpenClaw.
  • Cactus Compute has released Needle 2, another small model that’s worth a look. It’s a 45B-parameter model that has been designed for “tool calling, device use, and structured extraction.” Needle requires only 28 MB of RAM, so it will run on many laptops and small devices and microcontrollers.
  • Meta open-sourced Muse Glimmer, a 30B model designed for agentic applications. It can run on consumer hardware. Meta also released Muse Code and Muse Spark 1.2. Muse Code is a model designed for code generation. It implements an agent loop and a local event log that allows exact replays and restarts. Spark is a general-purpose model with near-frontier performance—Meta describes it as “a step towards the frontier.”

Software Development

Features that we associate with agents or harnesses, such as the ability to spawn subagents and delegate tasks to less-expensive models, are continuing to find their way into the models themselves. There’s also a countertrend: Individuals and organizations are building their own agents that are closely integrated into their working environment. Are we headed for walled gardens controlled by the leading providers? Or will a thousand flowers bloom, each reflecting an idiosyncratic way of working with AI? Don’t avoid tools from the major AI labs, like Claude Code and Codex, but don’t lock yourself into thinking that they’re the only option.

  • DeepSeek has open-sourced Harness, its agent harness. What makes Harness unique is that almost everything is a plugin, so it’s extremely flexible. It can be used with many models, and can delegate work to Claude Code and Codex.
  • TrueForge is an open source agent harness that can be used with any model. It includes tools to debug and govern agents in production.
  • Computer History is a new feature of ChatGPT Work and Codex that records how you use your computer. It’s similar to Microsoft’s controversial Windows Recall, but it’s based on key clicks and other actions rather than screenshots. Data is stored locally rather than sent to OpenAI. It’s off by default.
  • Zed’s Delta is a “multiplayer environment for coding with agents and reviewing what they build.” It’s a new take on Git and GitHub, designed specifically for the AI world. The company’s big insight is that the conversation about the code is as important as the code itself, and must be captured along with the source.
  • Companies are now building their own agents (a.k.a. harnesses). While they’re still using AI services from Anthropic, OpenAI, and other providers, many organizations are finding that custom agents are a useful way to incorporate their own workflows into an AI-driven development process.
  • Anthropic has added cross-session messaging to Claude Code. Messaging allows one agent to inform others about actions it has taken that might affect another agent’s work, reducing the need for a programmer to act as a communications medium.
  • Agent Plugins is a standard for extending agents with plugins built from reusable components. It’s supported by OpenAI, Microsoft, Cursor, and AWS, though not by Google or Anthropic.
  • OpenAI now has a hardware product. Codex Micro is a small terminal (certainly the wrong word) for remote AI work; it has 13 keys, a rotary encoder, a touch sensor, a joystick, and some status lights, and it hints at voice control (though I see no mention of a microphone). Its purpose is to allow you to control Codex workflows remotely.
  • “Just because a feature is easy to build doesn’t mean that it is worth shipping”: Good advice on using AI effectively for software development.
  • An update to the Model Context Protocol (MCP) addresses one of the most significant barriers to adoption by making it stateless.
  • Software developers who didn’t grow up with Linux frequently haven’t discovered the art of the command line. Atomic Object recommends four terminal tools: Ghostty, tmux, lazygit, and lazydocker. Try one of them—or all.

Infrastructure and operations

Optimizing AI usage has become its own discipline, sometimes called “tokenomics.” Tokenomics can’t be separated from safety, which has also been much in the news. Disposable containers built for agents, GPU scheduling that treats accelerators as a heterogeneous pool, and infrastructure providers publishing how they actually serve open models at scale all match workloads to hardware without waste or risk. AI performance isn’t just about models; it’s about infrastructure. Understanding how the model is run will prove more important than the model’s specs and benchmarks.

  • Taalas has built a chip that incorporates Llama 3.1 8B. All the weights are on the chip, which can’t be used for any other models. It’s extremely fast. Whether single-model chips make sense when new models are released almost daily is a good question.
  • Docker Sandboxes are isolated disposable containers that are designed for running AI agents safely.
  • Kubernetes’s Device Resource Allocation (DRA) makes it much easier to schedule jobs on heterogeneous clusters of GPUs.
  • Cloudflare has published a description about how it runs the Kimi and GLM models at scale. It’s worth reading.
  • WARP (formerly Waste) is an inference engine with one purpose: run Kimi K3 on a laptop. K3 is a 2.8T parameter model with 104B active parameters, typically requiring a small fleet of GPUs. WARP requires a 64 GB Macbook Pro with a few TB of disk. It’s slow (about 0.5 tokens/second), but it runs.

Security

Security work is inseparable from AI development, not a layer added afterward—but security professionals have been saying that about traditional software for years. Artificial intelligence is spawning new attacks as well as new defenses. While it’s always fascinating to look at new attacks, the most significant shift is in defense: rethinking security in terms of actions and resources rather than user identities, a change we’ve also covered on the Radar blog.

  • Anthropic, OpenAI, Google and many other AI companies have signed an open letter saying that defense against cyberattacks has to become a priority for governments, and that governments and organizations need to act collectively to build defenses. 
  • The Chrome browser has adopted device-bound service credentials (DBSC) to prevent session cookie theft, a critical step in account takeovers. DBSC stores an encryption key in a secure enclave or other trusted storage.
  • There is now a Python library that supports ML-KEM and ML-DSA, NIST-standard key encapsulation and digital signature algorithms for postquantum cryptography.
  • Simon Willison has published a timeline of OpenAI’s inadvertent attack against HuggingFace. His timeline is based on a postmortem that OpenAI presented at Black Hat. OpenAI has published a full incident report.
  • The ChainDrop credential stealing malware has compromised over 1,300 packages on npm, the Node package manager. The malware is self-propagating, and compromised packages appear to have legitimate provenance.
  • OpenAI has open-sourced Codex Security, a command-line tool and API that uses ChatGPT to analyze code for vulnerabilities. Their documentation says that the CLI and API are both in “limited beta,” possibly because of the model used to do the analysis.
  • Context Collapse is a three-part series that discusses context poisoning attacks against Copilot, culminating with self-propagating attacks against Word. Microsoft collaborated on the analysis and mitigations.
  • Google has introduced Beyond Zero, a new security model that takes zero trust a step further. Beyond Zero makes decisions on the basis of specific actions and resources, not just users or applications. Decisions are governed by both static policies and dynamic controls that can respond to changes in the environment.

People and Organizations

How do people use AI? Does AI use lead to greater productivity? We know surprisingly little about either question. We’re still learning how to use AI effectively; the best metric isn’t a simple measure of productivity but whether you can do things you couldn’t do before.

  • The AI Observatory collects data about how people use AI. What we know about the ways people use AI is surprisingly limited. We know that usage patterns vary from model to model, but model providers only publish the data they want to see; we still don’t understand the big picture.
  • How do you measure AI productivity? “Why AI Productivity Is a Faulty Metric” has some good ideas. Develop metrics around code quality and whether AI-generated code survives review, rather than counting lines of code.

Web

There’s now a specialized version of ChatGPT for teens; a site that serves different content to scrapers and humans; and an AI-generated animation of the start of The Lord of the Rings. The web is proving that it can adapt to anything that’s thrown at it. It’s where we learn and play, and AI isn’t changing that.

  • OpenAI has launched ChatGPT for Teens, a specialized mode for users between 13 and 17 years old. This new product stresses learning and studying rather than using AI to get answers, has stronger content safeguards, and tries not to become a surrogate for human interaction.
  • A theremin in the browser is something you don’t see every day! Use your mouse or your webcam to control it.
  • TIME magazine has started giving AI scrapers a minimal Markdown version of articles with additional advertisements. The site’s behavior depends on the User-Agent HTTP header. Some user agents are denied access, while humans are given HTML with graphics and layout.
  • Tired of pelicans on bicycles? Andrej Karpathy had Claude Opus animate the first paragraph of The Lord of the Rings with Three.js. The result isn’t great, but it’s certainly fun and points to some areas where the best current models aren’t yet strong enough.

Biology

  • The National University of Singapore’s Life Sciences Institute now has a server rack where the computational power comes from 16 million lab-grown human neurons. Life support is a problem, but power consumption is a small fraction of the power required by GPUs.
  • Claude has successfully run a complete protein design workflow, generating new designs for proteins that have been synthesized and tested in labs.
  • There could be a fly on your desktop. This one is driven by a simulation of over 23,000 neurons from a fly’s connectome. It behaves like the real thing (macOS only).

Zero to Agent in 30 Minutes: Building a Financial News Agent with Jayeeta Putatunda [Radar]

In this episode of Zero to Agent in 30 Minutes, Jayeeta Putatunda, forward deployed AI engineering lead at Turing, builds a multi-agent workflow that turns a daily flood of financial headlines into a structured analyst briefing. Financial analysts already have deep internal research, coverage assignments, and market views, but keeping that context current as new information arrives every day and surfacing which of it actually deserves an analyst’s attention is a harder problem.

How to build a source-backed briefing agent, step by step

  1. Define the scope. Start by setting the analyst’s focus area, research questions, and time window, whether that’s the last one day, seven days, or 30 days. Narrowing the scope up front, including a list of preferred sources, keeps the agent’s web searches directional instead of generic.
  2. Plan the coverage. A coverage planner agent breaks the research question into discrete sections, such as market backdrop or company catalysts, so the search that follows can run in parallel rather than one long sequential query.
  3. Search and gather. A news researcher agent runs multiple queries against the preferred sources first, then falls back to a general search if the preferred sources don’t return enough results.
  4. Validate the sources. A validation agent checks each link for a working, clickable URL and a correct publication date, removes duplicate stories covering the same news, and filters out paywalled pages that won’t return usable content.
  5. Generate the briefing. A briefing writer agent assembles the validated developments into a set structure, including an executive summary, key bullet points, and a section-by-section breakdown of what each development means for the analyst and what to watch next.
  6. Capture feedback into memory. A feedback agent logs corrections such as formatting or terminology preferences and saves them to a memory database, so the next briefing run applies those preferences automatically instead of requiring the analyst to re-prompt.

Jayeeta built the entire stack on open source models so newcomers can run it without an API key, and she recommends starting with a smaller model before scaling up. The takeaway extends well beyond finance. Building single-purpose agents rather than one large agent that handles every task means a failure at one stage doesn’t force a restart of the whole pipeline, and each agent’s output stays easier to trace and debug.

The full code base, including the sample data and the UI shown in the demo, is available in Jayeeta’s GitHub repo, so readers can clone it and run the briefing agent on their own systems.

Coming this week

This week, Maxim Salnikov joins Zero to Agent in 30 Minutes to build a supply chain for agent context. He’ll show how to source approved packages from a trusted registry, pin and hash-verify them on any harness, and enforce org policy with a CI gate that can’t be bypassed.

Architectural Guardrails for AI-Generated Code [Radar]

Consider a composite of a failure pattern that’s becoming increasingly common on teams that have scaled AI-assisted development past a handful of enthusiasts.

A staff engineer named Priya opens a pull request. The PR is 340 lines and adds an endpoint that writes to the customer table directly, bypassing the internal customer service API. The code is clean. The tests pass. The AI coding agent that wrote it has been the team’s most productive contributor for six months. The reviewer, three months into the team, approves. The PR ships that afternoon.

Two weeks later, during an integration debug, someone notices that customer records written by that endpoint are missing audit-log entries. The audit hooks live in the customer service API. The team banned direct database access two years ago for exactly this reason and wrote an architectural decision record (a versioned markdown document, one of dozens the team has accumulated, that captured what was decided, why, and what was superseded) to memorialize the rule. The engineer who wrote the ADR has since left. Nobody on the current team remembered the decision. The document was sitting in a directory the current workflow never touches.

The endpoint gets rewritten. Audit gaps get backfilled. The team spends most of a sprint on cleanup. Nobody calls this a failure of AI-assisted development. The AI wrote functional code. It just wrote code that violated a decision the team had already made, in a document the AI had no view into.

This is a specific failure mode. It is not a hallucination since the output was grounded, syntactically valid, and idiomatic. It’s not a model-quality problem since a better model on that same prompt wouldn’t necessarily help if the decision remained absent from its context. It’s a memory problem. Not the model-internal sense of context window, but the organizational sense. The ADR was available in the repository. It was never surfaced to the agent, and the reviewer had not read it.

This piece is about naming what would need to exist for that PR to have been caught, or better, never written in the first place.

The rework signal

Priya’s PR illustrates one source of a broader rework problem. Faros AI, an engineering analytics platform, published a report in 2026 based on telemetry from more than 22,000 developers across 4,000+ teams. AI-code acceptance rates had risen from 20% to 60% between periods of low and high AI adoption, while code churn (i.e., lines deleted within days of being added) had increased 861% over the same interval.

Faros is careful in how it frames the churn number. The increase may include not only rework but productive refactoring, previously unaffordable cleanup, or faster iterative improvement. But the number still exposes a gap between code entering the repository and code that survives there. A gap that has widened, at scale, alongside AI adoption. Anecdotally, engineers at teams running these tools describe returning to code they had already approved to fix issues that were not obvious at review time. Architectural drift, i.e., code that individually looks fine but collectively pulls the codebase away from where the team agreed it should go, is one plausible contributor.

I’ll call the discipline of preventing this failure mode architectural drift prevention: keeping generated code aligned with the architectural decisions a team has already made.

The pattern-matching explanation for that gap has been that the AI isn’t good enough at writing code yet, and a better model will close it. Watch enough review cycles at a team running AI-assisted development for a year, though, and a different pattern emerges. The generated code isn’t obviously bad. It compiles. It passes tests. What it doesn’t do is respect decisions the team has recorded but the AI has never seen.

Why current tools sit at the wrong layer

Teams commonly reach for several existing mechanisms to close this gap. Most of them are the wrong shape for the problem.

The closest attempts are files like Cursor Rules and CLAUDE.md, i.e., free-text markdown documents dropped into the project root so agents read them as standing instructions. These are the right instinct at the wrong resolution. Free text has no precedence rules, no versioning, no lifecycle. When one rule contradicts another, nothing arbitrates. When a rule is violated, nothing catches it. These are documents in the shape of configuration.

Linters and code formatters operate a layer below. They enforce that a function has a return type annotation or that a variable name follows a convention. They can’t enforce that customer-data writes must go through the customer service API, because that’s not a syntactic property. It’s a semantic decision recorded in a document the linter has no reason to read.

Dependency scanners, SCA tools, and lockfile audits close a related gap; they catch known vulnerable libraries, license violations, and outdated versions. They would’ve flagged nothing about Priya’s PR. Every dependency in it was current, had no known vulnerabilities, and was approved. The violation was a routing choice inside the team’s own architecture, not a library problem.

LLM-assisted code review is the fashionable answer. A second AI reads the pull request and comments on it. This catches surface issues like a swallowed exception, an off-by-one bug. It doesn’t catch drift, however, because the second AI has the same problem as the first: no durable access to the team’s recorded decisions. Two probabilistic passes over the same blind spot are not one deterministic pass with sight.

Human review is the last line, and it works when the reviewer knows the history and has enough time to inspect the change. Agentic development changes both conditions. Agents can produce multiple implementations, pull requests, and revisions in the time it takes a human reviewer to assess one. Code output scales; review attention does not. Asking humans to compensate by reviewing more and harder simply moves the constraint downstream.

None of these tools are bad. They’re just the wrong layer for the drift problem.

What the missing layer would need to do

The missing layer connects recorded engineering decisions to the tools that generate, review, and merge code. Its job is to make the team’s accumulated architectural decisions machine-readable, injectable, and enforceable. I’ll call this layer engineering governance, borrowing the term from adjacent categories like data governance and security governance, where it means the same thing: A structured way for an organization to make explicit and enforceable the rules it already implicitly follows.

At the shape level, the layer needs to do four things.

  • It needs to hold decisions in a structured corpus with precedence and lifecycle metadata so that a tool knows which ADR wins when two conflict, and which decisions are still active.
  • It needs to retrieve from that corpus reliably, so the same code produces the same set of relevant decisions each time.
  • It needs to inject those decisions into the AI’s context before the agent writes code, so the output accounts for them rather than needing to be caught afterwards.
  • It needs to enforce them in continuous integration, blocking or flagging code that violates them, with the verdict traceable to a specific ADR, a specific term that matched, a specific rule.

The critical property that ties those four together is a discipline about where probabilistic reasoning is allowed. Probabilistic systems may retrieve or recommend. They shouldn’t independently determine an enforcement verdict. Every block or warning has to reconstruct from artifacts on disk. That’s the code, the ADR, the retrieval log, the rule text. Then, when a developer asks “why did this fail?” or an auditor asks “on what basis?” the answer isn’t “the AI said so.” That’s what makes the layer defensible in the situations where defensibility matters: regulated environments, compliance review, incident retrospectives, and the everyday conversation where an engineer has to justify a blocked merge to the person whose code was blocked.

AI may help surface relevant decisions, but it shouldn’t be the final authority. The enforcement path must remain deterministic: every verdict should resolve to explicit rules, observable evidence in the code, and a result that another person can reproduce.

Defining the boundaries

Naming a category clearly requires naming what falls outside it. The engineering governance layer, done right, is not any of the following:

  • An agent. Nothing autonomous. Nothing iteratively deciding what to do next. The layer runs when called, produces a verdict, and stops.
  • Memory in the retrieval-augmented-generation sense. Retrieval methods can vary and may be probabilistic; the enforcement verdict cannot. The corpus of decisions is the source of truth.
  • Code reviewed by AI. Reviewing generated code with a second model doesn’t address the underlying problem, which is that the first model had no access to the team’s decisions.
  • Vendor-locked. Production teams increasingly run Cursor, Claude Code, GitHub Copilot, and Codex in parallel, sometimes on the same repository. They may also use open-weight or self-hosted models for sensitive codebases and internal workflows. Engineering governance must remain independent of both the coding tool and the underlying model, so the same architectural decisions and deterministic enforcement apply across them all.

The emerging engineering stack

The AI coding stack is assembling itself in the open, without anyone architecting it. Each of the major coding assistants is specializing on a different piece of the loop; editing, autonomous execution, review, planning. The layer this stack doesn’t yet have is engineering governance.

The need isn’t limited to regulated industries. Any team that values reliable engineering, long-term product quality, and the trust of its customers needs to know that generated code respects the decisions the system depends on. In higher-risk environments, that requirement becomes formal and auditable. Elsewhere, it’s simply part of building software responsibly. In both cases, the enforcement path should be deterministic and traceable, with the rules, evidence, and verdict open to inspection rather than hidden inside another model or a proprietary black box.

For engineering leaders reading this today

Priya’s PR is a pattern, not an incident. Three things worth doing this quarter, regardless of tooling choices.

  • Audit your architectural decision records. Are they current? Do they explicitly name the decisions they replace? If your team doesn’t write ADRs, this is the moment to start. The tooling that will exist in twelve months assumes structured architectural decisions as input.
  • Choose the enforcement posture deliberately. The system can either warn the developer and let them continue or block the change until the issue is resolved. Both approaches can work, but the team should agree on which applies instead of leaving the decision to each developer or each pull request.
  • Don’t assume a more powerful model will solve this problem. Better models can improve the code they generate, but they still can’t follow architectural decisions they’ve not been given. Preventing architectural drift requires changing the surrounding system, not simply waiting for the next model.

The productivity gains from AI-assisted development are real and worth having. So is the architectural coherence teams spent years building. Engineering governance is the layer that lets you keep both.

What You Measure [The Daily WTF]

Rachel joined a new team which was proudly "metrics driven". When she first met with her boss, Zane, he explained his thinking.

"We need to be data-driven to make good decisions, right? We're a manufacturing company. We make widgets. At the end of the day, we need to make the most widgets for the lowest cost of goods sold. So we track that, and that feeds into every decision."

The team oversaw an automated production line, which meant the software was a mix of robotics, embedded firmware, high-level web based monitoring tools, and thickets of dreaded PLC code. And because you can't build an entire factory for test purposes, they only way they could test real-world scales with real-world data was to roll changes out to production. They could simulate, they could run tests on subsets of the system, but a change in the production line software couldn't truly be validated until it rolled out into the real world.

Rachel's first task on the new team involved making some changes to their metrics dashboard. It was viewed as a good way to get her feet wet with the new team. As it turned out, the metrics dashboard was a Google Sheet, with a complex series of formulas that involved multi-level INDEX functions- essentially querying the spreadsheets like they were a database. Why not use an actual database? Oh, they did — six actually — but the company obeyed Remy's Law of Requirements Gathering: "no matter what the requirements the users ask for, what they really wanted was Excel". The database data was pulled into the spreadsheet for reporting.

Now, a complicated sheet pulling in data from not one, but six different databases, they must have a pretty complex model to explain how changes to their software would impact productivity. And since they needed to model the software to make predictions about how it'd behave in production, that model must be extremely useful.

Of course it wasn't. The only metrics they tracked were output metrics, variations on "widgets produced per unit time". There were some performance metrics, so you could maybe potentially identify "oh, our overall throughput dropped because unit 5 became a bottleneck and started taking 1.5 extra seconds per widget", but nothing that actually helped you understand how the complex system made decisions. Or even why unit 5 was taking longer.

For example, there was an automated quality control scanner. It examined widgets as they came off the line, and rejected defective ones based on a computer vision algorithm. Did that subsystem record why it rejected a widget? No, it did not. The CV model was able to tag widgets with a defect category based on what it saw, but that information didn't get recorded anywhere. In fact, it didn't even record how many widgets got rejected. The only way to know was to have an operator on the assembly line count widgets in the bin manually. Since that ate up a bunch of an operator's time, it never happened unless the developers begged for it. And since the operator still couldn't answer the question "why was this widget rejected", it wasn't all that useful anyway.

Every change to the software was scored against the overall output metrics. This meant that when Rachel was ready to push out her first software change, something that would record how many widgets were rejected and why, whether or not it could be deployed was dependent on seeing the change improve, or at least not regress, the widgets-over-time scores. But the widgets-over-time were a noisy metric; it varied based on which operators were working any given shift, or based on supply chain constraints. Or sometimes, based on when one of the machines was last calibrated- theoretically something that happened on a set schedule, but really was up to the operators. This meant the first three times Rachel rolled her code out for a test run, the metrics regressed. Nothing she changed should have impacted the metrics, but the metrics regressed due to environmental issues.

This meant making a simple change could take weeks, because you could only do final validation on the real system, which means you had to mark off a block of time for a test run, you could only run a handful of tests a day, and if metrics regressed you had to account for that before you could release the software for actual production use.

Over the first few months, Rachel added instrumentation to the code. Anything along the way to generating an output widget, she recorded. The hope was that once they had enough data, they could build a useful model of the system. Unfortunately, Zane had other ideas.

"So, you haven't improved our metrics," Zane said. "Which, I remind you, we're a metrics driven organization. Every change needs to improve our metrics."

"Sure, but I'm gathering more data so we have a better idea of what makes our metrics tick. We don't know why our system does some of the things it does, because we don't record any logging about the decisions it makes."

"Right, but we already gather the key metrics."

"But you don't gather the data that tells you why those metrics are what they are!"

"Sure," Zane said. "But those aren't our key metrics."

That, unfortunately for Rachel, was where things landed. Understanding their complex system was a low priority. Pushing top-level metrics without understanding what fed into them, that was the priority. That didn't mean Rachel was powerless: any time she made a change that she thought might help the top level metrics, she also made sure to add instrumentation that explained how that change behaved. It was the compromise that kept Zane happy: she released features that impacted the top-level metrics, but she also made the system more observable.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Representative Line: So Much Room [The Daily WTF]

Today's representative comment ran out of room.

int maxLen = getColumnSize(session, "audit", "text_value1") - 16; // Leave some room for

No, it isn't continued on the next line and just got trimmed out, except perhaps by a careless merge. This is the entire comment.

Clearly, written by David Chase, the creator of "The Sopranos".

There are so many things we might be leaving room for. We could leave some room for dessert. Leave some room for activities. Leave some room for the holy spirit. Leave some room for improvisation.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Tales from the World Cup [The Daily WTF]

All I can say in response to our anonymous submitter's story is, ALMOST?!

With the World Cup being hosted in North America this year, I remembered this story that happened back in 2014. At the time I was working in Brazil, for a company that builds software systems for public services. And, with the World Cup being hosted there, in came the opportunity for local agencies to invest in modernization, with pretty much a blank check to get new services, so long as it was deployed before the end of the World Cup. And so the sales people did what they did best, and went around trying to upsell whoever would be willing to buy — no matter our actual capacity for developing the things.

So it was that I was pulled into this new fancy digital system for the police force of a state capital. However, we had only about 4 engineers available, and what they sold was a project estimated for a team of 20, to be delivered in 3 months, with no room for delay. And it wasn't just our core C&D product, but this massive thing with customized public-facing websites, live tracking of the position of different police cars delivered to a tablet in each car, automated reporting, etc.

Germany and Argentina face off in the final of the World Cup 2014 -2014-07-13 (5)

First thing: We received a pile of 24 resumes, and were told to choose 16 of those. Maybe 3 were acceptable, but we had to waste 1 month hiring and onboarding 13 other people who were worse than useless. Classic man-month problem. We eventually had to tell management that nothing would be delivered this way, so they did the very best next thing: fly us to this other city, so we could work embedded there, in full crunch mode for the delivery. We pretty much worked 12+ hours a day, 7 days a week, for those next 2 weeks.

Another situation: they wanted this system where people could take a photo of an incident in progress, and submit via this app + website, to be verified by an operator in real-time. We nicknamed it the "dick-pic encyclopedia." Even worse, we only had the budget to run a single server, so this thing receiving public traffic would live in the same system that was tracking police car locations. Luckily they were convinced it was a bad idea so it was only ever online for a short period of time.

Next, was the police car tracking. This was done by a tablet installed in each car, which would be sending and receiving location information. But, 1 week before our deadline, we were hitting a serious bug: everything was working when we ran the tests ourselves, but the cops would report very weird bugs when testing it in the field. So we asked to do some field debugging, and I went on a ride-along. Things were working pretty much fine everywhere, so I asked to be taken to where he remembered seeing the tablets fail—to which the policeman just decides to drive off straight into one of the favelas around the city. I guess I can cross out "doing debugging in a police car passenger seat in a notoriously dangerous neighborhood" off my bucket list. Root cause: turns out cellphone connections would be pretty spotty in those areas, which we weren't handling properly.

Either way, we delivered something on time that was severely below spec, and very much over-budget. Company tried to squirm out of paying overtime (was told that we would gain "prestige" by doing those extra hours), but I put my foot down and left that job shortly after. Last I heard they actually got sued for this and a bunch of similar projects, and almost went under.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

[$] LWN.net Weekly Edition for September 3, 2026 [LWN.net]

Inside this week's LWN.net Weekly Edition:

  • Front: Python JIT; rnull block driver; steal time; GNOME governance; 7.3 merge window; LUKS.
  • Briefs: Kernel infrastructure; Debian AI; Dolphin 26.08; Firefox 155; Incus 7.4; OpenShot 4.0; Quotes; ...
  • Announcements: Newsletters, conferences, security updates, patches, and more.

[$] Securely suspending LUKS-encrypted disks [LWN.net]

When a laptop is asleep, its memory is not unreadable. The right tooling can attach to the computer's memory bus and read out its contents, and cold-boot attacks can theoretically read values from memory for a short time after the computer loses power. That is really an unavoidable fact about the hardware, but some users would still like to ensure that, even if this happens, their long-term encryption keys, such as the key for full-disk encryption, remain unreadable. In June 2026, Ingo Blechschmidt discovered that Linux kernel versions after 6.9 (released in May 2024) were not erasing disk-encryption keys when a laptop was put to sleep, even when configured to do so. He quickly identified a potential fix, which has been merged, but it was not a comprehensive solution.

[$] Governing GNOMEs: how the project's technical decision-making is evolving [LWN.net]

Emmanuele Bassi kicked off a project to improve GNOME's technical governance with a presentation about his ideas (video) at GUADEC 2025. His nudging has led the project to, slowly, work on creating more formal structures for technical governance. It is adopting a teams structure and looking toward creating a steering committee, as well as bootstrapping a Request for Comments (RFC) process. If adopted, GNOME would require RFCs for design, user experience, architectural, and other changes that carry a major impact on the project.

Incus 7.4 released [LWN.net]

Version 7.4 of the Incus container and virtual-machine management system has been released. Notable changes in this release include UEFI Secure Boot key management, "near-live" migration of containers between Incus instances, as well as burst I/O limits for disk and network devices.

Eight stable kernels for Wednesday [LWN.net]

Greg Kroah-Hartman has announced the 7.2.3, 7.1.13, 6.18.49, 6.12.108, 6.6.156, 6.1.187, 5.15.220, and 5.10.269 stable kernels. Each contains a number of important fixes throughout the tree. Users are advised to upgrade.

Note that 7.1.13 is the last of the 7.1 updates

Security updates for Wednesday [LWN.net]

Security updates have been issued by AlmaLinux (dbus-broker, freerdp, gegl, gegl04, gimp, gimp:2.8, glib2, grafana, gzip, iperf3, libssh, nodejs22, nodejs24, nodejs:22, nodejs:24, php:7.4, php:8.2, pipewire, ruby:3.3, ruby:4.0, tar, wget, and xmlrpc-c), Debian (cyrus-imapd, keystone, and lemonldap-ng), Fedora (bubblewrap, cockpit, emacs, gdk-pixbuf2, openssl, openvkl, python-linkify-it-py, python-llm, and rkcommon), Gentoo (Chromium, Google Chrome, Microsoft Edge, Opera, Vivaldi), Oracle (glib2, gzip, mingw-sqlite, nodejs22, xorg-x11-server, and xorg-x11-server-Xwayland), Red Hat (go-toolset:rhel8, golang, and grafana), Slackware (pcre2), SUSE (apache2-mod_auth_openidc, busybox, cups-filters, java-17-openj9, java-1_8_0-openj9, libapr-util1, libgcrypt, python-sqlparse, python3-sqlparse, python313-uv, terraform-provider-aws, terraform-provider-azurerm, terraform-provider-external, terraform-provider-google, terraform-provider-helm, terraform-provider-kubernetes, terraform-provid, ucode-intel, wicked, and yast2-auth-client), and Ubuntu (libevent, libgcrypt20, ncurses, opencryptoki, pam, pyasn1, rust-sudo-rs, and ubuntu-advantage-tools).

A note on subscription prices from LWN [LWN.net]

The online publication industry, as a whole, is struggling, with challenges coming from multiple directions. Thanks to the support of all of you, our readers, LWN would appear to be doing better than most. But the world has changed around us and, in particular, prices have changed considerably. By now, you probably know where this is going: subscription prices at LWN will be increasing as of September 15.

OpenShot 4.0 released [LWN.net]

Version 4.0 of the OpenShot video editor has been released.

OpenShot 4.0 has arrived, bringing some of the biggest creative workflow upgrades in our history. You can now record your screen, webcam, microphone, and system audio directly into a project. You can correct and grade footage with color wheels, curves, LUTs, and professional video scopes. You can also isolate subjects with locally run machine learning models and create everything from animated audio visualizations to cinematic film looks.

See the release notes for a full list of changes.

[$] A pause for the Python JIT [LWN.net]

In 2024 the Python 3.13 release added an experimental just-in-time (JIT) compiler to optimize the way that CPython executes Python code. Since then, work has proceeded on the JIT, albeit perhaps less formally than some might like. In June, Python's steering council (SC) put out an announcement that no new development on the JIT land (with the exception of bug and security fixes) in Python's main branch, until it accepts a Python Enhancement Proposal (PEP) that would make the case for the JIT as a supported part of CPython. That has led to the creation of PEP 836 ("JIT Go Brrr: The Path to a Supported JIT Compiler for CPython"), which is currently under discussion. As it stands, it seems likely that work on JIT will continue, but when that will happen is less certain.

Firefox 155 released [LWN.net]

Version 155 of the Firefox web browser has been released. Notable changes include a count in the address bar of how many ad trackers Firefox has blocked, container reordering, and ensuring that mailto: links are only opened by explicit user actions. There is also a change of the domain used for "captive portals" (such as the ones used to sign into hotel WiFI): Firefox now uses "firefox-portal-detection.com" instead of "detectportal.firefox.com", which may require a change in network allow lists.

The release also includes a number of changes that may impact web developers, as well as a number of bug fixes and security fixes.

Netdev 0x1A videos and slides are now live [LWN.net]

The Netdev 0x1A conference was held in Rome, Italy from July 13 through July 16. Conference organizer Jami Hadi Salim has let us know that the videos and slides for all sessions are now available. Topics include Linux QUIC, shared memory socket transport, eBPF-based DDoS protection, and more.

Security updates for Monday [LWN.net]

Security updates have been issued by Debian (kernel, libarchive, libdbi-perl, libnet-dns-perl, librabbitmq, roundcube, starlette, and xrdp), Fedora (bluez, postgresql16-anonymizer, pyOpenSSL, python-cryptography, python-pynitrokey, and rust-h2), Gentoo (Chromium, Google Chrome, Microsoft Edge, Opera, Freenet, and Tor), Mageia (golang and python-nltk), Red Hat (nodejs22, nodejs24, nodejs:22, and nodejs:24), SUSE (7zip, apptainer, broot, cadvisor, coredns, coturn, distribution-registry, gh, git-lfs, grafana, gzip, java-25-openjdk, libopenssl-3-devel, libsoup-3_0-0, mozjs102, OpenRGB, openssl-3, openvpn, podman, pyenv, python, python-PyPDF2, python310, python312, python313-Authlib, python36, rekor, rsync, rsyslog, tor, trivy, v2ray-core, vim, wget, and wicked), and Ubuntu (bzip2 and openjdk-26).

Security updates for Tuesday [LWN.net]

Security updates have been issued by AlmaLinux (gzip, iperf3, libxml2, mingw-sqlite, mysql:8.4, nginx:1.26, nodejs:24, php, and tar), Debian (expat and libdbd-csv-perl), Fedora (apache-ivy, bind, bluez, bubblewrap, curl, emacs, epiphany, expat, freerdp, gdk-pixbuf2, GitPython, hcloud, kbd, kernel, lego, libopenmpt, mqttcli, nebula, opkssh, python-mkdocs-git-revision-date-localized-plugin, python-pip, rpki-client, rubygem-mechanize, srt, and subfinder), Mageia (c-ares, clamav, expat, mingq-expat, firefox, nspr, nss, flatpak, hplip, jbig2dec, nodejs, openssl, perl-Catalyst-Plugin-Authentication, perl-Date-Manip, perl-HTML-FormHandler, perl-HTTP-Date, perl-Mojolicious, perl-Plack, postgresql15, postgresql18, python-hpack, redis, roundcubemail, thunderbird, varnish, and vim), Oracle (golang and libxml2), Red Hat (bind, bind9.18, dracut, glib2, golang, gzip, kernel, kernel-rt, openssl, osbuild-composer, tar, and unbound), SUSE (7zip, busybox, bzip2, c-ares, chromedriver, chromium, cpio, curl, dhcpcd, dovecot24, dracut, firefox, go1.25, go1.26, go1.26-openssl, google-cloud-sap-agent, gstreamer-plugins-bad, gzip, helm, ImageMagick, istioctl, jfrog-cli, jupyter-jupyterlab, libarchive, libcares2, libheif, liboqs, librest, openssl-1_1, openssl-3, owasp-modsecurity-crs, pcp, php-composer2, postgresql14, postgresql15, postgresql17, postgresql18, python-cryptography, python-httplib2, python-pip, python313, python313-djangorestframework, python313-starlette, qemu, qt6-svg, quagga, rav1e, rmt-server, rsync, rsyslog, snphost, sssd, thunderbird, unbound, vim, wget, xmlrpc-c, yast2-auth-client, and yast2-samba-client), and Ubuntu (attr, bind9, coreutils, cpio, diffutils, freerdp3, libssh, mysql-8.0, mysql-8.4, openjdk-17-crac, openjdk-21-crac, openjdk-25-crac, openssl, p11-kit, perl, pillow, udisks2, util-linux, webkit2gtk, zfs-linux, and zlib).

Feeds

FeedRSSLast fetchedNext fetched after
@ASmartBear XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
a bag of four grapes XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Ansible XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
Bad Science XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Black Doggerel XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
Blog - Official site of Stephen Fry XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Charlie Brooker | The Guardian XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Charlie's Diary XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
Chasing the Sunset - Comics Only XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Coding Horror XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
Comics Archive - Spinnyverse XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
Cory Doctorow's craphound.com XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Cory Doctorow, Author at Boing Boing XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
Ctrl+Alt+Del Comic XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
Cyberunions XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
David Mitchell | The Guardian XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
Deeplinks XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
Diesel Sweeties webcomic by rstevens XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
Dilbert XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Dork Tower XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Economics from the Top Down XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
Edmund Finney's Quest to Find the Meaning of Life XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
EFF Action Center XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
Enspiral Tales - Medium XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
Events XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
Falkvinge on Liberty XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
Flipside XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Flipside XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
Free software jobs XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
Full Frontal Nerdity by Aaron Williams XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
General Protection Fault: Comic Updates XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
George Monbiot XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
Girl Genius XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
Groklaw XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
Grrl Power XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Hackney Anarchist Group XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Hackney Solidarity Network XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
http://blog.llvm.org/feeds/posts/default XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
http://calendar.google.com/calendar/feeds/q7s5o02sj8hcam52hutbcofoo4%40group.calendar.google.com/public/basic XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
http://dynamic.boingboing.net/cgi-bin/mt/mt-cp.cgi?__mode=feed&_type=posts&blog_id=1&id=1 XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
http://eng.anarchoblogs.org/feed/atom/ XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
http://feed43.com/3874015735218037.xml XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
http://flatearthnews.net/flatearthnews.net/blogfeed XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
http://fulltextrssfeed.com/ XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
http://london.indymedia.org/articles.rss XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
http://pipes.yahoo.com/pipes/pipe.run?_id=ad0530218c055aa302f7e0e84d5d6515&amp;_render=rss XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
http://planet.gridpp.ac.uk/atom.xml XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
http://shirky.com/weblog/feed/atom/ XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
http://thecommune.co.uk/feed/ XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
http://theness.com/roguesgallery/feed/ XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
http://www.airshipentertainment.com/buck/buckcomic/buck.rss XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
http://www.airshipentertainment.com/growf/growfcomic/growf.rss XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
http://www.airshipentertainment.com/myth/mythcomic/myth.rss XML 10:42, Sunday, 06 September 11:24, Sunday, 06 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 10:35, Sunday, 06 September 11:19, Sunday, 06 September
http://www.godhatesastronauts.com/feed/ XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
http://www.tinycat.co.uk/feed/ XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
https://anarchism.pageabode.com/blogs/anarcho/feed/ XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
https://broodhollow.krisstraub.comfeed/ XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
https://debian-administration.org/atom.xml XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
https://elitetheatre.org/ XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
https://feeds.feedburner.com/Starslip XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
https://feeds2.feedburner.com/GeekEtiquette?format=xml XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
https://hackbloc.org/rss.xml XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
https://kajafoglio.livejournal.com/data/atom/ XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
https://philfoglio.livejournal.com/data/atom/ XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
https://pixietrixcomix.com/eerie-cutiescomic.rss XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
https://pixietrixcomix.com/menage-a-3/comic.rss XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
https://propertyistheft.wordpress.com/feed/ XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
https://requiem.seraph-inn.com/updates.rss XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
https://studiofoglio.livejournal.com/data/atom/ XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
https://thecommandline.net/feed/ XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
https://torrentfreak.com/subscriptions/ XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
https://web.randi.org/?format=feed&type=rss XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
https://www.baen.com/baenebooks XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
https://www.dcscience.net/feed/medium.co XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
https://www.DropCatch.com/domain/steampunkmagazine.com XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
https://www.DropCatch.com/domain/ubuntuweblogs.org XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
https://www.DropCatch.com/redirect/?domain=DyingAlone.net XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
https://www.freedompress.org.uk:443/news/feed/ XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
https://www.goblinscomic.com/category/comics/feed/ XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
https://www.loomio.com/blog/feed/ XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
https://www.newstatesman.com/feeds/blogs/laurie-penny.rss XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
https://www.patreon.com/graveyardgreg/posts/comic.rss XML 10:21, Sunday, 06 September 11:08, Sunday, 06 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 10:35, Sunday, 06 September 11:18, Sunday, 06 September
https://x.com/statuses/user_timeline/22724360.rss XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
Humble Bundle Blog XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
I, Cringely XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
Irregular Webcomic! XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
Joel on Software XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
Judith Proctor's Journal XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
Krebs on Security XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
Lambda the Ultimate - Programming Languages Weblog XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
Looking For Group XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
LWN.net XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
Mimi and Eunice XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
Neil Gaiman's Journal XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
Nina Paley XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
O Abnormal – Scifi/Fantasy Artist XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
Oglaf! -- Comics. Often dirty. XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
Oh Joy Sex Toy XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
Order of the Stick XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
Original Fiction Archives - Reactor XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
OSnews XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
Paul Graham: Unofficial RSS Feed XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
Penny Arcade XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Penny Red XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
PHD Comics XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Phil's blog XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
Planet Debian XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
Planet GNU XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
Planet Lisp XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Pluralistic: Daily links from Cory Doctorow XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
PS238 by Aaron Williams XML 10:21, Sunday, 06 September 11:09, Sunday, 06 September
QC RSS v2 XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
Radar XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
RevK®'s ramblings XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
Richard Stallman's Political Notes XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Scenes From A Multiverse XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
Schneier on Security XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
SCHNEWS.ORG.UK XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
Scripting News XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Seth's Blog XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
Skin Horse XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Tales From the Riverbank XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
The Adventures of Dr. McNinja XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
The Bumpycat sat on the mat XML 10:21, Sunday, 06 September 11:01, Sunday, 06 September
The Daily WTF XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
The Monochrome Mob XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
The Non-Adventures of Wonderella XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
The Old New Thing XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
The Open Source Grid Engine Blog XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
The Stranger XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
towerhamletsalarm XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
Twokinds XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
UK Indymedia Features XML 10:42, Sunday, 06 September 11:24, Sunday, 06 September
Uploads from ne11y XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
Uploads from piasladic XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September
Use Sword on Monster XML 10:21, Sunday, 06 September 11:08, Sunday, 06 September
Wayward Sons: Legends - Sci-Fi Full Page Webcomic - Updates Daily XML 10:21, Sunday, 06 September 11:07, Sunday, 06 September
what if? XML 10:21, Sunday, 06 September 11:02, Sunday, 06 September
Whatever XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
Whitechapel Anarchist Group XML 10:28, Sunday, 06 September 11:17, Sunday, 06 September
WIL WHEATON dot NET XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
wish XML 10:35, Sunday, 06 September 11:20, Sunday, 06 September
Writing the Bright Fantastic XML 10:35, Sunday, 06 September 11:19, Sunday, 06 September
xkcd.com XML 10:35, Sunday, 06 September 11:18, Sunday, 06 September