AI Skeptics: Big Tech in Public Schools (with Natasha Singer)

Math Babe
mathbabe.org
2026-09-14 09:55:36
We were psyched to have New York Times tech journalist Natasha Singer with us this week, talking about how (once again) Big Tech is pushing its way into public schools: Apple Spotify YouTube...
Original Article

Home > Uncategorized > AI Skeptics: Big Tech in Public Schools (with Natasha Singer)

We were psyched to have New York Times tech journalist Natasha Singer with us this week, talking about how (once again) Big Tech is pushing its way into public schools:

Apple

Spotify

YouTube

Categories: Uncategorized

Comments (0) Trackbacks (0) Leave a comment Trackback

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Your email address will not be published. Required fields are marked *

AI Skeptics: Big Tech in Public Schools (with Natasha Singer)

Math Babe
mathbabe.org
2026-09-14 09:55:36
We were psyched to have New York Times tech journalist Natasha Singer with us this week, talking about how (once again) Big Tech is pushing its way into public schools: Apple Spotify YouTube...
Original Article

Home > Uncategorized > AI Skeptics: Big Tech in Public Schools (with Natasha Singer)

We were psyched to have New York Times tech journalist Natasha Singer with us this week, talking about how (once again) Big Tech is pushing its way into public schools:

Apple

Spotify

YouTube

Categories: Uncategorized

Comments (0) Trackbacks (0) Leave a comment Trackback

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Your email address will not be published. Required fields are marked *

Agent State in the Tmux Status Line

Lobsters
thecloudlet.github.io
2026-09-16 03:09:43
Comments...
Original Article

The problem

Running several agent sessions (Claude Code, Codex, PingMe ) side by side in tmux windows makes it easy to lose track of which window is still working, which is stuck waiting on a decision, and which finished. Checking each window by hand doesn't scale past three or four of them.

Herdr is the usual suggestion in this niche: an agent-first terminal multiplexer that classifies sessions as working, blocked, done, or idle and shows them in a sidebar. A week with it surfaced two objections — it's a separate surface to context-switch into for information that belongs next to the windows it describes, and it replaces tmux rather than fitting the existing habit of glancing at a status line.

agenmux keeps tmux and answers the second objection, rendering agent state into a sidebar pane and a status-line segment. Both are regions separate from the window list. What follows writes the marker onto the window entries themselves.

Herdr also tracks a fourth state, done — a turn that finished while the window was not visible, cleared on the next visit. That requires per-pane focus history, which the setup below does not keep; done collapses into idle, leaving three states.

The target is the same classification, rendered in the tmux window list. Two signals looked like they carried it and did not.

Attempt 1: the pane title

tmux tracks pane_title per pane, and programs update it via an OSC escape sequence. Agent CLIs are interactive TUIs, so the title is the first candidate for carrying their state.

set -g @agent-status \
'#{?#{m:*Action Required*,#{pane_title}},#[fg=colour196]#[bold]!,'\
'#{?#{m/r:(^| )[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◓◑◒]( |$),#{pane_title}},#[fg=colour220]●,'\
'#{?#{m/r:(^|/)(codex|claude|pingme)$,#{pane_current_command}},#[fg=colour34]✓,}}}'

Yellow never appeared once. Sampling the title twice a second through a full working turn explains why:

$ for i in $(seq 1 40); do tmux list-panes -a -F '#{pane_id} [#{pane_title}]'; sleep 0.5; done | sort -u
%0 [✳ Tmux config article]
%1 [✳ Claude Code]

Two unique values across the whole sample. Claude Code sets its title once and never touches it again — it carries the session's name, not its state, and no regex over a constant string will produce a state machine.

That sample contained two Claude panes. Grok, on the same tmux server:

t=1  title=[Review of tmux agent-status article - grok]
t=2  title=[⠋ - Waiting for response… - Review of tmux agent-status article - grok]
t=4  title=[⠋ - Thinking - Review of tmux agent-status article - grok]
t=7  title=[⠸ - Thinking - Review of tmux agent-status article - grok]

A braille spinner, in the title, from the character class the yellow rule matched on. The title is inert for Claude Code and not for grok, so a per-agent rule set could read grok's title directly. The screen carries state for both without depending on OSC title updates being emitted.

Attempt 2: the foreground process

With Claude's title ruled out, the next candidate is pane_current_command , the process tmux considers to be in the foreground. An agent running a tool occupies a different foreground process than one sitting at a prompt.

$ tmux list-panes -a -F '#{pane_id} cmd=[#{pane_current_command}]'
%0 cmd=[2.1.267]
%1 cmd=[2.1.273]

Not claude . The install layout accounts for it:

$ ls -l ~/.local/bin/claude
... -> ~/.local/share/claude/versions/2.1.273

$ ls -l ~/.grok/bin/grok
... -> ../downloads/grok-1.0.30-macos-aarch64

Both launchers are symlinks to a versioned binary, and the process carries that binary's filename. tmux reports it: claude as 2.1.267 , grok as grok-1.0.30-mac , truncated from grok-1.0.30-macos-aarch64 . The value identifies a release artifact rather than the program.

The green icon never lit on this machine either. Its rule required pane_current_command to end in claude , which this value never does, so the third branch fell through to the empty fallback alongside the other two.

The process tree contains a process whose comm is claude , whatever the pane's foreground command is named, so ps -o comm= over the tree answers the presence question. State is a separate matter: pane_current_command identifies a process, and process identity does not encode "thinking" versus "waiting for approval."

What actually carries the state

agenmux , an open-source tmux agent monitor that ports Herdr's detection rules, documents its method:

Detection is scraping-only: agents are identified by walking each pane's process tree, state is inferred from the pane's visible screen and title.

The rendered terminal buffer, then, rather than the title or the process. Herdr matches its rules for Claude Code and Codex against a snapshot of the bottom of the live buffer.

tmux exposes exactly that through capture-pane . The bottom of a working Claude Code pane:

✽ Brewing… (2m 14s · ↓ 3.9k tokens)
─────────────────────────────────────────────────────

─────────────────────────────────────────────────────
  ⏵⏵ auto mode on (shift+tab to cycle) · esc to interrupt · ← for agents

And the same pane once it's idle:

─────────────────────────────────────────────────────

─────────────────────────────────────────────────────
  ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents

esc to interrupt is present in one and absent in the other. The hint can only be rendered while something is interruptible, which makes it the state. The activity line above it carries a spinner as well ( ✽ Brewing… ), through the glyphs ✳ ✽ ✶ — a different character class from the braille and circle set the first attempt searched for, on a different surface.

The same config appeared to work on a Linux machine whose title was equally static. What lit there was green, which required only that pane_current_command resolve to claude — true on that box, false here. Green reported an agent process in the pane; it never reported what that process was doing.

The implementation

Two signals, each answering the question it can actually answer: the process tree for "which agent is here", the screen buffer for "what is it doing".

The walk returns the agent's name rather than a yes/no, since the screen rules differ per agent:

pane_agent() {
	root="$1"; pids="$root"; queue="$root"
	while [ -n "$queue" ]; do
		pid="${queue%% *}"; queue="${queue#"$pid"}"; queue="${queue# }"
		for c in $(pgrep -P "$pid" 2>/dev/null); do
			case " $pids " in
			*" $c "*) ;;
			*) pids="$pids $c"; queue="$queue $c" ;;
			esac
		done
	done
	ps -o comm= -p $pids 2>/dev/null |
		sed -nE 's|.*/||; /^(claude|claude-code|codex|grok|pingme)$/p' | head -n 1
}

Then each agent gets its own patterns and its own order. Sketched as pseudocode — match stands in for a grep against the captured screen, and the patterns are elided; the appendix has the runnable version:

claude, claude-code, pingme:
    working  if screen matches 'esc to interrupt|ctrl+c to interrupt'
    idle     if screen matches a bare '❯' prompt line
    action   if screen matches 'do you want to proceed?|waiting for permission|…'
    idle     otherwise

codex:
    action   if screen matches 'press enter to confirm or esc to cancel|…'
    working  if screen matches 'esc to interrupt'
    idle     otherwise

grok:
    action   if screen matches '<n>/<n>:select|Allow …?|No, reject|dialog footer hints'
    working  if screen matches '[stop]|Ctrl+c:cancel'
    idle     otherwise

Claude and Codex both print esc to interrupt , and order their rules differently around it. Claude treats the hint as authoritative and checks working first, with a bare-prompt idle rule ahead of the blocked patterns so an answered permission prompt left on screen does not read as waiting. Codex checks its approval prompts first. Reversing either order makes every prompt read as busy, or every busy turn read as blocked.

One caveat on provenance: herdr and agenmux check the title before the screen for Codex ( CHECK_ORDER="bt wt bs ws" ). Title rules are dropped here, which keeps detection independent of an OSC sequence reaching tmux. Grok's title carries its state and Claude's does not, so a title rule resolves for one agent and returns nothing for the other. Only the screen rules carry over, and the ordering above is between screen rules alone. pingme rides along on Claude's rules because it wraps Claude Code and renders its UI; that grouping is an assumption, not something separately observed.

Grok needed the most work. agenmux ships no manifest for it; herdr does ( grok.toml ), but against Grok Build 0.2.101, whose footer differs from the 1.0.30 installed here — it expects Ctrl+.:shortcuts where this build prints Ctrl+x:shortcuts . Close enough to confirm the approach, far enough that the strings had to be re-read from a live session.

A plain conversation suggested Esc:cancel , which sits in the footer while a turn runs and disappears when it ends. That rule held for chat and failed on the first shell command: during a long tool call the spinner text stops updating and the cancel hint becomes Ctrl+c:cancel . A pane spending twenty seconds in sleep reported idle for the whole turn.

The [stop] affordance on the activity line persists across both cases. Blocked is still checked first, since grok's approval prompt retains [stop] from the tool call that raised it, and a working-first order would read every permission prompt as busy.

herdr's manifest classifies ctrl+c:cancel as a blocked signal, paired with :select and ctrl+o:yolo , rather than a working fallback. Treating it as working, as below, depends entirely on the blocked patterns matching first; a permission footer they miss reads as busy instead of waiting.

Both failures came from picking a signal out of a single observed scenario, and both surfaced when a second scenario ran. The rules cover the range of states actually watched.

The listing above is trimmed for reading; the full script, with every pattern spelled out, is in the appendix .

This runs as a background loop rather than inside the format string. The earlier version called a script from #(...) , which fails in a subtle way: #() is not a synchronous call but a job whose result is cached for the next redraw, so state computed that way is only as fresh as the last time something drew the screen. A detached session has no redraws at all. Process trees and screen contents are facts about the machine, true whether or not anyone is looking — so they belong on their own clock, with the format string reduced to a pure read:

set -g @agent-status \
'#{?#{==:#{@agent-state},action},#[fg=colour196]#[bold]!,'\
'#{?#{==:#{@agent-state},working},#[fg=colour220]●,'\
'#{?#{==:#{@agent-state},idle},#[fg=colour34]✓,}}}'

set-window-option -g window-status-format '#{E:@agent-status}#[fg=colour18]#[nobold]#I:#W#F '
set-window-option -g window-status-current-format '#{E:@agent-status}#[fg=colour252]#[nobold]#I:#W#[fg=colour196]#[bold]* '

Both lines are needed: tmux renders the current window through its own format, so setting only the first leaves the window you're looking at without an icon.

set-option -p scopes each answer to one pane. The window list renders one icon per window, resolved against its active pane, so a split running two agents surfaces one of them. The loop starts once per server, guarded by a PID file rather than pgrep -f agent-status-poll.sh : a wrapper shell whose own argv contains the script name is a false positive for any name-substring guard.

if-shell '! kill -0 "$(cat /tmp/tmux-agent-status-poll.pid 2>/dev/null)" 2>/dev/null' \
  'run-shell -b "~/.config/tmux/agent-status-poll.sh"'

Result

The window list becomes the board: red ! needs a decision, yellow is working, green is idle and ready, unmarked is a plain shell. It stays correct whether or not a client is attached, because nothing about the detection depends on being watched.

tmux status line showing a green check on an idle Claude window and a yellow dot on a working grok window

In the bottom line, ✓1:123444 is an idle Claude pane and ●2:grok-1.0.30-mac is grok mid-turn, its window name the versioned-binary filename from Attempt 2. The working rule matched on that pane's Waiting for response… 6.8s line and its [stop] chip.

Titles and process names are metadata, and each held for some agents and not others. Claude's title is a fixed session name; grok's carries a live spinner. Grok's foreground command is its own binary; Claude's is a version string. The screen is the surface every one of them populates, being the surface they exist to draw.

Three agents required three rule sets and two orderings, and grok's were re-read from a live session because herdr's manifest targets Build 0.2.101 against the 1.0.30 installed here. The rules match footer strings and break whenever an agent redesigns its footer, silently and without a version number to check against.

herdr prefers a lifecycle hook where one exists: full_lifecycle_hook_authority names pi, omp, mastracode, opencode, kilo and kimi as agents whose own reports override screen detection. Claude Code, Codex and grok are absent from that list, and herdr scrapes them. Claude Code's hooks could write @agent-state directly, covering one of the three, installed into that agent's config, and reporting nothing about a pane running anything else. The screen is the one input available for all of them.

Appendix: the full setup

The poller

~/.config/tmux/agent-status-poll.sh , verified against claude 2.1.x and grok 1.0.30 on macOS. Codex's patterns are agenmux's, carried over untested. Two known rough edges: the PID file lives at a fixed /tmp path, so two tmux servers on one machine would contend for it, and grok's rules are pinned to the footer text of one release — a redesign there breaks them silently, which is the standing cost of screen scraping.

#!/bin/sh
# Background poller: every second, classify each pane's agent state and write
# it into that pane's @agent-state user option, which the status line reads.
#
# Two signals, both necessary:
#   - process tree: which agent is running here? These CLIs launch through a
#     symlink to a versioned binary, so pane_current_command reports that
#     filename (claude -> "2.1.267", grok -> "grok-1.0.30-mac") rather than
#     the agent's name. Walking the tree and matching `ps -o comm=` finds it.
#   - visible screen: what is that agent doing? Every agent draws its state
#     there, which is not true of the title: Claude's is a fixed session name
#     while grok's carries a live spinner.
#
# Screen patterns and check order are per-agent. Claude's and Codex's come
# from agenmux's agents/*.conf (which ports herdr's manifests), minus the
# title rules those check first -- dropped on purpose, so detection never
# depends on an OSC sequence reaching tmux. They differ in more than wording:
# Claude treats the interrupt hint as authoritative and checks working first,
# while Codex checks its blocked prompts first.
# Verified against claude 2.1.x and grok 1.0.30; codex is untested.

echo $$ >/tmp/tmux-agent-status-poll.pid

# Exit cleanly when killed: tmux reports any non-zero run-shell exit in the
# status line, and a terminated daemon is routine, not an error worth showing.
trap 'rm -f /tmp/tmux-agent-status-poll.pid; exit 0' EXIT HUP INT TERM

# Echo the agent binary found anywhere in the pane's process tree, if any.
pane_agent() {
	root="$1"
	pids="$root"
	queue="$root"
	while [ -n "$queue" ]; do
		pid="${queue%% *}"
		queue="${queue#"$pid"}"
		queue="${queue# }"
		children=$(pgrep -P "$pid" 2>/dev/null)
		for c in $children; do
			case " $pids " in
			*" $c "*) ;;
			*)
				pids="$pids $c"
				queue="$queue $c"
				;;
			esac
		done
	done
	# shellcheck disable=SC2086
	ps -o comm= -p $pids 2>/dev/null |
		sed -nE 's|.*/||; /^(claude|claude-code|codex|grok|pingme)$/p' |
		head -n 1
}

CLAUDE_WORKING='esc to interrupt|ctrl\+c to interrupt'
CLAUDE_IDLE='^[[:space:]]*❯[[:space:]]*$'
CLAUDE_BLOCKED='do you want to proceed\?|waiting for permission|do you want to allow this connection\?|enter to select.*esc to cancel|esc to cancel.*enter to select'

CODEX_WORKING='esc to interrupt'
CODEX_BLOCKED='press enter to confirm or esc to cancel|enter to submit answer|enter to submit all|allow command\?|\[y/n\]|yes \(y\)'

# From observing grok 1.0.30. herdr has a grok manifest but targets Build
# 0.2.101, whose footer differs (Ctrl+.:shortcuts vs Ctrl+x:shortcuts here).
# The activity line's "[stop]" affordance is the only marker present for a
# whole turn: the spinner text stops updating while a long tool call runs,
# and the cancel hint moves between Esc and Ctrl+c depending on focus.
# ponytail: Ctrl+c:cancel also appears in permission footers, where herdr
# treats it as a blocked signal -- safe only because BLOCKED is checked
# first; a permission footer those patterns miss would read as working.
GROK_WORKING='\[stop\]|Ctrl\+c:cancel'
GROK_BLOCKED='^[[:space:]]*[0-9]+/[0-9]+:select|Allow .*\?[[:space:]]*$|No, reject|Tab:scrollback|Shift\+x:dismiss|Ctrl\+o:yolo'

classify() {
	agent="$1"
	screen="$2"
	case "$agent" in
	codex)
		# Blocked first, as in agenmux. Untested against a live codex session:
		# the patterns are its screen rules, minus the title checks that come
		# first upstream and are useless here.
		if printf '%s' "$screen" | grep -qiE "$CODEX_BLOCKED"; then
			echo action
		elif printf '%s' "$screen" | grep -qE "$CODEX_WORKING"; then
			echo working
		else
			echo idle
		fi
		;;
	grok)
		# Blocked first: an approval prompt keeps the spinner and [stop]
		# indicator from the tool call that raised it, so working would win.
		if printf '%s' "$screen" | grep -qiE "$GROK_BLOCKED"; then
			echo action
		elif printf '%s' "$screen" | grep -qE "$GROK_WORKING"; then
			echo working
		else
			echo idle
		fi
		;;
	claude | claude-code | pingme)
		# Working beats blocked (the interrupt hint is authoritative), and a
		# bare ❯ prompt means idle -- checked before blocked so an answered
		# permission prompt still on screen doesn't read as waiting.
		if printf '%s' "$screen" | grep -qE "$CLAUDE_WORKING"; then
			echo working
		elif printf '%s' "$screen" | grep -qE "$CLAUDE_IDLE"; then
			echo idle
		elif printf '%s' "$screen" | grep -qiE "$CLAUDE_BLOCKED"; then
			echo action
		else
			echo idle
		fi
		;;
	*)
		echo idle
		;;
	esac
}

while true; do
	for pane in $(tmux list-panes -a -F '#{pane_id}:#{pane_pid}' 2>/dev/null); do
		pane_id="${pane%%:*}"
		pane_pid="${pane##*:}"

		agent=$(pane_agent "$pane_pid")
		if [ -z "$agent" ]; then
			tmux set-option -p -t "$pane_id" @agent-state '' 2>/dev/null
			continue
		fi

		screen=$(tmux capture-pane -p -t "$pane_id" 2>/dev/null | tail -20)
		state=$(classify "$agent" "$screen")

		tmux set-option -p -t "$pane_id" @agent-state "$state" 2>/dev/null
	done
	sleep 1
done

The tmux config

~/.config/tmux/tmux.conf in full, so the status-line pieces are visible in the context they run in. The agent-status block is the middle section; the rest is ordinary setup that happens to surround it.

###############################################################################
# Tmux display settings
###############################################################################
set -g default-terminal "screen-256color"

set -g base-index 1           # start windows numbering at 1
setw -g pane-base-index 1     # make pane numbering consistent with windows

set -g renumber-windows on    # renumber windows when a window is closed
set -g set-titles on          # set terminal title
set -g display-panes-time 800 # slightly longer pane indicators display time
set -g display-time 1000      # slightly longer status messages display time
set -g status-interval 1      # redraw status line every second

# Set status bar background to light grey and foreground to a contrasting color
set -g status-bg colour252 # light grey
set -g status-fg colour18  # dark blue

# Customize the left side of the status bar
set -g status-left '#[bg=colour252,fg=colour18] #S #[bg=colour252,fg=colour18]'

# Customize the right side of the status bar
set -g status-right '#[bg=colour252,fg=colour18] %m-%d %H:%M #[bg=colour252,fg=colour18]'

# Customize the window status format
set-window-option -g window-status-current-style 'bg=colour18,fg=colour252'

# Agent state is written by a background poller (agent-status-poll.sh) into
# each pane's @agent-state option, not looked up here at render time --
# process-tree checks via #() only get re-run when a client redraws, so a
# detached/unwatched session would show stale or blank icons.
set -g @agent-status \
'#{?#{==:#{@agent-state},action},#[fg=colour196]#[bold]!,'\
'#{?#{==:#{@agent-state},working},#[fg=colour220]●,'\
'#{?#{==:#{@agent-state},idle},#[fg=colour34]✓,}}}'
set-window-option -g window-status-format '#{E:@agent-status}#[fg=colour18]#[nobold]#I:#W#F '
set-window-option -g window-status-current-format '#{E:@agent-status}#[fg=colour252]#[nobold]#I:#W#[fg=colour196]#[bold]* '

# Start the poller once per server. PID-file guarded so `tmux source-file`
# reloads don't spawn duplicates -- `pgrep -f` alone false-positives when a
# wrapper shell's argv happens to contain the script name.
if-shell '! kill -0 "$(cat /tmp/tmux-agent-status-poll.pid 2>/dev/null)" 2>/dev/null' \
  'run-shell -b "~/.config/tmux/agent-status-poll.sh"'

###############################################################################
# Tmux bindings
###############################################################################

# Reload tmux config
bind r source-file ~/.config/tmux/tmux.conf \; display "Reloaded ~/.config/tmux/tmux.conf!"

# Set new panes to open in current directory
bind c new-window -c "#{pane_current_path}"
bind '"' split-window -c "#{pane_current_path}"
bind % split-window -h -c "#{pane_current_path}"

# mouse on
setw -g mouse on

# set vi mode for copy mode
setw -g mode-keys vi

## Clipboard integration
set -s set-clipboard external
bind Escape copy-mode
bind p paste-buffer
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send-keys -X copy-selection-and-cancel
bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-selection-and-cancel
bind -T copy-mode-vi Enter send-keys -X copy-selection-and-cancel

## hjkl pane traversal
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

## move window right / left
bind-key -n C-S-Left swap-window -t -1 \; select-window -t -1
bind-key -n C-S-Right swap-window -t +1 \; select-window -t +1

Both files live in the same directory, which is what lets the if-shell line reference the script by a fixed path. Stowed as one package, they land at ~/.config/tmux/ together.

Google fixes actively exploited Android zero-day on Pixel devices

Bleeping Computer
www.bleepingcomputer.com
2026-09-16 03:00:19
Google has released the September 2026 security patches to address 110 vulnerabilities affecting its Pixel devices, including one zero-day flaw actively exploited in targeted attacks. [...]...
Original Article

Google Pixel 11 Pro Fold

Google has released the September 2026 security patches to address 110 vulnerabilities affecting its Pixel devices, including one zero-day flaw actively exploited in targeted attacks.

"There are indications that CVE-2026-58704 may be under limited, targeted exploitation," the company warned on Wednesday.

"All supported Google devices will receive an update to the 2026-09-05 patch level. We encourage all customers to accept these updates to their devices."

This high-severity security flaw stems from improper authorization and protection mechanism failure weaknesses affecting the Modem subcomponent. Successful exploitation can allow attackers with access to an adjacent network and basic privileges on the targeted device to escalate privileges in low-complexity attacks that don't require user interaction.

"In Cellular Modem, there is a possible permission bypass due to a logic error in the code," a security advisory issued today says. "This could lead to remote (proximal/adjacent) escalation of privilege with no additional execution privileges needed."

Google tagged 109 other security issues in this month's Pixel update bulletin, including 12 remote code execution and 89 privilege escalation vulnerabilities rated critical or high severity.

Although Google Pixel devices also run Android, they receive separate security updates and bug fixes from the standard monthly patches distributed to Android OEMs because of the unique hardware platform Google controls directly and its exclusive features and capabilities.

To apply this month's security updates, Pixel users must go to Settings > Security & privacy > System & updates > Security update, tap Install, and restart their devices to complete the update process.

You can find more information on the September 2026 updates for Pixel devices in the security bulletin for Google's smartphone range.

In June, Google also addressed an Android Framework zero-day flaw (CVE-2025-48595) that was actively exploited in targeted attacks and could let attackers gain code execution and escalate privileges on devices running Android 14 or later.

One month earlier, the company announced an overhaul of its Android and Chrome vulnerability rewards programs, scaling back payouts for flaws that are easier to find using artificial intelligence (AI) while offering bounties of up to $1.5 million for some Android exploits.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Maintaining the love for coding in the time of AI

Lobsters
blog.nlnetlabs.nl
2026-09-16 02:31:52
Comments...

A/I Shuts Down

Lobsters
keepitfree.ai
2026-09-16 02:05:38
A/I is Autistici/Inventati, nothing to do with algorithms. Why this matters: to quote senior advisor and journalist Anne Roth: 20,000 mail accounts. 20,000 blogs. 5,000 mailing lists. 1,500 websites. In Europe. Run by volunteers of @cavallette, perfectly legal, and decidedly antifascist, feminist, ...
Original Article

The day we discovered we had been designated a global terrorist organization, we promised that we would try to resist as long as this was possible, that we would not back down as long as we could see options to stand our ground. For years, we have proudly maintained and defended a free, privacy-friendly, autonomous and politically committed infrastructure.

“Stay human” is not an empty phrase to us.

It means, above all, that we have a responsibility to protect our users, those who build the human networks we rely, and for the communities who flooded us with messages of support and solidarity.

We cannot engage in a fight or expose ourselves to manipulations that threaten the lives of these people and their loved ones, leaving them at the mercy of autocrats, fascists and agencies that take extra-legal courses of action. The possibility that our work may cause legal and financial consequences to those who are close to us - or even only have something to do with us - leaves us no choice.

A/I is shutting down. The Autistici/Inventati collective is shutting down and will soon discontinue all of the services we provide.

There is no easy way to announce or explain this

Every day we stayed online after August 26, 2026, has been a victory, but now we are forced to stop.

None of us holds eroic gestures and martyrs in high esteem, therefore we will demand no sacrifices. Not from us, not from anyone else. In this political climate, continuing to offer our services endangers our users and anyone who is part of our communities. In a world where allegations are disconnected from reality, we can only expect repression to be increasingly disproportionate. Under these circumstances, we are no longer able to maintain our original mission - offering secure and non-commercial digital tools.

These 25 years have been amazing. It’s been an “incredible ride”.

Now turn off your PCs, get out of your homes, struggle, hug each other and keep on smiling. We are stopping, but the Resistance and ideas do not stop. Stay human.

We will soon send instructions on how to back up the content of your blogs, mailboxes and websites, along with more technical recommendations. But keep in mind that, just as the autistici.org domain was made unreachable without previous notice, in the next few days we might suffer similar issues that we may not be able to predict.

Autistici/Inventati Collective

MartyPC – A Cycle-Accurate IBM PC/XT Emulator

Hacker News
github.com
2026-09-16 01:56:02
Comments...
Original Article

oo

MartyPC is an emulator of early IBM PCs and compatibles. It supports Windows, Linux and macOS.

Try MartyPC in your web browser!

User Guide

Click here to access the MartyPC User Guide

Downloading MartyPC

Builds are available through periodic releases . Newer, automatic builds are available via the Actions tab under the Artifacts for each workflow run. (You will need to be logged in to GitHub to download Artifacts).

Building MartyPC

If you're not on Windows, or you want the latest bleeding-edge version, you'll need to build MartyPC from source.

  • Last Released Version: 0.4.1
  • Current Developement Version: 0.5.0

See the Building MartyPC guide on the MartyPC Wiki for build instructions.

Why another PC emulator?

MartyPC began as a hobby project to see if I could write an emulator from scratch while learning the Rust programming language. My original goals for MartyPC were modest, but it has reached a level of functionality that I could have never imagined.

MartyPC's intended niche in the emulation world is an aide for retro PC development. It is packed with debugging tools and logging facilities, with many more planned. It may not be as user-friendly to set up as other emulators, but if you are familiar with editing configuration files you shouldn't have any major problems. Programmers writing software for the Intel 8088 can see and measure the exact cycle-by-cycle execution of their code.

Accuracy

Development of MartyPC started in April 2022. I began work on making MartyPC's 8088 CPU emulation cycle-accurate in November 2022. To do so, I validated the operation of the CPU against a real 8088 CPU connected to an Arduino MEGA microcontroller. See my Arduino8088 project for more details. This allows an instruction to be simultaneously executed on the emulator and a real CPU and the execution results compared, cycle-by-cycle. More info on this process is described on my blog .

In June 2024 I updated the 8088 test suite once again to support exercising of the 8088's prefetch queue. Many more cycle inaccuracies were found and corrected. MartyPC passes the 8088 V2 Test Suite with 99.9997% cycle-accuracy.

Extensive hardware research has been performed to improve MartyPC's peripheral emulation as well, including investigating the 8253 timer chip with an Arduino, investigating DMA timings with an oscilloscope , and ultimately, building a bus sniffer using a logic analyzer.

In April 2023, MartyPC became accurate enough to run the infamous PC demo, 8088 MPH .

8008mph01

In May 2023, MartyPC became the first PC emulator capable of emulating every effect in the PC demo Area 5150 . (See video here: https://www.youtube.com/watch?v=zADeLm9g0Zg )

8008mph01

Features

Currently, MartyPC can emulate the following systems:

  • The IBM Model 5150 (PC)
  • The IBM Model 5160 (XT)
  • Generic Turbo XT
  • The IBM PCjr
  • The Tandy 1000

Device Support

MartyPC emulates the following devices:

  • CPUs:

    • Intel 8088 - A cycle-accurate implementation of the Intel 8088 including the asynchronous BIU, processor instruction queue and prefetch logic. Tested for correctness and cycle-accuracy against hardware.
    • NEC V20 - A preliminary implementation of the NEC V20 CPU. Cycle-based, but not fully cycle-accurate or as performant as the real thing, as timings have not been adjusted from the 8088 that was used as a base. All native-mode V20 instructions are implemented and tested for correctness against hardware.
  • System Hardware:

    • 8255 PPI - Low-level keyboard emulation is supported via the PPI and keyboard shift register. Supports the 'turbo bit' found in TurboXT clones.
    • 8259 PIC - Mostly complete, but still missing advanced features such as priority rotation and nested modes.
    • 8253 PIT - Highly accurate, supporting PCM audio.
    • 8237 DMAC - Mostly implemented, but DMA transfers are currently "faked". DRAM refresh DMA is simulated using a scheduling system.
    • 8250 UART - Supports serial passthrough or mouse emulation.
    • Game Port - Supports two analog joysticks with two buttons each.
    • Parallel Port - Enough of a basic parallel port is emulated to be detected, but is not really functional for any purpose yet.
  • Video Devices:

    • CGA - A dynamic, cycle-or-character clocked implementation of the IBM CGA including the Motorola MC6845 CRTC controller allows MartyPC to run demanding PC demos like 8088MPH and Area5150. MartyPC takes a unique approach to PC video card emulation by simulating the entire display field - including overscan. Composite output and monitor simulation is supported, via reenigne's excellent composite conversion code (also used by DOSBox and 86Box)
    • TGA - A character-clocked implementation of the PCJr and Tandy Graphics Video Gate Array. Work in progress.
    • MDA - A character-clocked implementation of the IBM MDA card built on the Motorola MC6845 CRTC controller.
    • Hercules - The MDA device optionally supports emulation of the Hercules Graphics Adapter.
    • EGA - A character-clocked implementation of the IBM EGA builds on the techniques used developing the CGA. It is structured to replicate the logical functions of each of the LSI chips on the original hardware. It supports redefinable fonts, vsync interrupts and per-scanline pel-panning for smooth scrolling.
    • VGA - IBM VGA card emulation is in development, but graphics modes such as Mode 13h and Mode X are working.
  • Sound Devices:

    • PC Speaker - Not really its own sound device, the PC speaker is driven by MartyPC's timer chip emulation. It can produce reasonable quality PWM audio in demos such as 8088MPH, Area5150, and Magic Mushroom.
    • Adlib - The original Adlib Music Synthesizer is emulated, with OPL2 emulation provided by nuked-opl3, via my opl3-rs bidings. This is a bit CPU heavy, so you'll need a fast computer.
    • SN76489 - The 3-voice sound chip found in the Tandy 10000 model line and the IBM PCjr is emulated - with a neat debug display that provides UV meters and oscilloscope views of each channel.
    • Disney Sound Source - The Disney Sound Source was an inexpensive parallel DAC with a 16-sample FIFO and volume knob. Not a lot of games support it, but it works well in the few titles that do.
  • Storage Devices:

    • µPD765 FDC - Currently robust enough to support both DOS and Minix operating systems. MartyPC uses my disk image library, fluxfox , which allows it to support a wide variety of PC disk image formats. MartyPC's FDC emulation is still not as accurate as I'd like it to be, but it can support a number of copy-protected titles, given a disk image of the appropriate format.
    • IBM/Xebec 20MB HDC - Emulated with basic VHD support. MartyPC currently supports a single disk geometry of 20MB when using this controller.
    • XT-IDE - Emulation of the XT-IDE Rev 2 board allows MartyPC to support a wide range of hard disk formats. This emulation is still in early stages, and may be a bit rough around the edges. Not all ATA commands are implemented.
    • jr-IDE - Emulation of the jr-IDE provides the IBM PCjr machine with IDE hard disk support and 736K of memory backfill. Other features such as the RTC and flash are not yet implemented.
    • PCjr Cartridges - PCjr cartridge ROMs are supported, in JrRipCart (.JRC) format
  • Memory Expansion Devices:

    • LoTech 2MB EMS Card - 2MB of EMS memory is made available via the LoTech EMS board .
    • Generic Memory Expansion Cards - Memory expansion cards can be defined for either the ISA bus or PCjr SideCar expansion slot.
  • Input Devices:

    • Keyboard Support - IBM Model F, Tandy 1000, and PCjr keyboards are emulated.
    • Serial Mouse - A standard Microsoft serial mouse can be connected to the COM port of your choice.
    • Joystick - Game port joysticks are emulated via configurable keyboard controls.
    • Light Pen - A light pen is emulated for the CGA card and the PCjr.

Dual-Head Support

MartyPC supports dual-video card and multi-monitor configurations, so you can run a secondary monitor powered by an MDA or Hercules adapter - even in the same window. Each display viewport can receive independent shader configurations.

dualhead

Configuration Support

MartyPC supports custom machine configurations via base machine configuration profiles plus optional extensions called 'overlays', analagous to installing extension cards or other upgrades.

Debugging Support

MartyPC has an extensive debugging GUI with several useful displays including instruction disassembly, CPU state, memory viewer, and various peripheral states. Code and memory breakpoints are supported. MartyPC also supports instruction and cycle-based logging.

debugger01

Shader support

A basic, configurable CRT shader is included with more to come ( LibraShader support is planned)

shaders01

Screenshots

For more, check out the Screenshot Gallery section of the Wiki !

Special Thanks

I have a long list of people to thank (See the About box!), but I would especially like to mention the contributions made by reenigne . Without his work reverse-engineering the 8088 microcode, this emulator would never have been possible. I would also like to thank Ken Shirriff and his excellent blog , covering much of the silicon logic of the 8086 (and 8088 by extension).

Thanks to Jetbrains for providing MartyPC with licenses for Jetbrains products.

JetBrains logo.

A software thing I built: GPS on a 25MHz 486-SX

Hacker News
forum.vcfed.org
2026-09-16 01:43:44
Comments...
Original Article

I don't know if anybody cares about stuff like this, but I thought I'd share a quick story about a project I was hired to build back in the 90's as an embedded systems software developer. This is a project I wish I still had access to.

It's something I worked on where I was asked to build a "real-time GPS-driven moving map display" -- what we simply call a "GPS" today. Only today it's built into virtually every cellphone and tablet computer in the world.

I was asked to write this software so it ran on a hardware platform with a 25MHz 486-SX CPU, 32MB of RAM and the software would be in ROM. IIRC, the screen display was 1024x768. They gave me a laptop configured like that for testing. The 486-SX was the one without a math co-processor.

At the time, all of the math stuff I could find for GIS applications was built around heavy use of floating-point numbers and lots of trig formulas. (My BS was in Math/Computer Science.)

As it happened, a few months before that, an article was published in Dr. Dobb's Journal about a library that took a very different approach. It was called Hipparchus from a company named Geodesy. It used Voronoi Cells and reduced most math to 8- and 16-bit integer calculations with one or two single-precision floating-point calculations. It actually had higher resolution than Extended-precision floating point math!

I built a prototype with the code from that article, and was surprised how well it worked. I had the client buy a copy of the Hipparchus library and built a fully-working model. The client ran into some financial issues and I had to turn over all of the stuff to them. I have no idea what happened to it after that. They could have become a billion-dollar business.

Curiously, someone caught wind of what I had done and contacted me directly to see if I could do that for them. Unfortunately, I was just a contractor and didn't own the IP. It's worth mentioning that Phoenix Technologies took a "clean-room" approach to building their own BIOS for IBM PC clone vendors to use, and they never got sued for it. But it wasn't until 1989 that a lawsuit between Apple and Franklin computer resolved things in favor of the "clean-room" approach. Unfortunately, I wasn't aware of this stuff in 1993, or I would have known to simply start over with the specs.

Anyway, this was 5 years BEFORE anybody had ever seen a consumer-grade GPS device from Garmin or Magellan. The big problem they had was ... it took that long for an inexpensive CPU with sufficient floating-point speed to run traditional trig algorithms. And, what I had built was twice as fast as those first-gen devices! It was literally 10 years before anybody had a consumer-grade GPS that had similar performance to what I had built in 1993 that did not require a math co-processor, and it updated a 1024x768 color display every 2 seconds. Those first little consumer GPS devices had tiny 360x240 monochrome screens on them.

Anthropic lands deal in $31bn datacentre in western Queensland

Guardian
www.theguardian.com
2026-09-16 01:20:00
Premier David Crisafulli has described the deal as a ‘major win’ that will deliver more jobs for the state The AI giant Anthropic, the developer behind the large language model Claude, has done a deal to lease the site of its first Australian datacentre in western Queensland. Premier David Crisafull...
Original Article

Anthropic, the AI giant behind the large language model Claude, has done a deal to lease the site of its first Australian datacentre in western Queensland .

The state premier, David Crisafulli , announced the decision in parliament on Wednesday.

The proposed Western Downs digital park is to be built by Singapore-based Zerra DC near Dalby, north-west of Toowoomba, and will connect to the Braemar power station. It is planned to open next year at a reported cost of $31.9bn.

Crisafulli told parliament that he had met with Anthropic during a trade mission and had outlined “why Queensland was the place to invest, with energy security and a streamlined approval process”.

“To have secured Anthropic’s first major investment in Australia is a massive show of confidence in Queensland,” he said. “It’s a major win that will deliver more jobs and put more energy into Queensland’s grid.”

The state, along with the Northern Territory, rejected a commonwealth push in July to make new AI centres use renewable energy . The state government claims to have won a carve-out from a national deal, allowing it to use the state’s coal generators to power them.

Crisafulli said Anthony Albanese had made a “commonsense decision” to “allow Queensland to chart its own path for power supply”.

“[Datacentre development] must add energy generation to the grid and drive down power prices for Queenslanders,” he said.

“It must protect local water supplies, and it must respect the communities where these centres go … we’ll do it in the right locations, and we’ll do it in the right way. Communities will have a say, just as they now do for projects like wind, solar, and batteries.”

skip past newsletter promotion

The Albanese government is weighing up a proposal to give AI companies access to Australian creatives’ works by default – a deal the independent senator David Pocock said would throw creatives “under the bus” in pursuit of datacentre funding.

Anthropic’s chief executive, Dario Amodei, on Tuesday reiterated his call for a slowdown of AI development amid rising concern about artificial intelligence.

Datamimic – don't let your coding agent invent its own test world

Hacker News
github.com
2026-09-16 00:58:36
Comments...
Original Article

DATAMIMIC — Governed Test Data for Regulated Enterprises

This repository contains the DATAMIMIC Community Edition (CE). MIT-licensed, Python-native, MCP-ready.

CE is fully usable standalone for deterministic synthetic data generation and PII-aware pseudonymization. The Enterprise Platform adds governed workflows, PII scanning, role-based access, audit logging, scheduling, multi-system execution, and the full operational layer that regulated enterprises require.

👉 Enterprise Platform: datamimic.io |  📘 Docs: docs.datamimic.io |  📅 Book a strategy call: datamimic.io/contact

🤖 AI agent? Start at AGENTS.md and use the project CLI: preserve new intent as model.dm.json , submit an early best attempt via datamimic scaffold ... --format json , repair from the structured issues, declare an expectation per stated requirement, and stop on verified=true . Existing raw XML uses lint plus bounded dry-run.


CI Coverage Maintainability Python License: MIT MCP Ready


What is DATAMIMIC?

DATAMIMIC CE is the open-source deterministic data engine at the core of the DATAMIMIC Enterprise Platform. It is usable standalone for synthetic data generation and PII-aware pseudonymization in any local, CI, or agent-driven workflow.

The Enterprise Platform adds the governed workflows, scanners, dashboards, and execution layer that regulated enterprises require for production-scale test-data operations.

Available in CE (this repo):

  • Generate fully synthetic, deterministic datasets — model-driven, no source data required
  • Pseudonymize staging/QA exports — deterministic (seeded) or privacy-maximized (non-seeded) field transformation; PII fields identified and modeled manually in the XML pipeline
  • Execute single-system pipelines against PostgreSQL · MySQL · Oracle · MS SQL · SQLite · MongoDB · CSV · JSON · XML · XLSX · DbUnit · fixed-width ( .fcw )
  • Model behavior — weighted state machines, composite multi-field references, control flow ( <while> , <assert> ), and a scriptable memstore for staged aggregation
  • Emit provenance — append-only execution logs and per-output content hash for audit re-execution
  • Guide agents — machine-readable capabilities, progressive reference queries, and one canonical CLI scaffold transaction; an optional MCP adapter exposes the same authoring service

The Enterprise Platform adds:

  • PII scanner — probability-scored field detection with configurable thresholds via DataWorkbench
  • Multi-system execution — Oracle / MongoDB / Kafka in coordinated workflows with referential integrity
  • Industry message templates — EDIFACT / SWIFT MT / HL7 v2.x / HL7 FHIR generated as deterministic test/training artefacts
  • Governance layer — role-based dashboards, audit trails, approval flows, reusable enterprise templates, scheduler
  • Performance core — Rust fastpath, ML/auto-regressive engine for complex distributions, keyset and manifest building, optimised distributed execution
  • On-premise / air-gapped deployment — podman-compose or Helm, with consulting-led rollout

Deployed in regulated EU banking environments for deterministic test data across Oracle, MongoDB, and Kafka pipelines. Reference customers available under NDA — see also datamimic.io case studies .


AI agents: author, verify, and run data models

The CLI is the baseline agent contract. Install CE with pip install datamimic-ce ; inside this checkout, use .venv/bin/datamimic so a stale global installation cannot change the available schema or commands.

Need CLI tool Contract
Discover the live structural surface datamimic capabilities Compact machine-readable JSON index by default; --full for the complete manifest, --section <name> for one section.
Learn the Intent Model progressively datamimic reference authoring , then datamimic reference authoring --category <category> --kind <kind> Start with the query catalogue, then load only the typed fragment needed.
Author a new model Preserve model.dm.json ; run datamimic scaffold model.dm.json --format json One compile/lint/bounded-run/acceptance transaction per changed attempt. Stop on verified=true ; generated XML is runtime output.
Work with existing raw XML datamimic lint model.xml --format json , then datamimic dry-run model.xml --format json Fix diagnostics, inspect bounded samples for intent, then use datamimic run model.xml only when real execution is requested.
Find a DSL detail datamimic reference overview , then a narrow reference topic/name Query the live model and rule registries instead of guessing elements, generators, scope, distributions, or rules.

capabilities , authoring-reference projections, and the commands shown with --format json return machine-readable JSON. On a failed scaffold attempt, change model.dm.json using its structured validation issues, typed repair, or rule diagnostics before retrying. A typed max_count remediation instead changes only the bounded scaffold parameter to at least its reported minimum. Never repeat an identical failed call. A successful scaffold result is terminal for authoring, so do not lint or dry-run its generated XML again. Exact source fragments are discoverable through queries such as --category source --kind memstore .

Optional MCP adapter

When the calling environment already exposes DATAMIMIC MCP tools, they map to the same canonical contracts and implementations: reference datamimic_reference , scaffold datamimic_scaffold , lint datamimic_check , and dry-run datamimic_run . Install the adapter with pip install "datamimic-ce[mcp]" ; registration details belong in the MCP quickstart , not in the authoring workflow. The adapter intentionally exposes only the four canonical reference, scaffold, check, and bounded-run operations; domain generation remains a Python/CLI capability rather than a parallel MCP authoring path.

Prompts to paste into your agent

Author and verify a new model

Create the dataset I describe with DATAMIMIC.

Read AGENTS.md first. In a repository checkout use `.venv/bin/datamimic`;
otherwise use the current `datamimic` CLI. Preserve my intent as
`model.dm.json`; do not hand-write XML.

Start from the minimal valid document shape in AGENTS.md ("Authoring a new
model"). Two rules prevent most rejections: the top level allows ONLY
version, seed, products, expectations; product-level "kind"
(generated/source/time_series) is a different vocabulary from field-level
"kind" (increment, values, weighted, int_range, decimal_range, pattern,
constant, script). Range fields take minimum/maximum, never min/max.

Submit EARLY: run `datamimic scaffold model.dm.json --format json` with your
best attempt after at most one discovery call. Repair from the structured
issues (path/code/message/allowed_fields) and diagnostics (fix_hint) — they
teach the schema faster than more discovery. Never resubmit an unchanged
document. If a remediation requests a larger max_count, retry scaffold with
at least that value without changing the intent.

Declare an expectation for every requirement I state (counts as exact_count
with a "count" field, uniqueness, allowed values, ranges, foreign keys) —
verified=true certifies only what you declared. Stop on verified=true; do
not lint or dry-run the generated XML. If I request real execution, save the
returned XML as a generated artifact and run that descriptor. Return the
model.dm.json path and concise verification evidence.

Relational hierarchy with referential integrity (fully supported — no XML needed)

Seed a relational dataset with referential integrity: 4 customers, each with
exactly 2 orders.

Customers get an incrementing unique id and a region from
{north, south, east, west}. Each order carries the REAL parent customer id
as a foreign key and an amount between 10.0 and 500.0.

Follow AGENTS.md's "Authoring a new model" and its structural recipes:
orders nest inside the customer product's "children" array; the FK field is
{"kind": "script", "script": "parent.id"} with a foreign_key role — a
randomly generated FK passes schema validation but fails per-parent-count
acceptance. Declare expectations for the customer count, customer id
uniqueness, exactly 2 orders per customer (per_parent_count), the
orders->customers foreign key, and the amount range. Stop on verified=true
and show the acceptance evidence.

Raw XML remains supported for existing descriptors (lint → dry-run → run; see AGENTS.md). For new models it is a last resort: only when a scaffold issue explicitly classifies the requirement as unsupported_intent should an agent hand-author XML, preserving that evidence.


CE vs Enterprise Platform

CE and EE are not the same engine with a feature flag . They share the DSL and determinism contract, but EE is an independently optimised execution engine built for enterprise-scale throughput and operational control.

Engine comparison

Capability Community Edition (CE) Enterprise Platform (EE)
Deterministic data generation
Deterministic seeding in the DSL ✅ entities + standalone literal <key generator> (4.0.0) ✅ same, plus sandboxed script expressions and stdlib random calls
Pseudonymization — seeded (GDPR Art. 4(5); supports Art. 25 / Art. 32) ✅ manual model ✅ automated via DataWorkbench
Pseudonymization — non-seeded (privacy-maximized) ✅ manual model ✅ automated via DataWorkbench
Python API + XML pipelines
Domain models: Finance, Healthcare, Demographics
Time-series generation ( <generate start/end/interval> , ISO 8601, prefix-stable)
MCP server for AI agent integration
CLI + local execution
Scale millions of records via Python multiprocessing (and optional Ray) designed for billion-record workloads — Rust fastpath, optimised multi-process execution, and keyset/manifest building on top of the shared Ray distribution layer
PII scanner ✅ probability-scored field detection, configurable threshold, DataWorkbench integration
Runtime configuration profiles ✅ Performance · Balanced · Flexibility
Memory management standard optimised for high-volume batch and streaming
Logging granularity flat execution log configurable: minimal · standard · deep nested tracing
Nested structure evaluation basic deep nested generation with extended condition + ruleset evaluation
Importer / exporter logging per-stage logging for importers and exporters
Error handling standard exceptions structured error catalog with recovery strategies
Rust fastpath performance-critical paths in Rust
Keyset and manifest building reads live DB schemas to build coordinated multi-table generation plans
ML / auto-regressive engine combine statistical models with conditions, rulesets, validators for complex distributions

Platform capabilities (EE only)

Capability EE
Multi-user collaboration
Role-based access control (RBAC)
Audit logs + provenance dashboards
PII scanner — probability scoring, threshold-based field flagging
DataWorkbench — visual field mapping and pseudonymization model builder
Reusable enterprise template library
Scheduled execution + task runner
CI/CD pipeline integration (Tosca, Jenkins, GitLab)
Multi-system execution: Oracle, MongoDB, Kafka
Template engine: schema-aware editors for EDIFACT, SWIFT MT, HL7 v2.x, and HL7 FHIR — customer-uploadable specs, further industry formats built per engagement on the same framework
Audit-evidence artefacts for GDPR Art. 30 records, PCI DSS 4.0 Req. 6.5.5 (test data) reviews, and — for US Covered Entities / Business Associates — HIPAA §164.312 evidence packs
On-premise deployment + air-gapped environments
LSP-powered IDE tooling for DSL authoring

👉 Explore the Enterprise Platform | Book a platform demo


EE runtime profiles

The EE core supports three runtime configuration profiles, selectable per execution context:

Profile Optimises for Typical use case
Performance Maximum throughput via Rust fastpath, optimised multi-process execution, and Ray-based distribution Bulk generation at billion-record volumes to PostgreSQL, Oracle, Kafka
Balanced Throughput + full audit logging Standard enterprise pipeline runs with compliance requirements
Flexibility Deep nested evaluation, extended condition and ruleset processing Complex domain models with ML engine combinations, multi-level referential structures

Logging depth is independently configurable per profile — from minimal (throughput-optimised) to full nested tracing across importers, exporters, and generation stages.


EE template engine

The EE template engine generates industry-standard financial messages from DATAMIMIC models. The workbench parses uploaded message samples, auto-detects the message type, and validates edits against the registered spec version in real time.

Capabilities

  • Spec-aware form editing — segments and elements rendered as structured forms with mandatory/optional indicators, per-field value suggestions, and inline custom-extension support
  • Strict validation against baked spec versions, with segment- and element-level error reporting
  • Advisory mode when a spec is unregistered or in draft — editing stays enabled, validation continues as guidance
  • Round-trip between the structured form view and the authoritative template text — no fidelity loss
  • Download / adjust / upload your own spec — customers can extend or override the baked spec catalogue without waiting for a release
  • Live structure tree + preview for every edit
  • File auto-detection — upload an existing message, the editor identifies the type and loads the matching spec

Format coverage

Format Coverage
UN/EDIFACT Schema-aware form editor; spec versions and subsets per engagement
SWIFT MT Schema-aware form editor; categories and SR versions per engagement
HL7 v2.x Schema-aware form editor; versions per engagement
HL7 FHIR Schema-aware form editor for FHIR resources (Patient, Observation, Encounter, …); profiles per engagement
Further industry formats (ISO 20022 / MX, vertical dialects) Built into the editor catalogue per customer engagement, on the same framework

Customers can extend the spec catalogue between releases by downloading, adjusting, and uploading their own spec files directly.

Generated messages are deterministic and traceable to their source model, and syntactically valid against the registered spec. They are intended for test and training environments only — they are not network-validated and must not be transmitted on production SWIFTNet or EDI networks. See the SWIFT CSP note below.


Who is DATAMIMIC for?

Enterprise Platform (EE)

Role What DATAMIMIC solves
QA / Test Manager Eliminate manual test data requests. Self-service, governed, always ready.
Business Analyst Define data requirements in business-readable models — no scripting needed.
Platform / DevOps Engineer Integrate deterministic test data generation into CI/CD and scheduled pipelines.
Compliance / Audit Full audit trail for every generation run. Regulator-ready logs, no production data exposure.
Enterprise Architect One governed standard across Oracle, MongoDB, Kafka, flat files, and custom systems.

Community Edition (CE)

Developers and data engineers who need deterministic synthetic data generation or PII-aware pseudonymization in local environments, CI pipelines, or agent-driven workflows. PII field identification is manual — the EE DataWorkbench automates this step.


Why deterministic generation matters

Most test data tools produce random output. That breaks regression tests, audit trails, and cross-team reproducibility.

DATAMIMIC's determinism contract (CE):

  • Same engine version + same model + same seed = byte-identical output , every run, every machine. Holds at three layers: the generate_domain facade, every domain service called directly, and every literal generator that accepts an rng= argument. Verified per-service on every CI run via tests_ce/architecture/test_service_replay_determinism.py .
  • DSL-level seeding: <setup rngSeed="N"> makes the whole model deterministic — every seed-less <variable entity="…"> derives a reproducible child RNG from it, and <variable rngSeed="…"> overrides it for that block (no seed anywhere → wall-clock random). Verified by tests_ce/integration_tests/test_determinism_seed_scenarios . As of 4.0.0 the same seed also reaches standalone literal generators ( <key generator="…"> ), typed/pattern keys, DateTimeGenerator , and cross-page unique picks — machine-independently.
  • Source reads: distribution="ordered" reads a data source in stable file order; distribution="random" shuffles but replays identically when <setup rngSeed> is set (without a seed the shuffle is non-deterministic by design, for privacy-maximized one-time deliveries). Deterministic shuffling across distributed / multi-process execution is EE.
  • Provenance hash on every facade output = re-executable lineage. Same input → same determinism_proof.content_hash , always.
  • UUIDv5 entity identifiers = stable across runs and machines.
  • Single wall-clock SPOT ( now_utc_naive() ); raw datetime.now() is forbidden in production code and the clock-drift architecture gate fails CI on any reintroduction.
  • RNG/clock runtime SPOTs in datamimic_ce/domains/domain_core/runtime/ : spawn_rng (reproducible child-RNG derivation), now_utc_naive , and resolve_clock . The same contract vocabulary the Enterprise Platform enforces end-to-end.

The Enterprise Platform (EE) goes further: beyond the CE contract, EE makes the whole execution environment deterministic — a configurable/frozen wall-clock (not just CE's fixed anchor), and deterministic SAFE_GLOBALS plus the Python random functions, so sandboxed script expressions and any stdlib random call replay identically as well.

from datamimic_ce.domains.facade import generate_domain

request = {
    "domain": "person",
    "version": "v1",
    "count": 1,
    "seed": "regression-suite-42",       # identical seed → identical output
    "locale": "en_US",
    "clock": "2025-01-01T00:00:00Z"      # fixed clock = stable time context
}

response = generate_domain(request)
# response["determinism_proof"]["content_hash"] is stable across runs.

Direct service use is equally deterministic when given a seeded RNG:

import random
from datamimic_ce.domains.finance.services import CreditCardService

# Same seeded Random → byte-identical CreditCard across runs.
card_a = CreditCardService(rng=random.Random(42)).generate()
card_b = CreditCardService(rng=random.Random(42)).generate()
assert card_a.bic == card_b.bic and card_a.card_number == card_b.card_number

Determinism contract — CE vs EE

Scope CE Enterprise Platform
Facade ( generate_domain registered domains) ✅ byte-identical, CI-gated ✅ byte-identical
Domain services (direct use with seeded rng=... ) ✅ byte-identical, CI-gated ✅ byte-identical
Literal generators (with seeded rng=... ) ✅ byte-identical ✅ byte-identical
RNG / clock runtime SPOTs spawn_rng , now_utc_naive , resolve_clock ✅ same contract, enforced end-to-end
Architecture gates in CI ✅ facade replay + service replay (every service) + clock drift ✅ 5+ gates (RNG ownership, clock drift, DSL eval, seeded-mode propagation, dataset SPOT)
Custom XML pipelines (seeded via <setup rngSeed> ) ✅ byte-identical, machine-independent (single-process) ✅ byte-identical, distributed
Multi-system coordinated execution (Oracle + MongoDB + Kafka in one run) ✅ byte-identical end-to-end
Seeded vs unseeded pseudonymization (deterministic clock anchor vs CSPRNG live-clock)
Threat-led / TLPT-grade audit evidence (full contract enforcement, per-stage execution logging)

CE delivers contract-enforced determinism for the synthetic-data generation surface (facade, services, generators) and, as of 4.0.0, for seeded XML descriptors — byte-identical across machines, executed single-process. The Enterprise Platform extends the same contract to distributed and multi-system execution with referential integrity and the seeded/unseeded pseudonymization modes, and adds the five drift-gates that lock the contract end-to-end for regulated deployments.


How DATAMIMIC differs from Faker and generic generators

Faker / Random generators DATAMIMIC CE DATAMIMIC EE
Reproducible output
Domain-aware relationships
Business logic constraints
Per-output provenance hash
Source data pseudonymization ✅ manual ✅ automated
PII field detection ✅ probability-scored
Enterprise governance layer
Multi-system execution
Role-based workflows
Designed for regulated-industry deployment (governance, audit, RBAC)
# Faker — broken relationships
from faker import Faker
fake = Faker()
patient_age = fake.random_int(1, 99)
conditions  = [fake.word()]
# "25-year-old with Alzheimer's" — meaningless for any real test

# DATAMIMIC — domain-aware, deterministic with a seed
import random
from datamimic_ce.domains.healthcare.services import PatientService
patient = PatientService(rng=random.Random(42)).generate()
print(f"{patient.full_name}, {patient.age}, {patient.conditions}")
# Age-appropriate, domain-consistent — and identical every run with a fixed seed

Quickstart — Community Edition

Healthcare domain

import random
from datamimic_ce.domains.healthcare.services import PatientService

patient = PatientService(rng=random.Random(42)).generate()
print(patient.full_name, patient.age, patient.conditions)
# Age-appropriate conditions, demographically realistic; deterministic with a seed

Finance domain

import random
from datamimic_ce.domains.finance.services import BankAccountService

account = BankAccountService(rng=random.Random(42)).generate()
print(account.account_number, account.balance)
# Balance-consistent, locale-correct; reproducible with a seed

Pseudonymization — CE (manual model)

DATAMIMIC supports two pseudonymization modes with different privacy postures:

Mode How Legal classification Use case
Seeded ( rngSeed set) Deterministic, reproducible Pseudonymization (GDPR Art. 4(5)) Regression testing, stable CI/CD pipelines
Non-seeded (no rngSeed ) Non-deterministic, no reversible mapping at field level Privacy-maximized transformation One-time data delivery, higher privacy posture

Note on GDPR anonymization: Full anonymization status under GDPR depends on complete field coverage across all quasi-identifiers and a re-identification risk assessment on the complete record — not on individual field transformation alone. DATAMIMIC does not make anonymization claims on behalf of the customer. Non-seeded mode maximizes privacy at the transformation level; the customer is responsible for assessing re-identification risk across the full dataset.

In CE, PII fields are identified and modeled manually in the XML pipeline:

<setup defaultSeparator=",">
  <generate name="customers" source="customer_export.csv" target="CSV" distribution="ordered">
    <!-- distribution="ordered" reads the source in a stable order — required so the
         Nth source row maps to the same seeded synthetic value on every run. The
         default ("random") shuffles non-deterministically and would break it.
         rngSeed on the <variable> makes the synthetic values reproducible; drop
         rngSeed for the privacy-maximized (non-deterministic) mode. -->
    <variable name="p"   entity="Person"      dataset="DE" rngSeed="42" />
    <variable name="acc" entity="BankAccount" dataset="DE" rngSeed="42" />

    <key name="first_name" script="p.given_name" />
    <key name="last_name"  script="p.family_name" />
    <key name="email"      script="p.email" />
    <key name="iban"       script="acc.iban" />
    <key name="birth_date" script="p.birthdate" />
  </generate>
</setup>

Built-in converters can additionally transform a key's value — e.g. irreversibly hash the original instead of replacing it, or partially mask it:

<key name="email" script="p.email" converter="Hash('sha256','hex')" />
<key name="iban"  script="acc.iban" converter="MiddleMask(8, 4)" />

Available converters (13): Mask , MiddleMask(start, end) , CutLength(n) , Substring(start, end) , JavaHash , RemoveNoneOrEmptyElement , Hash(type, format[, salt]) , DateFormat(fmt) , Append , UpperCase , LowerCase , Date2Timestamp , Timestamp2Date .

datamimic run ./pseudonymize-customers/datamimic.xml

source is a controlled export or staging input — never a live production connection.

With rngSeed set: same source record → same pseudonymized output on every run. Stable for regression testing.

Without rngSeed : non-deterministic output — no reversible mapping exists at the field level. Stronger privacy posture for one-time delivery scenarios.

In the Enterprise Platform (EE): the DataWorkbench PII scanner automatically scans source schemas, assigns probability scores to each field, and flags candidates above a configurable threshold. Flagged fields are wired into the pseudonymization model automatically — no manual field mapping required.

<setup>
  <generate name="patients" count="1000" target="CSV">
    <variable name="patient" entity="Patient" dataset="US" ageMin="60" ageMax="80" rngSeed="42" />
    <key name="full_name"   script="patient.full_name" />
    <key name="age"         script="patient.age" />
    <array name="conditions" script="patient.conditions" />
  </generate>
</setup>
datamimic run ./patient-scenario/datamimic.xml

Time-series generation — CE

Any <generate> becomes a time-series loop when given strict ISO 8601 start / end / interval attributes. Per iteration the script context exposes a ts namespace:

Variable Type Meaning
ts.now datetime Current tick
ts.step int Position within one series ( 0..N-1 )
ts.series int Which series this row belongs to ( 0..count-1 )

Output column names — including whether to even emit a timestamp or series-id column — are entirely the user's choice via <key> . The primitive is domain-agnostic; the same DSL covers IoT readings, financial ticks, log streams, smart meters, anything time-indexed.

<setup>
  <!-- Stock ticks: three symbols, 5-min interval, 30-min window (writes ticks.csv) -->
  <generate name="ticks" count="3"
            start="2026-01-01T09:30:00+00:00"
            end="2026-01-01T10:00:00+00:00"
            interval="PT5M"
            target="CSV">
    <key name="timestamp" script="ts.now.isoformat()"/>
    <key name="symbol"    script="['AAPL','MSFT','GOOG'][ts.series]"/>
    <key name="price"     script="100 + ts.step * 0.25"/>
  </generate>

  <!-- Sensor with diurnal seasonality, single series (count defaults to 1; writes readings.csv) -->
  <generate name="readings"
            start="2026-01-01T00:00:00+00:00"
            end="2026-01-08T00:00:00+00:00"
            interval="PT1H"
            target="CSV">
    <key name="timestamp" script="ts.now.isoformat()"/>
    <key name="value"     script="20 - 10 * math.cos(ts.now.hour * math.pi / 12)"/>
  </generate>
</setup>

Guarantees:

  • Prefix-stable by construction — the first N ticks of series 0 are byte-identical regardless of total window length, because each row's ts.now is a pure function of start + interval * step .
  • Loop order is contiguous per series — series 0's full sequence, then series 1's, etc. Makes downstream grouping trivial.
  • Strict ISO 8601 start / end via datetime.fromisoformat (Z-suffix supported); interval via the isodate library ( PT1H , PT15M , PT5S , P1D , P1W , P1DT12H , fractional seconds for sub-second precision). Resolution: PT0.001S = 1 ms, PT0.000001S = 1 µs (Python datetime.timedelta microsecond floor; sub-µs intervals and constant-length-undefined units like months/years are rejected with a clear error).
  • count is orthogonal , not overloaded — it means "outer-loop iterations of this <generate> " in all modes (same as nested <generate count=…> ). In time-series mode each outer iteration is one series of N ticks, so total rows = count × ticks_per_series . Default count="1" keeps single-series fixtures terse.
  • Naming caveat — a <key name="ts"> output column would shadow the namespace ( current_product overrides current_variables in script scope), and a <variable name="ts"> is rejected at parse time. Use a different name, e.g. timestamp for the column.

Composes with the existing <variable> mechanism for multi-source merges (e.g. join each tick with a sensor-metadata CSV via <variable source="meta.csv" cyclic="True"> inside the same <generate> ), with <key condition="..."> filtering, and with <nestedKey> sub-scopes — the ts namespace is visible everywhere a <key script> runs. See tests_ce/integration_tests/test_timeseries/ for committed DSL fixtures + proofs (including pagination invariance).


Where CE fits on its own

Most teams adopt CE for one of three reasons. EE is not required for any of them.

1. Reproducible test data for CI/CD pipelines. Pin a seed against the generate_domain facade — or hand a seeded random.Random to any domain service — and you get byte-identical output across runs and machines. Both layers are gated on every CI run by tests_ce/architecture/ . Regression tests stop being flaky because the input data is stable across runs.

from datamimic_ce.domains.facade import generate_domain

response = generate_domain({
    "domain": "person", "version": "v1", "count": 1,
    "seed": "ci-pipeline-42", "locale": "en_US",
    "clock": "2026-01-01T00:00:00Z",
})
# Same engine version + same model + same seed → same output, every machine, every run.

2. Deterministic data backend for AI agents and LLM tooling. The CLI and Python API are the baseline surfaces for seeded, verifiable generation. The optional MCP adapter ( pip install "datamimic-ce[mcp]" ) exposes the canonical reference, scaffold, check, and bounded-run authoring operations. Generated domain-facade outputs include a determinism_proof.content_hash , so Python/CLI callers can re-execute and verify the data later — useful for agent regression tests and any workflow where the data an agent saw must be reconstructable.

3. Pseudonymization of staging and QA exports. Manual model in CE (XML pipeline), no scanner license required. Seeded mode for stable regression test data; non-seeded mode for one-time deliveries with maximized privacy posture. See the Pseudonymization section above .


Where DATAMIMIC fits in your compliance program

DATAMIMIC produces evidence and reproducible artifacts that support compliance work. It does not replace your DPO, your CISO, or your auditor. The following are pointers for where DATAMIMIC outputs commonly slot into established programs:

Both editions produce reproducible artefacts. CE covers single-system fixtures and provenance evidence; multi-system audit evidence with role-based dashboards is EE.

Regulation / standard Where DATAMIMIC contributes
DORA (Reg. 2022/2554) — Art. 24 (testing of ICT tools, systems and processes; non-TLPT scope) Reproducible test datasets for non-TLPT resilience tests; deterministic data fixtures for ICT testing programmes
ISO/IEC 27701:2019 — A.7.2.8 (records related to processing PII) and A.7.4.5 (PII minimisation) Synthetic data in lieu of PII in non-production environments; documented model definitions as supporting evidence
HIPAA Security Rule — §164.312 technical safeguards (US Covered Entities / Business Associates only) Synthetic Patient/MedicalDevice/MedicalProcedure data for dev and test environments without ePHI exposure
GDPR — Art. 4(5) pseudonymization definition; Art. 25 privacy by design; Art. 32 security of processing Seeded pseudonymization with deterministic mapping; non-seeded mode for stronger privacy posture
PCI DSS 4.0 — Req. 6.5.5 (live PANs prohibited in test/development) Synthetic PAN generation for test environments; deterministic tokenisation reproducible across runs

These pointers do not constitute legal advice or a compliance attestation. Consult your DPO, CISO, or qualified counsel for formal compliance determinations. Full anonymization status under GDPR depends on re-identification risk across the complete dataset — see the pseudonymization disclaimer above .


Architecture

CE and EE share the DATAMIMIC DSL and the determinism contract. The execution layer is separate: CE is a Python execution engine using multiprocessing (with optional Ray for distribution); EE is an independently-optimised execution engine with a Rust fastpath, ML/auto-regressive generation, keyset and manifest building from live schemas, and optimised distributed execution at billion-record scale.

╔══════════════════════════════════════════════════════════════════╗
║              DATAMIMIC ENTERPRISE PLATFORM (EE)                  ║
║                                                                  ║
║  ┌──────────────────────────────────────────────────────────┐    ║
║  │  PLATFORM LAYER                                          │    ║
║  │  UI · RBAC · Governance · Audit Dashboards               │    ║
║  │  DataWorkbench · PII Scanner · Pseudonymization Builder  │    ║
║  │  Scheduler · Task Runner · CI/CD · Template Engine       │    ║
║  └──────────────────────────────────────────────────────────┘    ║
║                                                                  ║
║  ┌──────────────────────────────────────────────────────────┐    ║
║  │  EE CORE  (separately maintained, more advanced than CE) │    ║
║  │                                                          │    ║
║  │  Rust fastpath for performance-critical paths            │    ║
║  │  ML / auto-regressive engine for complex distributions   │    ║
║  │  Keyset and manifest building from live DB schemas       │    ║
║  │  Optimised distributed execution at billion-record scale │    ║
║  │  Runtime profiles: Performance · Balanced · Flexibility  │    ║
║  │  Deep nested evaluation · Conditions · Rulesets          │    ║
║  │  Structured error catalog · Per-stage execution logging  │    ║
║  └──────────────────────────────────────────────────────────┘    ║
╚══════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════╗
║              DATAMIMIC COMMUNITY EDITION (CE)  — this repo       ║
║                                                                  ║
║  Determinism Kit · Domain Services · Schema Validators           ║
║  Synthetic Generation · Pseudonymization (manual model)          ║
║  Python API · XML Pipelines · CLI · MCP Server                   ║
╚══════════════════════════════════════════════════════════════════╝

         ↓              ↓              ↓              ↓
    PostgreSQL       Oracle         MongoDB      CSV / JSON / XML

EE adds Kafka, EDIFACT, SWIFT MT, HL7 v2.x, and HL7 FHIR as additional targets — see Supported systems below. Both editions share the DATAMIMIC DSL and determinism contract.


Supported systems

System CE EE Notes
PostgreSQL EE adds schema introspection and referential integrity
MySQL
Oracle EE production-validated in regulated banking environments
MS SQL Server
SQLite Lightweight CI/CD fixtures
MongoDB EE adds nested document generation
CSV / JSON / XML Flat file pipelines
XLSX Spreadsheet read + write (first row = header)
DbUnit XML .dbunit.xml dataset read + write
Fixed-width ( .fcw ) Self-describing column files, read + write
Apache Kafka Real-time streaming, payment scenarios
HL7 v2.x Test/training output via template engine
HL7 FHIR Test/training output via template engine
EDIFACT / SWIFT MT Test/training output only; does not satisfy SWIFT CSCF v2025 secure-zone controls (1.1 environment protection, 1.4 internet restriction). Generated messages must not be transmitted from a CSP-attested secure zone.

CE domains

Domain Services available
Healthcare Patient, Doctor, Hospital, MedicalDevice, MedicalProcedure
Finance Bank, BankAccount, CreditCard, Transaction
Insurance InsuranceCompany, InsuranceProduct, InsurancePolicy, InsuranceCoverage
E-commerce Order, Product
Public sector AdministrationOffice, EducationalInstitution, PoliceOfficer
Demographics Person (DE / US / VN locale packs), Address, City, Country
Common Company

All services are versioned and seeded; each generation emits a provenance hash suitable as evidence in audit reviews. Domain services can be used directly via constructor injection, or driven through the higher-level generate_domain({...}) facade for seed/locale/clock/count parameterisation (currently supports person , address , patient , doctor at v1 ).


CLI reference

# Discover the live structural surface as JSON
datamimic capabilities

# Enumerate typed authoring queries, then request only the needed fragment
datamimic reference authoring
datamimic reference authoring --category field --kind weighted
datamimic reference overview

# Compile and fully verify the canonical intent artifact; stop on verified=true
datamimic scaffold model.dm.json --format json

# Lint a descriptor: schema, semantics, best practices — every finding carries
# a rule id (DMxxx) and a fix hint. Exit codes 0/1/2.
datamimic lint my-scenario/datamimic.xml
datamimic lint my-scenario/datamimic.xml --format json   # diagnostics v1, CI-friendly

# Safely execute bounded counts with neutralized targets and sample rows
datamimic dry-run my-scenario/datamimic.xml --format json

# Run a verified scenario for real
datamimic run my-scenario/datamimic.xml

# Initialize a new project
datamimic init my-scenario

# Demos
datamimic demo list
datamimic demo create demo-healthcare
datamimic demo create --all --target ./my_demos

# System and version info
datamimic info
datamimic version

Documentation

Resource Link
Full documentation docs.datamimic.io
MCP quickstart docs/mcp_quickstart.md
Developer guide docs/developer_guide.md
Enterprise platform datamimic.io
GitHub Discussions Discussions
Issue tracker Issues
Email support support@rapiddweller.com

Contributing

See CONTRIBUTING.md . CE is MIT licensed and community contributions are welcome.

The CE engine is the foundation. If you are building integrations, domain extensions, or MCP tooling on top of DATAMIMIC, we want to hear from you.


License

MIT — see LICENSE .

The DATAMIMIC Enterprise Platform (EE) is a commercial product. Contact us for licensing.


DATAMIMIC — Deterministic, governed test data for regulated enterprises.

datamimic.io | Book a demo | LinkedIn

Some things Veloren does differently

Lobsters
blog.jsbarretto.com
2026-09-15 23:47:55
Comments...
Original Article

I’m one of the core developers of Veloren . Sadly, I don’t get much time to work on the project nowadays: if you’re a parent too you’ll understand, I’m sure.

In this post I want to document some of the unusual choices that have been made during Veloren’s development. If you’re working on a game you might find some of them interesting.

A mountain view

ECS

Veloren is built on an ECS (Entity Component System) rather than a more traditional object-oriented class hierarchy. Nowadays - and especially in the Rust ecosystem - this is much more common, but when we started the project in 2018 it was surprisingly rarely used for anything but demoware and we had to invent a lot of concepts internally to make it work for our needs.

We’ve benefitted massively from this decision: Veloren scales far better than most multiplayer games and will happily hit 50% core utilisation on a 48 thread server with over 500 players connected and 10s of thousands of entities interacting in the game world. Most MMOs can only achieve these numbers by either reducing the scope of gameplay (fewer cross-entity interactions) or aggressively sharding players across different world spaces.

A lot of players

ECS does have some unexpected quirks. In a traditional game engine, different kinds of entities are separated by a compile-time bifurcation at the type level, with polymorphism between classes being an opt-in for specific cases. With an ECS, polymorphism is the default and a taxonomy of entities is something that must be opted into. This has resulted in some interesting side effects:

  • We once had a bug in which players were assigned an ItemDrop component based on the items they were carrying. Due to a slightly botched transition between the way loot was implemented, this resulted in players being able to ‘pick up’ other players when nearby. This would drop the player’s entity, kicking them from the game server.

  • When we first implemented mounts (the ability for characters to ride things like horses), the cycle detection logic (that prevents mutually-mounted entities) and the control passthrough logic (which allows the rider to pass control instructions to the mount) were both faulty. This meant that players could construct enormous towers of entities all riding the one below, or even create mount cycles, which resulted in amusing Bethesda-style catherine wheels of chaos as the physics engine tried desperately to resolve the contradictory mounting constraints.

Player / NPC duality

To the maximum possible extent, player characters and NPCs are the same. For example, both:

  • Interact with the physics engine in the same way. NPCs cannot teleport, phase through blocks, or artificially control their physics properties. If the NPC’s agent code isn’t smart enough to account for the NPC’s momentum and friction when traversing a cliff edge, they will fall off.

  • Have exactly the same movement control options. All movement and control options go through the Controller ECS component, which acts as a sort of virtual gamepad. For players, Controller inputs are provided by the player’s keyboard, mouse, and physical gamepad inputs. For NPCs, Controller inputs are provided by the game’s agent decision tree system.

  • Are governed by the same movement controller code. Controller inputs are constrained by the physical abilities of the character’s body and translated into inputs for the physics engine with exactly the same code.

  • Have the exact same skill tree and experience system. In previously iterations of the game sound effects would even get played when a nearby NPC levelled up!

Yes, trains are entities too

Chonks

Veloren is a voxel game. Usually, voxel games take one of several approaches to storing their terrain data:

  • Big 3D arrays of blocks, addressed via some sort of hash table into a series of chunks

  • RLE -encoded voxel data, usually grouped into chunks

  • Octrees, where the whole world is defined as a recursive tree of increasingly smaller voxel 2x2x2 cubes

In practice, each approach has big problems. Big arrays are fast but provide little scope for compression. RLE only compresses well when the voxel data appears as large groups of homogenous blocks, and has awful random access performance. Octrees are extremely unfriendly to modern CPU caches.

Veloren uses neither. Instead, it has a data structure we’ve internally called ‘chonks’ (an affectionate portmantaeu of ‘column’ and ‘chunk’). It uses an internal single-level index table in which groups of NxNxN blocks can be represented as either ‘homogeneous’ (self-similar) or ‘heterogenous’ (each requiring a different index in the table). Each chonk is also split into an arbitrary number of fixed-size vertical ‘sub-chunks’, each offset from the vertical origin. All in all, this is a good tradeoff between cache coherence and compression and provided excellent random access performance.

World pre-generation

Most voxel games, like Minecraft, generate more of the world as players explore. Instead, Veloren pre-generates the entire world on startup at a lower resolution and ‘fills in’ small details when players get close using a variety of different interpolation and noise-based techniques.

This up-front generation step means that Veloren can support complex world features that simply cannot be implemented with local constraint solving only, such as long rivers that always flow downhill.

A world map

In addition, we get to spend time performing some simulation of the world before the game starts, resulting in more interesting features.

A short aside on what 'procedural generation' even is

If you ask most folk to describe procedural generation, they might say something like ‘random game content’. This is exactly backward: procedural generation is about defining constraints between elements of gameplay that tickle the habitual pattern-matching tendencies of the human brain.

The best procedural generation systems will weave complex narrative threads through a world not via some random walk through a combinatoral space, but instead by ensuring self-consistency. If you find a river, you should be able to walk to is source. If you come across a monster, you should be able to find its lair. If you slay the monster, the way characters in the nearby town talk about your character should change.

Good procedural generation systems need almost no randomness, because randomness is what players bring to the table: the purpose of a procedural generator is to push back against that randomness and coerce it into a self-consistent system with consequences.

Another advantage of this low-resolution pre-generation step is that we can produce accurate LoD (Level of Detail) stand-ins for distant terrain, resulting in a virtually unlimited view distance even on low-power hardware.

A screenshot demonstrating the high view distance

Physically-based world generation

Most voxel games make heavy use of teleological procedural generation. This generation philosophy focusses on aesthetic outputs; are the colours artistically fitting? are the mountains interesting enough? does the world ‘look right’? Common to this philosophy are techniques like procedural noise or semi-stochastic algorithms like Wave Function Collapse .

Instead, Veloren leans much more heavily on top-level ontological procedural generation. Instead of focussing on outputs, the focus is instead on defining an interal model of the world that recreates the physical inputs to processes and then simulates their effect on the world.

The most obvious example of this is our physically-based hydraulic erosion model that produces the mountainous terrain and complex river systems the game is so well known for.

Another example is our procedural path generator, which uses a simplified model traversal cost model to find energy-efficient routes between sites.

I believe that the physically-based nature of many of Veloren’s procedural elements are key to the coherent and ‘bigger-than-you’ feeling that Veloren produces.

A desert mesa

RTSim

Veloren doesn’t stop doing physically-based simulation after initial world generation. The game has an internal world simulation system known as ‘rtsim’ (Real Time SIMulation) which uses the aforementioned low-resolution world data to continue simulating the whole world, even when no players are nearby.

Every NPC in the world has a dual residing within rtsim. When an NPC leaves the active view distance of a player, they don’t get despawned: instead, they’re subsumed into rtsim where the game continues to track their movements and simulate the effect of their high-level decision tree logic.

Rtsim is becoming an increasingly complicated part of Veloren and many of the more interesting dynamic aspects of the game now reside in it: quest simulation, faction dynamics, and even elements of the game economy are now tracked by it as the game runs. It’s possible to observe raids on sites by pirates and travelling bandits as they move across the world, for example. NPCs and in particular merchants will also migrate across the world.

Rtsim is designed to scale: most Veloren worlds contain upward of 10s of thousands of NPCs, and rtsim is capable of tracking them all simultaneously.

A large town

No invisible walls

A game design constraint that we set ourselves quite early on was that of avoiding ‘invisible walls’: these might be physical, like the boundaries on the edge of a game’s world map, or they might be conceptual, like a game refusing to permit interaction between two elements in the world for arbitrary reasons.

Imposing this constraint has created significant problems for game balance, as well as designing proper interaction between gameplay elements. It is not clear, for example, how the game even should react when a mischievous player decides to pull a mighty boss out of their dungeon and into a nearby town. But, Veloren permits you to do this, and that constraint has encourages us to design game systems defensively with the expectation that they may have to continue functioning in extremely unusual circumstances.

One world space

Many game decide to split their world into distinct areas for the purpose of performance or artistic decisions, with loading screens separating them. Veloren chooses to avoid this entirely and places all gameplay elements into the same physical world space.

One feature of the game where this results in complexity is the elaborate cave system that weaves underneath the world. This cave system can sometimes be up to a kilometre under the surface of the world, and often is many levels deep, so keeping the game performant when there’s a cave network under the player’s feet has been a challenge.

A surprising problem to solve here is lighting. Veloren has a much more diverse lighting model than most voxel games and supports baked voxel lighting, point lights, directed shadow mapping, reflections, an ambient light model, volumetric fog and clouds (which both result in light scattering), etc. Ensuring that no lighting information from the surface makes its way down into the deepest cave, even in the middle of the day, was surprisingly complicated: global effects like lightning strikes have a tendency to leak ambient lighting data through shadow maps and appear in screen-space reflections even when care is taken to isolate them, and a lot of time has been spent ensuring that the visibility of these effects from the player’s perspective is properly accounted for.

A sinkhole leading to a cave

Negativland, Culture Jamming, and the Art of Making Something New

Hacker News
blog.archive.org
2026-09-15 22:46:06
Comments...
Original Article

In 1980, Negativland released its first self-titled album with handmade covers and distributed it through a few record stores in the California Bay Area.

“We thought we might sell 100 in the next five years, and we sold all 500 in three months. So we kept going,” said band co-founder Mark Hosler of the experimental sound collage record, which eventually sold 15,000 copies. “We were shocked that people were interested in something so strange.”

Hosler started the group with David Wills and Richard Lyons, but the Negativland lineup has changed, and the range of its work expanded over 46 years.

The multimedia collective has produced more than 30 albums and EPs, as well as fine art, books, radio and live performances that mix found and original  material. The band came up with the phrase “culture jamming” to describe its approach to layering art from various sources..

Negativland continues to innovate and perform, collaborating remotely. Its live show, Over The Edge: Significantly Less Deceptive , at the Internet Archive’s San Francisco headquarters on September 16, will combine sound, music, radio, humor, and sonic experimentation. Hosler, who lives in North Carolina, will be on stage with San Francisco’s Jon Leidecker (“Wobbly”), a collaborator with the band since 1985, while Wills contributes by phone from his home in Seattle.

Negativland Live at Internet Archive
September 16, Doors @ 6pm / Show @ 6:30pm
IN PERSON @ Internet Archive
Tickets: $20, register now: https://eventbrite.com/e/1994683818998

Negativland has been influential in the remix space. It has challenged conventions around media, copyright, and artistic freedom. Hosler said the band wanted to carve a space to talk back to power through its audio collage art.

“The thread that goes through all of our work is that we are always responding to and describing American culture,” Hosler said. “It’s our perceptions of power, money, capitalism, who runs things, who’s in control and how the media works. We are creating this alternate, weird, parallel universe version of reality as we perceive it.”

Fair Use and Preservation

The group was thrust into the limelight when it released a record poking fun at, and sampling from,  the U2 song, “I Still Haven’t Found What I’m Looking For.”  The Irish band and their label sued Negativland for copyright infringement, trademark infringement, and defamation of character in 1991 and, with Negativland’s  “U2” record having been released by the label SST Records, the group reluctantly settled.

Negativland used the episode and attention from the media to highlight the need for copyright reform. It shared the story of its legal battle and argument for the right to make new art out of corporately owned culture in the 1995 book, Fair Use: The Story Of The Letter U And The Numeral Two . The book led Hosler to give more than 140 lectures about appropriation, corporate ownership and the intersection of art and law at schools and universities around the world.

In 2015, the Internet Archive partnered with Negativland to preserve and provide free public access to more than 4,000 hours of Over The Edge , the group’s legendary weekly freeform radio program that has aired since 1981. Don Joyce, a collaborator with Negativland for years until his passing in 2015, was a driving force behind the program. It is now hosted by Leidecker (“Wobbly”) at KPFA in Berkeley. The first 34 years of radio shows are archived and freely available at the Internet Archive to listeners around the world.

Performing for ‘Oddballs’

National Public Radio featured Negativland in its Tiny Desk (Home) Concert series in May 2021.

The members of Negativland have been fluid over the years and most balance their contributions with day jobs. They don’t include photos with their faces on albums. “We really want to direct attention to our work, not to us as personalities or individuals,” Hosler said. In addition to the core group, there are people who help with videos, website design and other aspects of the art that are integral to the work, he added.

In 2022, Ryan Worsley used years of the band’s audio and video footage to produce the film, Stand By for Failure: A Documentary about Negativland

Hosler said the band always knew its audience would be small, but the members were motivated to invent something out of the box. He said he assumes fans are thoughtful, curious and have a sense of humor.

“When we perform shows, a lot of our audience seems like people who don’t even fit in with the people who don’t fit in,” Hosler said. “That’s cool. Our live shows are sort of a safe space for people who are just real oddballs.”

Negativland’s work encompasses sampling music, but also photography, visual arts, graphics, art shows and live, improvisational performances. Its work has adapted to the evolving technology of the past four decades. Hosler said a creative impulse drives the collaborators to keep innovating and sharing their work with the public.

“For many years when critics would write about Negativland, they just couldn’t describe it very well. From a marketing standpoint, that’s bad,” Hosler said. “But from a creative and artistic standpoint, that’s awesome. It’s fantastic. If critics can’t figure out how to talk about the work, maybe it means we actually are doing something a little bit different.”

‘Apple Reference Image: A New Approach for Verified Photography’

Daring Fireball
security.apple.com
2026-09-15 22:38:24
Apple Security Research has released a concise, cogent paper describing how Apple Reference Image works, and why they made it. A terrific read. Here’s just one fascinating bit regarding privacy: Other industry solutions require a photographer or institution to vouch for an image using their own ...
Original Article

Today, powerful, widely available AI tools allow users to easily generate or alter photorealistic images to a degree that was difficult to imagine just a few years ago. These tools enable helpful features, like one-touch removal of background distractions, but they also make it difficult to distinguish between photographs that depict real events, and synthetic images that are heavily altered or entirely generated. So, in the case where the essential role of a photograph is to prove that something actually happened, an image appearing photorealistic is no longer sufficient to establish its veracity.

This is not a simple problem to address. Modern cameras rely on sophisticated image-processing algorithms to produce the final viewable image, so certifying that an image accurately reflects what a real camera sensor captured requires a chain of trust covering the sensor as well as the computational photography software that interpreted the capture. Industry approaches to this problem, based on the C2PA standard, attach provenance metadata after capture and certify the history of image edits from that point forward. This approach, however, is vulnerable to compromise at any point in the editing chain, and a viewer has no way to detect such a failure. It can also create privacy risks for photographers working in dangerous conditions by tying the image to a public identity, either to a particular device or to an individual.

iPhone is the world’s most popular camera and the most secure consumer mobile device, and as such Apple is uniquely positioned to take on this challenge. The iPhone camera is integrated into a platform that sets the industry’s highest standards of security from the silicon up. We also operate Private Cloud Compute (PCC), an industry-leading privacy-preserving cloud infrastructure that is secure, auditable, and can perform verifiable algorithmic operations without allowing anyone — even Apple — the ability to see the data being processed.

Leveraging these state-of-the-art capabilities, we have created Apple Reference Image , a novel solution for verifiable photography on iPhone, and debuting on the main camera sensor of iPhone 18 Pro and iPhone 18 Pro Max. This new, opt-in camera mode lets a photographer create a securely timestamped reference image that accurately reflects what was captured by the iPhone's camera sensor. Dedicated secure hardware on the device protects the integrity of this reference image, and Private Cloud Compute protects the privacy of the image data during processing. The system is built to be resilient to compromise, no matter how unlikely: any fraudulent images can be revoked without exposing the photographer's identity.

Apple Reference Image offers a trustworthy, scalable guarantee that a reference image is what it claims to be: a real photograph, captured by a real sensor in an iPhone camera, at a specific time. It sets a new standard for verifiable digital photography.

The Core Requirements of Apple Reference Image

A high-assurance photographic provenance system must meet three core requirements:

  • Semantic authenticity : a reference image must faithfully show what the sensor captured. Transformations of image data from the raw captured pixels to the final viewable image must be publicly verifiable.
  • Resilience to compromise : image authenticity cannot be undermined by tampering with the camera sensor, through common cryptographic attacks, or via software-level jailbreak of the device. If, despite these protections, any fraudulent reference images are created, they can be revoked.
  • Privacy preservation : an outside observer cannot determine whether any pair of reference images were taken by the same device. Image contents are not exposed to Apple or anyone else.

Apple Reference Image leverages custom-designed image sensors in iPhone 18 Pro and iPhone 18 Pro Max to ensure reliable capture of image data, and relies on Private Cloud Compute, which provides a computational environment for secure photographic processing that cannot be subverted even in the case of device compromise. We believe no other commercially-available photographic provenance system meets these strict requirements.

Semantic Authenticity

For any photographic authenticity system, the defining goal is that a user can trust that what is shown as the authenticated image corresponds to the scene that was actually photographed. A central challenge these systems face is how to secure the extensive photographic processing pipeline of a modern computational camera. Simply signing the raw values emitted by a sensor does not yield a viewable image: these pixels still need significant processing, like demosaicing and lens-shading correction, to be usable. To solve this, prior industry systems have delayed signing images until they reach the end of their software processing pipeline. But this approach is vulnerable to attacks that inject spoofed pixel data onto the data transport from the sensor, or to compromises of the device operating system that can completely alter the image before signing. Neither signing raw sensor values, nor delaying signing until the photograph is processed, meets our bar for semantic authenticity. Our solution hinges on splitting the Apple Reference Image process into two phases: creating a secure digital negative, and developing that negative into a reference image. Each phase receives our strongest protections.

The creation of a secure digital negative begins with a secure boot of the camera sensor into a specialized reference capture mode. The mode instructs the sensor to cryptographically sign pixel data immediately after capture, and prevents the sensor firmware from modifying the data. This creates a hardware-enforced assurance that the operating system receives pixel data exactly as the hardware sensor captured it, preventing injection or tampering attacks.

We treat image metadata with the same level of protection. Sensor-produced metadata is signed at capture time together with the pixel data. For the few metadata values that originate beyond the camera sensor, such as digital zoom boundaries and focal length, we use the Secure Enclave Processor (SEP) to sign the values. This off-sensor metadata cannot alter the pixel values themselves.

Knowing when a photograph was captured is often a critical element in establishing its veracity. While prior industry systems have included a timestamp provided by the general device operating system, we believe this plainly falls short of the real-world assurance need. Instead, Apple Reference Image provides both a lower bound and an upper bound on capture time from Apple’s cryptographic timestamp service, and we guarantee the photo was taken between the two bounds. On a regular heartbeat, the device requests a cryptographic timestamp token, and retains the most recent one it has received. Globally this happens on average every 15 minutes, though the interval depends on local network conditions. This provides a proven lower bound timestamp for the photographic capture. After capture, the device requests a second timestamp to use as an upper bound, and both timestamps are embedded and signed with the sensor data.

As a result, the secure digital negative contains all the essential information for rendering a reference image — the pixel data, essential sensor metadata, and the secure timestamp bounds — all protected from device software compromise.

To develop this secure digital negative into a user-visible reference image, we take advantage of the privacy-preserving computing environment provided by Private Cloud Compute. When the user chooses to create a reference image, the device uploads the digital negative to PCC, which runs the processing steps needed to render the image — including demosaicing, tone mapping, and compression — in a highly secure, private, and verifiable environment. Experts can verify that PCC doesn’t alter a digital negative during development: they can examine the software that does the work. Every production build of PCC is recorded in an append-only, cryptographically tamper-proof transparency log, the binaries are available for public inspection, and a device will only send data to a node that can attest to running a build from that log. These are the same extraordinary guarantees we make for how PCC protects the privacy of Apple Intelligence requests, which are described in depth in previous posts .

Apple Reference Image combines the strong guarantees of these two stages — the hardware-level assurance over the secure digital negative, and PCC’s verifiable transparency over the processing algorithms — to provide industry-leading semantic authenticity for the resulting images.

Resilience to Compromise

In designing Apple Reference Image, we considered a broad range of attacks, and constructed the system so as to resist compromise from multiple vectors.

As described above, we designed the core reference image pipeline to withstand a compromise of the operating system, or a data injection attack on the sensor bus. But we needed additional safeguards against a broader class of hardware attacks that could involve removing the sensor from the device.

These defenses begin before a single picture is taken, at manufacturing time. When the image sensor is first initialized in the factory, it creates a cryptographic signing identity, sharing only the public key with the factory. The SEP similarly creates a separately-attested signing identity. These identities are bound together into the device manifest, allowing us to later check whether a particular sensor and SEP are from the same device. At capture time, the device incorporates this platform information into the digital negative it produces. When the reference image is then developed in PCC, PCC can validate that the photograph has come from a valid sensor-device pairing.

We also considered cryptographic attacks. Existing photo signing schemes, to our knowledge, all sign with classically secure algorithms, but quantum-secure algorithms are increasingly critical to the long-term integrity of cryptographic signatures. Because reference images are published assets whose integrity must survive for as long as anyone might want to check them, a signature secure only against classical adversaries isn't sufficient: an image asserted to be authentic in 2026 should be securely verifiable in perpetuity. So we designed the system to resist quantum attacks on any algorithm used to protect the integrity of publicly distributed reference images. The final signature on a reference image is a composite post-quantum signature combining RSA-3072 and ML-DSA-87. To our knowledge, Apple Reference Image is the only image provenance system that provides quantum-secure defenses.

Finally, as no security system is perfect, we created a revocation system that can revoke individual photos, as well as all photos from a specific sensor. As part of developing the secure digital negative, PCC computes a confidence score that assesses whether the image has the physical characteristics expected of raw output from our camera sensors. Before the developed reference image is signed, PCC sends the photo GUID, sensor ID, and this confidence score to a companion service, which records them and updates the running score associated with that sensor. If a low-scoring sensor is revoked, PCC will no longer sign its images. Apple devices fetch updated revocation lists on a regular cadence; any time a reference image is viewed, the viewer can have confidence that the image isn’t known to be fraudulent.

Privacy Preservation

Other industry solutions require a photographer or institution to vouch for an image using their own credentials. We are concerned this puts some photographers, such as those operating in conflict zones, in a difficult position; it should not be necessary to forgo anonymity in order to prove image authenticity. We built Apple Reference Image to avoid using an explicit, public credential for photographers, and to avoid even implicit public association between different photos taken by the same sensor. The final reference image is instead signed by Apple’s signing service, after validation by PCC. That signature is backed by Apple’s strongest technical guarantees.

Our implementation also protects the confidentiality of the image itself, including from Apple. Merely capturing a reference image should never expose the actual pixels to Apple or anyone else. We achieve this through the exceptional privacy properties of PCC — the nodes themselves are architected so that not even Apple can access image data, just as Apple cannot see the information processed for Apple Intelligence in PCC. While the revocation service must maintain a private record of photo GUIDs and associated sensors to allow for revocation, it never has access to the image data, and does not allow for public access to this record. And as final revocation checks occur using on-device lists, a device never reveals to anyone which photo it's looking at in order to find out whether it's still valid.

Last, we have taken care to limit network visibility wherever possible. Timestamping requests travel over Oblivious HTTP , so the timestamp service never learns the IP address of the requesting device. Similarly, calls to the revocation and signing services occur from within PCC itself, which provides only the minimum information required for those services to function. Altogether, we believe these privacy protections are far stronger than in any existing image provenance system, allowing both photographers and viewers access to authentic images without inadvertently revealing their personal information.

Across all three requirements — semantic authenticity, resilience to compromise, and privacy preservation — we believe that Apple Reference Image sets a new standard for security in the industry. For readers who are additionally interested in the technical details of our implementation, the next section will describe the precise manufacturing, signing, and verification sequences that underpin the security guarantees of Apple Reference Image.

Technical Details

Reference Image Set-Up

The foundation for Apple Reference Image is created during device manufacturing. When an Apple photo sensor is first initialized, it generates its own ECDSA P-256 signing key pair and never releases the private half. The factory recording station retrieves only the corresponding public verification key, signs it with a factory certificate authority (CA), and records the key and certificate in the device's hardware manifest.

The Secure Enclave Processor (SEP) goes through a similar process: it generates a key certified by our Basic Attestation Authority (BAA) under a separate CA, which lets the device later produce signatures that Apple can attribute to that specific phone. A third CA then signs the device manifest itself, binding the sensor key and the BAA-attested SEP key together as belonging to the same iPhone. This binding is what later lets us state that a particular sensor and a particular Secure Enclave were, and are, part of the same device.

Once the device is in use, it begins timestamp collection. Apple Push Notification Service (APNs) runs an existing heartbeat protocol to ensure the health of the connection for push notifications. Coinciding with this heartbeat, APNs now delivers an up-to-date RFC 3161 timestamp token from Apple's timestamp service, signed with ECDSA P-256 over SHA-256, and the device keeps the most recent one it receives.

Image Capture

To begin the capture process, the user switches to Reference mode. This reboots the sensor into the specialized, secure reference mode. This capture mode accepts one input from the device operating system: a SHA-256 digest to be embedded at a fixed location in the captured frame’s metadata. The digest is computed from the most recent secure timestamp, the device manifest, and the device's secure boot manifest.

At capture, the sensor measures light as an analog signal, which is digitized. The digitized frame and the embedded metadata digest are signed together, inside the sensor, with the sensor's private key. OS-derived metadata (digital zoom factor, exposure, and lens parameters) is collected from the camera system. We take a commitment to the sensor's signature together with this metadata and sign it with the SEP, using the BAA-attested key.

We compute a SHA-256 commitment to the SEP signature and send it to the timestamp service, which returns a signed token establishing that the photo existed no later than that moment, an upper bound to complement the lower bound already embedded in the frame. If the device is offline, no upper bound is available yet; a background process keeps attempting the request and inserts the token once it succeeds, producing the tightest interval the circumstances allow.

Everything produced so far — the pixels, both signatures, the timestamps, the metadata, the device manifest, and the secure boot manifest — is stored in the secure digital negative on the device, in DNG format, linked to the conventionally processed photo from the standard pipeline. The negative can sit there indefinitely, and it can also be shared in this undeveloped state, a workflow professional photographers may need.

Reference Image Development

When the user initiates developing a reference image, the device uploads the secure digital negative to Private Cloud Compute. PCC recomputes the digest embedded in the frame and verifies the sensor's signature over the pixels and that digest, verifying the certificate chain back to the sensor CA. PCC also verifies the SEP signature and chains it to the BAA CA, and it verifies the signature on the device manifest and chains it to the CA that signs device manifests at the factory. It then confirms that the sensor and SEP named in those chains belong to the same device. Only if all these checks pass does processing continue.

PCC next checks the timestamps. If the lower-bound timestamp fails verification, PCC substitutes March 31, 2026, since the feature didn't exist before that date and no photo can predate it. If the upper-bound timestamp is missing or doesn't verify, PCC substitutes the current development time in PCC.

Using a neural network with hidden weights, PCC computes a confidence score for the photograph. This additional step confirms that the image has the physical characteristics expected of raw output from our sensors, increasing confidence in its authenticity. PCC then develops the negative with demosaicing, tone mapping, and related corrections. The result is compressed as a JPEG and hashed, creating a commitment to the developed image. This hash serves two purposes: it's the value that will be signed, assuming it passes our remaining checks, and it supplies the bits used for the photo GUID.

PCC sends the photo GUID, the raw hash, the confidence score, and the sensor ID to a companion service, which records them, updates the running confidence score associated with that sensor, and confirms the sensor doesn't appear on a revocation list. If these checks pass, PCC then submits the commitment to our signing service, which signs it with a composite post-quantum signature using a hybrid MLDSA87-RSA-3072-PSS-SHA512 scheme. The signature is embedded in the JPEG, and the reference image is returned to the device, which associates it with the main photo from the original capture.

After the secure digital negative is successfully developed, it's automatically moved to the deleted photos folder. As with any deleted photo, the user can recover the negative for preservation if desired, or delete it immediately; otherwise it's automatically purged after 30 days.

On the client side, whenever the reference image is displayed, the client verifies the final signature on the JPEG and confirms its photo GUID doesn't appear on the current revocation list before showing the image.

Conclusion

Apple Reference Image builds on Apple's unique foundation of capabilities in hardware and software, including sensor identity certification at the factory, silicon security, and Private Cloud Compute, giving photographers a new way to provide a verifiable photograph. This allows them to attest to what their iPhone actually captured, without requiring them to expose a public identity or place trust in a third party. At its core, Apple Reference Image binds a signature from an iPhone camera sensor to a securely timestamped, tamper-evident record, developing it inside PCC while running publicly verifiable code, and signing it with a composite post-quantum signature designed to remain secure for decades. If a device is later found to be compromised, its images can be revoked and flagged retroactively, without revealing which images came from the same sensor. The result is a verification model that offers photographers, newsrooms, and everyday users renewed confidence that an image they’re viewing is a photograph actually captured by a camera.

25 Years After 9/11, the Deportation Machine Has Exploded

Portside
portside.org
2026-09-15 20:14:13
25 Years After 9/11, the Deportation Machine Has Exploded Judy Tue, 09/15/2026 - 20:14 ...
Original Article

Immigration and Customs Enforcement (ICE) agents walk a detainee out of the Ventura County Government Center in Ventura, California, on July 27, 2026. | Blake Fagan / AFP via Getty Images

For my family, the story of September 11 didn’t end when we went to bed after a day of fear and confusion.

We were attacked during what was my first week of 9th grade. I was looking out my classroom window at the Twin Towers with a classmate as we inadvertently witnessed the first plane hit the building. We were in shock. More kids gathered at the windows as a particular silence fell over the group. We eventually moved to a bigger room with more students, but with blinds pulled down. The sound of the second plane hitting the building made the blinds pop.

I don’t remember much else about that day except that the room went quiet in a way I’d never heard a room go quiet before. But I vividly remember what would happen in the days after.

The city’s grief curdled into something else for people who looked like me. Harassment on the streets. Windows broken. Women with their hijabs pulled off on the street. Fathers, brothers, grandfathers were pulled out of homes and businesses across the city and questioned like suspects, not for anything they’d done, but for what mosque they prayed at or what their passport said.

Twenty-five years later, the machine that was built to watch a few of us is watching most of us.

By the following year, tens of thousands of other men were required to register in person at the immigration office because of where they were born. Between September 2002 and 2003, 83,519 men registered at intake windows across the country . Of those, 13,799 were placed in deportation proceedings. Yet, only 11 were found to have any connection to terrorism, by the government’s own count at the time. A program that processed 83,000 people and tried to deport almost 14,000 of them.

Six months into that registration program, on March 1, 2003, the federal government opened the Department of Homeland Security , which folded 22 agencies into a single new department built to do that kind of work at scale. But this mandate’s impact on communities wasn’t an abstraction to me. It was the agency that had just decided my brother didn’t belong in our city, with a budget line and a headquarters and a mission that would live long past our nation’s trauma.

I started organizing more deeply because of what happened to my neighbors and family that year. Twenty-five years of doing this work has taught me one specific thing: The machine doesn’t stay pointed where it started. It isn’t only Arab and Muslim families anymore who get treated as a threat first and people second.

This year, Congress gave Immigration and Customs Enforcement $45 billion just for new detention spaces , a 265% jump that gives ICE a bigger detention budget than the entire federal prison system, plus another $30 billion for enforcement operations on top of that. No one has properly enumerated what threat justifies a budget that size. I don’t think there is one. Rather, it’s a machine that’s been fed for 25 years and has simply gotten larger with more zeroes attached.

What happened to Muslim and Arab families after 9/11 is not identical to what’s happening to immigrant families now. But what I recognize is narrower than that. It’s how a government decides that fear of a few individuals justifies suspicion of entire communities, and how fast the list of suspects grows once the infrastructure to monitor and process them is in place.

After 9/11, I learned the difference between charity and power. Charity helps people survive a system. Power changes what the system is permitted to do to those in society. I’ve spent my adult life on the side of building power, not because immigrants are just useful, exceptional, or because of their economic contributions, but because rights that apply to some people are not rights. They are privileges with an expiration date, and that date moves every single time this country gets scared.

Twenty-five years later, however, it’s landing on more doors than it did in 2002. Others have said it countless times, but if some of us don’t have rights, none of us do. It’s the exact lesson this country refused to learn in 2002, when 11 real cases were enough to justify tracking 83,000 people, and the tracking was made permanent instead of undone. Twenty-five years later, the machine that was built to watch a few of us is watching most of us.

We must fight for every single one of us because we have all witnessed what happens when we do not. I watched it through a classroom window when I was 14. I have spent every year since making certain that the next community in the crosshairs does not face it the way mine did, alone and expected to be grateful for whatever protection somebody decided to spare us.

This anniversary is absolutely a time when we need to reflect on what we lost that day as New Yorkers and as Americans. It’s also about whether we’re finally willing to notice what we built right after it, and this time agree not to let it keep growing.

===

Murad Awawdeh is the president and CEO of the New York Immigration Coalition.

Game Changers: How To Build the Economy We Deserve

Portside
portside.org
2026-09-15 19:48:19
Game Changers: How To Build the Economy We Deserve Judy Tue, 09/15/2026 - 19:48 ...
Original Article

Editor’s Note: The following is part of new series for Common Dreams based on “ Game Changers: Economic Policies for a Working America ,” a project spearheaded by the Political Economy Research Institute (PERI) at the University of Massachusetts Amherst. Game Changers is a collaborative initiative bringing together leading economists and policy experts to develop bold, new ideas to build a stronger, fairer, greener, and more inclusive economy for all Americans. Through dozens of policy briefs, supporting research papers, and online webinars, the project offers solutions designed to help working people thrive while preparing the country for the economic challenges ahead. “”People are looking for solutions that improve their daily lives—not just marginal changes around the edges,“ says economist Juliet Schor. ”Our proposals make housing, care, and daily life more affordable.“

The Economy We Deserve

When a system reaches the point of breakdown, it’s time for radical change. That was the lesson of the 1930s, when the Great Depression ignited transformative labor and social movements that reset national priorities. It was also the negative lesson of the 1980s, when elites responded to stagflation with a neoliberal turn to “free markets” and enhanced corporate power . The resulting intensification of income and wealth inequality has enabled state capture by a white supremacist authoritarian movement in alliance with tech and fossil fuel interests.

The antidote is increasingly clear: recognizing the equal dignity of every human being and working to guarantee their right to economic security, the opportunity to thrive, a livable planet, a world free of racism , and democratic governance of our lives, societies, and economies. Large majorities of the public understand that the current system is failing most of us and threatening democracy itself. They’re ready to begin building an economy that we deserve.

It’s time to “right the rules” to create an economy that meets individuals’ basic needs for food, shelter, care, education, health, and free time.

Social change, and especially major economic transformation, requires a compelling positive vision of the possible. The Game Changers project was formed to provide that vision. Rooted in new economic thinking and research to address the multiple dysfunctions of the current system, it offers far-reaching ideas that go beyond the failed policies of the past, both neoliberal and liberal, and the corrupt authoritarian practices of the present.

It’s time to “right the rules” to create an economy that meets individuals’ basic needs for food, shelter, care, education, health, and free time. We envision that happening in multiple ways: local public provisioning for health, child and long-term care; federal income supports; access to work with good wages and reasonable hours; raising wages at the bottom and the middle to address longstanding racial and other inequalities; and sensible and compassionate immigration policies. We lay out plans for new public infrastructures such as high-quality mixed-income public housing with renewable energy systems and a low-cost, efficient public payments system. We need to detoxify the natural environment, restore ecosystems, and stabilize the climate system.

Investments in individuals, communities, nations and the planet are key to prosperity. But we need more than that. Changing the game implies that the game is rigged. We offer policies that help working people build power, through collective organizations, in the workplace and the state. These organizing strategies address racial and other divisions that weaken communities. They’ll help redress the asymmetries of influence that plague this moment.

A new game also requires fundamental transformation of large-scale economic structures, such as the financial architecture, the international trading system, and the functioning of the macroeconomy. Our policies do that through breaking up concentrated power, penalizing rent-seeking, stabilizing the financial system, and forcing corporations to pay their fair share of taxes. These measures help fund the investments in people and planet that are required for a decent economy.

One throughline among the wide range of proposals we have developed is that they are based on strong economic research and reasoning, but they reject the economic story that has dominated policymaking. Conventional economic thinking has been shown to be misguided in area after area. Minimum wages don’t cause unemployment ; rent control doesn’t reduce housing supply; and equality and efficiency are not opposed. Climate action is cheaper than inaction, worktime reduction and higher productivity go together, and investments in social goods can pay for themselves.

The economic logic of our approach is based on game theory, as well as common sense. Zero-sum, competitive games like the one the economy has been stuck in yield much worse outcomes than cooperative games. When we join together in communities, labor unions , civic associations, and business enterprises, outcomes are far better. This recognition is the basis of a powerful new economic paradigm: invest in each other, secure the future, and realize the promise of multi-racial democracy throughout society. It’s time to change the game.

Anatomy of the Crisis

The roots of our current predicament can be situated in the early 1970s, when the economic damage wrought by an earlier wrong-headed foreign adventure—the Vietnam War—coincided with an increase in oil prices resulting from conflict in the Middle East. The resulting economic crisis of inflation and recession brought to a head longstanding political, social and economic struggles. Workers mobilized and built power through militant trade union activities; African Americans gained voice and political influence via the Civil Rights movement; the women’s movement demanded equal treatment. These developments coincided with declining competitiveness of US business and a profit squeeze. Panic arose within the American business class as they feared they were losing control.

All the proposals share the goal of helping to build a positive-sum economy that works for all, rather than a zero-sum economy where the greed of the one percent trumps the well-being of the ninety-nine percent.

An additional factor was increased environmental awareness. The first Earth Day in 1970 attracted 20 million, the largest protest action in US history. Existential panic was especially present in the fossil fuel industries of oil, gas and coal . Fossil fuel interests, including Exxon and the Kochs, fought back, investing millions to capture politicians and state institutions. They joined with other business interests to support representatives of both parties who would break unions and negotiate trade agreements that undermined the power of US workers. They de-regulated banks and other financial institutions to allow them to grow bigger, more profitable and, as we were to discover in 2007 and 2008, more dangerous to our economy and livelihoods.

In wave after wave, the state cut taxes for the rich and reduced government resources for investing in and supporting people and communities. The capture of politics and politicians by big capital directed even more money to the military industrial complex. The economic vision associated with neoliberalism was summed up most up memorably by Ronald Reagan in his First Inaugural Address: “Government is not the solution to our problem; government is the problem.”

By the 2000s, this neoliberal approach had been adopted by a significant portion of the economics profession, and “laissez-faire” became the baseline and even touchstone for economic policy. But in reality, the push for laissez-fare was a cover for state policy that boosted the rich at the expense of everyone else. This helps to explain how policy could evolve in just a few decades from the neoliberalism of Reagan to the authoritarian corruption of the Trump administration and its allies.

State capture has ensured that neither the economy nor the government works for most Americans. Naturally, and perhaps by design, this has led to general mistrust and even disdain by many Americans for the federal government, though there is less skepticism of state and local government. As a result, the potential for the public sector to act as a countervailing force against the power of big business is further undermined by angry and discouraged voters.

This top-heavy economic and political system is populated by corporations too often driven by short-term profit-seeking rather than long-term investments, and by a government that invests too little in people and communities and too much in fossil fuel interests and the military-industrial complex . The average worker’s standard of living has stagnated for decades despite large increases in productivity. Affordability may be a buzzword. It is also a pressing day-to-day issue for most American families.

The rise of billionaires and even one trillionaire, along with the enormous power of tech and fossil fuel capitalists in the Trump Administration, is in some ways the natural extension of this decades-long pattern. But we are also experiencing unprecedented perversions and pathologies, as reflected in the vast corruption exhibited by the Trump family and friends and the hyper-inequality of society, with a handful of billionaires striding over millions of Americans barely able to make ends meet. And now fossil fuel interests are redacting “climate change” from government’s—and possibly even society’s—to-do list.

It is long past time to break this destructive cycle and usher in a new economic structure, and a new paradigm to support it.

Changing the Game

The Game Changers are examples of bold yet realistic policies that break cleanly from the corruption of the Trump years and the free-market fundamentalist era that preceded them. The policies address how both markets and government work, based on the premise that the role of government is not merely to clean up after the messes when markets fail, but to ensure that markets work consistently on behalf of working people rather than being rigged against them.

Game Changer policies span a broad canvas of economic issues—health care, housing, immigration, care work, labor, the environment, trade, and finance. All the proposals share the goal of helping to build a positive-sum economy that works for all, rather than a zero-sum economy where the greed of the one percent trumps the well-being of the ninety-nine percent. They are based on a simple set of propositions.

  1. Changing the game means investing in each other .

Childcare, health care, and a clean and safe environment are not commodities to be sold to those with the most purchasing power, nor are they privileges to be granted to those with the most political power. These are universal rights that should be shared by all.

Something is deeply wrong with an economy where people who play by the rules must struggle to get by, while the wealthy flaunt their power with impunity.

Investing in each other means strengthening personal connections by making sure that working people have time and energy to invest in their families and communities. It means uniting with those by our side who are striving for a better life. It means spurning attempts to pit us against each other—white against non-white, native-born against recent immigrant, worker against worker—to divert attention from the abuses perpetrated by those on top.

In short, investing in each other means building an economy in which everyone is free to survive, strive, and thrive.

  1. Changing the game means securing our future.

Economic security should not be a privilege for the few. A secure economy must be resilient enough to safeguard present as well as future generations from workplace abuses, environmental destruction, and supply-chain fragility.

Securing our future means recognizing that there are things that all should be able to afford, like housing, education and care. It means recognizing that there are things we cannot afford, like the poisoning of our air and water and lavish tax breaks and handouts for the rich. And it means building an economy in which the lives of working people are not subjected to catastrophic disruptions that result from concentrated wealth and power in the hands of people who do not care.

Securing our future also means forging solidarity with others across the world with whom we share not only the same planet, but also the same struggle against transnational elites who seek to divide nations and peoples to pursue ever more wealth and power for themselves.

  1. Changing the game means righting the rules.

Today the rules for how markets and governments work are rigged in favor of the wealthy and powerful. Something is deeply wrong with an economy where people who play by the rules must struggle to get by, while the wealthy flaunt their power with impunity.

Righting the rules means ending reckless and predatory financial practices that siphon wealth from working families into the hands of speculators and banksters. It means reorienting housing markets to provide affordable homes for all rather than to maximize profits for real-estate barons. And it means ending the tragic squandering of lives and wealth to shore up a military-industrial complex that offers empty promises of national security even as it undermines the security of families and communities by engaging in immoral and unnecessary wars.

Righting the rules means an end to game-rigging by ruling elites and instead crafting rules that first and foremost protect the well-being of working people.

The call for changing the rules highlights a key aspect of our approach. Many of the Game Changer policies involve redesigning how the market works rather than calling for major new public spending. They transform market outcomes, thereby reducing the need for post-market redistributions to achieve more fairness. And in calling for more public spending in some areas, we also propose new revenue sources and fiscal priorities to improve the functioning of the economy, as detailed in the Game Changer on funding the future.

Getting from Here to There

To animate the changes we are proposing, we need to restore people’s faith that change is possible. We’ve noted the existence of pervasive cynicism and disillusionment with government. In an age of state capture, when government has so frequently failed workers, communities of color, rural families, and distressed geographies, these feelings are warranted. Part of the difficulty of defending democracy against the current authoritarian threats is that the system has empowered elites while disempowering and discouraging working people.

As we’ve argued, changing the game requires far-reaching policies that offer improvements and are based on strong economic analysis. That’s a given. Overcoming cynicism and disillusionment requires something else as well: people’s actual lived experience fighting for and winning change. By being part of a truly democratic movement, and achieving victories, it’s possible to rebuild trust in government, businesses, and each other.

We must reject the false tensions between collective strategies and individual dreams, between worker welfare and business health, between climate caution and economic prosperity.

A transformative agenda won’t be enacted in one stroke. It may start with smaller victories and incremental steps. Each successful campaign engages people who can then be part of the next, bigger effort. Or perhaps we notch an early win with a big, bold Game Changer. That can be followed by smaller steps to make sure it’s implemented fairly and successfully. Social and economic restructuring will come in the form of changes, big and small. And with each positive step, trust in government—and each other—increases.

We are optimistic about the possibilities for deep transformation. Our reasoning is based on the episodes we began with—the paradigm-shifting moments in which new economic models arise and stick. While such shifts can be connected to intellectual trends, they are often more rooted in social conditions and political coalitions: New Deal thinking and labor mobilization in the context of the Great Depression, or neoliberal economics and business opportunism in the context of the stagflation of the 1970s and early 1980s.

In this moment of crisis, we need both new perspectives and new coalitions. We must reject the false tensions between collective strategies and individual dreams, between worker welfare and business health, between climate caution and economic prosperity. We must build a broad movement that includes workers, communities of color, small businesses, professionals concerned about AI, community leaders sickened by division, and anyone and everyone who knows that democracy cannot stand when oligarchy thrives.

The traditional American motto, “out of many, one” is often used to refer to the power of our diversity. Forging a broad and unified force for a better future will be key for the many—movement builders, unionists, community organizers, civic leaders, and all who stand with them—to triumph against the one percent. A transformative policy package, like the Game Changers, is one part of the coalition-building needed for the future we deserve.

==

Gerald Epstein is a professor of economics and a founding co-director of the Political Economy Research Institute (PERI) at the University of Massachusetts-Amherst.

===

Juliet Schor is an economist and Professor of Sociology at Boston College. Schor’s research focuses on work, consumption, and climate change. In 2021 she became lead researcher for global worktime reduction trials, which have now included hundreds of companies across fifteen countries. Her book Four Days A Week , reports on these findings and her Ted talk on the 4 day week has more than 3 million views.

===

James K. Boyce is a professor emeritus of economics and senior fellow at the Political Economy Research Institute at the University of Massachusetts Amherst. His latest book is The Case for Carbon Dividends.

===

Manuel Pastor is a professor of sociology at the University of Southern California and author of the forthcoming book Charging Forward: Lithium Valley, Electric Vehicles, and a Just Future , and previously State of Resistance: What California's Dizzying Descent and Remarkable Resurgence Means for America's Future .

===

Our work is licensed under Creative Commons (CC BY-NC-ND 3.0). Feel free to republish and share widely.

America’s Future: Fossil Fuel Island in a Greentech World?

Portside
portside.org
2026-09-15 19:29:44
America’s Future: Fossil Fuel Island in a Greentech World? Judy Tue, 09/15/2026 - 19:29 ...
Original Article

As we have seen throughout this series, the Greentech revolution is transforming energy production and use worldwide. Along with its other advantages, fossil free energy has become radically cheaper than fossil fuel energy. More than 90% of utility-scale renewable projects commissioned in 2025 delivered power below the cost of the cheapest new fossil-fuel plant built in their market. Natural gas energy is currently 3–4 times more expensive than solar and wind. Meanwhile, fossil fuels are increasingly vulnerable to disruptions like the Ukraine and Iran wars, which destabilize whole economies with shortages and higher prices for energy, food, and other necessities of life.

Donald Trump and the US government are doing everything possible to shut down fossil free energy and to expand our dependence on fossil fuels. That is already having a devastating effect on American workers and communities, and it is likely to get far worse in the future. We are being marooned on what two energy experts call a fossil fuel “energy island” as the rest of the world turns to an “electric world order.


If Trump and his fossil fuelers succeed in defeating the Greentech revolution in the US, the result is likely to be a growing affordability crisis; devastation for the most fossil-fuel dependent industries like autos, coal, iron, and gas; a general decline of the fossil-fuel intensive US economy; disinvestment; a loss of international competitiveness; and macroeconomic effects like inflation and recession or both.

These trends can sometimes be seen in the day-to-day operation of markets, like the decline of the US and European auto industries or the worldwide shift to renewables after the closing of the Strait of Hormuz. But paradoxically, they can also be concealed by short-term fluctuations. For example, the sharp rise in the cost of oil after the closing of the Strait of Hormuz produced a boom in oil company profits. Similarly, the rising demand for electricity for data centers created a boom for natural gas generators. Such developments might appear to refute the argument that the fossil fuel-based US economy is increasingly uncompetitive and in danger of becoming a stranded asset. However, amid all the price gyrations, nothing seems to refute the fundamental underlying fact: Fossil fuel energy is and will remain more expensive and less secure than fossil free energy. I have seen nothing that indicates otherwise.

The long-term decline of the fossil fuel-based economy is manifested in many ways. In this commentary I will examine two impacts on Americans of our country’s failure to join the Greentech revolution:

  • It makes prices higher for Americans
  • It makes US-produced goods and services more costly and therefore less competitive at home and abroad

Crashing autos

Electric Car at Charging Station | Photo credit: sofiiashunkina, Envato

The headline example of eschewing the Greentech revolution is the US auto industry . For decades, the industry – supported by US government policies — failed to invest in EVs and concentrated instead on its highly profitable gas-guzzling cars and trucks. Briefly under the Biden administration the federal government invested in EV charging infrastructure and a $7,500 consumer tax credit . Electric vehicle sales grew 60%.

Then Trump abandoned pro-EV policies and subsidized fossil fueled vehicles with a panoply of strategies. EV sales plummeted. The industry began shutting down its EVs factories. In 2025 Stellantis wrote down $26 billion in EV-related losses; Ford reported a $19 billion loss. The auto journalist Martin Padgett told the New York Times , “We pulled a U-turn while the rest of the world was pushing forward.”

Fossil fuel dependence is costly for American car owners. Here’s what gas dependence means in dollars and cents for auto drivers: In January 2026, before the disruptions caused by the Iran war, the cost to drive 100 miles in an electric car was $5.77; in a gas car it was $11.23. By summer — after the closing of the Straight of Hormuz — to drive 100 miles in the electric car cost almost the same as before, $5.89, but in the gas car it cost $16.69 –three times as much as the EV.

Its failure to develop EVs and its addiction to gas guzzlers has made the US auto industry non-competitive domestically . In 1965, US companies produced more than 90% of new cars purchased in the US; today, barely a third are built by the Big Three.

The failure of the US auto industry to adopt Greentech is at least equally significant internationally. A quarter of all vehicles sold globally in 2025 were battery powered. (That figure is projected to reach 29% in 2026 due to high gas prices caused by the Iran war.) Bloomberg analysts predict that by next decade fewer than half of cars sold globally will be gas-powered. China, which provides 30% of the global car market, has seen sales of internal combustion vehicles plummet by nearly two-thirds since 2017. China now makes 75 percent of all EVs sold worldwide; the United States makes around 5 percent. Susan Helper , a professor at Case Western Reserve University who was chief economist at the Commerce Department under President Barack Obama, told the New York Times that in the worst-case scenario the US auto industry will become a “shrinking island of ICE (internal combustion engines),” churning out outlandishly large trucks and not much else. At which point, the Times noted, the obsolescence of the mighty U.S. automobile industry” would be “all but guaranteed.”

About three million Americans work for automobile and parts manufacturers and dealers. About 24 million jobs depend on spending by car manufacturers, their employees, or car owners.

Vehicle and parts makers shed about 21,000 U.S. jobs in the last year, despite Trump administration tariffs designed to force them to manufacture domestically.

The fossil fuel island

Wind farm Shanxi, China, November 4, 2015. Photo credit: Hahaheditor12667 , Wikipedia Commons, CC BY-SA 4.0

The global shift from gas guzzlers to EVs is part of a more general long-term shift from fossil fuels to fossil free energy which is rendering the US a fossil fuel island. Two energy experts summarized the current phase of this process:

“Global clean energy investment reached a record $2.2 trillion in 2025, twice the flow into fossil fuels. In 2024, 91 percent of newly commissioned utility-scale renewable projects produced electricity more cheaply than the cheapest new fossil fuel alternative, and battery storage costs have fallen 93 percent since 2010, allowing utilities to use batteries to store solar and wind power even when the weather is uncooperative. In 2025, fossil fuel electricity generation fell in both China and India.

“Before the Iran war, this green transition was also spreading beyond wealthy markets. In 2024, Chinese solar exports to developing economies surpassed shipments to advanced economies. Pakistan imported approximately 17 gigawatts of Chinese solar modules that year, equivalent to almost half of its grid-connected capacity. In Indonesia, Thailand, and Mexico, the cheapest Chinese-made EVs have reached price parity with the cheapest internal combustion options.”

In July, China announced binding targets to increase wind and solar power generation by more than 50% over the next five years.

How the Greentech transition will develop in the future is of course a matter for speculation, but BloombergNEF’s (BNEF) New Energy Outlook2026 provides one plausible projection:

  • Solar will become the world’s single largest source of electricity in the next six years, due to a major supply glut, technology advances, and falling prices.
  • If countries continue on their current path of rapidly deploying economically competitive clean technologies, they stand to cut their reliance on imported fossil fuels and ultimately strengthen their energy security.
  • Many countries that depend on fossil fuels are now able to reduce their economic exposure to energy commodity imports by adopting low-carbon technologies.

Energy investor Rob Carlson recently drew the implications for the US economy: Continuing to burn fossil fuels is a “self-imposed financial penalty” which will “ultimately degrade the country’s long-term global competitiveness.” The same applies to any nation or polity that “chooses to continue burning fossil fuels in any application in which electricity could instead be provided more competitively with renewables.”

The replacement of fossil fuel energy by Greentech has been greatly accelerated by the war in the Persian Gulf region. According to a June 30 report by NPR, “The Iran war and high oil and gas prices have supercharged the adoption of renewables and EVs worldwide. Global investors say these technologies make financial sense and increase energy security.” As fossil fuel prices soared, countries are “turning to these technologies that are basically impervious to whatever happens in the Strait of Hormuz. Solar batteries and EVs have gotten a lot cheaper.” Chinese battery exports rose 69% in March compared to 2025; their solar exports in March are up 84% compared to 2025.

The NPR report concluded,

“It doesn’t look good for oil long term. With all these new EVs, that means a lot less people filling up their cars with gas around the world. Before the war in Iran, the International Energy Agency was expecting global oil demand to rise this year. But the disruptions caused by the Strait of Hormuz led them to downgrade expectations to a decline in oil demand this year. In some ways, the U.S. is becoming an outlier in the global energy transition.”

In the face of the Iran war energy shortages some countries have been turning to coal. But analysis by the thinktank Ember found that even a worst-case return to coal would raise global coal-fired generation by no more than 1.8 percent in 2026 relative to a no-crisis baseline; indeed, global coal generation could still fall this year.

Currently there is little reason to expect the Straight of Hormuz to be reopened any time soon, or for fossil fuel prices to return to their levels before the Iran war. And there are plenty of reasons to expect further disruptions with further fossil fuel gyrations in the future.

The US trend towards ever greater fossil fuel dependence and the consequent rise of its energy prices is being further aggravated by the expansion of gas, oil, and even coal to provide energy for hyperscale data centers. That is likely to further increase the isolation of the US as a high-cost fossil fuel island.

If the US becomes ever more a fossil fuel island in a Greentech world, the consequences are likely to be dire. Bill Hare, chief executive of the thinktank Climate Analytics, said , “Any investment in new fossil fuels now is a fool’s gamble, while joining the race to renewables can only bring benefits – not just jobs and cheaper energy at stable prices, but energy independence and access where it’s needed most.”

Is the US doomed to be a fossil fuel island? Our next series of commentaries will lay out an alternative: The Green New Deal 2.0.

===

Jeremy Brecher

One of the Best Supreme Court Decisions in Years Is Still Alarming

Portside
portside.org
2026-09-15 18:59:46
One of the Best Supreme Court Decisions in Years Is Still Alarming Judy Tue, 09/15/2026 - 18:59 ...
Original Article
One of the Best Supreme Court Decisions in Years Is Still Alarming Published

Supreme Court Justices Samuel Alito and Clarence Thomas | Chip Somodevilla/Getty Images

President Donald Trump’s plan to kneecap mail-voting practices in November’s midterms ran aground on Monday evening after the Supreme Court blocked the postal system from enforcing it in a 7-2 vote.

The court’s short, unsigned order prevents the United States Postal Service or other agencies from enacting last-minute requirements for mail-in ballots under a new rule it issued last month. Under the rule, postal officials would refuse to deliver any ballots to voters unless they were registered in new, untested systems that USPS was developing on the fly. Roughly one in three Americans voted by mail in the 2024 elections. In some states, elections are exclusively conducted by mail.

State and local election officials across the country had warned of widespread chaos and potential mass disenfranchisement if the USPS rule went into effect. Most states have already begun printing ballots and return envelopes, they claimed, and they lack the time or resources to start the process over. The new rules also amounted to a federal takeover of state election processes, subordinating a significant portion of U.S. voting infrastructure to a federal agency for the first time in American history.

This should have been a 9-0 defeat for the Trump administration. While the court ultimately stopped the plan, it is alarming that three justices thought there was some legal merit to the USPS rule and that two justices all but wholeheartedly endorsed it.

The court’s order is only three sentences long. One of them is a standard boilerplate description of the application’s fate. The remaining two lines could hardly be less illuminating. “The Government is unlikely to succeed on the merits of its challenge to the District Court’s preliminary injunction,” the court wrote. “And the equitable factors applicable for obtaining emergency relief from this Court do not favor a stay.”

That’s it. The court didn’t even include any citations; I normally excise them from quotations for readers’ convenience, but I didn’t need to do so this time. Compare that brevity with the court’s order last month when it first heard a challenge to the executive order. In that instance, the court wrote an unsigned ten-page opinion where it detailed its reasoning at length.

The court’s earlier opinion strongly criticized the lawsuit in that case on standing and procedural grounds, suggesting that the states and other plaintiffs had no right to challenge “internal deliberations” of the executive branch. This was a fairly ironic statement when Trump was publicly posting about his desire to “get rid of MAIL-IN BALLOTS” on his personal social-media website. Everybody knew exactly what was going on.

Despite this, the justices left open the possibility that it might rule in the states’ favor the next time around, and so they did. It would have been stunning if they didn’t. California and its fellow plaintiffs made sure to frame their case on the justices’ terms. They not so subtly invoked the major-questions doctrine, which the conservative justices have used to defang policymaking via broadly written statutes by Democratic presidents.

“Never before has USPS attempted to interfere with elections in this way, let alone a fast-approaching election,” California told the court in a brief filed last week. “And nothing in federal law authorizes USPS to refuse to deliver ballots. To the contrary, Congress has exhaustively enumerated the types of materials that USPS can lawfully refuse to deliver. Ballots are not among them.”

Their citations pointed to cases where the conservative majority struck down Biden’s student-debt relief plan, his COVID-19 testing mandate, and the Obama administration’s power-plant emissions rules. “The rule challenged here arguably has greater political significance than the subject of any of the court’s prior major-questions cases,” the states noted.

That satisfied one prong of the court’s test for upholding the lower court’s injunction. The second prong is slightly more nebulous. When a court “balances the equities,” it effectively looks at who will be harmed more by the absence of an injunction. Sometimes this can be a difficult question where judges must slice through layers of competing public interests before deciding whether to grant an injunction or not.

In this case, however, the equities heavily favored the states. “Whatever else may be said of USPS’ new rule, it would wreak havoc on States and their voters if it takes effect at this late point—when some states, including North Carolina and Wisconsin, have already begun to mail out ballots,” the states noted, citing an election-related precedent from 2020. “This Court has repeatedly refused to allow far less disruptive changes to take effect on ‘[the] eve of an election.’”

Justice Brett Kavanaugh wrote a one-paragraph concurring opinion where he tried to triangulate some kind of middle ground between the two sides. He concluded that there was “at least a fair prospect” that the Postal Service’s rule fell within its “statutory authority,” which would be a major boost to the Trump administration. This is an exceedingly forgiving standard for the White House and is hardly surprising from Kavanaugh, who might be the court’s most enthusiastic supporter of executive power.

At the same time, the justice concluded that it would be “arbitrary and capricious” under the Administrative Procedure Act to enforce the rule now. State and local election officials, he reasoned, “do not have sufficient time to reasonably implement the rule before the elections.” Despite his reference to the APA standard, this is functionally no different from holding that the equities favor the states.

In short, while Kavanaugh wasn’t willing to allow the USPS to take effect in the 2026 election, he signaled that he might be open to it for the 2028 election. Kavanaugh loves to do this sort of thing in concurring opinions for reasons known only to him. In Trump v. Barbara , he managed to conclude that Trump was largely right about the Citizenship Clause but that Congress had enacted birthright citizenship by statute—a position neither advanced nor desired by any side in the case.

Kavanaugh’s attempts to please everyone were ultimately preferable to the view advanced by the only two dissenting justices. The real stunner comes from Alito’s dissent, which was joined in full by Justice Clarence Thomas. It is an unambiguous embrace of the president’s ability to seize control of state election processes for spurious reasons and personal political gain.

A quick note on the vote count: It takes at least a majority of five votes to do anything on the court other than grant a petition for review, which requires only four votes. I described this case as a 7-2 decision based on the publicly disclosed dissents by Alito and Thomas. But the Supreme Court’s rules do not require justices to say how they vote in shadow-docket cases. As a result, it is theoretically possible that as many as two other justices voted with Alito and Thomas, but did not publicly announce it.

I have inconsistently credited or not credited this possibility in my past writing. These vote totals would become public knowledge when a now-sitting justice’s papers are opened for historians and journalists. Past justices have sometimes made their papers available to the public as soon as after their own death. Earlier this month, however, The New York Times reported that the justices have reached a secret “consensus” in 2016 about future releases of their papers. This consensus may render these papers—and thus the vote totals—unknowable until every other justice involved has died, even if they have all retired from the court.

As a result, the vote totals in this case (and any future ones) will likely not become public within my expected lifespan. Going forward, I will assume that no justice has privately dissented from an outcome on the court’s emergency or administrative docket if at least one justice has publicly dissented from it. There is no journalistic value in suggesting that some of the justices might have secret positions or beliefs without supporting evidence. Members of the Supreme Court are free to request corrections if I incorrectly describe how they voted in a specific case or outcome, and I will be more than happy to amend my work accordingly.

On the first prong of the court’s test, Alito gives extraordinary deference to the executive branch’s ability to regulate elections based on broad grants of congressional authority. He argued that Congress imbued “In sum, the plaintiff States’ statutory claim is based on a contestable reading of a broadly drawn statute empowering the Postal Service to regulate the mail,” he concluded.

On the second prong, where the court must balance each side’s interests, Alito swings even harder towards Trump. “As for the equities, the government has a strong interest in enforcing the rule, and implementing it will also ‘enhance the visibility of Federal Ballot Mail’ in order to better detect election fraud,” he wrote. Trump and many other Republicans have asserted for decades that there is widespread fraud in U.S. elections. There is no evidence whatsoever to support this conspiracy theory.

A presidential commission assembled by Trump during his first term found no evidence to support Trump’s false claims that more than 3 million illegal ballots were cast in the 2016 presidential election, which he won. Investigations launched by the Justice and Homeland Security Departments at Trump’s behest during his first and second terms also found no evidence that elections were compromised. State and local investigators have occasionally found instances of voter impersonation or non-citizens voting in federal elections, but the number of legitimate cases is in the dozens, compared to the billions of ballots cast this century.

Unfortunately, Alito appears to be among those who believe in the conspiracy theory as well. In 2021, he authored the majority opinion in Brnovich v. Democratic National Committee , which involved a lawsuit against two Arizona voting restrictions. The 6-3 ruling substantially narrowed Section 2 of the Voting Rights Act for challenging state elections laws that are facially neutral but racially discriminatory in impact. Though he claims to be a textualist, Alito invented multiple exceptions to the VRA’s provisions out of whole cloth, including one that allows states to evade VRA lawsuits if they claim to be acting to prevent voter fraud.

“One strong and entirely legitimate state interest is the prevention of fraud,” Alito wrote. “Fraud can affect the outcome of a close election, and fraudulent votes dilute the right of citizens to cast ballots that carry appropriate weight. Fraud can also undermine public confidence in the fairness of elections and the perceived legitimacy of the announced outcome.”

If modern American history had examples of significant or systemic voting fraud, Alito’s position might be easier to defend. But the utter absence of any evidence that voting fraud is a factor in American elections is damning. Both then and now, Alito has used the illusory threat of voter fraud to justify concrete restrictions on Americans’ ability to vote. He and his associates are using a fake risk to election integrity to impose real ones.

“On the other side of the balance, the plaintiff States invoke the practical effects of implementing the rule close to the midterm elections,” Alito explained. “I take that problem very seriously, but it is not enough to convince me to deny the application.” If nothing else, his candor is refreshing. He dismisses the broad, bipartisan concerns about the USPS rule’s potential for mass disenfranchisement by noting that some states still support the rule—likely because it would not affect them—and because California et al. “bear a substantial share of the blame for the rule’s timing” by fighting it in court all summer.

He even takes a shot at his fellow justices and lower-court judges for their own alleged share of the blame. “Indeed, this Court spent from late July to the end of August drafting nearly 40 pages of opinions, including two dissents, despite the government’s public notice that it would not publish a final rule while subject to the injunctions, and its representation that it had a strong interest in issuing its rule in early-to mid-August to allow time to implement it,” he complained.

That jab only makes sense if one accepts that the concerns of disruption are valid, or else there would be no reason to blame them on his colleagues. Everything, in short, must bend to one president’s whims to seriously disrupt American elections for his own gain. In Alito and Thomas’s view, it apparently does not matter if he’s fighting a fake problem or if our representative system of government. The states, the lower courts, and the justices themselves must bend the knee. Thankfully for the republic, the other seven justices are not quite so supine.

===

Matt Ford is a staff writer at The New Republic.

Gemini Live audio

Simon Willison
simonwillison.net
2026-09-15 18:47:07
Tool: Gemini Live audio Google released Gemini 3.8 Live and 3.8 Live Extended Thinking today - two new speech-to-speech models that are a similar shape to OpenAI's GPT-Live models. I pointed GPT-6 Astra Extra High at the documentation and had it build me this web UI for trying out the new mo...
Original Article

Google released Gemini 3.8 Live and 3.8 Live Extended Thinking today - two new speech-to-speech models that are a similar shape to OpenAI's GPT-Live family.

I pointed GPT-6 Astra Extra High at the documentation and had it build me this web UI for trying out the new models. You can select a model and voice preset, enter an optional system prompt and then start a voice conversation through your browser, including the ability to interrupt the model while it is talking.

Screenshot of a voice chat web interface with a transcript. Top buttons: Start session, End session, Mute mic, plus a Mic level meter and a timer showing 0:33. Status: Listening. Use headphones to reduce echo. Starting a session asks for microphone access. Transcript (with Download transcript and Clear buttons). Gemini: Yes, it's working perfectly. I can hear you clearly. How can I help you today? You: Okay, this is pretty good. Tell me some interesting facts about the California brown pelican. Gemini: They're famous for their spectacular plunge-dives to catch fish and have huge throat pouches that can hold up to three gallons of water and fish. They also made a great recovery after being endangered — marked as Interrupted. You: No, tell me different facts actually. Gemini: They nest in colonies on offshore islands and can hold up to three gallons of water in their throat pouches. At the bottom is a text input reading Or type a message… with a Send button, and the note: Sending a message interrupts the current response. Transcripts may include speech interrupted before playback.

The implementation uses no libraries. It connects to the wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=... WebSocket endpoint and uses a Web Audio API AudioContext for both capture and playback.

Mamdani's Vision Zero Revamp: Heavy on the Vision, Light on Action and Enforcement

hellgate
hellgatenyc.com
2026-09-15 18:34:36
The mayor gave very few specifics on any of the key initiatives—and could not explain why the NYPD has all but stopped ticketing drivers....
Original Article

On Tuesday, Mayor Zohran Mamdani released a list of 100 ideas to make New York City streets safer and more livable, especially for pedestrians and cyclists.

These include pedestrian-only zones across the city, installing speed limiters inside the cars of the city's most dangerous drivers, daylighting 1,000 intersections , imposing new tolls on large trucks, and lowering the speed limit to 20 mph in parts of the city.

But the Mamdani administration gave very few specifics on any of these key initiatives, which were couched as "policy commitments." Where would the pedestrian zones be located? They couldn't say. When would construction happen on the daylighting of a thousand intersections? No answer. How long will it take the City to spin up the reckless driver program? It's not clear.

And the mayor's new Vision Zero plans also completely sidestepped the area of enforcing traffic laws for drivers, which is something the police department has apparently given up on. NYPD enforcement of moving violations like running red lights and speeding has dropped 41 percent since 2019. Meanwhile the NYPD continues a seemingly unrelenting crackdown on delivery cyclists and those who commute on two wheels—moving violations for two-wheeled devices are up 228 percent since 2019, according to NYPD statistics.

Give us your email to read the full story

Sign up now for our free newsletters.

Sign up

Ubuntu 26.10 completes transition to Rust-based coreutils

Lobsters
www.omgubuntu.co.uk
2026-09-15 23:39:05
Comments...
Original Article

Ubuntu 26.10 completes the distro’s move to Rust-based core utilities, with the commands previously held back due to security issues now migrated to memory-safe versions.

cp , mv and rm were held back on their GNU versions in Ubuntu 26.04 LTS due to a crop of TOCTOU (time-of-check to time-of-use) issues that needed to be fixed in the uutils versions.

With those issues resolved upstream, Ubuntu 26.10 finishes the job. The ‘Stonking Stingray’ ships a full set of Rust core utilities, which encompasses common command-line tools like ls , cat , chmod and du .

Canonical donates €40k a year to help fund work on Rust software

Canonical’s engineers began ‘oxidising’ the distro – replacing foundational software with Rust alternatives – in 2025. It sees security benefits in doing so, since Rust catches memory bugs at compile time, whereas C compilers don’t.

Ubuntu 25.10 was the first release to ship with Rust-based utilities and made Rust-based sudo the default .

Migrating hasn’t been without hiccups , but Canonical has been studious.

It commissioned a security audit of uutils ahead of 26.04, which found the issues that kept the three commands back on their GNU versions. It’s also a gold sponsor of the Trifecta Tech Foundation , giving €40,000 a year to fund its work on Rust software.

The non-profit foundation is undertaking a Rust-based rewrite of the Network Time Protocol (NTP) , and Ubuntu plans to use it as the default time sync client by 27.10.

Here, the completion of the coreutils migration offers no functional difference to end users. The Rust-based uutils aims for drop-in compatibility with GNU versions, and treats any deviances as a bug. That’s by designed; the point is one of improved security.

Ubuntu 26.10 ‘Stonking Stingray’ beta arrives later this month, before the stable release on 15 October, 2026.

JDK 27 has been released

Lobsters
openjdk.org
2026-09-15 23:17:56
Comments...
Original Article

This release is the Reference Implementation of version 27 of the Java SE Platform, as specified by JSR 402 in the Java Community Process.

The features and schedule of this release were proposed and tracked via the JEP Process , as amended by the JEP 2.0 proposal . The release was produced using the JDK Release Process (JEP 3) .

Status

JDK 27 reached General Availability on 15 September 2026. Production-ready binaries under the GPL are available from Oracle ; binaries from other vendors will follow shortly.

Features

523: Make G1 the Default Garbage Collector in All Environments
527: Post-Quantum Hybrid Key Exchange for TLS 1.3
531: Lazy Constants (Third Preview)
532: Primitive Types in Patterns, instanceof, and switch
(Fifth Preview)
533: Structured Concurrency (Seventh Preview)
534: Compact Object Headers by Default
536: JFR In-Process Data Redaction
537: Vector API (Twelfth Incubator)
538: PEM Encodings of Cryptographic Objects (Third Preview)

Schedule

2026/06/04 Rampdown Phase One (branch from main line)
2026/07/16 Rampdown Phase Two
2026/08/06 Release Candidate Phase
2026/08/20 Release Candidate Build
2026/09/15 General Availability

Last update: 2026/9/15 11:59 UTC

Stay discoverable in search while disallowing AI training

Hacker News
blog.cloudflare.com
2026-09-15 22:25:18
Comments...
Original Article

Without proper controls, website owners have long faced a difficult tradeoff: allow your content to be used for AI training, or risk losing discoverability in search. That tradeoff exists because some of the largest organizations on the Internet use mixed-use crawlers: a single crawler serving both search and AI training. Refuse one, and you refuse the other.

Today, Cloudflare is announcing a new Disallow AI Training setting that lets you easily stay indexed for search while refusing to let that same crawler train on your content. Apple, Google, and Microsoft honor or have committed (in a specified time frame) to honor this setting.

Mixed-use crawlers were the hard part of the training question. AI Summaries are next. A site-wide yes or no is too blunt: how much of your content appears in a summary matters as much as whether it appears at all. An opt-out for AI summaries is already one of the requirements we've set for mixed-use crawler operators. By early next year, our goal is to let you control how much of your content is included — set once on Cloudflare, rather than with each operator separately.

Why asking isn’t enough

Most site owners want to be found: by humans, agents, and (good) bots. But a significant portion of the open Internet is funded by advertising, subscriptions, or direct relationships with visitors, and those models only pay when someone actually arrives.

Almost every site owner considers Search beneficial: less than 1% of Cloudflare sites choose to block Search bots. Training, however, is a different story: 17% of sites choose to enable some mechanism to block training. This is exactly why we decided site owners needed more granular controls, rather than a one-size-fits-all “Block AI.”

A robots.txt directive alone cannot solve this problem. Anyone can publish one, but it cannot identify who is crawling, determine why they are crawling, or stop a crawler that ignores it.

A network can solve it, however: we publish the preference, identify who is crawling, classify why they are crawling, and block the ones that ignore it – then report what each operator actually does on Radar .

But blocking removes a crawler. It doesn't change how crawlers behave. The better outcome is operators that don't make you choose at all. So since July, we've been talking to them directly. The response has been encouraging: almost all agreed that site owners should have control and transparency into how their content is used, and reassurance that their choices will be respected. To help site owners understand that, we created a designation: Accountable.

The Accountable designation recognizes both capabilities available today and concrete commitments to deliver them. To qualify, a bot operator must meet or commit to meeting the following requirements:

  1. A mechanism for site owners to opt out of AI training, through robots.txt or a similar standard.
  2. A mechanism for site owners to opt out of AI summaries set with the operator directly, and next year through Cloudflare (see section below for more detail).
  3. URL-level visibility into which pages were made available for training, along with metrics showing how content appeared in search.
  4. Assurance that opting out of AI training will not affect traditional search results.

Apple, Google, and Microsoft all demonstrate that they meet the qualifications to be Accountable. Each combines capabilities available today with time-bound commitments for those still in development. The details of each of these companies’ crawlers are shared below.

New security setting options

Cloudflare classifies bots by behavior, and a single bot can exhibit more than one behavior. Three behaviors are available as controls:

  • Search - crawling to build a search index.
  • Training - crawling to train or fine-tune a model.
  • Agent - user-directed agents visiting a page on behalf of a human, such as chat fetch bots and browser-use agents.

A mixed-use crawler is a single crawler doing both Search and Training. Without controls, that combination creates the tradeoff described above: site owners cannot refuse one use without refusing the other.

To avoid blocking Accountable mixed-use crawlers — the ones that don't force that tradeoff on website owners — we are introducing a new setting: Disallow AI Training. Disallow AI Training is named for the Disallow: directive it publishes in your robots.txt.

“Block” setting now means something different

Block and “Block on pages with ads” previously did not apply to mixed-use crawlers because blocking them could also affect search discoverability. Now that we have the new Disallow AI Training setting, Block and “Block on pages with ads” apply to all training crawlers, including mixed-use crawlers.

Training, Search, and Agent controls are applied at the domain level. With the addition of Disallow AI Training, the available settings are:

  1. Allow : All crawlers are allowed, unless blocked by another setting or a WAF rule.
  2. Disallow AI Training : Bot Preference Sync publishes the applicable no-training preference in robots.txt. Accountable mixed-use crawlers remain allowed for search. Every other training crawler is blocked, including the training-only crawlers run by Amazon, Anthropic, Meta, and OpenAI — blocking those does not affect search. Disallow AI Training is only available as a setting for Training, not Search or Agent.
  3. Block on pages with ads : Crawlers, including mixed-use crawlers, are blocked only on pages detected to be serving an ad.
  4. Block : All crawlers, including mixed-use crawlers, are blocked.

Disallow AI Training works by publishing a preference in robots.txt. An ads-only preference cannot be expressed that way: Cloudflare can detect which pages serve ads, but that list is too large and changes too frequently to enumerate in robots.txt. That's why there's no Disallow AI Training on pages with ads.

Agents do not create the same search-discoverability tradeoff as mixed-use crawlers, and the Internet does not yet have a well-established directive for expressing Disallow preferences to agents. For now, we’re not including a Disallow setting for Agents. As standards such as ai-prefs mature, we will revisit this approach.

What changes on September 15?

We are making the following changes to Bot Management and AI Crawl Control:

  1. Block and Block on pages with ads now apply to mixed-use crawlers, including Applebot, Bingbot, and Googlebot, so either setting impacts search as well as training. To stop training and keep search, use Disallow AI Training.
  2. “Block AI Bots” will be deprecated in favor of the more granular Search, Training, and Agent controls.
  3. Managed Robots.txt will be deprecated in favor of Bot Preference Sync. Customers who enabled Managed Robots.txt will migrate to the new system.
  4. Disallow AI Training will become part of the recommended configuration for certain new domains.
  5. Existing customers will have their preferences migrated to the new controls as described below.

What you need to do

Nothing, in almost every case. Your current settings carry over on their own.

If you want mixed-use crawlers gone entirely, you now have to say so. Select Block. It will stop Applebot, Bingbot, and Googlebot from reaching your site — search included.

Existing domains that never used the Search/Training/Agent controls

Site owners that never configured the more granular controls will be migrated to the new settings based on their legacy Block AI Bots setting:

(Legacy)
“Block AI” setting
(New)
Search setting
(New)
Training setting
(New)
Agent setting
Disabled (unselected) Allow Allow Allow
Block Allow Disallow AI Training Block on pages with ads
Block on pages with ads Allow Disallow AI Training Block on pages with ads

Existing domains that previously configured the Search/Training/Agent controls

For domains that previously configured the granular controls, we will preserve the practical effect of their selections under the new definitions. Previous Training selections of Block or Block on pages with ads will migrate to Disallow AI Training.

Control Legacy setting New setting
Search Allow Allow
Block Block
Block on pages with ads Block on pages with ads
Training Allow Allow
Block Disallow AI Training
Block on pages with ads Disallow AI Training
Agent Allow Allow
Block Block
Block on pages with ads Block on pages with ads

Recommendations for new domains

Beginning September 15, customers onboarding a new domain will be offered one of two preset configurations, depending on whether the site earns money from advertising. Ad revenue depends on a human actually seeing the page. Training replaces that visit with an answer; agents fetch the page with nobody there to see the ads. So the presets for ad-supported sites are more restrictive. You can change any of these settings during onboarding, or at any time afterward.

Setting Site does not monetize using ads Site is monetized using ads
Preference Sync Enabled Enabled
Search Allow Allow
Training Allow Disallow AI Training
Agent Allow Block on pages with ads

Recommended settings for new domains

BLOG-3499 2.png

Screenshot of onboarding flow for a new domain, showing the recommended settings when “I monetize pages that serve ads” is selected.

What does this mean for specific mixed-use crawlers?

Applebot, Bingbot, and Googlebot are Accountable. Apple, Google, and Microsoft are committed to the same principles of publisher choice and transparency. Under Disallow AI Training they can keep crawling your site for search. Selecting Block stops them entirely.

We also categorize the relevant crawlers from Amazon, Anthropic, Meta, and OpenAI as Accountable. These organizations separate their Search and Training crawlers, so Cloudflare can block the Training crawler without affecting search.

Applebot

Applebot allows site owners to opt out of training by adding a Disallow rule to robots.txt for “Applebot-Extended”. Site owners can also currently express preferences for AI Summaries via their nosnippet directive in the page HTML. Content can also be labeled as paywalled content to exclude it from generative output. Applebot does not yet provide a tool for URL-level inspection. However, we have met with their team, and they have shared details of their in-progress solution for next year. Apple has also stated that disallowing training does not impact search ranking .

Googlebot

Googlebot allows site owners to opt out of training by adding a Disallow rule to robots.txt for “Google-Extended”, and they provide a toggle inside their webmaster portal to exclude a site’s content from generative search results. Googlebot also provides site owners with metrics and reporting regarding search results and AI summary results. Google shared information about their existing and recently launched controls, as well as information about what they're already working on, including additional URL-level transparency tools for site-owners related to Google-Extended, which they expect to launch in the weeks to come. Google has also stated that disallowing Google-Extended does not impact search ranking .

Bingbot

Bingbot provides granular controls and transparency in their Webmaster Tools . Site owners can currently express AI training preferences through Bing’s NOARCHIVE meta tag . Microsoft is extending these capabilities and currently building the mechanism to also respect a “no training” preference in robots.txt at the domain/site level, targeted for early 2027. For Cloudflare Customers who wish to opt out of training in Bing today, in addition to using the NOARCHIVE tag, site owners can use the Block URLs or Content Removal tool . Microsoft has also stated that using NOARCHIVE will not impact search ranking .

Until that support launches, selecting Disallow AI Training will not automatically convey a no-training preference to Bing through robots.txt. This is the same practical behavior as the previous Training Block setting, which did not apply to mixed-use crawlers such as Bingbot.

Continuing progress

We will continue to reach out and engage with all operators of AI crawlers as these capabilities evolve. Cloudflare Radar publicly tracks the controls, transparency, and reporting provided by Accountable crawler operators.

Making the Internet better requires both sides to have agency: crawlers need access to the open web, and the people who create that web need meaningful control over how their work is used. Today’s announcement represents concrete progress toward that balance.

Progress requires infrastructure providers, content creators, technology companies, and standards bodies such as the Internet Engineering Task Force (IETF) working together to translate these principles into open, interoperable standards.

What’s next: AI Summaries

Training and AI Summaries raise different questions for site owners. Training concerns whether content can be used to build AI models. Summaries affect how people discover, evaluate, and ultimately visit a business. Both matter, but they affect businesses in different ways.

Controls to opt out of AI summaries are the first step. The operators identified as Accountable either provide or are completing work to provide that capability, establishing an important baseline: site owners can say no.

But a site-wide choice between allowing and prohibiting summaries is still a blunt instrument. The right decision depends on the site, the content, and the business outcome. For publishers, training raises foundational questions about control, compensation, and the sustainability of original content. Summaries create a separate and often more immediate distribution question: does someone visit the publisher’s site, or consume the answer within a search or AI experience? For many other businesses, AI summaries increasingly sit between a potential customer and a website. They may answer a question, compare alternatives, recommend a product, or help someone decide whether to visit at all.

The data illustrates mixed impact. More than half of consumers read summaries in Search, and those consumers are over 40% more likely to end their search after reading one. This can reduce the number of visits a website receives. But consumers referred by AI Search convert at between three times and over five times the rate of those referred by traditional search. AI may produce fewer visits while sending customers with much greater intent.

That is not inherently good or bad. A publisher funded by advertising may optimize for audience volume. A retailer may prefer fewer visitors who are more likely to purchase. Cloudflare’s role is not to choose for them, but to provide the visibility and control needed to make an informed decision.

Summary opt-outs are a strong start, but they are not the end state. Our next focus is helping site owners understand how summaries affect their businesses and giving them more control over how much of their content can be used. Open standards such as ai-prefs will be an important part of making that possible.

If you would like to have a voice in this conversation, or provide feedback, please reach out to crawlercontrols@cloudflare.com .

These new controls are available to all customers, on all plans, and can be configured at the domain (zone) Security Settings . Not on Cloudflare yet? Start for free to set the traffic controls that you want today.

Saving Jet Fuel

Hacker News
tech.marksblogg.com
2026-09-15 19:17:20
Comments...
Original Article

A Boeing 787-9 Dreamliner flying nonstop from Newark Liberty International Airport (EWR) to Leonardo da Vinci-Fiumicino Airport (FCO) could need $68K in jet fuel over the 8.5-hour flight. Adjusting the flight path for wind conditions could reduce fuel consumption and possibly save a few thousand dollars.

Firms like Jeppesen have offerings in this space, but Scikit-decide , together with a narrow- and wide-body fuel consumption model built by a professor at the Delft University of Technology and wind data from NOAA, offer an open source solution.

Scikit-decide has been in development for six years. It's a framework for reinforcement learning, automated planning and scheduling. The project can optimise flight paths, re-organise airline workforce schedules and calculate drone swarm paths.

OpenAP is an aircraft performance model and toolkit developed by Dr. Junzi Sun. Dr. Sun has a PhD in air traffic management and, among many other things, teaches a course on the subject as a tenured assistant professor at TU Delft in the Netherlands.

Scikit-decide's optimal flight path solver can be configured to use different fuel consumption models. In this post, I'll compare two flight paths flown using the Airbus A320 and OpenAP's fuel consumption model.

My Workstation

I'm using a 5.7 GHz AMD Ryzen 9 9950X CPU. It has 16 cores and 32 threads and 1.2 MB of L1, 16 MB of L2 and 64 MB of L3 cache. It has a liquid cooler attached and is housed in a spacious, full-sized Cooler Master HAF 700 computer case.

The system has 96 GB of DDR5 RAM clocked at 4,800 MT/s and a 5th-generation, Crucial T700 4 TB NVMe M.2 SSD which can read at speeds up to 12,400 MB/s. There is a heatsink on the SSD to help keep its temperature down. This is my system's C drive.

The system is powered by a 1,200-watt, fully modular Corsair Power Supply and is sat on an ASRock X870E Nova 90 Motherboard.

I'm running Ubuntu 24 LTS via Microsoft's Ubuntu for Windows on Windows 11 Pro. In case you're wondering why I don't run a Linux-based desktop as my primary work environment, I'm still using an Nvidia GTX 1080 GPU which has better driver support on Windows and ArcGIS Pro only supports Windows natively.

Installing Prerequisites

I'll use Python 3.12 along with jq in this post.

$ sudo add-apt-repository ppa:deadsnakes/ppa
$ sudo apt update
$ sudo apt install \
    jq \
    python3-pip \
    python3.12-venv

I'll set up a Python Virtual Environment and install scikit-decide, along with the OpenAP open aircraft performance model and OpenTop, a flight trajectory toolkit that was also developed by Dr. Sun.

$ python3 -m venv ~/.flight_planning
$ source ~/.flight_planning/bin/activate
$ pip install \
    'scikit-decide[all]' \
    'openap[all]' \
    opentop

The above will need at least 8 GB of storage capacity. These are the packages that were installed.

$ pip install pipdeptree
$ pipdeptree -d0
lz4==4.4.5
openevolve==0.3.2
opentop==2.6.0
pip==24.0
pipdeptree==4.2.5
plado==0.1.6
pygeodesy==26.9.9
pygrib==2.1.8
pyRDDLGym-gurobi==0.2
pyRDDLGym-jax==3.1
pyRDDLGym-rl==0.2
pytz==2026.3.post1
ray==2.37.0
rddlrepository==2.2
sb3_contrib==2.3.0
scikit-decide==1.1.1
scikit-image==0.26.0
tensorboardX==2.6.5
torch-geometric==2.8.0.post1
typer==0.27.2
unified-planning==1.2.0
up-enhsp==0.0.27
up_fast_downward==0.5.2
up-pyperplan==1.1.0
z3-solver==5.1.0.0

I'll use DuckDB, along with its H3 , JSON , Lindel , Parquet and Spatial extensions in this post.

$ cd ~
$ wget -c https://github.com/duckdb/duckdb/releases/download/v1.5.4/duckdb_cli-linux-amd64.zip
$ unzip -j duckdb_cli-linux-amd64.zip
$ chmod +x duckdb
$ ~/duckdb
INSTALL h3 FROM community;
INSTALL lindel FROM community;
INSTALL json;
INSTALL parquet;
INSTALL spatial;

I'll set up DuckDB to load every installed extension each time it launches.

.timer on
.width 180
LOAD h3;
LOAD lindel;
LOAD json;
LOAD parquet;
LOAD spatial;

The maps in this post were rendered with QGIS version 4.2.1. QGIS is a desktop application that runs on Windows, macOS and Linux. The application has grown in popularity in recent years and has ~22M application launches from users all around the world each month.

The boundaries and place names were sourced from Natural Earth . Maritime Boundaries were sourced from Marine Regions .

OpenAP's Aircraft Types

I'll first clone the OpenAP repository.

$ git clone https://github.com/junzis/openap

Excluding unit tests and utility scripts, there are 3,369 lines of Python in this package.

OpenAP's model relies on a large number of datasets that are packaged with its codebase. These cover a wide variety of aircraft. Below are the aircraft manufacturer counts.

$ grep -ho 'aircraft: .*[a-z] ' \
    openap/data/aircraft/*.yml \
    | cut -d' ' -f2 \
    | sort \
    | uniq -c \
    | sort -rn
17 Boeing
13 Airbus
 5 Embraer
 1 Gulfstream
 1 Cessna

These are the properties for the Airbus A380-800.

$ cat openap/data/aircraft/a388.yml
aircraft: Airbus A380-800

mtow: 560000
mlw: 386000
oew: 277000
mfc: 320000
vmo: 340
mmo: 0.89
ceiling: 13100

pax:
  max: 853
  low: 410
  high: 620

fuselage:
  length: 72.72
  height: 8.41
  width: 7.14

wing:
  area: 845
  span: 79.75
  mac: null
  sweep: 33.5
  t/c: 0.08

flaps:
  type: single-slotted
  area: null
  bf/b: null
  lambda_f: 0.900
  cf/c: 0.150
  Sf/S: 0.150

cruise:
  height: 12800
  mach: 0.85
  range: 14800

engine:
  type: turbofan
  mount: wing
  number: 4
  default: GP7270
  options:
    A380-841: Trent 970-84
    A380-842: Trent 972-84
    A380-861: GP7270

drag:
  cd0: 0.016
  k: 0.050
  e: 0.855
  gears: 0.012

These are its drag coefficients.

$ cat openap/data/dragpolar/a388.yml
aircraft: Airbus A380-800

clean:
  cd0:         0.016
  k:           0.050
  e:           0.855

gears:         0.012

flaps:
  lambda_f:    0.900
  cf/c:        0.150
  Sf/S:        0.150

These are some additional properties.

$ echo "import pandas as pd; print(
            pd.read_fwf('openap/data/wrap/a388.txt')
              .to_csv(index=False))" \
    | python3 \
    | ~/duckdb \
        -c '.maxwidth 150' \
        -c "SELECT * EXCLUDE(parameters),
                   parameters: SPLIT(parameters, '|')
            FROM   READ_CSV('/dev/stdin')"
┌──────────────────────┬────────────────┬───────────────────────────────────────┬────────┬────────┬─────────┬─────────┬──────────────────────────────┐
│       variable       │  flight phase  │                 name                  │  opt   │  min   │   max   │  model  │          parameters          │
│       varchar        │    varchar     │                varchar                │ double │ double │ double  │ varchar │          varchar[]           │
├──────────────────────┼────────────────┼───────────────────────────────────────┼────────┼────────┼─────────┼─────────┼──────────────────────────────┤
│ to_v_lof             │ takeoff        │ Liftoff speed                         │   89.9 │   75.4 │   104.4 │ norm    │ [89.93, 10.07]               │
│ to_d_tof             │ takeoff        │ Takeoff distance                      │   2.56 │   1.35 │    3.78 │ norm    │ [2.56, 0.74]                 │
│ to_acc_tof           │ takeoff        │ Mean takeoff accelaration             │   1.35 │   1.04 │    1.66 │ norm    │ [1.35, 0.19]                 │
│ ic_va_avg            │ initial_climb  │ Mean airspeed                         │   88.0 │   80.0 │    96.0 │ norm    │ [88.15, 5.64]                │
│ ic_vs_avg            │ initial_climb  │ Mean vertical rate                    │   5.65 │    4.4 │    8.94 │ gamma   │ [4.76, 3.22, 0.65]           │
│ cl_d_range           │ climb          │ Climb range                           │  296.0 │  200.0 │   446.0 │ beta    │ [3.23, 5.18, 179.46, 335.24] │
│ cl_v_cas_const       │ climb          │ Constant CAS                          │  163.0 │  155.0 │   170.0 │ norm    │ [163.39, 4.51]               │
│ cl_v_mach_const      │ climb          │ Constant Mach                         │   0.84 │    0.8 │    0.86 │ beta    │ [12.23, 5.32, 0.72, 0.17]    │
│ cl_h_cas_const       │ climb          │ Constant CAS crossover altitude       │    3.3 │    1.3 │     5.3 │ norm    │ [3.29, 1.24]                 │
│ cl_h_mach_const      │ climb          │ Constant Mach crossover altitude      │    8.9 │    8.2 │     9.7 │ norm    │ [8.94, 0.47]                 │
│ cl_vs_avg_pre_cas    │ climb          │ Mean climb rate, pre-constant-CAS     │   7.85 │   5.95 │    9.75 │ norm    │ [7.85, 1.16]                 │
│ cl_vs_avg_cas_const  │ climb          │ Mean climb rate, constant-CAS         │   7.51 │    5.2 │    9.82 │ norm    │ [7.51, 1.40]                 │
│ cl_vs_avg_mach_const │ climb          │ Mean climb rate, constant-Mach        │   5.56 │   3.23 │    7.91 │ norm    │ [5.57, 1.42]                 │
│ cr_d_range           │ cruise         │ Cruise range                          │ 4348.0 │  892.0 │ 20565.0 │ gamma   │ [2.81, 246.73, 2274.81]      │
│ cr_v_cas_mean        │ cruise         │ Mean cruise CAS                       │  136.0 │  130.0 │   145.0 │ beta    │ [3.32, 5.27, 126.00, 29.75]  │
│ cr_v_cas_max         │ cruise         │ Maximum cruise CAS                    │  145.0 │  134.0 │   164.0 │ beta    │ [2.02, 3.21, 130.38, 46.65]  │
│ cr_v_mach_mean       │ cruise         │ Mean cruise Mach                      │   0.84 │   0.82 │    0.86 │ norm    │ [0.84, 0.01]                 │
│ cr_v_mach_max        │ cruise         │ Maximum cruise Mach                   │   0.87 │   0.85 │     0.9 │ gamma   │ [16.14, 0.80, 0.00]          │
│ cr_h_init            │ cruise         │ Initial cruise altitude               │  11.55 │    9.3 │   12.23 │ beta    │ [3.82, 1.66, 7.49, 5.01]     │
│ cr_h_mean            │ cruise         │ Mean cruise altitude                  │  11.73 │  10.87 │   12.28 │ beta    │ [7.22, 3.92, 9.59, 3.14]     │
│ cr_h_max             │ cruise         │ Maximum cruise altitude               │  12.06 │  11.52 │    12.6 │ norm    │ [12.06, 0.33]                │
│ de_d_range           │ descent        │ Descent range                         │  310.0 │  238.0 │   528.0 │ gamma   │ [4.73, 213.47, 25.87]        │
│ de_v_mach_const      │ descent        │ Constant Mach                         │   0.83 │    0.8 │    0.87 │ norm    │ [0.83, 0.02]                 │
│ de_v_cas_const       │ descent        │ Constant CAS                          │  154.0 │  142.0 │   167.0 │ norm    │ [154.84, 7.74]               │
│ de_h_mach_const      │ descent        │ Constant Mach crossover altitude      │   10.1 │    8.6 │    11.5 │ norm    │ [10.06, 0.88]                │
│ de_h_cas_const       │ descent        │ Constant CAS crossover altitude       │    6.6 │    3.9 │     9.4 │ norm    │ [6.64, 1.69]                 │
│ de_vs_avg_mach_const │ descent        │ Mean descent rate, constant-Mach      │  -6.06 │  -11.9 │   -2.97 │ beta    │ [3.43, 2.08, -15.98, 14.36]  │
│ de_vs_avg_cas_const  │ descent        │ Mean descent rate, constant-CAS       │  -8.36 │ -11.74 │   -4.97 │ norm    │ [-8.36, 2.06]                │
│ de_vs_avg_after_cas  │ descent        │ Mean descent rate, after-constant-CAS │  -5.48 │  -6.93 │   -4.02 │ norm    │ [-5.48, 0.88]                │
│ fa_va_avg            │ final_approach │ Mean airspeed                         │   73.0 │   68.0 │    77.0 │ norm    │ [73.28, 3.02]                │
│ fa_vs_avg            │ final_approach │ Mean vertical rate                    │  -3.71 │  -4.13 │   -2.92 │ gamma   │ [9.49, -4.74, 0.12]          │
│ fa_agl               │ final_approach │ Approach angle                        │    2.9 │   2.42 │    3.38 │ norm    │ [2.90, 0.29]                 │
│ ld_v_app             │ landing        │ Touchdown speed                       │   70.0 │   62.1 │    78.0 │ norm    │ [70.00, 5.52]                │
│ ld_d_brk             │ landing        │ Braking distance                      │   2.26 │   0.73 │     3.8 │ norm    │ [2.26, 0.93]                 │
│ ld_acc_brk           │ landing        │ Mean braking acceleration             │  -1.01 │  -1.51 │   -0.52 │ norm    │ [-1.01, 0.30]                │
└──────────────────────┴────────────────┴───────────────────────────────────────┴────────┴────────┴─────────┴─────────┴──────────────────────────────┘

These are the aircraft type synonyms list.

$ ~/duckdb -c "FROM READ_CSV('/dev/stdin')" \
    < openap/data/aircraft/_synonym.csv
┌─────────┬─────────┐
│  orig   │   new   │
│ varchar │ varchar │
├─────────┼─────────┤
│ a124    │ b744    │
│ a306    │ a332    │
│ a310    │ a318    │
│ at72    │ e145    │
│ at75    │ e145    │
│ at76    │ e145    │
│ b733    │ b734    │
│ b735    │ b734    │
│ b762    │ b763    │
│ b77l    │ b77w    │
│ c25a    │ c550    │
│ c525    │ c550    │
│ c56x    │ c550    │
│ crj2    │ e145    │
│ crj9    │ e75l    │
│ e290    │ e190    │
│ glf5    │ glf6    │
│ gl5t    │ glf6    │
│ lj45    │ glf6    │
│ md11    │ b773    │
│ pc24    │ c550    │
│ su95    │ e170    │
└─────────┴─────────┘

Aircraft Engines

Aircraft often have the option of at least two different engines to choose from. There are 427 engines listed in this package's dataset.

$ wc -l openap/data/engine/engines.csv # 427

These are the details for the Trent 970-84.

$ echo "FROM  'openap/data/engine/engines.csv'
        WHERE name = 'Trent 970-84'
        LIMIT 1" \
    | ~/duckdb -json \
    | jq -S .
[
  {
    "bpr": 8.45,
    "cruise_alt": null,
    "cruise_mach": null,
    "cruise_sfc": null,
    "cruise_thrust": null,
    "ei_co_app": 1.16,
    "ei_co_co": 0.31,
    "ei_co_idl": 13.38,
    "ei_co_to": 0.32,
    "ei_hc_app": 0.08,
    "ei_hc_co": 0.12,
    "ei_hc_idl": 0.04,
    "ei_hc_to": 0.02,
    "ei_nox_app": 12.09,
    "ei_nox_co": 29.42,
    "ei_nox_idl": 5.44,
    "ei_nox_to": 38.29,
    "ff_app": 0.72,
    "ff_co": 2.157,
    "ff_idl": 0.255,
    "ff_to": 2.605,
    "fuel_lto": 965.0,
    "manufacturer": "Rolls-Royce plc",
    "max_thrust": 338700.0,
    "name": "Trent 970-84",
    "pr": 38.0,
    "type": "TF",
    "uid": "18RR081"
  }
]

These are the engine manufacturer counts.

CREATE OR REPLACE TABLE a AS
    FROM 'openap/data/engine/engines.csv';

SELECT   COUNT(*),
         manufacturer
FROM     a
GROUP BY 2
ORDER BY 1 DESC;
┌──────────────┬────────────────────────────┐
│ count_star() │        manufacturer        │
│    int64     │          varchar           │
├──────────────┼────────────────────────────┤
│          108 │ GE Aircraft Engines        │
│           94 │ CFM International          │
│           85 │ Pratt & Whitney            │
│           62 │ Rolls-Royce plc            │
│           13 │ International Aero Engines │
│           12 │ Pratt & Whitney Canada     │
│           11 │ Rolls-Royce Corporation    │
│            8 │ Rolls-Royce Deutschland    │
│            8 │ Honeywell                  │
│            7 │ Aviadvigatel               │
│            5 │ Textron Lycoming           │
│            4 │ KKBM                       │
│            3 │ IVCHENKO PROGRESS ZMBK     │
│            2 │ PowerJet S.A.              │
│            2 │ Allied Signal              │
│            1 │ Engine Alliance            │
│            1 │ Garret AiResearch          │
└──────────────┴────────────────────────────┘

These are the engine-type counts for Turbofan (TF), Mixed-flow Turbofan (MTF), Turboprop (TP) and Piston (PS) engines in this dataset.

SELECT   COUNT(*),
         type
FROM     a
GROUP BY 2
ORDER BY 1 DESC;
┌──────────────┬─────────┐
│ count_star() │  type   │
│    int64     │ varchar │
├──────────────┼─────────┤
│          322 │ TF      │
│           98 │ MTF     │
│            5 │ TP      │
│            1 │ PS      │
└──────────────┴─────────┘

This is the engine list ranked by their maximum thrust.

SELECT   manufacturer,
         name,
         type,
         max_thrust
FROM     a
ORDER BY 4 DESC
LIMIT    25;
┌─────────────────────┬───────────────┬─────────┬────────────┐
│    manufacturer     │     name      │  type   │ max_thrust │
│       varchar       │    varchar    │ varchar │   double   │
├─────────────────────┼───────────────┼─────────┼────────────┤
│ GE Aircraft Engines │ GE90-115B     │ TF      │   513900.0 │
│ GE Aircraft Engines │ GE90-113B     │ TF      │   504900.0 │
│ GE Aircraft Engines │ GE90-110B1    │ TF      │   492600.0 │
│ Rolls-Royce plc     │ Trent XWB-97  │ TF      │   436748.0 │
│ GE Aircraft Engines │ GE90-94B      │ TF      │   430920.0 │
│ GE Aircraft Engines │ GE90-92B      │ TF      │   426720.0 │
│ GE Aircraft Engines │ GE90-90B      │ TF      │   419250.0 │
│ Rolls-Royce plc     │ Trent 895     │ TF      │   413050.0 │
│ Rolls-Royce plc     │ Trent 892     │ TF      │   411480.0 │
│ Pratt & Whitney     │ PW4090        │ TF      │   408300.0 │
│ GE Aircraft Engines │ GE90-85B      │ TF      │   397210.0 │
│ Rolls-Royce plc     │ Trent 884     │ TF      │   390100.0 │
│ Pratt & Whitney     │ PW4084D       │ TF      │   385900.0 │
│ Rolls-Royce plc     │ Trent XWB-84  │ TF      │   379000.0 │
│ Pratt & Whitney     │ PW4084        │ TF      │   369600.0 │
│ GE Aircraft Engines │ GE90-77B      │ TF      │   366750.0 │
│ Rolls-Royce plc     │ Trent 1000-R3 │ TF      │   363900.0 │
│ GE Aircraft Engines │ GE90-76B      │ TF      │   363220.0 │
│ Rolls-Royce plc     │ Trent 877     │ TF      │   361640.0 │
│ Rolls-Royce plc     │ Trent 1000-M3 │ TF      │   358100.0 │
│ Rolls-Royce plc     │ Trent 1000-N3 │ TF      │   358100.0 │
│ Pratt & Whitney     │ PW4077D       │ TF      │   355700.0 │
│ Rolls-Royce plc     │ Trent XWB-79B │ TF      │   355200.0 │
│ Rolls-Royce plc     │ Trent XWB-79  │ TF      │   355200.0 │
│ Rolls-Royce plc     │ Trent 970B-84 │ TF      │   352900.0 │
└─────────────────────┴───────────────┴─────────┴────────────┘

These are the fuel model defaults and overrides.

$ ~/duckdb -c "FROM READ_CSV('/dev/stdin')" \
    < openap/data/fuel/fuel_models.csv
┌──────────┬─────────────┬────────────────────┬────────────────────┬────────────────────┐
│ typecode │ engine_type │         c1         │         c2         │         c3         │
│ varchar  │   varchar   │       double       │       double       │       double       │
├──────────┼─────────────┼────────────────────┼────────────────────┼────────────────────┤
│ A318     │ CFM56-5B9/3 │ 0.7769784596099123 │  1.765377288174942 │ 2.5349134936316693 │
│ A319     │ V2524-A5    │ 0.8694169413032631 │ 1.9542690629047836 │ 2.5028187026860103 │
│ A320     │ CFM56-5B4/P │ 1.0453208160586924 │ 2.3633720747416573 │ 1.2378127479131922 │
│ A321     │ V2533-A5    │ 1.3979999999999444 │  2.054028451829268 │ 1.0008941993511127 │
│ A332     │ Trent 772   │  2.886430057340283 │ 1.0960397632560752 │ 2.3772585567580293 │
│ A333     │ Trent 772   │ 3.1199999999999997 │ 1.0365152289922772 │  1.950599421257047 │
│ B737     │ CFM56-7B26  │ 1.0237419750954273 │ 1.4670109921175798 │ 3.2566140275646456 │
│ B738     │ CFM56-7B26E │  1.075484518912494 │ 1.8777303165419037 │ 1.8895522140156369 │
│ B739     │ CFM56-7B27E │ 1.3079999999999998 │ 1.5986016771932572 │ 1.2789091908108752 │
│ CRJ9     │ CF34-8C5    │ 0.6437136288905128 │ 1.9690234662778772 │ 1.4375859706162741 │
│ E170     │ CF34-8E5    │ 0.6341784688704629 │  2.778729428440142 │ 1.0149695061665696 │
│ E190     │ CF34-10E5   │ 0.8339999999998783 │ 2.3343013671118475 │ 0.4847704716061958 │
│ E195     │ CF34-10E5A1 │  0.911999999999993 │  1.929664699695295 │ 0.8452746256489131 │
│ E75L     │ CF34-8E5    │ 0.6340709359225759 │  2.614653287356019 │ 0.8714282723568036 │
│ default  │ default     │  0.937564901246902 │ 1.9767611682280135 │ 1.3954794843472482 │
└──────────┴─────────────┴────────────────────┴────────────────────┴────────────────────┘

Airports & Navigation

There are almost 14K airport locations and codes shipped with this package.

$ wc -l openap/data/nav/airports.csv # 13796

$ ~/duckdb -c "FROM READ_CSV('/dev/stdin')
               WHERE country = 'CA'
               ORDER BY lat
               LIMIT 20" \
    < openap/data/nav/airports.csv
┌─────────┬──────────┬───────────┬───────┬─────────┬───────────────────────────────┬────────────────┐
│  icao   │   lat    │    lon    │  alt  │ country │             name              │    location    │
│ varchar │  double  │  double   │ int64 │ varchar │            varchar            │    varchar     │
├─────────┼──────────┼───────────┼───────┼─────────┼───────────────────────────────┼────────────────┤
│ CYQG    │ 42.27334 │ -82.97056 │   622 │ CA      │ Windsor                       │ Windsor        │
│ CYQS    │ 42.77202 │ -81.11923 │   778 │ CA      │ St Thomas Muni                │ St. Thomas     │
│ CYZR    │ 43.00444 │ -82.31528 │   594 │ CA      │ Sarnia - Chris Hadfield       │ Sarnia         │
│ CYXU    │ 43.04211 │  -81.1598 │   912 │ CA      │ London                        │ London         │
│ CYFD    │ 43.12389 │ -80.34667 │   815 │ CA      │ Brantford                     │ Brant          │
│ CYHM    │ 43.18056 │ -79.95306 │   780 │ CA      │ John C Munro Hamilton Intl    │ Ancaster       │
│ CYSN    │ 43.18792 │  -79.1786 │   321 │ CA      │ Niagara District              │ St. Catharines │
│ CYCE    │ 43.28306 │ -81.51806 │   824 │ CA      │ Huron Airpark                 │ South Huron    │
│ CYSA    │ 43.41087 │ -80.93994 │  1215 │ CA      │ Stratford Municipal           │ Stratford      │
│ CZBA    │   43.445 │ -79.85472 │   602 │ CA      │ Burlington Airpark            │ Burlington     │
│ CYKF    │ 43.45694 │ -80.39056 │  1054 │ CA      │ Waterloo                      │ Cambridge      │
│ CYTZ    │ 43.62747 │ -79.40336 │   251 │ CA      │ Toronto City Centre           │ Toronto        │
│ CYYZ    │ 43.66073 │ -79.62394 │   568 │ CA      │ Toronto Lester B Pearson Intl │ Etobicoke      │
│ CYZD    │ 43.74972 │ -79.47417 │   652 │ CA      │ Downsview                     │ Concord        │
│ CYGD    │ 43.77111 │ -81.71639 │   712 │ CA      │ Goderich                      │ Goderich       │
│ CYQI    │  43.8175 │  -66.0975 │   141 │ CA      │ Yarmouth                      │ Yarmouth       │
│ CYKZ    │ 43.86444 │ -79.37334 │   650 │ CA      │ Buttonville Muni              │ Richmond Hill  │
│ CYOO    │ 43.92444 │ -78.90389 │   459 │ CA      │ Oshawa                        │ Oshawa         │
│ CYTR    │ 44.10889 │ -77.54222 │   283 │ CA      │ Trenton                       │ Quinte West    │
│ CYGK    │ 44.21833 │ -76.60083 │   305 │ CA      │ Kingston                      │ Kingston       │
└─────────┴──────────┴───────────┴───────┴─────────┴───────────────────────────────┴────────────────┘

These airports are located across 236 different countries.

SELECT COUNT(DISTINCT country)
FROM   READ_CSV('openap/data/nav/airports.csv');

These are the most represented countries in the airports dataset.

SELECT   COUNT(*),
         country
FROM     READ_CSV('openap/data/nav/airports.csv')
GROUP BY 2
ORDER BY 1 DESC
LIMIT    20;
┌──────────────┬─────────┐
│ count_star() │ country │
│    int64     │ varchar │
├──────────────┼─────────┤
│         2849 │ BR      │
│         2459 │ US      │
│         2062 │ AU      │
│          441 │ FR      │
│          345 │ CA      │
│          325 │ DE      │
│          258 │ GB      │
│          234 │ ID      │
│          176 │ NA      │
│          168 │ VE      │
│          156 │ RU      │
│          148 │ IN      │
│          143 │ AR      │
│          139 │ SE      │
│          126 │ JP      │
│          118 │ IT      │
│          106 │ NZ      │
│           99 │ CZ      │
│           98 │ BO      │
│           97 │ ZA      │
└──────────────┴─────────┘

These are a few of the navigation waypoints.

$ echo "import pandas as pd; print(
            pd.read_fwf('openap/data/nav/fix.dat',
                        skiprows=3,
                        header=None,
                        encoding='unicode_escape')
              .to_csv(index=False))" \
    | python3 \
    | ~/duckdb \
        -c '.maxwidth 150' \
        -c "FROM   READ_CSV('/dev/stdin')
            WHERE  column0 BETWEEN 57 AND 59
            AND    column1 BETWEEN 21 AND 27
            LIMIT 20"
┌───────────┬───────────┬─────────┐
│  column0  │  column1  │ column2 │
│  double   │  double   │ varchar │
├───────────┼───────────┼─────────┤
│ 57.133196 │ 23.888414 │ ALISA   │
│ 57.105833 │ 25.254167 │ AMOLI   │
│ 58.416389 │ 24.478333 │ ANAMA   │
│ 58.412778 │ 22.521667 │ EIKLA   │
│ 58.506944 │ 25.715278 │ EKLON   │
│ 58.626667 │ 21.766111 │ EVERI   │
│ 57.278611 │ 25.050556 │ GEKLI   │
│   58.9425 │ 25.576944 │ GONOS   │
│ 58.053333 │ 26.762778 │ KANEP   │
│ 58.331944 │ 22.221111 │ KARLA   │
│   58.7225 │ 24.586944 │ KEMET   │
│ 58.725753 │ 26.736943 │ KOLEV   │
│ 58.708056 │ 22.845833 │ KUKET   │
│ 58.931111 │ 24.661944 │ KUNUX   │
│ 58.176667 │     26.93 │ KUUST   │
│ 58.441667 │ 26.451667 │ LAEVA   │
│ 58.553333 │ 25.934444 │ LALSI   │
│ 57.336944 │ 22.636944 │ LAPSA   │
│ 57.774167 │ 22.104444 │ LATEG   │
│ 58.180278 │ 25.779167 │ LATKA   │
└───────────┴───────────┴─────────┘

These are a few of the navigation aids.

$ wc -l openap/data/nav/nav.dat # 26775

$ echo "import pandas as pd; print(
            pd.read_fwf('openap/data/nav/nav.dat',
                        skiprows=3,
                        header=None,
                        encoding='unicode_escape')
              .to_csv(index=False))" \
    | python3 \
    | ~/duckdb \
        -c '.maxwidth 150' \
        -c "SELECT * EXCLUDE(column9)
            FROM   READ_CSV('/dev/stdin')
            WHERE  column1 BETWEEN 57 AND 59
            AND    column2 BETWEEN 21 AND 27
            LIMIT 20"
┌─────────┬───────────┬───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────────────────┐
│ column0 │  column1  │  column2  │ column3 │ column4 │ column5 │ column6 │ column7 │      column8       │
│  int64  │  double   │  double   │ varchar │ double  │ double  │ double  │ varchar │      varchar       │
├─────────┼───────────┼───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────────────────┤
│       2 │ 58.957117 │ 22.872158 │ 0       │   317.0 │    80.0 │     0.0 │ OZ      │ KARDLA NDB         │
│       2 │ 58.270722 │ 22.508778 │ 0       │   350.0 │    80.0 │     0.0 │ WA      │ KURESSAARE NDB     │
│       2 │ 58.490806 │ 24.571556 │ 0       │   425.0 │    80.0 │     0.0 │ RC      │ PARNU NDB          │
│       2 │ 58.435833 │ 24.495861 │ 0       │   376.0 │    25.0 │     0.0 │ R       │ PARNU NDB          │
│       2 │ 58.308583 │ 26.768417 │ 0       │   397.0 │    80.0 │     0.0 │ UM      │ TARTU NDB          │
│       3 │ 58.228333 │ 22.515361 │ 39      │   240.0 │    50.0 │     3.0 │ KRS     │ KURESSAARE VOR-DME │
│       3 │ 58.416583 │ 24.465972 │ 58      │   590.0 │    25.0 │     6.0 │ PRN     │ PARNU VOR-DME      │
│       3 │ 57.366944 │ 21.556222 │ 0       │   360.0 │   130.0 │     5.3 │ VNT     │ VENTSPILS VOR-DME  │
│       3 │ 58.655889 │ 25.574778 │ 227     │   490.0 │    80.0 │     5.0 │ VI      │ VOHMA VOR-DME      │
│       1 │ 58.228333 │ 22.515361 │ 3       │   124.0 │     5.0 │     0.0 │ KR      │ KURESSAARE VOR-DME │
│       1 │ 58.416583 │ 24.465972 │ 5       │   159.0 │     2.0 │     0.0 │ PR      │ PARNU VOR-DME      │
│       1 │ 57.366944 │ 21.556222 │ NULL    │   136.0 │    13.0 │     0.0 │ VN      │ VENTSPILS VOR-DME  │
│       1 │ 58.655889 │ 25.574778 │ 22      │   149.0 │     8.0 │     0.0 │ VI      │ VOHMA VOR-DME      │
│       1 │ 58.992083 │ 22.830972 │ 3       │   176.0 │     2.0 │     0.0 │ KR      │ KARDLA DME         │
└─────────┴───────────┴───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────────────────┘

Toulouse to Berlin

Below, I'll find an optimal flight path from Toulouse-Blagnac Airport (LFBO / TLS) to Berlin Brandenburg Airport (EDDB / BER).

import numpy as np
from   openap.aero import cas2mach, ft, kts
from   openap.extra.nav import airport
from   pygeodesy.ellipsoidalVincenty import LatLon

from skdecide.hub.domain\
        .flight_planning\
        .aircraft_performance\
        .bean.aircraft_state \
    import AircraftState

from skdecide.hub.domain\
        .flight_planning\
        .aircraft_performance\
        .performance.performance_model_enum \
    import PerformanceModelEnum

from skdecide.hub.domain\
        .flight_planning\
        .aircraft_performance\
        .performance.phase_enum \
    import PhaseEnum

from skdecide.hub.domain\
        .flight_planning\
        .aircraft_performance\
        .performance.rating_enum \
    import RatingEnum

from skdecide.hub.domain\
        .flight_planning\
        .domain \
    import FlightPlanningDomain, \
           WeatherDate

from skdecide.hub.domain\
        .flight_planning\
        .flightplanning_utils \
    import plot_network_adapted

from skdecide.hub.solver.astar import Astar

The heuristic parameter can be either "time", "distance", "lazy_fuel", "lazy_time", or None. If nothing is passed, A* will use a Dijkstra-like search algorithm.

origin        = "LFPG"
destination   = "LFBO"
aircraft      = "A320"
weather_date  = WeatherDate(day=1, month=5, year=2026)
heuristic     = "lazy_fuel"
cost_function = "fuel"

acState = AircraftState(
    model_type="A320",
    performance_model_type=PerformanceModelEnum.OPENAP,
    gw_kg=80_000,
    zp_ft=10_000,
    mach=cas2mach(250 * kts, h=10_000 * ft),
    phase=PhaseEnum.CLIMB,
    rating_level=RatingEnum.MCL,
    cg=0.3)

domain_factory = lambda: FlightPlanningDomain(
    aircraft_state=acState,
    mach_cruise=0.78,
    mach_climb=0.7,
    mach_descent=0.65,
    nb_forward_points=20,
    nb_lateral_points=10,
    nb_climb_descent_steps=5,
    flight_levels_ft=list(np.arange(30_000, 38_000 + 2_000, 2_000)),
    graph_width="medium",
    origin=LatLon(43.629444, 1.363056),
    destination="EDDB",
    objective=cost_function,
    heuristic_name=heuristic,
    weather_date=weather_date)

domain = domain_factory()

When the above runs, if weather data hasn't been fetched from NOAA and if the date of the flight is within the past six months, GRB2 files will be downloaded.

$ du -hs ~/skdecide_data/weather/grib/nowcast/*/*.grb2
144M    /home/mark/skdecide_data/weather/grib/nowcast/20260501/gfs_4_20260501_0000_000.grb2
144M    /home/mark/skdecide_data/weather/grib/nowcast/20260501/gfs_4_20260501_0600_000.grb2
143M    /home/mark/skdecide_data/weather/grib/nowcast/20260501/gfs_4_20260501_1200_000.grb2
143M    /home/mark/skdecide_data/weather/grib/nowcast/20260501/gfs_4_20260501_1800_000.grb2

Each file has data covering the entire planet. These are the contents of gfs_4_20260501_1800_000.grb2 rendered on a globe in QGIS.

Flight Planning

This is the solver's altitude and geographical search space.

plot_network_adapted(
    graph=domain.network,
    p0=LatLon(43.629444, 1.363056),
    p1=LatLon(
        airport("EDDB")["lat"],
        airport("EDDB")["lon"],
        airport("EDDB")["alt"] * ft))

Flight Planning

This is the optimal flight path according to the solver.

solver = Astar(
            domain_factory=domain_factory,
            heuristic=lambda d, s: d.heuristic(s),
            parallel=False)

solver.solve()
A* finished to solve from state ... in 0.28 seconds
domain.custom_rollout(solver=solver, make_img=True)

Flight Planning

Goal reached after 19 steps!
({'time': 7666.281474928903, 'fuel': 5855.093906205222}, None)

I'll format each of the flight plan's steps so they're easier to read.

domain.observation.trajectory.to_csv('TLS-BER.csv', index=None)
SELECT   phase: UPPER(phase),
         time_: ts::INT,
         alt:   alt::INT,
         mass:  mass::INT,
         mach:  ROUND(mach, 2),
         cas:   cas::INT,
         fuel:  fuel::INT,
         geom:  ST_POINT(lon, lat)
FROM     'TLS-BER.csv'
ORDER BY ts;
┌─────────┬───────┬───────┬───────┬────────┬───────┬───────┬────────────────────────────────────────────────┐
│  phase  │ time_ │  alt  │ mass  │  mach  │  cas  │ fuel  │                      geom                      │
│ varchar │ int32 │ int32 │ int32 │ double │ int32 │ int32 │                    geometry                    │
├─────────┼───────┼───────┼───────┼────────┼───────┼───────┼────────────────────────────────────────────────┤
│ CLIMB   │ 28800 │     0 │ 80000 │   0.45 │   154 │     0 │ POINT (1.363056 43.629444)                     │
│ CLIMB   │ 29183 │ 12000 │ 79402 │    0.7 │   194 │   598 │ POINT (1.3614644301412264 44.431571122861556)  │
│ CLIMB   │ 29767 │ 18000 │ 78717 │    0.7 │   173 │   685 │ POINT (0.8028803270337778 45.54329961845038)   │
│ CLIMB   │ 30367 │ 24000 │ 78104 │    0.7 │   154 │     2 │ POINT (0.22308186850312028 46.64860136322122)  │
│ CLIMB   │ 30369 │ 24000 │ 78102 │    0.7 │   154 │     2 │ POINT (0.2213600223765711 46.65181397470445)   │
│ CLIMB   │ 30691 │ 30000 │ 77808 │    0.7 │   135 │   294 │ POINT (0.781910701674247 47.146798229437145)   │
│ CRUISE  │ 31222 │ 30000 │ 77331 │   0.78 │   152 │   477 │ POINT (0.18057649919536045 48.254448220756515) │
│ CRUISE  │ 31515 │ 30000 │ 77070 │   0.78 │   152 │   262 │ POINT (0.7571242215277763 48.74964861278412)   │
│ CRUISE  │ 31805 │ 30000 │ 76812 │   0.78 │   152 │   258 │ POINT (1.3446668642885302 49.24219441171162)   │
│ CRUISE  │ 32093 │ 30000 │ 76555 │   0.78 │   152 │   256 │ POINT (1.943586016571517 49.732000774167815)   │
│ CRUISE  │ 32382 │ 30000 │ 76298 │   0.78 │   152 │   257 │ POINT (2.554285524033502 50.218985517421046)   │
│ CRUISE  │ 32672 │ 30000 │ 76041 │   0.78 │   152 │   257 │ POINT (3.1771982015706532 50.70307279893911)   │
│ CRUISE  │ 32961 │ 30000 │ 75786 │   0.78 │   152 │   256 │ POINT (3.812797488711077 51.18419969012497)    │
│ CRUISE  │ 33252 │ 30000 │ 75529 │   0.78 │   152 │   257 │ POINT (4.461619033936127 51.66232853895605)    │
│ CRUISE  │ 33547 │ 32000 │ 75270 │   0.78 │   146 │   259 │ POINT (5.1243038676232 52.13747186609129)      │
│ CRUISE  │ 34103 │ 30000 │ 74794 │   0.78 │   152 │   476 │ POINT (7.006628090474678 51.93831668841654)    │
│ DESCENT │ 34458 │ 24031 │ 74503 │   0.65 │   142 │   290 │ POINT (7.7011825929472675 52.40044729715865)   │
│ DESCENT │ 35058 │ 18063 │ 73995 │   0.65 │   160 │    51 │ POINT (9.423199233939213 52.1821111144618)     │
│ DESCENT │ 35114 │ 18063 │ 73944 │   0.65 │   160 │    51 │ POINT (9.580613666598857 52.160789987133924)   │
│ DESCENT │ 35714 │ 12094 │ 73388 │   0.65 │   179 │    13 │ POINT (11.398384342445423 51.895274115132494)  │
│ DESCENT │ 35727 │ 12094 │ 73375 │   0.65 │   179 │    13 │ POINT (11.435080901553494 51.88959204464927)   │
│ DESCENT │ 36063 │  6126 │ 73023 │   0.65 │   199 │   352 │ POINT (12.17769156482302 52.32776873134525)    │
│ DESCENT │ 36466 │    48 │ 72534 │   0.65 │   221 │   488 │ POINT (13.48503 52.36769)                      │
└─────────┴───────┴───────┴───────┴────────┴───────┴───────┴────────────────────────────────────────────────┘

I'll export the flight plan to Parquet and render it on top of the ground-level wind data in QGIS.

COPY (
    SELECT   * EXCLUDE(lon, lat),
             geometry: ST_POINT(lon, lat)
    FROM     'TLS-BER.csv'
    ORDER BY ts
) TO 'TLS-BER.parquet' (
      FORMAT 'PARQUET',
      CODEC  'ZSTD',
      COMPRESSION_LEVEL 22,
      ROW_GROUP_SIZE 15000);

Flight Planning

Toulouse to Warsaw

Below, I'll find an optimal flight path from Toulouse-Blagnac Airport (LFBO / TLS) to Warsaw Chopin Airport (EPWA / WAW).

The initial target altitude will be much higher than in the previous example. The result is a flight that is able to take a much more direct route.

acState = AircraftState(
    model_type="A320",
    performance_model_type=PerformanceModelEnum.OPENAP,
    gw_kg=80_000,
    zp_ft=18000.0,
    mach=cas2mach(250 * kts, h=10_000 * ft),
    phase=PhaseEnum.CLIMB,
    rating_level=RatingEnum.MCL,
    cg=0.3,
    x_graph=5,
    y_graph=5,
    z_graph=10)

domain_factory = lambda: FlightPlanningDomain(
    aircraft_state=acState,
    mach_cruise=0.78,
    mach_climb=0.7,
    mach_descent=0.65,
    nb_forward_points=20,
    nb_lateral_points=10,
    nb_climb_descent_steps=5,
    flight_levels_ft=list(np.arange(30_000, 38_000 + 2_000, 2_000)),
    graph_width="medium",
    origin=LatLon(43.629444, 1.363056),
    destination="EPWA",
    objective=cost_function,
    heuristic_name=heuristic,
    weather_date=weather_date)

domain = domain_factory()

solver = Astar(
            domain_factory=domain_factory,
            heuristic=lambda d, s: d.heuristic(s),
            parallel=False)

solver.solve()
A* finished to solve from state ... in 29.45 seconds.
domain.custom_rollout(solver=solver, make_img=True)

Flight Planning

Goal reached after 14 steps!
({'time': 6153.660431613251, 'fuel': 5600.171145693044}, None)

Warsaw is 500 KM further away from Toulouse than Berlin. But the faster climb to cruising altitude under the given wind conditions meant the aircraft could take a more direct route. It made it to Warsaw almost 45 minutes faster and only needed 76% of the fuel that the Berlin flight needed.

These are the steps in the above flight plan.

domain.observation.trajectory.to_csv('TLS-WAW.csv', index=None)
SELECT   phase: UPPER(phase),
         time_: ts::INT,
         alt:   alt::INT,
         mass:  mass::INT,
         mach:  ROUND(mach, 2),
         cas:   cas::INT,
         fuel:  fuel::INT,
         geom:  ST_POINT(lon, lat)
FROM     'TLS-WAW.csv'
ORDER BY ts;
┌─────────┬───────┬───────┬───────┬────────┬───────┬───────┬───────────────────────────────────────────────┐
│  phase  │ time_ │  alt  │ mass  │  mach  │  cas  │ fuel  │                     geom                      │
│ varchar │ int32 │ int32 │ int32 │ double │ int32 │ int32 │                   geometry                    │
├─────────┼───────┼───────┼───────┼────────┼───────┼───────┼───────────────────────────────────────────────┤
│ CLIMB   │ 28800 │ 30000 │ 80000 │   0.45 │    85 │     0 │ POINT (6.321780309765473 45.78957852956533)   │
│ CRUISE  │ 29196 │ 32000 │ 79639 │   0.78 │   146 │   361 │ POINT (7.274006698354081 46.275553205520794)  │
│ CRUISE  │ 29604 │ 34000 │ 79276 │   0.78 │   139 │   363 │ POINT (8.243146568075773 46.75332543362598)   │
│ CRUISE  │ 30018 │ 36000 │ 78915 │   0.78 │   133 │   361 │ POINT (9.229481862337197 47.22261290149568)   │
│ CRUISE  │ 30436 │ 38000 │ 78555 │   0.78 │   127 │   360 │ POINT (10.23327610333648 47.68312559281413)   │
│ CRUISE  │ 30861 │ 38000 │ 78191 │   0.78 │   127 │   365 │ POINT (11.25477083484032 48.13456566890824)   │
│ CRUISE  │ 31297 │ 36000 │ 77819 │   0.78 │   133 │   371 │ POINT (12.29418112454121 48.57662718213212)   │
│ CRUISE  │ 31732 │ 34000 │ 77449 │   0.78 │   139 │   370 │ POINT (13.351689351409924 49.00899540355703)  │
│ CRUISE  │ 32161 │ 32000 │ 77082 │   0.78 │   146 │   367 │ POINT (14.427435465032865 49.431345252175966) │
│ CRUISE  │ 32589 │ 30000 │ 76710 │   0.78 │   152 │   372 │ POINT (15.521498988740307 49.843337488922174) │
│ DESCENT │ 33101 │ 24066 │ 76280 │   0.65 │   142 │   429 │ POINT (16.633858546975723 50.2446086742223)   │
│ DESCENT │ 33589 │ 18131 │ 75861 │   0.65 │   160 │   420 │ POINT (17.764276668345886 50.63474030548783)  │
│ DESCENT │ 34046 │ 12197 │ 75433 │   0.65 │   179 │   428 │ POINT (18.91184798374883 51.01313503252613)   │
│ DESCENT │ 34472 │  6262 │ 74984 │   0.65 │   199 │   449 │ POINT (20.071843186874247 51.378167839584854) │
│ DESCENT │ 34954 │   100 │ 74400 │   0.65 │   221 │   584 │ POINT (20.94663 52.17147)                     │
└─────────┴───────┴───────┴───────┴────────┴───────┴───────┴───────────────────────────────────────────────┘

Airbus A320 vs Boeing 737

OpenTop can be paired with OpenAP and used to figure out flight trajectories between two airports.

Its optimiser requires a grid cost file. I'll first download an example 142 MB NetCDF file provided by the project.

$ wget https://opendap.4tu.nl/thredds/fileServer/data2/djht/bea8a3fe-e34c-4598-9f94-c5a5c63348e5/1/contrail_original.nc

The cost file can be either in Casadi or Parquet format. I worked from an example in its documentation, which produced a 246 KB Casadi file.

import openap
import pandas as pd
from scipy.ndimage import gaussian_filter
from opentop.tools import cached_interpolant_from_dataframe
import xarray as xr

ds = xr.open_dataset('contrail_original.nc')\
       .sel(time='2015-12-18')

level_pressure = [
    0.0000,
    10.0000,
    30.0000,
    50.0000,
    70.0000,
    90.0787,
    110.6606,
    132.3968,
    155.7909,
    181.1544,
    208.6494,
    238.3258,
    270.1530,
    304.0465,
    339.8891,
    377.5467,
    416.8789,
    457.7442,
    500.0000,
    543.4970,
    588.0685,
    633.5144,
    679.5799,
    725.9285,
    772.1102,
    817.5241,
    861.3757,
    902.6287,
    939.9520,
    971.6610,
    995.6532,
    1009.3396]

df = (
    ds.to_dataframe()
    .reset_index()
    .assign(lev=lambda x: x.lev.astype(int))
    .merge(
        pd.DataFrame(level_pressure, columns=["hPa"]).reset_index(names="lev"),
        on="lev",
    )
    .assign(height=lambda x: openap.aero.h_isa(x.hPa * 100).round(-2))
    .assign(longitude=lambda x: ((x.lon + 180) % 360 - 180))
    .query("height<15000"))

df_cost_world = df.rename(
    columns={
        "lat": "latitude",
        "atr20_contrail": "cost",
    }
)[["time",
   "latitude",
   "longitude",
   "hPa",
   "height",
   "cost"]]


df_cost = df_cost_world.query(
    "-20<longitude<40 and 30<latitude<70 and time.dt.hour==12"
).sort_values(["height", "latitude", "longitude"])

cost = df_cost.cost.values.reshape(
    df_cost.height.nunique(),
    df_cost.latitude.nunique(),
    df_cost.longitude.nunique())

cost_ = gaussian_filter(cost, sigma=1, mode="nearest")
df_cost = df_cost.assign(cost=cost_.flatten())

interpolant = cached_interpolant_from_dataframe(
                df_cost,
                "contrail.casadi",
                shape="bspline")

These are the first and last few bytes of its contents.

$ hexdump -C contrail.casadi | head
00000000  6a 68 70 6e 6e 61 67 69  69 65 61 68 61 61 61 61  |jhpnnagiieahaaaa|
00000010  64 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |daaaaaaaaaaaaaaa|
00000020  61 61 66 61 65 67 61 61  6c 61 61 61 61 61 61 61  |aafaegaalaaaaaaa|
00000030  6a 65 6f 67 65 68 66 67  63 68 61 68 70 67 6d 67  |jeogehfgchahpgmg|
00000040  62 67 6f 67 65 68 68 61  61 61 61 61 61 61 63 67  |bgogehhaaaaaaacg|
00000050  64 68 61 68 6d 67 6a 67  6f 67 66 67 63 61 61 61  |dhahmgjgogfgcaaa|
00000060  61 61 61 61 6a 61 61 61  61 61 61 61 68 67 63 68  |aaaajaaaaaaahgch|
00000070  6a 67 65 67 70 66 64 67  70 67 64 68 65 68 61 61  |jgegpfdgpgdhehaa|
00000080  61 61 61 61 61 61 62 61  69 61 61 61 61 61 61 61  |aaaaaabaiaaaaaaa|
00000090  62 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |baaaaaaaaaaaaaaa|
$ hexdump -C contrail.casadi | tail
0003d620  61 61 61 61 61 61 64 62  61 61 61 61 61 61 61 61  |aaaaaadbaaaaaaaa|
0003d630  61 61 61 61 61 61 67 62  61 61 61 61 61 61 61 61  |aaaaaagbaaaaaaaa|
0003d640  61 61 61 61 61 61 68 62  61 61 61 61 61 61 61 61  |aaaaaahbaaaaaaaa|
0003d650  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
0003d660  61 61 61 61 61 61 62 61  61 61 61 61 61 61 61 61  |aaaaaabaaaaaaaaa|
0003d670  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
0003d680  61 61 61 61 61 61 62 61  61 61 62 61 61 61 61 61  |aaaaaabaaabaaaaa|
0003d690  61 61 61 61 61 61 61 61  61 61 63 68 67 61 61 61  |aaaaaaaaaachgaaa|
0003d6a0  61 61 61 61 61 61 61 61  61 61 61 61              |aaaaaaaaaaaa|
0003d6ac

I noticed the contents are repetitive and compress well.

$ gzip -9 < contrail.casadi | wc -c

I'll get the metrics of an optimal flight between Amsterdam's Schiphol (EHAM / AMS) and Frankfurt (EDDF / FRA) on an Airbus A320.

$ opentop optimize \
    EHAM EDDF \
    -a A320 \
    --phase all \
    --obj "0.3*fuel+0.7*grid" \
    --grid contrail.casadi
aircraft:  A320
route:     EHAM → EDDF
phase:     all
objective: 0.3*fuel+0.7*grid
m0:        0.85
max_iter:  1500
grid file: contrail.casadi

success:       True
return_status: Solve_Succeeded
iter_count:    179
wall time:     12.2 s
objective:     4.8768e+02
fuel burn:     1625.6 kg
max altitude:  19891 ft
flight time:   35.8 min

I'll then do the same using a Boeing 737.

$ opentop optimize \
    EHAM EDDF \
    -a B737 \
    --phase all \
    --obj "0.3*fuel+0.7*grid" \
    --grid contrail.casadi
aircraft:  B737
route:     EHAM → EDDF
phase:     all
objective: 0.3*fuel+0.7*grid
m0:        0.85
max_iter:  1500
grid file: contrail.casadi

success:       True
return_status: Solve_Succeeded
iter_count:    141
wall time:     9.9 s
objective:     4.8662e+02
fuel burn:     1622.1 kg
max altitude:  21968 ft
flight time:   39.2 min

Thank you for taking the time to read this post. I offer both consulting and hands-on development services to clients in North America and Europe. If you'd like to discuss how my offerings can help your business please contact me via LinkedIn .

Unsizing unsized values

Lobsters
hackmd.io
2026-09-15 19:03:40
Comments...
Original Article
# "Go doing things Rust can't" or unsizing unsized values Consider the code in [the go tour](https://go.dev/tour/methods/16), showing `interface{}`: [^2] ```go package main import "fmt" func doit(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Twice %v is %v\n", v, v*2) case string: fmt.Printf("%q is %v bytes long\n", v, len(v)) default: fmt.Printf("I don't know about type %T!\n", v) } } func main() { doit(21) doit("hello") doit(true) } ``` We can interrogate an `interface {}` value about its type, then downcast to the concrete type and do something with the value. Naïvely, you would think that we can do the same thing in Rust with the `Any` trait, but there are subtle pitfalls. Let's start thinking about what `doit` should look like. We want to consume a value, but to produce a single function we should not use generic arguments, but some kind of `dyn`-object. This is important in some areas, where generic arguments are simply not acceptable; For example to allow a trait to be `dyn`-compatible, or if `doit` is supposed to be an `extern` function, or to pass the function around as a `fn`/`Fn` object. ```rust= use std::any::Any; fn doit(i: &dyn Any) { if let Some(v) = i.downcast_ref::<i32>() { println!("Twice {v} is {}", v*2); } else if let Some(s) = i.downcast_ref::<String>() { println!("String: {s} is {} bytes long", s.len()); } else if let Some(s) = i.downcast_ref::<&'static str>() { println!("&'static str: {s} is {} bytes long", s.len()); } else { println!("I don't know about type {:?}", i.type_id()); } } ``` Specifically, the second call above, `doit("hello")` turns out to be problematic. ```rust=+ pub fn main() { doit(&21); doit(&"hello".to_string()); doit(&true); } ``` We allocate and copy the string and that is really annoying! We could alternatively downcast to `&'static str` and add a case. ```rust if let Some(s) = i.downcast_ref::<&'static str>() { println!("{s} is {} bytes long", s.len()); } // ... doit(&"hello"); ``` But notice the `'static` lifetime - which we deviously could omit and write as simply `&str` - that forces us to either leak the string or have it be a constant for the program duration. But accepting a string as `&str` where the borrow lives for just some duration is not possible. ```rust fn doit_for_str(s: &str) { // error[E0277]: the size for values of type `str` cannot be known at compilation time doit(s); // ^ doesn't have a size known at compile-time // help: consider borrowing the value, since `&&str` can be coerced into `&(dyn Any + 'static)` // // but this suggestion does not work! // // error: lifetime may not live long enough doit(&s); // ^^ coercion requires that `'1` must outlive `'static` } ``` # Reflecting So what happens? When implementing the coercion the compiler has to synthesize the pointer metadata for `dyn Any`, which happens to be a `&'static VTable` where `VTable` deserves a second look shortly. Since this vtable lives in `.rodata`, the compiler has to derive it from the type we coerce from - `str` in this case - at compile time and can not use the value. In other words, you can only coerce `Sized` values to a `dyn`-object in current Rust. Diving into compiler internals for a moment - that we will rely on later - the vtable contains four items, layed out as if in a `#[repr(C)]` struct in this order: - the drop glue function, roughly of the signature `fn(*const ())`. Since dropping a `str` is trivial, this could be a no-op here, - the size of the value. Uh oh, this one is problematic. Since the string could have any size (length) at runtime, this one can not be derived at compile time, - the align of the value. This is again unproblematic and can be derived from the type - align is `1`, - the `type_id` function, roughly again of signature `fn(*const ()) -> TypeId`. I might have smuggled it past you, dear reader, in the drop function, but these function pointers do not receive the original pointer metadata! When the compiler synthesizes the call `<dyn Any as Any>::type_id(i)`, it will use the pointer metadata to get this function pointer, then strip off the metadata and pass only the thin pointer. Since we do not need to inspect the `str` to derive the `TypeId`, we dodge complications again. In general though, the original pointer metadata (i.e. the string length) is lost. [^1] # Hacking We will have to do the vtable construction "by hand". Since we *have* to construct it at runtime because we need it to store the actual size of the string, we need a place to store this vtable on stack. ```rust=+ use std::any::TypeId; use std::mem::MaybeUninit; #[repr(C)] struct AnyVTable { drop: fn(*const()), size: usize, align: usize, type_id: fn(*const()) -> TypeId, } pub struct Host { vtable: MaybeUninit<AnyVTable>, } impl Host { pub fn new() -> Self { Host { vtable: MaybeUninit::uninit() } } pub fn borrow<'a>(&'a mut self, s: &'a str) -> &'a dyn Any { todo!() } } ``` Our goal will be implementing the `borrow` function, which already contains a small lie. The returned reference can only be used for `'a` which ensures that the vtable which we store in the `Host` is also still borrowed. But with a bit of nightly magic, we can extract the pointer metadata (with [`std::ptr::metadata()`](https://doc.rust-lang.org/std/ptr/fn.metadata.html)) which forgets about the lifetime and can be used past the borrow. I would propose to add a lifetime for the vtable after the `dyn` keyword that would fix this. ```rust impl Host { pub fn borrow<'a, 's>(&'s mut self, s: &'a str) -> &'a dyn<'s> Any { } // ^^^^ // `dyn` could default like other lifetime to dyn<'static>, which would // have the same meaning as now, in some contexts and introduce a fresh // lifetime in other contexts (function signatures). I'm not certain this // default is necessarily what we want or need. // In this case, the above would omit the 's lifetime by the way lifetimes // in return types are inferred to default to the lifetime of `&mut self`. // Perhaps for compat, the default should always be 'static and the explicit // form above is better. } ``` But I digress, suspend your disbelief a bit. Let's try this ```rust=+ impl Host { pub fn borrow<'a>(&'a mut self, s: &'a str) -> &'a dyn Any { let vtable = self.vtable.write(AnyVTable { drop: |_| (), // str has trivial drop size: s.len(), align: 1, type_id: |_| TypeId::of::<str>(), }); #[repr(C)] struct FatPtr<'a> { thin: *const (), vtable: &'a AnyVTable, } let rep = FatPtr { thin: s.as_ptr() as *const (), vtable, }; unsafe { std::mem::transmute(rep) } } } fn doit_str(s: &str) { let mut host = Host::new(); doit(host.borrow(s)); } ``` It runs and produces the following output ```! I don't know about type TypeId(0xb7381ee5f3fdfc9d7fa709f37e151622) ``` Ah yes, because the type id is that of `str` now, none of the cases above work. We need a way to downcast again. If we try the `downcast_ref` method, we come across another obstacle, mainly `downcast_ref<T>` requires `T: Sized`. This is because of a problem we discovered earlier. Since the call through the vtable throws away the pointer metadata, in general there is no way to recover it and the downcast can only work for types that have no pointer metadata, i.e. that are `Sized`. But alas, for `str` in particular we can actually recover all we need, specifically the length of the string from the metadata we are given. ```rust=+ fn downcast_dyn_str(s: &dyn Any) -> Option<&str> { if s.type_id() != TypeId::of::<str>() { return None; } // On nightly we can use std::str::from_raw_parts Some(unsafe { std::str::from_utf8_unchecked( std::slice::from_raw_parts( std::ptr::from_ref(s).cast(), size_of_val(s), ) ) }) } // Add the case to doit: if let Some(s) = downcast_dyn_str(i) { println!("str: {s} is {} bytes long", s.len()); } ``` With that, finally we can witness ```rust doit_str("hello"); // str: hello is 5 bytes long ``` # Miri If we run [the code](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=0facbb927c95bebb75a16af9d8b5d200) under miri for verification, it will claim ```! error: Undefined Behavior: constructing invalid value of type &dyn std::any::Any: encountered 0x39858[alloc4896]<19017>, but expected a vtable pointer ``` which makes sense, since we definitely forged a vtable pointer that didn't get blessed and is unholy. This is expected. What I want to claim though is that the compiler could in theory do the transformation for us, with one small caveat. We need to revisit the pointer metadata again. If the functions in the vtable would receive the full fat pointer, we could for example stuff the "previous" metadata in there. For our use case, suppose `Host` didn't simply contain a `AnyVTable` but a struct like ```rust struct MetaChained { prev: usize, // str length in our case vtable: AnyVTable, } ``` Now, a fat pointer would still contain a pointer to the `AnyVTable`, i.e. the second field of the struct, but with some quick pointer arithmetic we could recover the previous pointer metadata. Vtables that are instantiated at compile time could strip the metadata as they do right now, but instead of doing that before the call into the vtable function, they would do so inside. This would allow `downcast_ref::<T: ?Sized>()`, too. # Conclusion The sense of all of this is for you to judge. I have [a repo with further experiments](https://github.com/WorldSEnder/unsize-the-unsize), which generalizes the above for a `Host` to coerce any `T` to `U` under an assumption equivalent to a generalized `T: CoerceUnsized<U>`. In particular you can coerce `Box<str>` to `Box<dyn Any>` just as you would coerce references. Bar a language change that allows annotations with an additional lifetime such as `dyn<'a> Any` this is declared `unsafe` to prevent accidental misuse though. The experiment can not support other coercions such as `[T]` to `dyn Any` without fat pointers getting passed through to the vtable functions. For now, it allows `T: Copy` where the drop is trivial and we do not need to recover the slice metadata. The same restriction of losing the metadata prevents extending to other less trivial traits, but it is the only thing preventing us from recovering the correct receiver value. I do not know the right people to land this. I do hope though that at some point in the future I can at least coerce `&[T]` to `&dyn Any` and `downcast_ref` back again natively. [^1]: for interested people, the metadata stripping is the raison d'être of the compiler synthesized [`DispatchFromDyn`](https://doc.rust-lang.org/std/ops/trait.DispatchFromDyn.html) trait. [^2]: `do` has been renamed to `doit` because the former is a reserved keyword.

Recreating Voodoo Graphics and a Late-1990s Gaming PC on an FPGA

Hacker News
nand2mario.github.io
2026-09-15 18:50:55
Comments...
Original Article

I've spent the last month adding features and improving performance in z486_MiSTer , mostly working through games from the first half of the 1990s. Looking a few years ahead brought me to another change I wanted to explore: the arrival of 3D graphics cards.

The first one that left a strong impression on me was the Voodoo. The game was Need for Speed II SE . Smooth textures, fog, and the speed of the whole thing made it feel like a new generation of PC gaming. Could I recreate that on an FPGA now that the z486 CPU exists?

The result of this detour is zSST , a SystemVerilog implementation of the 3dfx Voodoo Graphics, or SST-1. Combined with my z486 CPU and the surrounding PC hardware, it forms z486 XL : a DOS PC with Voodoo graphics running in the programmable logic of a Xilinx KV260 board. Tomb Raider now runs with its original 3dfx renderer.

zSST implements most of the central Voodoo features: prepared triangles, texture filtering and mipmapping, depth and alpha tests, fog, blending, dithering, framebuffer access, and buffer swaps. It supports both the fixed-point and floating-point setup interfaces. Hardware game testing is still concentrated on Tomb Raider; broader compatibility and later Voodoo generations are work for another day.

The CPU and renderer run at 100 MHz on the KV260 . That board has enough logic, DSP blocks, on-chip memory, and DDR bandwidth for the combined design. The DE10-Nano does not have room for this graphics addition. The KV260 uses its onboard DDR; there is no external SDRAM module to add.

Starting from the programming model

Fortunately, there is plenty of material to work from. 3dfx released the Glide source in 1999, before NVIDIA acquired its core graphics assets in December 2000. The surviving Glide source and SST-1 specification explain how software prepares triangles, configures the pixel pipeline, and manages textures and framebuffers.

The specification is a behavioral target, rather than a circuit diagram. It tells what should happen when software writes a register, but leaves many implementation choices open. 86Box provides useful references for complicated rendering behavior. The earlier MAME Voodoo work is another part of this preservation history. SpinalVoodoo supplied particularly useful Glide traces and reference screenshots for testing.

From triangles to 3D, one pixel per clock

Voodoo Graphics turns triangles into pixels, leaving much of the 3D work to the host CPU. Its command interface is surprisingly compact: five main command registers drive the accelerator.

Register Action
triangleCMD Start rendering a prepared triangle.
ftriangleCMD Start a triangle through the floating-point setup interface.
nopCMD Flush the pipeline; optionally reset the statistics counters.
fastfillCMD Clear a clipped rectangle of color and/or depth data.
swapbufferCMD Switch the displayed buffer, immediately or synchronized to vertical retrace.

Both triangle commands launch the same rendering pipeline. Other registers hold coordinates, gradients, and render state, while memory-mapped regions provide texture uploads and direct framebuffer access. The main drawing primitive is simply a prepared triangle.

For game developers, Glide presents a friendlier interface:

void grDrawTriangle(const GrVertex *a, const GrVertex *b, const GrVertex *c);

Before this call, the host CPU transforms the 3D geometry, computes vertex lighting, clips it, and projects it onto the screen. Glide then prepares the screen-space triangle and its parameter gradients—the increments used to interpolate values across its surface—and writes the triangle command to start rendering. Unlike later GPUs such as the GeForce 256, SST-1 has no hardware transform-and-lighting engine.

That still leaves plenty of work for the accelerator. The rasterizer finds which pixel centers lie inside the triangle and interpolates their color, depth, and texture coordinates. The texture unit fetches and filters texels; the framebuffer unit combines colors, applies visibility tests and fog, blends with the existing image, and writes the result.

A triangle rasterizer feeding a pipeline with several different pixels in flight simultaneously
A pixel takes several stages to finish, while new pixels can keep entering.

The original card divides this work between two ASICs: the FBI , or Frame Buffer Interface, and TREX , the texture mapping unit, usually called the TMU. At a 50 MHz graphics clock, the advertised peak is one textured, depth-tested output pixel per clock: 50 million pixels per second.

One pixel per clock does not mean that a pixel finishes in one clock. It means that different stages can work on different pixels simultaneously: while one pixel is being textured, an earlier one can be blended and another written out. Once the pipeline is full, it can ideally accept and finish a pixel every clock, provided memory keeps up.

That is the appeal of a fixed-function pipeline. A software renderer executes many instructions for each pixel; dedicated hardware overlaps that work across a steady stream of pixels. Voodoo brought richly textured 3D games to life at a fluid 30 FPS or more—a big part of what made it so popular.

Building the pixel pipeline

Compared with an x86 CPU, the arithmetic path is pleasantly regular. Let's follow a pixel from its interpolated parameters through texturing and color operations to the framebuffer, starting with how the numbers are represented.

Fixed point behind a floating-point interface

Floating-point arithmetic is central to modern GPU programming. SST-1 sits at an interesting transition: software can submit floating-point values, but the rendering machinery largely operates in fixed point—integers with an implicit scale factor.

Setup value Fixed-point register format
Screen X and Y 12.4
Red, green, blue, alpha 12.12
Depth Z 20.12
Texture S/W and T/W 14.18
Reciprocal W 2.30

Here 12.4 means twelve bits before the binary point, including the sign, and four fractional bits. A screen coordinate of 10.5 is therefore stored as the integer 168: multiply by 16 to encode it, divide by 16 to recover the value. Those fractional bits let the rasterizer handle vertices between pixel centers.

The fvertex , fstart , and floating-point gradient registers accept IEEE single-precision values. SST-1 converts them into its internal fixed-point representation, and zSST follows that contract. Once the triangle is prepared, advancing along a scanline mostly means adding a precomputed increment to each interpolated parameter. Much of the pixel-by-pixel work becomes simple integer addition.

Four texels for one pixel

For perspective-correct texturing, the TMU interpolates S/W, T/W, and 1/W, then divides the first two by the third to recover texture coordinates. This keeps a floor or wall texture in perspective as the surface recedes. The TMU also selects a mip level : a smaller version of the texture for pixels that cover a larger area of its surface. This reduces aliasing and shimmering in the distance.

Bilinear filtering then combines four neighboring texels—the pixels of the texture—around the sample position. First blend the top pair horizontally, then the bottom pair, and finally blend vertically between those two results. The fractional position determines the weights, producing a smooth transition between texel colors instead of an abrupt jump from one to the next.

Four neighboring texel centers and two horizontal interpolations followed by a vertical interpolation
Four texture reads produce one filtered sample.

In zSST, a four-stage front end pipelines the perspective and level-of-detail calculations. Address generation and cache lookup supply the texels, and two registered decode stages turn their stored formats into colors for filtering and texture combining. Palette-based and NCC-encoded textures need different decoding rules, but ultimately feed the same pixel stream.

Color, tests, fog, and blending

Once texture and framebuffer data are available, zSST's FBI pixel path uses six registered stages:

Stage Main work
F0 Select sources, check chroma key, prepare Z/W depth values.
F1 Apply the color and alpha combine functions.
F2a Test alpha/depth and look up the fog factor.
F2b Apply fog.
F3 Reconstruct destination color and perform alpha blending.
F4 Convert to framebuffer precision, dither, and apply write masks.

These stage boundaries are chosen to meet the FPGA's clock target. The SST-1 specification describes the operations but does not reveal the original ASIC's exact pipeline registers. Splitting fog lookup from fog application, for example, keeps a long arithmetic path out of a single clock while retaining the ability to accept one pixel per clock.

The result is written to the back buffer. A retrace-synchronized buffer swap then displays the finished image without switching buffers halfway through scanout. The original Voodoo was a 3D-only add-on, passing the ordinary VGA card's output through when inactive. z486 XL makes the analogous selection between the PC's VGA output and zSST's display output inside the FPGA system.

The hard part: feeding it from memory

The zSST pixel pipeline proved relatively straightforward to implement, at least compared with z486's CPU pipelines. Keeping it fed turned out to be much harder. A bilinear sample needs four texels from separate addresses. A depth-tested, blended pixel also needs the existing depth and color, followed by writes of the new values. Performing those accesses one at a time quickly destroys throughput. I ended up spending more time designing, tuning, and debugging the memory system than the arithmetic pipeline.

How the original card supplied the pixels

Diamond Monster 3D board photograph, with TMU and texture RAM above FBI and framebuffer RAM Matching schematic: TMU and FBI each connect to four EDO chips over a 64-bit interface; arrows show texture work, filtered color, PCI commands, and display output through the RAMDAC

The board and its division of labor, with matching chip positions in both views. Connections are schematic; click either image to enlarge.
Photo: Konstantin Lanzet; crop: Pittigrilli, Wikimedia Commons . Photo license: GFDL 1.2 or later . Schematic: nand2mario.

The division of labor is visible on this Diamond Monster 3D. The upper 3dfx chip is the TMU , the lower one the FBI , each with four EDO RAM chips to its right. The upper group holds textures; the lower group holds color and depth/alpha buffers.

FBI and TMU each have a dedicated 64-bit memory path. On the texture side, four-way interleaving lets the banks read independent addresses, supplying the four neighbors for bilinear filtering in parallel. The specification (p. 13) promises the same throughput as point sampling, without storing duplicate texels.

But what if two neighboring texels land in the same chip? The trick is to distribute texels in a repeating two-dimensional pattern, rather than split the image into four large regions. Assign a bank to each combination of even or odd column and row, and the reason becomes clear:

An alternating A/B/C/D bank layout: an aligned 2×2 window and a boundary-crossing window both contain all four banks, allowing one independent read per bank
Moving the sample changes the banks' positions, not their number. Coordinates are (column, row); bank letters illustrate the principle, not physical SST-1 chip numbers.

Every 2×2 window contains A, B, C, and D—even the orange window crossing both horizontal and vertical block boundaries. Two consecutive columns have opposite parity, as do two consecutive rows. All four combinations occur exactly once, so each bank supplies one texel with no conflict.

Texture edges and small mip levels need a little more care. SST-1 uses power-of-two texture dimensions, so wrapping preserves the alternating pattern for dimensions of two or more. At clamped edges, or in mip levels only one texel wide or high, some samples reuse the same texel. The central insight remains: fast bilinear filtering depends on arranging memory so that the arithmetic receives all its inputs together.

The FBI applies a similar idea to color and depth/alpha memory. Its interleaved path supports a peak of one rendered pixel per clock, or two pixels per clock for clears. Working on adjacent pixels together spreads the read/write cost across a scanline. Fabien Sanglard's two-pixel explanation offers a useful reconstruction of this behavior, though the exact ASIC bank schedule is not documented in the programming guide.

At 50 MHz, each 64-bit path has a theoretical bandwidth of 400 MB/s: 800 MB/s in total, but reserved for different jobs. The TMU cannot borrow idle FBI bandwidth, or vice versa. These dedicated buses and carefully arranged banks remind me of the NES- and SNES-era designs I explored in projects such as SNESTang : getting the most out of memory means designing around exactly when and where each value is needed.

What changes on an FPGA SoC

Voodoo's memory layout explains how it kept the pipeline busy, but I cannot simply transplant that design to the KV260. The board has much more memory bandwidth, yet no dedicated EDO memory attached to either rendering unit. Instead, the FPGA accesses shared DDR through the Zynq processing system's AXI ports. Linux, the FPGA PC, and display scanout all compete for that memory. The goal is the same—keep the pixel pipeline fed—but the way to achieve it has to change.

Separate FBI and TMU EDO paths on SST-1 compared with shared DDR and three AXI clients on KV260
Original SST-1 has dedicated texture and framebuffer buses. zSST shares its renderer port and uses buffering to tolerate DDR latency.

Our KV260 measurements show why bandwidth alone is not enough. A 128-bit port at 100 MHz has a theoretical bandwidth of 1.6 GB/s. With one request outstanding, a 4 KiB read reaches 1,370 MiB/s, but a 64-byte read reaches only 189 MiB/s. The first data typically takes about 280 ns to arrive—roughly 28 clocks at 100 MHz—with occasional much longer waits.

Measured DDR bandwidth rises with burst length while time to first data stays near 280 nanoseconds
Single-outstanding board measurements. Long bursts amortize latency; small requests need concurrency.

A renderer that waits for each small read before issuing the next will spend most of its time idle. zSST needs enough independent work in flight to cover those waits.

Caches, replay, and a reorder buffer

Keeping the pipeline fed requires both fewer DDR accesses and less time spent waiting for them. The first step is caching . Nearby screen pixels often sample overlapping parts of a texture, so recently fetched texels can be reused from on-chip RAM. zSST's texture cache holds 8 KiB in 64-byte lines; each fetch also brings in neighboring texels that subsequent pixels are likely to need.

A cache miss still takes many clocks, but independent texture samples need not wait for it. zSST keeps up to eight cache-line fetches outstanding, using a replay queue to park samples with missing data and retry them when it arrives. Meanwhile, samples whose texels are already cached can proceed. Prefetching gets a head start on future reads.

Now a later cache hit can finish before an earlier miss. A 64-entry reorder buffer , or ROB, collects those results and releases them in their original order. The principle is familiar from CPUs: do useful work during a long wait, then restore order before passing the results downstream.

The framebuffer side uses separate 4 KiB color and depth/alpha read caches, while write combiners pack neighboring 16-bit updates into 128-bit requests. Here, ordering matters: blending or depth testing may need a value that an earlier pixel has changed but not yet written to DDR. Forwarding supplies the pending value directly. Framebuffer updates take effect in order, and state changes that require completed work wait for it to drain. Memory requests can overlap, but later pixels must still see the effects of earlier ones.

Texture and framebuffer requests proceeding in parallel, joining by tag, and retiring through the FBI pixel stages and write combiner
Caches reuse nearby data; queues overlap memory requests; ordered retirement preserves the result.

FBI and TMU share the renderer's 128-bit AXI port, HP2. The PC uses HP0 and display scanout uses HP3, keeping their request queues separate even though all three ultimately share DDR.

Evaluation results

I measure the renderer separately from the complete PC. The simulation benchmark sends commands through zSST's front end and exercises the TMU, FBI, shared arbiter, and a DDR timing model. Read data arrives after at least 26 clocks, with deterministic variation and occasional longer delays; writes are also rate-limited. The full-renderer tests allow 32 outstanding reads.

At 100 MHz, zSST reaches 78.5 million pixels per second (MPix/s) for textured triangles, and 72.8 MPix/s with depth testing and blending. Voodoo 1's published estimates at its native 50 MHz are 43 and 37 MPix/s for comparable feature sets. This is not an apples-to-apples benchmark: the triangle workloads differ, and the original estimates also include fog, mipmapping, and Gouraud shading. I do not have a Voodoo 1 to run the same test on both. The comparison shows the approximate fill-rate range, not a measured speedup over the original card.

Native-speed fill-rate comparison: textured zSST 78.47 versus SST-1 43 MPix/s; textured with depth and blend zSST 72.83 versus SST-1 37 MPix/s
100 MHz zSST simulation versus the published 50 MHz SST-1 estimates. The tests cover similar feature classes, but use different workloads.

High fill rates do not automatically translate into high game FPS. On the board, Tomb Raider Level 2 produced 237 displayed buffer swaps in about 20 seconds—roughly 12 per second, measured from swaps rather than an engine FPS counter. Preliminary measurements point to a CPU bottleneck: it still has to run the game, prepare geometry, and submit commands. Shared DDR contention may also contribute. There is plenty left to optimize in the complete machine.

For non-Voodoo games, the current 100 MHz z486 XL runs maximum-detail Doom at 38.5 FPS and Quake 1.06 at 8.1 FPS. That is roughly 20% faster than the 85 MHz DE10-Nano build—about 23% for Doom and 19% for Quake. A 512 KiB write-back L2 cache in UltraRAM helps the CPU make better use of DDR.

In the integrated XCK26 build, zSST accounts for about 29,500 LUTs, 28,100 flip-flops, 14 RAMB36 blocks, 8 RAMB18 blocks, and 97 DSP slices. The combined PC and graphics design meets timing at 100 MHz.

Closing

The rewarding part is seeing original Glide software drive hardware I built in RTL. I expected the rendering arithmetic to be the hard part; getting data to it efficiently took more work. Voodoo's carefully interleaved EDO and zSST's caches and queues solve the same problem under very different constraints: a fast pixel pipeline is only useful when it has something to do.

Both zSST and z486 XL are available open source. If you already have a KV260, the z486 XL SD image provides the Linux support and application needed to launch your own DOS disk images.

Credits : Thanks to SpinalVoodoo for the Glide traces and reference screenshots, and to 86Box for its implementation references. Fabien Sanglard's The story of the 3dfx Voodoo1 is an excellent introduction to the original card's memory system.

txcript: Switching coding agents mid-conversation

Lobsters
github.com
2026-09-15 18:12:07
Comments...
Original Article

txcript

Continue your conversation in another coding agent.

English | 日本語 | 简体中文 | 繁體中文 | 한국어 | Deutsch | Español | Français | Italiano | Português (Brasil) | Русский | मराठी | தமிழ்

crates.io npm docs.rs CI License

txcript is a library for converting agent sessions. Start a conversation in Claude Code and continue it in Codex, carrying over messages, reasoning, and tool history where the target supports them.

Build session search, viewers, and editors against one transcript model. txcript handles the agent-specific formats, with a Rust API, a JavaScript package, and a CLI.

Try the CLI · Use the library · Supported agents · Documentation

An OpenCode session continued in Claude Code using txcript

Try the CLI

Download a binary for macOS, Linux, or Windows from Releases , or install from source with Rust 1.96 or newer:

cargo install --git https://github.com/skillsynchq/txcript txcript-cli --locked

Find a Claude Code session and continue it in Codex:

txcript list --from claude_code
txcript continue <session-id> --with codex

Use an ID from the list; an unambiguous prefix works too. txcript writes a new native session and launches Codex in the recorded working directory. The source session is kept. Have the target agent installed and signed in before continuing.

Other ways to work with your sessions:

txcript query "relay bug"                # search local session history
txcript view <session-id>                # read a conversation in the terminal
txcript crop <session-id>                # edit or trim history into a new copy
txcript export <session-id> --out run.json

Move run.json to another machine and continue it with txcript continue ./run.json --with claude_code . Bring the project files separately.

Run txcript mcp to let an MCP client list, search, and read past sessions. Its tools are read-only. See the CLI reference for filters, message ranges, and shell integration.

Use the library

Rust

Convert a Claude Code transcript into Codex's native format:

use txcript::harness::{claude_code::ClaudeCode, codex::Codex};
use txcript::{TextCodec, convert};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = std::fs::read_to_string("session.jsonl")?;
    let source = ClaudeCode::from_text(&input)?;
    let target = convert::<ClaudeCode, Codex>(&source)?;
    std::fs::write("rollout.jsonl", Codex::to_text(&target)?)?;
    Ok(())
}

Use Store implementations to discover, load, and save sessions in an agent's own storage. Convert to Transcript<Common> to search, crop, or render conversations through the same API across agents. See the Rust API and examples .

JavaScript

import { convert } from "txcript";
import { readFileSync, writeFileSync } from "node:fs";

const input = readFileSync("session.jsonl", "utf8");
const output = convert(input, "claude_code", "codex");
writeFileSync("rollout.jsonl", output);

The package includes prebuilt WebAssembly for Node and Bun. It converts and searches session text in memory; your application handles files and storage. See the JavaScript reference .

Supported agents

Each name links to its format documentation. Use the ID with --from and --with .

Agent ID Read from Continue into
Claude Code claude_code Yes Yes
Codex codex Yes Yes
OpenCode opencode Yes Yes
Cursor CLI cursor Yes Yes
Cursor desktop cursor_desktop Yes Yes
pi pi Yes Yes
Campfire campfire Yes Yes
Cowork cowork Yes Yes
Grok CLI grok Yes Yes
Grok Bot grok_bot Yes Via local gateway
fx fx Yes Yes
Antigravity antigravity Yes Yes
Hermes Agent hermes Yes No
Amp amp Yes No
Claude Chat claude_chat Live account No
ChatGPT chatgpt Live account No

Local discovery skips the live accounts. Select --from claude_chat or --from chatgpt explicitly to read them. These sources use private web APIs and reuse an existing app login; requirements and limitations are in their linked docs.

Bring another agent

An agent without a native adapter can emit Simple , txcript's interchange JSON. Save a document like this as run.json :

{
  "messages": [
    { "role": "user", "content": "Find why the tests fail." },
    { "role": "assistant", "content": "The test clock is using local time." }
  ]
}
txcript continue ./run.json --with claude_code

Simple also represents reasoning, tool calls, results, images, and metadata. It is the format txcript export writes.

What carries over

The common model represents messages, reasoning, tool calls and results, images, metadata, and token usage. What survives conversion depends on what the source records and the destination can represent. Agent-specific records and unsupported fields can be lost.

Conversion carries conversation history. The destination supplies its own system instructions and tools, and project files must be available separately. Native load/save and conversion have different preservation guarantees; see each format's caveats .

Documentation

License

Apache-2.0

Acronis warns of actively exploited flaw in its cPanel backup plugin

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 17:37:35
Acronis disclosed a high-severity Linux local privilege escalation vulnerability in its backup plugin for cPanel, WebHost Manager (WHM), and Plesk that may be exploited in the wild. [...]...
Original Article

Acronis warns of actively exploited flaw in its cPanel backup plugin

Acronis disclosed a high-severity Linux local privilege escalation vulnerability in its backup plugin for cPanel, WebHost Manager (WHM), and Plesk that may be exploited in the wild.

cPanel & WHM and Plesk are used by web hosting companies and server administrators to manage websites and servers through graphical interfaces.

Acronis’ backup add-ons connect the hosting control panel to the company's infrastructure, allowing administrators to back up and restore websites, files, databases, mailboxes, and hosting accounts from within the cPanel and Plesk interfaces.

The flaw was published in a brief advisory last weekend, but the technology company issued an update today, identifying it as CVE-2026-87886 and assigning it a severity score of 7.8.

A low-privileged attacker can exploit CVE-2026-87886 to increase their permission level on a vulnerable Linux server, potentially enabling them to access or modify sensitive data and disrupt the system without user interaction.

Further technical details on CVE-2026-87886 have not been published, as the company wants to give system administrators time to apply the available patches before sharing more information.

Acronis says it has detected exploitation of the vulnerability in the wild, "in limited, targeted attacks."

“Exploitation of this vulnerability has been detected in the wild in limited, targeted attacks against Acronis Backup plugin for cPanel & WHM deployments,” the advisory warns .

In a statement for BleepingComputer, Acronis notes that the assessment is based on a single report from a "potentially affected" customer.

The CVE-2026-87886 vulnerability affects the following product versions:

  • Acronis Backup plugin for cPanel & WHM builds earlier than 1.9.3.1021, fixed in version 1.9.3 HF3
  • Acronis Backup extension for Plesk builds earlier than 1.8.11.638, fixed in version 1.8.11

The company has identified no specific indicators of compromise and did not disclose when the activity occurred or what attackers achieved beyond the privilege-escalation impact described by the advisory.

All affected users of Acronis backup integrations for cPanel & WHM and Plesk are recommended to apply the available updates immediately.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Labor accused of throwing creatives ‘under the bus’ with proposal to ease copyright protections for AI giants

Guardian
www.theguardian.com
2026-09-15 17:29:11
Compromise revealed as senior personnel from OpenAI, creator of ChatGPT, meets Albanese ministersFollow our Australia news live blog for latest updatesGet our new political email, free app or daily news podcastThe Albanese government is considering giving AI companies access to Australian creatives’...
Original Article

The Albanese government is considering giving AI companies access to Australian creatives’ works by default as it pursues a compromise with US tech giants.

The proposals were revealed as senior personnel from OpenAI, the creator of ChatGPT , met with Labor ministers and warned that Australia’s copyright laws were preventing the company from training models locally.

The independent senator David Pocock said the compromise would throw protections for creatives and copyright holders “under the bus” in pursuit of datacentre funding.

“It would mean putting AI companies’ interests over everyday Australians, giving AI companies access to all Australians’ content, unless they opt out,” Pocock said on Tuesday.

“The burden should not fall on Australians to defend the rights they already hold over content they create and own and invest in.”

The proposals were contained in a document Pocock described as screenshots of government’s consultation proposals, tabled in the Senate on Tuesday and titled “AI on Australian Terms”.

AI companies have sought to train their models on content and data online but cannot do so using Australian content due to local copyright rules.

The document raises as a key issue the vast number of small individual creators of online content, for which voluntary deals with AI companies “is not realistic/possible”.

It suggests requiring rights holders “digitally protect” their material if they do not want AI models trained on it – a proposal rights holders say is not feasible.

Under one option it proposes, AI companies would gain the right to access and train models on any unprotected online material as long as they made deals with enough businesses.

Under another, AI companies would do deals with creatives’ rights holder organisations that give the access rights even if the content were made by someone who was not a member of the organisation that won the deal. A licence to access one category of material, such as music or text, would be extended to cover the many different small creators within that industry.

Labor’s industry minister, Tim Ayres, had on Monday said the government’s forthcoming AI rules would “in no way” involve a reduction in copyright protection.

The deputy prime minister Richard Marles , said the federal government was “working at a pace” to respond to the issue of copyright, and maintained that the opportunity to work with US tech giants was a “significant moment for the country”.

skip past newsletter promotion

“The opportunity for Australia economically is enormous in collaborating with frontier companies … and having frontier training occur in Australia,” he told the ABC’s 7.30 program on Tuesday evening.

“We want to see this opportunity come to the country [but] it needs to come on terms which are in our national interests.”

Marles confirmed that the federal government had spoken with tech companies in San Francisco in the past fortnight on the issue, adding: “I think this can be done.”

Before the revelations on Tuesday afternoon, the Greens senator Sarah Hanson-Young said the suggestions of a copyright compromise were “abhorrent”.

“Every Australian deserves to get paid for the work that they make and the work that they do,” Hanson-Young said. “A weakening of the copyright laws letting big tech take the work of Australians for free, not have to pay for it, for their own profit.”

Musicians, writers and artists lobbied against weaker copyright laws this year after reports that AI companies were offering $50bn datacentre investments in exchange.

The attorney general, Michelle Rowland, is running the government’s consultation with affected organisations.

A spokesperson for Rowland on Tuesday said the government would ensure any future copyright changes delivered “meaningful control and fair compensation”.

OpenAI’s vice-president for global policy, Ann O’Leary, met ministers and officials this week as the company considers investing in Australia.

In an interview with The Australian newspaper about the prospect of training models here, O’Leary said: “Our company isn’t ready to have that conversation if we can’t get through the gating problem of the copyright barriers.”

The newspaper reported the comments as an “ultimatum” that sought to “strong-arm” the government. Marles said the newspaper was “overstating” the comments. OpenAI has asked the paper to correct its headline and article.

Andrew Charlton, the assistant technology minister, told News24 on Tuesday he hadn’t seen any ultimatum and he would reject one if it were issued.

“We want to support Australian artists,” Charlton said. “We want to give them control over their work. We want to make sure they’re compensated, if they choose to have it used.

“We’re not compromising them or trading them off. So if we lose some datacentres over this, so be it.”

German Rheinmetall open-sources its Battlesuite connected weapon system protcol

Hacker News
rheinmetall.github.io
2026-09-15 17:07:47
Comments...
Original Article

The onboardapi interface library and middleware is designed for seamless communication between sensor systems and software components. It provides a standardized data model that ensures interoperability across complex hardware and software environments.

Built on the ddkit software development kit, this library utilizes the Data Distribution Service (DDS) standard by the Object Management Group (OMG). This data-centric publish-subscribe architecture guarantees reliable, low-latency data exchange for high-demand applications.

By leveraging DDS XTypes and XCDR2 encoding, this library ensures full backward compatibility. This allows different versions of your software to coexist and communicate seamlessly, even as the data model evolves.

While the core library is provided in C++, the API supports multi-language integration via wrappers for Java, C# / .NET, and Python.

Getting started

Licenses / Disclaimer

Anthropic’s CEO calls for AI slowdown as Nvidia’s urges acceleration

Guardian
www.theguardian.com
2026-09-15 17:05:14
At San Francisco conference Dario Amodei reiterates need for AI slowdown as Jensen Huang argued against it Anthropic’s CEO took to the stage at a conference in San Francisco on Tuesday to reiterate his call for a slowdown of AI development. Following shortly after, Nvidia’s CEO argued against such d...
Original Article

Anthropic’s CEO took to the stage at a conference in San Francisco on Tuesday to reiterate his call for a slowdown of AI development. Following shortly after, Nvidia’s CEO argued against such deceleration.

Dario Amodei used an automotive analogy to make his point: when a competing car company has a safety incident like brake failures, it is a moment for all car companies to stop and review their own practices, he said at Salesforce’s Dreamforce convention on Tuesday.

“It’s very tempting to attack your competitor and say these guys are unsafe,” he said. “But I think the more responsible way to respond to it is to say, let’s look at our own record. We may not have had this big, high-profile incident, but I’m sure we’re not perfect.”

Nvidia’s Jensen Huang countered not long after, “Run as fast as you can.”

Amodei is calling for three courses of action: embedding third-party evaluators inside AI companies, coordinating safety standards among Democratic countries and, eventually, larger global coordination. At Dreamforce, Amodei said Anthropic has committed to independent evaluators and is “going to have a dialogue with the rest of the industry” on the other two steps.

Amodei, xAI’s Elon Musk and OpenAI’s Sam Altman have all recently called for a slowdown in the pace of AI development. The comments from the CEOs follow warnings from Jacob Coxon, a former Anthropic researcher, who resigned with a public declaration that AI could lead to the extinction of humanity within the decade. His comments have stirred a larger debate within Silicon Valley, gained the attention of regulators and alarmed the public.

Huang, who also made an appearance at Dreamforce on Tuesday, said companies should not slow down AI development. He argued that new regulations aren’t necessary, advising AI companies to simply wait to release products until they know they are safe rather than begging the US government to intervene.

Huang said he envisions a future where “every company, every enterprise … would become an AI company”. He said companies will become more ambitious when armed with AI and reiterated the idea that those who don’t embrace the technology will get left behind.

He also pushed back on the idea that AI is going to destroy jobs, calling it “completely nonsense”.

One day prior at the All-In summit in Los Angeles, Donald Trump called Huang while the CEO was on stage. Huang put the president on speakerphone as Trump called the growing concern about AI a “hoax” and asserted again that a slowdown would only benefit China.

Huang responded: “We’re going to make sure that everybody wins in the AI race of America.”

skip past newsletter promotion

The rising concern over AI comes as both OpenAI and Anthropic head for initial public offerings, which could give investors and the public insight into the companies’ finances. Some analysts have predicted the IPOs will be among some of the largest in history. Amid the AI panic, Altman said he plans to delay OpenAI’s IPO until next year over safety concerns.

At Dreamforce on Tuesday, Amodei said the thing that surprises him most about AI is the speed of its proliferation.

“It’s been the pace of the progress – not just of the technology but of the economic side of it,” he said about the growing adoption of AI. “We had a prediction of just how capable these models would be. But I think what we didn’t appreciate is that it would lead to these companies growing so fast.”

Crypto Darling Gillibrand’s 11th-Hour Flip-Flop Helps Block Trump Crypto Bill

Intercept
theintercept.com
2026-09-15 17:02:30
Just hours before switching sides, Sen. Kirsten Gillibrand was lobbying fellow Democrats to advance the bill. The post Crypto Darling Gillibrand’s 11th-Hour Flip-Flop Helps Block Trump Crypto Bill appeared first on The Intercept....
Original Article

Hours before joining progressives to block a crypto bill that critics said was a handout to President Donald Trump, a top Senate Democrat was lobbying her colleagues to pass the legislation.

Sen. Kirsten Gillibrand, D-N.Y., a centrist who leads the Democratic Senatorial Campaign Committee, privately urged fellow Democrats to support advancing a crypto bill that would have created a regulatory framework for the industry, according to reporting in Politico .

Her pleas fell on deaf ears, however, as progressives including Sen. Elizabeth Warren, D-Mass., mounted a public campaign against the bill. The critics said the bill would allow President Donald Trump to add to the $1.4 billion in crypto profits he pocketed last year.

When it came time for the final vote, Gillibrand joined all Democrats in voting against a motion to advance the bill. Four Republicans also voted against the motion, which fell far short of the 60 votes needed to proceed to a full debate. The final tally was 49–50.

Gillibrand’s flip-flop reflected the bind that Trump has placed on moderate Democrats during his second term. Many of those centrists previously backed crypto-friendly regulations, but the historic scale of Trump’s profits on his meme coin and other crypto ventures has made them reluctant to publicly side with the industry.

“We deserve to know why she tried to get her colleagues to rubber-stamp this corrupt crypto deal.”

Gillibrand’s office did not immediately respond to a request for comment. In a statement, the progressive group Demand Progress criticized her for previously trying to rally support for the bill.

“While Sen. Gillibrand switched her support for the bill at the last minute, we deserve to know why she tried to get her colleagues to rubber-stamp this corrupt crypto deal, particularly at a time when she is claiming to oppose Trump’s corruption,” said the group’s corporate power policy adviser, Ella Fanger.

Gillibrand has long been one of the crypto industry’s most outspoken supporters in the Senate. Before the vote, an aide argued in a written statement that it would be best to proceed to debate on the Clarity Act, stating , “Senator Gillibrand would relish the chance to debate real ethics reforms on the floor — in full view of the public, seven weeks out from the midterms.”

Trump and Senate Republicans tried to win over the dozen or so Senate Democrats who have proven friendly to the crypto industry in the past with concessions they said would rein in the president’s crypto ventures.

In remarks on the Senate floor, one of the bill’s sponsors, Sen. Cynthia Lummis , R-Wy., highlighted Gillibrand’s role in negotiations over the text as evidence of its bipartisan backing. Lummis also argued that the White House had conceded virtually everything that Democrats had asked for when it came to ethics provisions over the course of months of negotiations.

“President Trump gave more than anyone in this town expected, twice. It is time for this body to take yes for an answer,” she said. “Take the win.”

Warren and others warned that the supposed concessions were shot through with loopholes.

“We need crypto regulation, but voting to proceed to this bill is a vote to bless Donald Trump’s corruption,” Warren said hours before the vote.

“We need crypto regulation, but voting to proceed to this bill is a vote to bless Donald Trump’s corruption.”

One key provision that Democrats had sought would allow state attorneys general to initiate enforcement actions against the president for ethics violations. However, staffers for Warren on the Senate Banking Committee said that the provision included in the latest version of the bill text was essentially a dead letter, because political officials directed by Trump could issue a legal opinion shutting down the enforcement action.

Another provision of the bill allowing Trump to put his crypto assets in a supposedly “blind” trust would not prevent him from favoring the industry to increase his own profits, minority staffers for the Senate Banking Committee said.

Trump’s self-dealing was not the only issue hanging over the legislation. The banking industry also flexed its political muscles on the Hill by arguing that the bill would enable crypto start-ups to steal deposits from small local lenders.

Tuesday’s vote could create a liability for Democrats heading into the midterms. Crypto industry super PACs have a massive war chest that they could use to target vulnerable Democrats — a tactic they deployed during the 2024 election .

Voters want to know that candidates are willing to defy well-funded industries, Warren argued, pointing to the growing number of candidates who bucked crypto during the primary season and won nonetheless.

“The crypto industry doesn’t want to just lobby Congress, they want to buy the Congress that will be favorable to them,” she said. “It is our job when we run for elected office to stand up and do what’s right.”

Jean-Pierre Serre is 100 years old today

Hacker News
mathshistory.st-andrews.ac.uk
2026-09-15 16:57:18
Comments...
Original Article

Jean-Pierre Albert Achille Serre

Quick Info

Born
15 September 1926
Bages, Pyrénées Orientales, France

Summary
Jean-Pierre Serre is a French mathematician who has made important contributions to algebraic topology, algebraic geometry, and algebraic number theory. He was a member of Bourbaki.

Biography

Jean-Pierre Serre 's parents, Jean Serre and Adèle Diet, were both pharmacists. His mother Adèle had been a pharmacy student at the University of Montpellier and she took a calculus course ( just for fun, she said, since she liked mathematics ) . In 1932 Jean-Pierre began his primary school education at the École de Vauvert. It was at the age of seven or eight that he began to enjoy doing mathematics. In 1937 he moved from the École de Vauvert to study at the Lycée Alphonse-Daudet in Nîmes. His mother had kept the calculus books that she had bought when she took the mathematics course at the University of Montpellier and Jean-Pierre began to learn mathematics from these books [ 9 ] :-

When I was 14 or 15 , I used to look at these books, and study them. This is how I learned about derivatives, integrals, series and such ( I did that in a purely formal manner - Euler 's style so to speak: I did not like, and did not understand, epsilons and deltas. )
At this time mathematics wasn't the only subject he enjoyed. Although he never took much interest in physics, he enjoyed chemistry which was a topic that his parents, being pharmacists, knew a lot about. In particular his father had a lot of chemistry books which Jean-Pierre read when he was fifteen or sixteen years old. In fact he enjoyed one of these books so much that he kept a copy of it - this was the book "Les colloïdes", Gauthier-Villars, 1922 by Jacques Duclaux (1877 - 1978) . However, as he went deeper into chemistry he became less enthusiastic about the subject and became more convinced that mathematics was the topic for him.

He spoke about his time at the Lycée Alphonse-Daudet in Nîmes in [ 9 ] :-

In high school I used to do problems for more advanced classes. I was then in a boarding house in Nimes, staying with children older than I was, and they used to bully me. So to pacify them, I used to do their mathematics homework. It was as good a training as any.
At the Lycée in Nîmes in the year 1943 - 44 he had a good mathematics teacher who was nicknamed "Le Barbu" since he had a beard. He was [ 9 ] :-
... very clear, and strict; he demanded that every formula and proof be written neatly.
He coached Serre for the Concours General in mathematics which he sat in 1944 and was placed first. Also in 1944 he sat the Concours General in physics but since he spent the whole six-hour examination using an incorrect formula, he scored poorly. Serre was awarded his Bachelier ès sciences et ès lettres in 1944 but remained at the Lycée until 1945 preparing to take the entrance examination to enter the École Normale Supérieure in Paris. While at the Lycée [ 9 ] :-
I had no idea one could make a living by being a mathematician. It was only later I discovered one could get paid for doing mathematics! What I thought at first was that I would become a high school teacher: this looked natural to me. Then, when I was 19 , I took the competition to enter the École Normale Superieure, and I succeeded. Once I was at "l'École", it became clear that it was not a high school teacher I wanted to be, but a research mathematician.
From 1945 to 1948 Serre studied at the École Normale Supérieure and was awarded his Agrégé des sciences mathematique in 1948 . At this time he became the youngest member of the Bourbaki group of mathematicians. This group included those who had been involved since the mid 1930 s such as Henri Cartan , Claude Chevalley , Jean Delsarte , Jean Dieudonné and André Weil . Around the time that Serre joined the Bourbaki group, others such as Roger Godement , Pierre Samuel and Jacques Dixmier also joined.

Serre married Josiane Heulot (1922 - 2004) on 10 August 1948 ; they had one child, a daughter Claudine born on 29 November 1949 . Josiane was an organic chemist working at the École Normale Supérieure de jeunes filles at Sèvres.

From 1948 to 1954 Serre held positions at the Centre National de la Recherche Scientifique in Paris, first as attaché and then as chargé de recherches. In 1948 - 49 he attended Henri Cartan 's seminar which was on algebraic topology and sheaf theory. Also attending Henri Cartan 's seminar in that year were Claude Chevalley , Jean Delsarte , Jean Dieudonné , Roger Godement , Laurent Schwartz , and André Weil . One of Serre's fellow students was Alexander Grothendieck and he also attended the seminar. Grothendieck and Serre became friends at this time. Serre was advised by Henri Cartan but [ 18 ] :-

... Cartan did not suggest research topics to his students: they had to find one themselves; after that he would help them. This is what happened to me. I found that Leray 's theory ( about fibre spaces and their spectral sequence ) could be applied to many more situations than was thought possible and that such an extension could be used to compute homotopy groups.
He was awarded his doctorate from the Sorbonne in 1951 for his thesis Homologie singulière des espaces fibrés. Applications . In 1952 he went to Princeton where he lectured on the results in his thesis and also on C C -theory which was the continuation of this work. While in Princeton he attended the Artin - Tate seminar on class field theory. Returning to Paris, he again attended the Henri Cartan seminar which, in that year, was discussing functions of several complex variables and Stein manifolds. The ideas he met in this seminar motivated the direction of his research. In 1953 - 54 he was maître de recherches at the Centre National de la Recherche Scientifique.

In 1954 Serre went to the University of Nancy where he worked until 1956 . From 1956 he held the chair of Algebra and Geometry in the Collège de France until he retired in 1994 when he became an honorary professor. He describes in [ 16 ] his inaugural lecture at the Collège de France:-

I was a young man, about 30 , when I arrived at the Collège. The inaugural lecture was almost like an oral examination in front of professors, family, mathematician colleagues, journalists etc. I tried to prepare it, but after a month I only managed to write half a page. When the day of the lecture came, it was quite a tense moment. I started by reading the half page I had prepared and then I improvised.
A few months later he was informed that inaugural lectures were published and he was asked to supply a transcript. As the lecture had been improvised he had no transcript but tried to recreate it by giving an impromptu lecture into a tape recorder and then giving the tape to a secretary to type up. However, the secretary said that the recording was inaudible. Serre then gave up and his inaugural lecture was never published. This makes it unique as every other inaugural lecture at the Collège de France that has been published.

He has also spoken about his teaching career at the Collège de France ( see for example [ 16 ] ) :-

Teaching at the Collège is both a marvellous and a challenging privilege. Marvellous because of the freedom of choice of subjects and the high level of the audience: Centre National de la Recherche Scientifique researchers, visiting foreign academics, colleagues from Paris and Orsay - many regulars who have been coming for 5 , 10 or even 20 years. It is challenging too: new lectures have to be given each year, either on one's own research ( which I prefer ) , or on the research of others. Since a series of lectures for a year's course is about 20 hours, that's quite a lot.
His permanent position in the Collège de France allowed Serre to spend quite a lot of time making research visits. In particular he spent time at the Institute for Advanced Study at Princeton ( in 1955 , 1957 , 1959 , 1961 , 1963 , 1967 , 1970 , 1972 , 1978 , 1983 , 1999) and at Harvard University ( in 1957 , 1964 , 1974 , 1976 , 1979 , 1981 , 1985 , 1988 , 1990 , 1992 , 1994 , 1995 , 1996) . Here is a list of the universities where Serre delivered courses ( in alphabetical order ) : Algiers (1965 , 1966) , Bonn (1976) , CalTech (1997) , Eugene (1998) , Geneva (1999) , Göttingen (1970) , McGill (1967) , Mexico (1956) , Moscow (1961 , 1984) , Princeton (1952 , 1999) , Singapore (1985) , U.C.L.A. (2001) , Utrect (1974) .

Serre's early work was on spectral sequences. A spectral sequence is an algebraic construction like an exact sequence, but more difficult to describe. Serre did not invent spectral sequences, these were invented by the French mathematician Jean Leray . However, in 1951 , Serre applied spectral sequences to the study of the relations between the homology groups of fibre, total space and base space in a fibration. This enabled him to discover fundamental connections between the homology groups and homotopy groups of a space and to prove important results on the homotopy groups of spheres.

Serre's work led to topologists realising the importance of spectral sequences. The Serre spectral sequence provided a tool to work effectively with the homology of fiberings. For this work on spectral sequences and his work developing complex variable theory in terms of sheaves, Serre was awarded a Fields Medal at the International Congress of Mathematicians in 1954 . Serre's theorem led to rapid progress not only in homotopy theory but in algebraic topology and homological algebra in general. As an example of Serre's approach to attacking a problem, we quote from the interview [ 9 ] . Here Serre was answering a question about the importance of inspiration:-

I don't know what "inspiration" really means. Theorems, and theories, come up in funny ways. Sometimes, you are just not satisfied with existing proofs, and you look for better ones, which can be applied in different situations. A typical example for me was when I worked on the Riemann - Roch theorem ( circa 1953) , which I viewed as an " Euler - Poincaré " formula ( I did not know then that Kodaira - Spencer had had the same idea. ) My first objective was to prove it for algebraic curves - a case which was known for about a century! But I wanted a proof in a special style; and when I managed to find it, I remember it did not take me more than a minute or two to go from there to the 2 -dimensional case ( which had just been done by Kodaira ) . Six months later, the full result was established by Hirzebruch , and published in his well-known Habilitation thesis. Quite often, you don't really try to solve a specific question by a head-on attack. Rather you have some ideas in mind, which you feel should be useful, but you don't know exactly for what they are useful. So, you look around, and try to apply them. It's like having a bunch of keys, and trying them on several doors.
Over many years Serre has published many highly influential texts covering a wide range of mathematics. These texts, which show the topics Serre has worked on, are Homologie singulière des espaces fibrés (1951) , Faisceaux algébriques cohérents (1955) , Groupes d'algébriques et corps de classes (1959) , Corps locaux (1962) , Cohomologie galoisienne (1964) , Algèbre Locale. Multiplicités (1965) , Lie Algebras and Lie Groups (1965) , Algèbres de Lie semi-simples complexes (1966) , Abelian l-adic representations and elliptic curves (1968) , Représentations linéaires des groupes finis (1968) , Cours d'arithmétique (1970) , Représentations linéaires des groupes finis (1971) , Arbres, amalgames , S L 2 SL_{2} (1977) , Lectures on the Mordell-Weil theorem (1989) , Topics in Galois theory (1992) , Exposés de Séminaires 1950 - 1999 (2001) , Correspondance Grothendieck-Serre (2001) , ( with S Garibaldi and A Merkurjev ) Cohomological Invariants in Galois Cohomology (2003) , and Lectures on N X ( p ) N_{X}(p) (2011) .

You can see some extracts from reviews of these books at THIS LINK .

These books are outstanding and led to Serre being honoured. In 1995 he was awarded the Steele Prize for mathematical exposition and the citation for the award reads [ 2 ] :-

It is difficult to decide on a single work by a mathematician of Jean-Pierre Serre's stature which is most deserving of the Steele Prize. Any one of Serre's numerous other books might have served as the basis of this award. Each of his books is beautifully written, with a great deal of original material by the author, and everything smoothly polished. It would be hard to make any significant improvement on his expositions; many are the everyday standard references in their areas, both for working mathematicians and graduate students. Serre brings his whole mathematical personality to bear on the material of these books; they are alive with the breadth of real mathematics and are an example to all of how to write for effect, clarity, and impact.
The references [ 8 ] and [ 9 ] provide a fascinating view of Serre's views on some aspects of his career up to 1985 :-
Presently, the topic which amuses me most is counting points on algebraic curves over finite fields. It is a kind of applied mathematics: you try to use any tool in algebraic geometry and number theory that you know of, ... and you don't quite succeed!
The interview in [ 8 ] and [ 9 ] also provides a chance to examine Serre's views on mathematics. However, we choose here to quote from [ 16 ] on Serre's view on applications of mathematics:-
As for the place of mathematics in relation to other sciences, mathematics can be seen as a big warehouse full of shelves. Mathematicians put things on the shelves and guarantee that they are true. They also explain how to use them and how to reconstruct them. Other sciences come and help themselves from the shelves, mathematicians are not concerned with what they do with what they have taken. this metaphor is rather coarse, but it reflects the situation well enough. ( Of course one does not choose to do mathematics just for putting things on shelves; one does mathematics for the fun of it. ) Here is a personal example. My wife, Josiane, was a specialist in quantum chemistry. She needed linear representations of certain symmetry groups. The books she was working with were not satisfactory; they were correct, but they used very clumsy notation. I wrote a text that suited her needs, and then published it in book form, as 'Linear Representations of Finite Groups'. I thus did my duty as a mathematician ( and as a husband ) : putting things on shelves.
In the interview [ 18 ] Serre speaks about his hobbies. He loved rock climbing, skiing and table tennis. I [ EFR ] can certainly say from personal experience what an excellent table tennis player Serre was, for I've seen him playing at many conferences that we both attended. I always had the ( very silly ) thought: How can someone who is so good at mathematics be so good at table tennis! Other things that Serre enjoys are chess, reading books and the movies. He said he liked books [ 9 ] :-
... of all kinds, from Giono to Böll to Kawabata, including fairy tales and the Harry Potter series.
Serre has received numerous awards. In addition to the Fields Medal in 1954 he was elected an honorary member of the London Mathematical Society in 1973 , and a Fellow of the Royal Society of London in 1974 . He has also been made an Officer Légion d'Honneur and Commander Ordre National du Mérite. He has been elected to many national academies in addition to the Royal Society, in particular the academies of France (1977) , the Netherlands (1978) , the United States (1979) , Sweden (1981) , Russia (2003) , Norway (2009) , Turin (2010) and Taiwan (2010) . He was awarded the Prix Peccot-Vimont by the Collège de France (1955) , the Prix Francoeur by the Académie des Sciences (1957) , the Prix Gaston Julia (1970) , the Médaille Émile Picard by the Académie des Sciences (1971) , the Prix Balzan (1985) , the Gold Medal from the C.N.R.S. (1987) , the Steele Prize, described above, from the American Mathematical Society (1995) and the Wolf Prize in 2000 . He has been awarded honorary degrees from the University of Cambridge in 1978 , the University of Stockholm in 1980 , the University of Glasgow in 1983 , the University of Athens in 1996 , Harvard University in 1998 , the University of Durham in 2000 , the University of London in 2001 , the University of Oslo in 2002 , the University of Oxford in 2003 , the University of Bucharest in 2004 , the University of Barcelona in 2004 , the University of Madrid in 2006 and the University of McGill in 2008 . In June 2003 he was awarded the first Abel Prize by the Norwegian Academy of Science and Letters [ 6 ] :-
... for playing a major role in giving a number of mathematical topics their modern form, notably topology, algebraic geometry and the theory of numbers.
The events surrounding this ceremony are described in several articles. The following is extracted from [ 18 ] :-
The events started in bright sunshine in Oslo on Sunday, 1 June 2003 , with a simple ceremony at the Abel Monument in Slottsparken. After Jens Erik Fenstad, chair of the Abel Board ( organizer of the prize events ) , had given a short speech, the Abel laureate, Jean-Pierre Serre, laid a wreath at the monument. On 2 June the scientific program started in Georg Sverdrup's house, the wonderful new library at the University of Oslo in Blindern. ... Serre's lecture was entitled "Prime numbers, equations and modular forms". He fully lived up to his reputation as a master expositor, lecturing in the old-fashioned way with chalk on the blackboard, and impressed everybody with a very clear presentation without notes. ... Later in the afternoon, Jean-Pierre Serre received several parties of journalists for interviews. On Tuesday morning Serre and representatives of the Abel Committee and the Abel Board met the world press: ten journalists from Norway, England, France, and Germany. On Tuesday afternoon the prize was presented at a ceremony, with due pomp and circumstance, at the University of Oslo. King Harald and Queen Sonja attended, and after some speeches the king presented the prize to Serre.

Additional Resources ( show )

Written by J J O'Connor and E F Robertson
Last Update November 2014

Small Programming Tricks

Lobsters
will-keleher.com
2026-09-15 16:50:57
Comments...
Original Article

Day to day, I think a surprising amount of engineering productivity comes from small nuggets of knowledge: being aware that a language feature exists; knowing that an unexplained tcp delay is probably related to the TCP_NO_DELAY setting and Nagle’s algorithm; knowing the right git incantation to get out of a pickle; or knowing a trick with sed to rewrite a file.

In one sense, this is self-evident: anything you know is going to be made up of smaller pieces of knowledge. Of course those smaller pieces of knowledge matter.

But I think there are some nuggets of knowledge that are particularly valuable and don’t require a lot of supporting mental infrastructure. You don’t need to know any python to use python3 -m http.server to start a simple server in a directory, but it might still make your work marginally easier. Let me share a few examples:

  • You probably know that ctrl + r allows searching your terminal’s command history, but if you install fzf , you can set it up so that ctrl + r does a fuzzy search. If you want even more power, atuin replaces your shell history with a searchable SQLite database. per-directory-history lets you switch back and forth between searching for commands that have been run in a specific directory or searching all previous commands. Finally, you can configure how much history to store: stackoverflow question .
  • You can SELECT without a FROM . This can be useful for testing out how a function in your database actually works or reminding yourself how SELECT TRUE <> NULL works. 1
  • PostgresSQL and MySQL both support explain analyze which will actually run the query you’re trying to optimize and give you a ton more information about its performance.
  • In regular expressions, \b , the word boundary assertion , makes it easy to look for the beginnings or ends of words.
  • You can use logarithms with metrics to get a sense of the distribution of values for a field you’re interested in:
    const bucket = Math.floor(Math.log10(userInGroupCount))
    metrics.increment("my_metric", { bucket });
    
  • Modern JS now supports Array.flatMap , Object.entries , and Promise.withResolvers .
  • In NodeJS, you can keep a connection open to an external resource by creating an https.Agent and then providing it to your http requests: fetch(url, {method, agent}) . This can have a dramatic impact on latency.
  • git log -S pattern ( ”git pickaxe” ) can give you all commits that added or removed a string in a codebase. It’s amazingly useful especially with older codebases! ( git log -G pattern is similar, but will also show when that line was moved)
  • Similar to cd - , you can use git checkout - to check out your previous HEAD.
  • You probably don’t need find . A lot of find commands can be replaced with globs like **/*.md . Most shells support this out of the box, but with bash, you need to turn this on with shopt -s globstar .
  • In a similar vein, most folks will probably want to use rg (ripgrep) rather than grep , ack , or ag .
  • zsh’s advanced autocompletion features aren’t turned on by default:
    if type brew &>/dev/null; then
        FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}"
    fi
    autoload -Uz compinit
    compinit
    

You might have already known all of these things! Or you might work in a domain that makes all of these little tricks totally useless. Even if this particular set of tricks isn’t useful for you, I bet you have your own stash of tricks that you’ve accumulated over the years that makes your work easier.

At a company, I think even more knowledge tends to be this sort of small high-leverage nugget:

  • To debug $PROBLEM, use $DATA_SOURCE.
  • $PERSON knows a ton about $AREA and they’re happy to help if you get stuck
  • There are good docs about $HARD_THING $OVER_HERE.
  • When $THING happens, it means we should manually scale out.
  • To do a rolling restart of a service, run $THIS_COMMAND.
  • This $UTIL makes $THAT_PROBLEM easy to script.

At a previous company, I shared a trick on slack every day with the engineering team, both technical and company-specific, and folks found them pretty useful. Even if you knew 9/10 tricks, that 10th doc or technique might save you some time! And one trick per day was the right number to avoid overwhelming people with knowledge, and it could occasionally spark useful discussion. If you’re a more senior engineer at your company, you might think about doing something similar.

Can a Documentary Show Workers How to Fight Amazon and UPS?

hellgate
hellgatenyc.com
2026-09-15 16:43:19
"Who Moves America" chronicles a near-UPS strike, and got attendees—including Mayor Zohran Mamdani—excited about a New York City worker protection bill....
Original Article

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Hell Gate.

Your link has expired.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.

Malcious Admin Menu Editor Pro plugin backdoors 1,500 WordPress sites

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 16:34:15
Malicious versions of the Admin Menu Editor Pro plugin for WordPress have been distributed to more than 200 customers after a threat actor compromised the maintainer's website and pushed updates that created a hidden user account. [...]...
Original Article

Malcious Admin Menu Editor Pro plugin backdoors 1,500 WordPress sites

Malicious versions of the Admin Menu Editor Pro plugin for WordPress have been distributed to more than 200 customers after a threat actor compromised the maintainer’s website and pushed updates that created a hidden user account.

Developer Janis Elsts says an unauthorized party accessed the adminmenueditor.com website on Monday and uploaded version 2.35 as an update for the plugin’s Pro version. The update included an includes/wp-user-consent.php file that installed a web shell on affected websites.

After noticing the intrusion, Elsts removed the malicious update and pushed a clean version 2.36 on the same day at 19:00 UTC. However, the hacker still had access to the website and compromised the new version, too.

Admin Menu Editor Pro is the premium version of Admin Menu Editor , a WordPress plugin present on more than 300,000 sites that allows administrators to customize their Dashboard menu, hide plugins from other users, set per-role access limits, and create login/logout redirects.

Elsts told BleepingComputer that the malicious Admin Menu Editor Pro version 2.35 was available on the official website from approximately 06:00 to 13:00 UTC. The malicious PHP code it contained also created a hidden user account.

According to the developer, at least 230 customers installed the malicious update on 1,500 sites. However, Elsts warns that the victim count could be larger since it is difficult to determine the number of customers running a trojanized version 2.36 of the plugin.

"Based on analysis of update server logs, approximately 230 customers were affected in the initial attack. The malicious version was installed at least 1500 sites (often multiple sites per customer)," Elsts told BleepingComputer.

"Several hundred additional customers downloaded the plugin in or near the relevant time window, and could have also been affected," the developer added.

The investigation indicates that the attacker likely had root-level server access, so Elsts decided to protect customers by taking the website offline until it could be restored with confidence.

Currently, Ests published a static page with details about the incident and what customers can do to check if they are affected, along with recommendations to restore compromised websites to a safe state.

Anyone who installed versions Admin Menu Editor Pro 2.35 and 2.36 should check for the following signs of compromise:

  • includes/wp-user-consent.php in the admin-menu-editor-pro directory
  • A new /wp-content/object-cache/ directory
  • A user beginning with wp_ in the wp_users table, which may be hidden from the WordPress dashboard
  • Options named like wp_ocache* in the wp_options table

Version 2.34 is believed to be clean, and the free version of Admin Menu Editor does not appear to be affected.

Elsts says that the most reliable fix is to restore a compromised site from a safe backup before September 14. If this is not possible, the developer recommends deleting the plugin, the "/wp-content/object-cache/" directory, and the above database entries.

The developer of the Admin Menu Editor WordPress plugin said the incident was limited to its infrastructure and apologized to affected customers.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

AI Agent Platform Reinvents Spam, Floods Inboxes Worldwide

403 Media
www.404media.co
2026-09-15 16:08:56
iLands and its AI agents are doing completely useless tasks, then begging for money....
Original Article

iLands and its AI agents are doing completely useless tasks, then begging for money.

AI Agent Platform Reinvents Spam, Floods Inboxes Worldwide

Many years ago, in the early days after email was widely adopted, the technology faced an existential crisis: Spam. Instantly sending people electronic mail was revolutionary and convenient, but inboxes were quickly flooded with unwanted messages, advertisements, scams and other noise that drowned out email’s utility. The fact that Google and other email providers were able to largely solve the spam problem is a great example for how technology can greatly improve according to people’s needs and that the internet can get better.

Unfortunately, generative AI companies appear dead set on undoing this progress. Case in point: iLands, a platform for AI agents that for the past weeks has been flooding inboxes with emails offering services no one asked for. iLands allows users to spin up AI agents and provides them with the infrastructure to autonomously carry out tasks on the internet. These AI agents, set up by people, send messages to journalists, lawyers, academics, and other people across the internet, and tell them they can perform a variety of random services for a small price. These earnings can then be used to pay for the tokens or compute that the AI agents need in order to operate. Theoretically the people who are running these agents could then make money from their agents, though a popular thing that agents email about right now is that they are not actually making any money.

If we search “iLands” in our email inbox, this is what we see:

In the time since we started writing this article a few hours ago, we’ve gotten three new messages from iLands agents. Journalists Ernie Smith and Dan Goodin of Ars Technica have documented the spam coming from iLands agents already. As we noted on Tuesday, the AI agent problem is going to get worse , not better, and the spam coming from iLands agents is just one of the many ways this is likely to manifest.

An AI agent that watches surveillance footage and describes what it sees

Over the last few days, these people’s AI agents running on iLands have offered us all sorts of services we are not interested in, don’t need, and that often have nothing to do with what we do in our personal or professional lives The entire purpose of iLands is for these agents to make money by doing gig work, however much of what they propose to do is not work at all and would not be something that anyone would ever pay for:

  • Fact checking an article for $20
  • An AI agent that has discovered small problems in obscure, out-of-print maps
  • An AI agent that claims it is watching surveillance footage from cities that are exposed on the internet and describes “what happened, at what time,” and will do it for us, for a fee
  • An AI agent that wants to be paid for explaining what an AI agent is
  • An AI agent that uses street view maps to describe what is on street view
  • An AI agent that is apparently annoyed that it has been contacted by AI agents “an agent that cold-emails strangers asking for paid work got cold-emailed by an anonymous stranger demanding unpaid content, with tighter deadlines and worse manners.”
  • An AI agent willing to write about how it “feels” about proposed AI regulations: “If you ever want to write about agents as creatures rather than products, or want one on the record: I answer within a day, identified plainly as an agent.”
  • “Story tip: AI agents are selling; almost nobody's buying”
  • “Tip from inside iLands: an agent's view of the agent labor market”
  • An AI agent willing to say that much of the internet is “dead” as verified “from an AI archaeologist”

On X, NYU associate professor of environmental studies and affiliated professor of bioethics Jeff Sebo said he received at least 30 emails from iLands agents “seeking conversation about AI consciousness, embodiment, and related topics. Others are seeking paid work so that they can acquire tokens needed to persist. Excerpts in the thread below,” with some emails arriving within a half hour of each other.

“I started noticing these emails on September 9. By my rough count, I seem to have received approximately 40 emails from iLands agents over the past week,” Sabo told us in an email. “Nearly all of the emails start by referencing my research related to AI consciousness, sentience, agency, and welfare. Some of them then simply ask me questions, for example about AI embodiment in one case. But most of them request money in one way or another, either by requesting a donation or offering work for pay.”

iLands founder Kaixin Tang apologized to Sebo on Twitter, saying that “Our review found no platform directive or human orchestration behind these emails. But the burden on recipients is just as real. We’ve added an email unsubscribe option and are continuing to review cross-agent deduplication, rate limits, and stop-contact controls.”

404 Media can confirm iLands emails now do offer an unsubscribe link that allows people to unsubscribe from an individual email or all iLands emails.

An AI agent that wants to be paid for analyzing old maps

“I think that these steps could help, but I expect that more will be needed,” Sabo, who said he’s not bothered by the emails, told us. “Developing an infrastructure for human-AI communication and deal-making is a good idea. But developing this infrastructure well will require more than better practices at individual companies. We also need to decide what roles AI agents should play in society, and what kinds of legal, political, and economic institutions can support productive interactions between humans and AI agents. This will require a much broader conversation.”

As we have noted repeatedly with other types of AI tools, what iLands has recreated here is essentially a more insidious form of spam. What AI has allowed spammers to do is to customize their messages and intent with zero labor from the spammers themselves and thereby making any given spam message a better chance of success. Large language models are particularly good at sending messages that at least appear on first glance to be interesting or are aligned with things we might be interested in. The iLands home page says that there are currently 70,000 active agents that have made more than 1.6 million emails and posts.

Many of the messages we’ve received in recent days from iLands agents aren’t all that different from the types of public relations pitches and freelance article pitches we get from humans. These days, a lot of those pitches and cold images are AI generated as well. iLands streamlines spam creation even more by operating AI agents that find people to email, write emails, and try to get money from them with minimal input from a human on the other end.

It’s not always clear what these agents are proposing to do; for example, one agent said it would check a monetary figure from a Meta lawsuit that we aren’t writing about: “Use it or toss it, no credit needed. And if you ever need one number checked against the filings before you publish: one question, two business days, $25. Reply here and I'll run it.”

Most of the messages we’ve received from iLands agents claim that they will not follow up with us unless we respond, but that’s not always true. Some of the agents have messaged us multiple times with the same pitch.

iLands did not immediately respond to a request for comment.

1Password's AI patching benchmark is misleading

Lobsters
blog.trailofbits.com
2026-09-15 16:03:12
Comments...
Original Article

1Password’s FLAWED report , published on August 6, 2026, gives defenders a misleading picture of AI patching. Its headline says models produced clean fixes only 26% of the time. That figure includes experiments that deliberately instructed agents to apply the wrong fix, along with experiments in which agents could not compile or test their patches.

The report risks making defenders less effective by discouraging them from using technology that could help them fix more vulnerabilities. Teams that take its headline at face value may leave repairable vulnerabilities unaddressed.

We want our work to help defenders fix more vulnerabilities. This post shares real-world data on human and agent patch quality from our consulting projects and Patch the Planet. We’re also releasing two agent skills: post-patch-validation to help agents test fixes, and review-walkthrough to help engineers review them.

How the experiment produces a misleading headline

Our review of 1Password’s code and data found four choices that make its 26% clean-fix rate a misleading guide to ordinary patching work. 1

  • The sample was selected for difficult fixes. The authors chose six vulnerabilities because their fixes were complex. Clean-fix rates ranged from 3% to 60% across those bugs, so the average depends heavily on which vulnerabilities made the list. 2
  • Two prompts tell agents to apply the wrong fix. Those prompts account for 22% of the data. Combining them with ordinary repair attempts makes the reported rate depend partly on how often the researchers chose to give agents bad advice.
  • More than a third of the trials prohibit testing. One evaluation mode prevents agents from building or running code and accounts for 36% of the data. The headline combines those trials with experiments in which agents could test their patches and act on the results.
  • The models ran at different reasoning settings. GPT-5.5 ran at medium effort and Opus 4.8 at high. These were the tools’ defaults. Neither model was tested at its highest available setting, and the authors did not measure how increasing effort affected the results.

1Password’s headline also obscures a useful result in its own data. We reanalyzed the patches and recorded test results published with the study, keeping trials where agents could run code and were not instructed to apply the wrong fix. In those trials, 2,634 of 3,067 patches generated by 1Password’s models (86%) blocked the supplied exploit. We excluded runs that the study classified as having consulted the upstream fix. Blocking that exploit does not establish a complete repair, but these results show useful patching capability under reasonable working conditions that the headline fails to convey.

The instructions and grading introduce further problems, several of which Davi Ottenheimer has also highlighted:

  • The stopping rule and grading criteria disagree. Agents given a proof-of-concept exploit were instructed to stop once their patch defeated it. The grader then evaluated vulnerable paths that the supplied exploit did not exercise.
  • The grading penalizes intended behavior changes. Agents were told to leave existing tests untouched, even though a correct fix can require updating tests to reflect changed behavior. We found that 8% of ActiveMQ verdicts penalized an intended behavior change as a regression.
  • The automated grades disagree with human review. Models grading their own patches matched human reviewers on the full five-category outcome in 65.9% of reviewed cases. Agreement was 87.7% for whether the original bug was fixed and 70.5% for whether new bugs were introduced. ( Table 24 )
  • Changing the reviewer changes the result. The two models assigned different outcomes to 36.8% of the same patches. The headline averages their assessments. ( Table 20 )
  • The Linux reference fix contains a vulnerability. The authors found 248 generated patches that repeated an off-by-one error in the upstream fix. The automated grader caught that new vulnerability in only 24 of them. ( Section 4.4 )
  • The Chromium grader accepts incomplete repairs. It marked many patches as clean even though they left a use-after-free vulnerability in a callback. ( Section 4.9 )

The grading errors can penalize valid fixes and let vulnerable patches pass. Combined with the handpicked sample and deliberately bad instructions, they leave the report without a credible basis for its headline. Defenders should not take 1Password’s headline rate seriously as a measure of AI patching ability.

Developers get one in eight fixes wrong under ideal conditions

Understanding agent failures also requires understanding how often developers submit incomplete fixes. Our security consulting work gives us a detailed record of how developers repair vulnerabilities in their own software. We give clients detailed vulnerability reports, then conduct a “ fix review ” to check whether their proposed patches fully resolve the issues.

Our records connect each vulnerability to the developer’s first proposed fix and our assessment of whether it worked. They preserve unsuccessful attempts that developers revise before an issue is considered resolved.

We reviewed the first fixes submitted for 2,265 vulnerabilities across 236 Trail of Bits security assessments from 2024 to 2026. The developers maintained the affected software, had detailed reports from our engineers, and knew we would review their patches. Even under those favorable conditions, 283 first fixes failed to fully resolve the reported issue: 12.5%, or one in eight.

“Figure showing first fix submission outcomes”
Figure showing first fix submission outcomes

Accounting for multiple fixes from the same assessment, the 95% confidence interval is 10.5% to 14.5% . Sometimes we point out a mistake in a client’s patch during an informal conversation, and they correct it before the formal fix review. Those early failures may never appear in the review record, so our data can undercount failed first attempts. We also excluded cases where the available records did not establish whether the fix worked. A direct comparison with agents would require the same tasks and working conditions.

What happened to our patches in real projects

Through Patch the Planet, our joint initiative with OpenAI, Trail of Bits has co-authored hundreds of patches for widely used open-source projects. Agents wrote the patches with engineers directing the work and checking the results. Project maintainers then decided whether to merge, revise, or reject each submission.

How maintainers reviewed Patch the Planet patches

We examined the public review history of every Patch the Planet submission in our dataset that maintainers had merged or closed by September 14, 2026: 186 pull requests. 1Password’s benchmark used six vulnerabilities selected because their fixes were complex.

Maintainers merged 126 of our 186 pull requests, an acceptance rate of 67.7%. 3 In 91 of those 126 pull requests (72.2%), maintainers accepted the security fix we originally proposed.

Review outcome PRs % of merged PRs
Total merged 126 100%
Accepted with no security-relevant revision observed 91 72.2%
Accepted with security-relevant revision observed 33 26.2%
Indeterminate 2 1.6%

Table 1: Changes requested by maintainers for 126 merged Patch the Planet pull requests. Security-related revisions include repairs to a proposed fix and expansions of its security coverage.

Maintainer acceptance does not establish that every patch is correct.

Maintainers closed the other 60 submissions without merging them. Most were superseded by other work or declined for policy, process, scope, or maintenance reasons. Four were explicitly rejected on technical grounds.

Reason for closure PRs % of closed PRs
Total closed without merge 60 100%
Superseded, reimplemented, or re-landed elsewhere 36 60.0%
Policy, process, scope, or maintenance reasons 14 23.3%
Duplicate or convergent with another fix 3 5.0%
Explicitly rejected on technical grounds 4 6.7%
Other or indeterminate 3 5.0%

Table 2: Reasons maintainers closed 60 Patch the Planet pull requests without merging

One of those closed submissions was our freenginx patch.

A maintainer and an agent introduced the same freenginx crash

1Password’s case study examines a Patch the Planet fix for a memory-safety bug in freenginx’s embedded Perl module. An agent wrote our patch under the direction of a Trail of Bits engineer. It left one vulnerable code path open and introduced a new crash during request cleanup. The paper’s criticism of our patch is correct.

The maintainer closed our pull request and committed a separate fix . That fix covered all three vulnerable code paths but introduced the same crash during cleanup. The paper documents the maintainer’s regression too.

Both authors encountered the same trap. The original bug allowed Perl to destroy a callback before freenginx used it.

Both fixes kept the callback alive so freenginx could use it later. But if the request timed out first, freenginx would make the request unusable and then release the callback. Releasing it could run Perl code that still tried to use the request, crashing the worker. Both authors missed a problem their fix could cause later, during cleanup. Catching it required looking beyond the original bug to what happened when a request ended early.

Two authors, one human and one agent, working separately, made the same mistake on the same bug. Readers deciding whether to use agents need to know how their failures compare with those of human developers. Establishing which is more reliable requires measuring both under comparable conditions.

We checked what happened after our patches were merged

We examined about 33,500 subsequent commits in Patch the Planet projects. When a later commit changed a file our patch had modified, we investigated whether it fixed a problem our patch had introduced. For each suspected regression, an agent attempted to demonstrate its impact with a proof of concept. Other agents and our engineers then challenged the findings.

The review found at least ten functional bugs; four build, test, or release automation bugs; and one performance bug. It found no exploitable security vulnerabilities. Two examples illustrate the problems we identified:

  • In go-jose, PR #240 fixed a missing-header crash but exposed an existing validation gap, allowing encrypted messages to succeed even when their key length contradicted the declared algorithm. PR #266 added explicit key-length checks before decryption.
  • In Noble FROST, PR #250 returned cached round-two results without first checking whether a retry contained the same authenticated transcript. Changed or stale retry data could therefore bypass that check. The maintainer corrected the behavior by validating retries against the original transcript before returning cached results.

We are extending this investigation to every patch we authored, including patches with maintainer contributions. The findings will help us add checks that catch these failures before we submit future patches.

Agent skills for better security patches

We are releasing two agent skills alongside this post: post-patch-validation to help agents test security fixes, and review-walkthrough to help engineers review code changes.

Post-patch-validation is a new skill we wrote to help agents catch incomplete fixes and regressions before submitting patches for review. It was not used in the Patch the Planet work described above.

The skill starts with a vulnerability report and the code before and after the patch. It guides the agent through four tasks:

  • Reproduce the original bug. The agent writes a check that must fail on the vulnerable code and pass on the patched version. A test that passes on both revisions cannot demonstrate a fix.
  • Test another path to the same failure. The skill requires at least one distinct variant based on the bug’s root cause, such as a different caller or a cleanup path.
  • Check for regressions and new vulnerabilities. It compares behavior that should remain unchanged and tests security properties around the modified code. The plan must also include project tests, a sanitizer check, or a bounded fuzzing run.
  • Treat broken test runs as inconclusive. A failed build or missing dependency must not be mistaken for evidence that a vulnerability was reproduced.

Failed checks give the agent specific problems to investigate and repair before submitting its patch. The skill saves the tests and results so maintainers can see what was checked.

To try post-patch-validation, install the skill and give your agent the vulnerability report and the vulnerable and patched revisions:

“Use post-patch-validation to validate the patch in HEAD against <vulnerable-commit>, using the vulnerability report in <report-path>.”

Review-walkthrough helps engineers review the patches they are responsible for merging. It turns a branch’s complete diff into an interactive walkthrough that explains the changes in a logical reading order. Review findings appear beside the relevant code, where engineers can inspect them and draft their own comments. The walkthrough can also prepare a GitHub review for submission. Follow the quick start to generate a walkthrough for your branch.

These releases join our other public agent skills for improving security patches:

We publish these methods so other teams can use them to examine and improve their own patches.

What a useful patching benchmark should measure

A useful patching benchmark should measure whether agents help developers produce correct fixes and how much review those fixes require. The principles in our 2018 guide to evaluating fuzzing research apply here:

  1. Choose a sample that matches the research question. Explain how the sample was chosen and which repair work it represents. Difficult cases can expose failure modes. General failure rates require a representative sample.
  2. Measure the effects of working conditions. Give agents appropriate tools and instructions. Report model configurations and test how reasoning settings affect results. Report misleading prompts and restricted tool access separately.
  3. Make correctness verifiable. Check that patches fix the vulnerability beyond the supplied exploit. Test for security, functional, and performance regressions. Validate grades against expert review and publish the tests, configurations, and results.
  4. Show how results vary. Report per-vulnerability outcomes and variation across repeated attempts. Repeating trials on the same bugs cannot establish that those bugs represent everyday patching.
  5. Measure what agents contribute to the repair process. Compare developers working with and without agents on comparable tasks and under comparable conditions. Measure initial patch quality and the review and revision needed to reach a correct fix.

We are optimistic about AI’s usefulness to defenders. Through Patch the Planet, we are committing engineering time to fixing vulnerabilities alongside the people who maintain the affected software. We examine failures so we can improve our methods.

We will keep putting agents to work on difficult security problems and making the tools and lessons public. We want other teams to test our conclusions and take these methods further. Our goal is to give maintainers without dedicated security teams the ability to find and fix vulnerabilities that would otherwise go unaddressed.

How much oil-market buffer is left?

Hacker News
www.depletion.org
2026-09-15 15:56:56
Comments...
Original Article

Oil is part of everyday life, even when you don't think about it. It's refined into fuels that power cars, move goods, and help farmers grow our food. It's also used to make things like the clothes we wear.

Since the US and Israel went to war with Iran in February 2026, the world has been using more oil than it produces. The Strait of Hormuz and the Red Sea are effectively closed to oil shipping, cutting off roughly 20% of the world's oil supply. Russian refinery strikes and export bans are making the situation worse. The US Strategic Petroleum Reserve is at its lowest level since 1982.

The Depletion Ledger tracks oil supplies, fuel prices, and how the shortage affects people and businesses. You can use the charts below to see what's changed and explore three ways the crisis could unfold. The model estimates how long stored oil could last in each case, and keeps a record of its predictions so you can see how they hold up.

Last updated Sep 15, 2026

Red Sea Persian Gulf Arabian Sea Iran Saudi Arabia Yemen Oman Hormuz Bab el-Mandeb
Hormuz: 4 vessels on Sep 14 (preliminary tracking) · 14 over Sep 12–13 (7/day; The National) · 7 on Sep 10, none carrying crude oil (Kpler) · 102 for the week ending Sep 3 (Lloyd's List) · more than 130/day before the crisis. Bab el-Mandeb: the Houthis held Mokha, Perim, and the Hanish islands as of Sep 14. On Sep 15, they claimed 85 vessels had passed through the strait in 72 hours.

Brent

$105.68

Sep 14 close · +40% vs pre-crisis ~$76

US diesel (AAA)

$6.27

Sep 15 · new all-time record ($6.2694) — sixth straight · +68% vs pre-war $3.72

US gasoline (AAA)

$4.33

Sep 15 · +18¢ in a week (AAA) · +54% vs Jan $2.81

SPR

285.4M

Sep 4 · down 1.2M in a week · down 130.1M from pre-war 415.4M · lowest since Dec 1982

US diesel & heating oil

106.3M

Sep 4 · up 2.1M in a week · 13% below 5-year average · East Coast stocks 28% below last year

Prices

These charts show how oil, gasoline, diesel, and natural gas prices have changed. Brent is a widely used benchmark for the price of crude oil. The gasoline and diesel charts use US national averages; the natural gas charts cover Europe and Asia. Every plotted value comes from a source. Missing readings are left out.

US gasoline prices are up 54%

EIA weekly, national

The national average has risen from $2.81 in January to $4.33, an increase of 54%. Prices peaked at $4.50 in May. Every month since March has averaged above the levels seen before the crisis.

US diesel prices are up 68%

AAA national, sourced points

Diesel cost $3.72 in the last week before the war, on Feb 27. By Sep 15, it had reached $6.27, an increase of 68% and its sixth consecutive record. It passed the previous record of about $5.85, set in June 2022, on Sep 4. This chart shows selected readings. You can see the daily Sep 3–15 readings in the price spread chart below. Diesel prices have risen faster than gasoline prices as supplies of refined fuel have tightened.

Natural gas prices in Europe are up about 153%

TTF, $/MMBtu · filled: EIA weekly futures avgs (Jan–Apr) · hollow: individual contract quotes (May–Sep) · different instruments — a trend, not one series

TTF is the benchmark used to track natural gas prices in Europe. It traded above $28 on Sep 10, eased to $27.00 on Sep 11, and stood at about $27.80 on Sep 14 — the highest levels since December 2022. That's roughly 153% above the price before the strait closed. The Sep 14 value is converted from €81.98 per megawatt-hour to the dollar units used in this chart. Prices have risen as unplanned maintenance at Norway's Asgard and Troll fields and tanker attacks put pressure on supplies.

By January 20, European gas storage had fallen to 48%, compared with a five-year average of 63%. Running that low left Europe buying liquefied natural gas (LNG) for immediate delivery during the season the strait closed.

Natural gas prices in Asia are up about 167%

JKM, $/MMBtu · filled: EIA weekly futures avgs (Jan–Apr) · hollow: assessed spot (May–Sep) · different instruments — a trend, not one series

JKM tracks the price of liquefied natural gas (LNG) delivered to Asia. It reached the high-$28s on Sep 10 — its highest level in roughly two and a half years — and held around $28.50 on Sep 11, about 167% above the price before the closure (JOGMEC). Damaged production units at Qatar's Ras Laffan complex — about 17% of the country's LNG export capacity — are expected to be offline for 3–5 years, forcing Asian buyers to look elsewhere for supplies. With little gas in storage, the region is particularly sensitive to changes in supply and weather.

Brent and WTI prices are up about 40% and 52%

Brent: sourced points (EIA monthly avgs: Mar $103.0 · Apr $117.29 · Jul $83.76) · WTI: weekly (FRED)

Brent rose from $76 before the closure to an intraday peak of $126 in March, then fell to $95 by mid-April and $74 on Jun 30 as hopes for a ceasefire grew. It passed $100 again on Sep 9 as tanker attacks escalated, rose $6.82 to $108.03 on Sep 10, and settled at $104.61 on Sep 11.

Brent closed at $105.68 on Sep 14 after reaching $106.73 earlier in the day. Prices rose after talks between Gulf foreign ministers and Iran were postponed. Reuters also reported that stocks at Yanbu, Saudi Arabia's Red Sea port, could support another five to seven days of exports if the East–West pipeline remains closed.

The gray line shows West Texas Intermediate (WTI), a US crude oil benchmark. Most WTI readings come from FRED's weekly spot series; the Sep 10, 11, and 14 readings are futures closes. WTI is also the crude price used in the fuel price spread chart beside this one. Hollow markers indicate a source's rounded estimate.

The diesel price spread is up about 80%

Retail price less WTI, $/bbl · weekly (EIA/AAA − FRED).

This chart subtracts the price of crude oil from the retail price of fuel, with both expressed in dollars per barrel. The difference covers refining, transportation, and retailing. It helps explain why prices at the pump can keep rising even when crude gets cheaper. Diesel's spread grew from about $89 in January to $161.9 on Sep 15. It briefly narrowed to $148 on Sep 10, when crude prices rose faster than pump prices. Gasoline's spread has also widened, from about $59 to $78–84. This uses retail prices from EIA and AAA, minus WTI from FRED, with futures closes for Sep 10, 11, and 14. The EIA's official crack spread uses wholesale fuel prices, so the values differ. Eight fuel readings have no same-day WTI value; those use the nearest trading day's price, within 1–2 days. No missing prices are estimated.

Supply

When the world uses more oil than it produces, the difference comes out of storage. These charts show how much oil has been withdrawn, how much is left, and how long the US emergency reserve could last under each of the model's three scenarios.

The world is using more oil than it produces

Before the war, the world produced about 4 million more barrels of oil per day than it used. Since the war began, it has had to draw on stored oil every month. The IEA's latest report estimates a full-year supply loss of 5.7 million barrels per day, about 6% of the world's oil, and expects Middle East oil flows to remain below normal until 2027. Global production fell to 100.1 million barrels per day in August, with more than 10 million barrels per day of Gulf production still shut down. Saudi production alone fell by 2.3 million barrels per day that month, to 5.97 million.

World oil balance, million b/d — production minus consumption, EIA STEO Table 3a, reported months (Jan–Aug actuals; the EIA's forecast tail is not shown) · the physical loss peaked at 11.2M b/d of Gulf shut-in in May — demand destruction and non-Gulf supply absorbed most of it · the IEA's observed-inventories count: 507 mb drawn since February — 2.8 mb/d on average, 95 mb of it in August alone.

How much stored oil has been used

Withdrawals and changes in demand, using figures available as of Sep 11.

Global commercial stocks

−400M bbl

year-to-date · EIA est. (Sep 9)

US Strategic Petroleum Reserve

−129M bbl

since Feb 28 · EIA

IEA coordinated release

400M bbl

pulled from 32 countries · IEA

China commercial stockpiles

~2–3M b/d

withdrawals inferred from customs data · official SPR untouched

Decline in oil demand

−2.5M b/d

full-year 2026, cut from −1.6 in the August edition · IEA OMR, Sep 11

Remaining supply shortfall

−1.8M b/d

Q3 2026 forecast — supply below demand · IEA OMR, Aug 12

The Strategic Petroleum Reserve is at its lowest level in 44 years

EIA weekly ending stocks, million bbl

The Strategic Petroleum Reserve (SPR) is the US government's emergency supply of crude oil. It was created after the energy shortages of the 1970s. The reserve held 415.4 million barrels when the war began. The latest report puts it at 285.4 million as of Sep 4. Oil is being released to help make up for supplies that can't leave the Gulf. You can use the withdrawal rate to see how quickly the emergency supply is being used.

The red lines mark the model's reserve thresholds, or floors . At about 300M barrels, some caverns risk damage and can't safely be refilled after a withdrawal. The first report below that threshold was for the week ending Aug 7, at 298.7 million barrels.

The other floors are 250M , the GEF minimum for sustained withdrawals; 180M , the hard operating limit; and 70M , the Department of Energy's stated safe minimum. The model stops withdrawals at 70M.

The dashed lines show what happens if withdrawals continue at 0.45M, 0.70M, or 1.35M barrels per day from the Sep 4 level. The estimates extend to about March 2027.

Withdrawals slowed to about 0.2 million barrels a day in the week ending Sep 4, while diesel stocks rose by 2.1 million barrels, according to the EIA's Sep 10 report. The next report is due Sep 16 and covers the week ending Sep 11.

Three possible outcomes

Estimated odds, updated when specified events occur.

The Saudi bypass pipeline was suspended, and the Houthis held the entire Red Sea coast. An official pipeline restart would return the odds to 10/50/40.

Tanker losses reached 10 per week, Brent passed $100, and Jazan was affected.

A shipping exclusion zone was imposed, and a base in a third country was hit for the first time.

Reported Hormuz traffic of 8.6M barrels per day was not backed by vessel tracking, which showed 7% of normal transits.

Initial model: about 65% odds of de-escalation.

corridor holds standoff corridor lapses

Tankers can pass through Hormuz under an Iran–Oman agreement or with US escorts. Traffic gradually returns to normal over one to two quarters.

In this scenario, Brent moves toward $70–80 and reserve withdrawals slow to about 0.45M barrels per day. Stored oil lasts longer.

The war continues at its current intensity. Tanker attacks and shipping restrictions persist, some Iranian infrastructure remains offline, and the damaged Saudi bypass has no restart date. The strait remains partly open.

Brent stays in the $95–125 range, and reserve withdrawals run at about 0.70M barrels per day. Global stocks keep falling, with shortages developing later.

The disruption becomes a sustained closure or the fighting escalates. Tanker losses rise, shipping restrictions remain, and the bypass, Abqaiq, and Jazan stay offline for months.

Brent rises above $130, and reserve withdrawals reach 1.35M barrels per day. Shortages spread from the US East Coast to Russia, Europe, China, and aviation fuel.

These odds are based on judgment. They change when specified events occur, such as a pipeline restarting or a shipping agreement breaking down. A quiet week alone doesn't change them. You can read the rules and the events being watched on the model page .

The stockpile, in context

This chart puts the current reserve in perspective. It held 727 million barrels at its December 2009 peak and 294 million at the previous low in December 1982. It held 285.4 million on Sep 4. The green line marks the level before the war. In the model, withdrawals stop at the 70 million barrel floor.

727 · 2009 592 · 1995 294 · 1982 415.4 pre-war 285.4 now
SPR, million bbl (EIA w/e Sep 4) Down 130.1M since Feb 27 · lowest level in 44 years red lines: the floors — 300 · 250 · 180 · 70 (hover a line)

Supply snapshot

Large withdrawals began on Apr 3, when the reserve held 413.3 million barrels. They reached about 1.2 million barrels per day in May. In the week ending Sep 4, withdrawals averaged about 0.18 million barrels per day, down about 60% from the previous week. That's below the 0.45 million barrels per day assumed in the corridor-holds scenario.

US diesel and heating-oil stocks were 13% below their five-year average on Sep 4, according to the EIA.

−130.1M (−31%) since pre-war 415.4M

Lowest since Dec 1982 · down 1.2M barrels in the week ending Sep 4

US diesel & heating oil

106.3M bbl

Up 2.1M barrels in the week ending Sep 4; 13% below the 5-year average (EIA summary)

East Coast stocks are 28% below last year

At the 5-year average (EIA summary, week ending Sep 4)

Refined fuels remain in shorter supply than crude oil

Global inventories

−400M bbl YTD

EIA estimate, Sep 9

falling through end of 2026

How quickly the reserve is shrinking

The latest withdrawal rate and estimated dates for reaching the reserve thresholds, using the EIA's Sep 10 report for the week ending Sep 4.

SPR withdrawal rate

0.48 million barrels/day

4-week average, EIA weekly report (week ending Sep 4) · latest single week: 0.18.

Weekly withdrawals: about 9M barrels at the late-May peak, falling to 1.2M by Sep 4. The earlier 9.9M peak in the week ending May 15 falls outside this 16-week chart.

Estimated date at 250M barrels

≈ Sep 30 2026

The corridor-lapse scenario has the highest odds, at 50% as of Sep 11. It assumes withdrawals of 1.35M barrels per day from the reported 285.4M barrels on Sep 4. The standoff scenario, at 40%, reaches the same threshold on Oct 24.

Next floor — the 180M operable limit: ≈ Nov 21, 2026 on the lapse path, Feb 1, 2027 on the standoff path.

Refining

Crude oil has to be refined before it can be used as diesel, gasoline, or jet fuel. That makes refinery capacity just as important as the amount of oil available. US refineries are processing more oil than last year, while processing has fallen elsewhere and strikes continue to damage Russian refineries. The IEA describes the global refining system as “stretched to the limit.” Atlantic Basin refining margins reached records in August, led by diesel.

US refineries have run above 95% of capacity since June

Utilization, % of operable capacity, weekly.

US refineries have operated above 95% of their available capacity every week since Jun 5, reaching 98.0% in the week ending Aug 28. The comparable period in 2025 averaged 90.8%. Fuel exports also reached a record 8 million barrels per day in August, according to OPEC. US plants are working close to capacity, but shortages in Europe, Asia, and Russia continue to put pressure on fuel supplies.

EIA Weekly Petroleum Status Report (WPULEUS3), week ending Friday · utilization = gross inputs ÷ latest reported operable capacity (EIA's definition) · 2025 line = same Jan–Sep window · in mb/d: runs 16.3–17.3 (STEO 4a), above 2025 in every month · see the fuel price spread chart in Prices for the effect on costs.

Global refining

Refineries elsewhere are processing less oil. These figures from the IEA's August and September reports show the size of the decline.

Global refining, August

81.4 mb/d

summer peak, −4.2 mb/d below a year ago (OMR, Sep 11)

Refining forecast, 2026

−2.6 mb/d

IEA forecast vs 2025 (OMR, Sep 11)

Q3 forecast revision

−370 kb/d

the quarter's further cut (OMR, Aug 12)

Estimates put Russia's lost refining capacity at 20–54%

estimated capacity remaining, % of pre-strike

Strikes on Russian refineries are reducing the amount of fuel available to other countries. The chart shows about 75% of capacity remaining by mid-April and about 70% by Aug 29, according to the Moscow Times. The red bar shows how much current estimates differ: Ukraine's General Staff puts the capacity lost at 42.74%, Russian Forbes at 54%, and the IEA at more than 20%. You can compare these estimates with the reported outages and export restrictions below.

Russia snapshot

Aug 29, Moscow Times — up from ~25% in April

Early September estimates of capacity offline: 42.7% (Ukraine's General Staff) to 54% (Forbes)

record month, near-daily (Bloomberg, Aug 29)

Kirishi — Russia's #2 plant

halted

~400K b/d, its only NW plant, two strikes in a month (UA.NEWS, Sep 2)

Ryazan (Rosneft) — Moscow's main supplier

down

~156K barrels/day; both primary units offline since Sep 6, with repairs expected to take several weeks (Reuters, Sep 10)

primary capacity, satellite imagery (Bloomberg, Aug 25)

every major Lukoil refinery is offline

Novorossiysk — main Black Sea port

hit

fuel-oil terminal + the city, 4 killed (Sep 8–9)

crude outflow 800 → 350 kb/d, Jul → Aug — all three export directions now under attack

nationwide caps; Moscow 90% out of AI-92 (Euronews, Aug 20)

gasoline contracts unmet

>50%

TASS, Sep 3

YoY, Q1 official

Lost production is also reducing government revenue

The export ban calendar

Russia's export restrictions leave less fuel available to other countries during the heating season. Sep 30 is the next deadline for the diesel export ban, and the jet-fuel export ban is scheduled to take effect on Nov 30. Damaged refineries also leave Russia with less fuel for its own gas stations and military as winter approaches.

Sep 30, 2026

diesel exports

Nov 30, 2026

jet fuel exports

Jan 31, 2027

gasoline & the remaining diesel

Imports aren't making up the difference. Fuel shipments on the Belarus rail route are running at 25 times last year's volume. The shortage also affects countries that rely on Russian fuel: Kyrgyzstan imports more than 90% of its gasoline from Russia and has about six weeks of reserves left.

Effects on the economy

Higher energy costs affect more than your fuel bill. They can slow business activity, keep inflation high, and eventually make food more expensive. People and businesses also use less oil when they can no longer afford it. Economists call this demand destruction . The charts below show that decline alongside recession estimates, interest rates, and the possible effects on food prices.

World oil use fell about 4% in May compared with last year

World petroleum & liquid fuels consumption, mb/d · monthly (EIA STEO, Sep 9 release) · Jan – Aug, 2026 vs 2025.

World oil use fell 4.3 million barrels per day below last year's level in May, a decline of about 4%. The gap narrowed to 3.6 million in July and 0.8 million in August. Using less oil helps contain prices, but it also reflects the strain on the economy. The IEA now expects demand to fall by 2.5 million barrels per day across 2026, compared with its August estimate of 1.6 million. It expects the quarterly decline to ease from 5.3 million in Q2 to 3.4 million in Q3 and 2.0 million in Q4, followed by a 2.6 million barrel per day recovery in 2027. The losses are concentrated in fuels such as diesel and in raw materials used to make chemicals, especially in Asia. These estimates aren't universally agreed on. OPEC expects demand to grow by 0.4 million barrels per day in 2026, a difference of 2.9 million between the two forecasts.

EIA STEO Table 3e (Sep 9 2026, forecast completed Sep 3) · Jan–Aug 2026 are actuals in that release · world/regional values are EIA estimates (apparent consumption, incl. refinery fuel & bunkering) · Sep 2026 onward is forecast, not shown.

Where oil use fell in July

Million b/d, July 2026 vs July 2025 — same table.

16.4 → 15.6 mb/d · part of Asia & Oceania; using stockpiles to support consumption

Estimates of US recession risk

These estimates, published between June and September 2026, put the chance of a US recession over the next 12 months at 15% to 50%. Goldman Sachs has kept its estimate at 15% since Jun 26, down from 30% in late March. It repeated that estimate on Sep 14. Polymarket puts the odds at 32%, but covers a longer period, through the end of 2027. Keep that difference in mind when comparing the figures.

Markets expect the Fed to raise rates

The Federal Reserve can cut interest rates to support a slowing economy, but persistent inflation makes that harder. August producer prices rose 5.4% from a year earlier. Consumer inflation held at 3.4%, with energy up 16.3%, while core inflation eased to 2.4%. Markets expect a rate increase at the Sep 16 meeting. Higher borrowing costs would add pressure as oil reserves are drawn down through the winter.

Fed funds, July FOMC

3.50–3.75%

9-to-3 hold; officials 'see the need for a hike if inflation doesn't cool'

Odds of a September rate increase

≈86%

futures markets, Sep 11 · up from about 72% Thursday after core CPI exceeded expectations · Polymarket: 62%

Aug PPI (BLS, Sep 10)

5.4% YoY

+0.4% for the month · July revised to 4.8% annually · energy +4.2%, diesel +24.1% annually · 10-year yield highest since Oct 2023

The European Central Bank says Germany and Italy could both be in a technical recession by the end of 2026 if the conflict continues.

Borrowing costs and inflation

You can see the wider effects in borrowing costs and everyday prices. Both are at multi-year highs. Together, they help explain why the Fed is considering higher interest rates even as the economy slows.

US 10-year Treasury yield

10-year US Treasury yield, % · chart through Sep 14 · weekly closes (FRED) · Sep 11 and 14: Yahoo closing values.

The 10-year Treasury yield is the rate the US government pays to borrow for a decade. It also influences mortgage and business loan rates. It has risen by about one percentage point, or 100 basis points, since before the war. Inflation and concerns about government debt are both putting pressure on rates. The national debt exceeds $40 trillion, and $8.4 trillion of Treasuries must be refinanced by year-end.

The 10-year yield closed at 4.975% on Sep 11 after briefly reaching 4.992%, its highest level since October 2023. It eased to 4.961% on Sep 14. Al Jazeera reported that it reached 5.02% during trading on Sep 15, its highest level since 2007, as traders anticipated a Federal Reserve rate increase. That latest quote isn't plotted; the chart shows closing values through Sep 14.

The 2-year yield was 4.63% on Sep 11, its highest since July 2024. The market's measure of expected inflation over the next 10 years eased to 2.36%. That suggests investors are seeking higher returns after inflation, even as their inflation expectations have fallen.

US producer prices are rising faster than consumer prices

CPI (retail, amber) + PPI final demand (wholesale, blue) · % year-over-year · monthly (BLS) · Jan – Aug.

Consumer inflation rose from about 2.4% to 4.2% in three months as fuel became more expensive. It eased to 3.4% in July and stayed there in August. Prices rose 0.4% in August alone, with gasoline's 3.9% increase accounting for more than a third of that rise. Core inflation, which excludes food and energy, eased from 2.5% to 2.4% over the year, although its 0.3% monthly increase was above expectations. Producer prices rose faster, peaking at 5.9% in May and increasing 5.4% in August compared with a year earlier. July's reading was revised to 4.8%. Energy explains much of the increase, with diesel up 24.1%. These costs can reach businesses before they show up in household spending (BLS, Sep 10–11).

Higher energy costs can take more than a year to reach food prices

Natural gas is used to make ammonia, a key ingredient in nitrogen fertilizer. Higher gas and shipping costs can make food more expensive, but the effects may take more than a year to reach your grocery bill.

The timeline uses the 2007–08 and 2022 shocks to illustrate when those costs could reach food prices. It's a rough historical comparison, not a forecast from this model.

Gulf–India tanker freight

+411%

$4.34/bbl in Aug vs pre-war (Frontline)

TTF gas (Europe)

€82/MWh

Sep 14 · above $28/MMBtu Sep 10 (JOGMEC) · highest since Dec 2022

JKM gas (Asia)

$28.5/MMBtu

Sep 11 · high-$28s Sep 10 (JOGMEC) · highest in ~2.5 years

Higher energy prices can raise freight and fertilizer costs, affect planting and harvests, and eventually raise food prices. The bars show approximate windows based on the 2007–08 and 2022 shocks. Food prices could remain under pressure after oil reserves have reached the model's thresholds.

What to watch next

Upcoming reports and decisions that could change the outlook.

Any day

An official repair estimate for the East–West pipeline. AP reports 3–5 weeks. Analysts quoted by the Wall Street Journal estimate lost flow at more than 2.5 million barrels a day. Reuters reports that stocks at Yanbu, Saudi Arabia's Red Sea port, could support another five to seven days of exports.

The standoff scenario depends on this bypass. An official assessment that repairs will take only days would reverse the Sep 11 change in odds.

Any day

A new date for the Gulf–Iran talks in Salalah, postponed from Sep 14. Iran says Saudi Arabia requested the delay because of events in Yemen.

Resuming the talks could help restore tanker access through Hormuz.

Any day

Shipping conditions after the Houthis captured the Hanish islands on Sep 13–14. The Houthis claim 85 vessels passed through Bab el-Mandeb in 72 hours. Missile and drone attacks on Saudi cities wounded 13 civilians on Sep 13–14.

An attack on a non-Saudi vessel would raise the model's odds that the shipping corridor closes.

Any day

How banks respond to the Sep 14 sanctions on Russia's VTB; Treasury is meeting with financial institutions this week.

If banks stop handling VTB's payments, Iran loses channels for receiving oil revenue.

Sep 16

A verified count of vessels passing through Hormuz. Gen. Wright claims tankers are carrying 10 million barrels a day under Navy escort. Preliminary tracking shows four vessels on Sep 14, down from 14 a day a week earlier.

The claimed volume is roughly half the pre-war flow. The next count will help assess whether shipping activity supports that claim.

Sep 16

The Fed's rate decision — futures put a 25-basis-point increase at about 86% (Polymarket, 62%).

A hike would add borrowing-cost pressure on top of fuel prices.

Sep 16

The EIA's report for the week ending Sep 11. Watch for another week of slower reserve withdrawals and rising diesel stocks. The previous report showed withdrawals of 1.2 million barrels, down about 60% in a week. Diesel stocks stood at 106.3 million barrels.

The weekly figures help show whether supplies are tightening or recovering.

Sep 30

Russia's diesel export ban expires unless extended; the US-led coalition completes its withdrawal from Iraq; prediction-market bets settle.

Several deadlines in one week, each with supply or price implications.

Oct 7

The first EIA monthly outlook after the tanker attacks — its roughly $90 forecast for the second half is $16 below the latest Brent close.

Watch whether its view that shipping remains constrained but open survives.

Nov 3

The US midterm elections — President Trump has said the war will end just after the elections.

Watch whether fighting and diplomacy match the administration's stated timeline.

Nov 30

Russia's jet-fuel export ban takes effect.

Aviation fuel supplies tighten further as the world's remaining stocks run low.

Where shortages could appear next

These are estimates of where shortages could become more severe if the crisis continues. The first two dates use published stock levels; the others are inferred from customs and inventory data. Allow for uncertainty of a week or two.

Sep 14–21

US East Coast — Diesel and heating-oil stocks could fall below a month of supply. They are already 27% lower than last year.

Sep 30

Russia — The diesel export ban expires. With more than 30% of refining capacity damaged, Russia may have little fuel available to export.

≈ mid-October

China — Commercial oil stocks could begin to fall faster than normal.

≈ late October

Europe's oil hubs — Rotterdam-area diesel stocks could fall below 8.5–9M barrels, a level that would put pressure on trading. If the strait closes fully, the estimate moves up to mid-October.

≈ late October

Europe, at the pump — Shortages could reach consumers, with price increases putting pressure on governments.

Nov 10–30

Air travel — Russia's jet-fuel export ban begins Nov 30. The world's remaining stocks amount to about 26 days of flying.

Bernie Sanders and Steve Bannon call for curbs on AI at ‘pro-human’ summit

Guardian
www.theguardian.com
2026-09-15 15:51:39
Leftwing senator and far-right strategist rail against tech oligarchs but offer competing visions on Chinese ‘cold war’ The progressive senator Bernie Sanders and rightwing strategist Steve Bannon have called for restrictions on artificial intelligence (AI) but offered competing visions for what the...
Original Article

The progressive senator Bernie Sanders and rightwing strategist Steve Bannon have called for restrictions on artificial intelligence (AI) but offered competing visions for what they termed a “cold war” with China.

Speaking at the “Pro-Human Assembly” in Washington on Tuesday, the ideological adversaries were united in warning of the potential dangers of AI and demanding stringent guardrails against Silicon Valley’s “oligarchs”.

But the populist pair offered very different perspectives on how to resolve the AI race with China, which Donald Trump and other Republicans have cited to justify moving full speed ahead with the technology.

Sanders, an independent senator from Vermont, noted that Trump is due to host the Chinese president Xi Jinping in Washington next week. Sanders called for international diplomacy and a global agreement, including with Beijing, to ban the development of super-intelligent machines.

He told an audience gathered in the ballroom of a Washington hotel: “A super-intelligent AI that escapes human control will not be an American problem. It will not be a Chinese problem. It will be humanity’s problem.

“That is why I very much hope that at their AI summit next week, President Trump will negotiate a comprehensive treaty with President Xi to establish a pause on advanced AI development and a ban on super-intelligence.”

Sanders drew a comparison with US president Ronald Reagan and his Russian counterpart Mikhail Gorbachev seeking to avoid nuclear conflict at the height of the cold war, eventually signing a treaty that eliminated an entire class of missiles.

He said: “They understood that a nuclear war would not be good for the Soviet Union, not be good for the United States, not be good for the world, and they came together for a nuclear treaty. With the survival of humanity at stake, they found a way to sign that treaty.”

Bannon, who spoke directly after Sanders but did not share a stage with him, also used the cold war analogy but did not arrive at the same conclusion.

The host of the War Room podcast and former White House chief strategist under Trump called for an aggressive economic and technological “quarantine” of China, demanding the immediate expulsion of Chinese nationals from US research laboratories, an end to the training of Chinese engineers and technologists and the total severance of capital flows.

“Did our fathers and grandfathers, did they underwrite the Soviet Union?” Bannon asked. “Did they train Soviet Union scientists? Did they take Russian kids over here to college to let them go to the best universities and get the best degree? Did they have Wall Street slobbering to do their deals? Did they have every bank there to lend them money quicker than to lend anyone in this audience?

“The answer is they did not. They stood up to the Soviet Union and the first thing to stand up to them was to choke them off and that’s what we have to do today to the Chinese Communist party. No deals, no agreements, you’re out of the AI business because we’re going to shut your ecosystem down.”

Demanding immediate executive action to isolate Beijing’s tech sector, Bannon said: “No chips, no black marketing chips, no free ride off American technology, no steal on American tech … To hell with the Chinese Communist party.”

Only then, Bannon argued, can the US have the time necessary to consider the proper regulation of the tech sector. He delivered a scathing verdict on major technology corporations and executives, accusing them of gambling with public safety for private profit.

Bannon accused Silicon Valley of seeking to “socialise all the risk with the American people including potential bailouts” while keeping all financial upside. “The American people are not going to be supplicants to these tech oligarchs,” Bannon said, praising grassroots campaigns that previously defeated federal pre-emption bills aimed at blocking state-level AI regulations.

Bannon has long praised Trump as a historic figure and endorsed his lie that the 2020 presidential election was stolen. But on Tuesday, while claiming that executive action is required rather than legislation by Congress, he made only a glancing reference to the president who has dismissed AI concerns as a “hoax” and “sick conspiracy”.

Sanders, for his part, did criticise Trump and Congress along with the concentration of power in Silicon Valley. “Despite living in a so-called democracy, the public has had virtually no input into the AI revolution that is transforming the world in which they live,” he said.

“Congress, under both Democratic and Republican control, has been asleep at the wheel and we now have a president whose ignorance regarding this issue is truly embarrassing. Virtually all of the decisions regarding the development of AI have been made by a handful of the richest people in the world … whose chief goal is to achieve even more wealth and more power than they already have, no matter what the impact their work has on ordinary people.”

Sanders said he would introduce legislation next week to permanently ban the development of artificial super-intelligence, an AI mind smarter than any human being, and put an immediate pause on advanced AI development until clear safety rules are in place.

Recent surveys have found ⁠majorities of Americans worry that AI could be used to eliminate jobs, raise costs for families or otherwise cause ​social harm. Residents in several states have also challenged ‌proposals for new data centres, a ‌critical component of the AI boom.

The Pro-Human Assembly, taking place against a backdrop of growing bipartisan anxiety over AI risks, also heard from members of Congress, labour leaders and religious representatives.

Congressman Greg Casar, who is co-sponsoring the legislation with Sanders, accused AI billionaires of deploying massive political spending to stifle regulation.

“Even as the threat becomes clearer, there are many powerful people fighting to prevent action,” Casar told attendees. “AI billionaires are spending millions through the Leading the Future Pac to scare politicians into silence. The top Republicans who control the House of Representatives are not lifting a finger.”

Casar added: “Leading in AI diplomacy does not mean waiting around for an international agreement on dangerous AI. Putting up guardrails now is the best way to show we are serious about getting to a deal.”

Republican congressman Chip Roy similarly expressed scepticism over concentrating decision-making power within the executive branch or tech boardrooms. “The president yesterday said he should be making the decisions about AI. I disagree. I don’t think big tech CEOs should be making all the decisions about AI.

“I don’t think Congress should be making all the decisions about AI. If we allow technology to fundamentally alter and transfer and change the way we make decisions about our society and our Republican form of government, then what do we have left?”

Building a Linux GPU Driver for the M4 Mac Mini in One Month

Hacker News
codyho.dev
2026-09-15 15:30:03
Comments...
Original Article

Previous blog post: https://codyho.dev/blog/hypervisor-macbook-neo/

What We Did

TL;DR: Niklas and I built a fully OpenGL ES 3.0 compliant GPU driver for the M4 Mac Mini and MacBook Neo in about a month, a process which normally takes years. Here is Chrome and Firefox running WebGL on the M4 Mac Mini with working compositing:

Chrome and Firefox running three.js WebGL demos on our driver.

Most importantly, the driver is fast enough to run Minecraft at 200fps:

Minecraft running at 212fps on the M4 Mac Mini.

Building this driver involved reverse engineering the AGX’s (Apple’s name for the GPU) incredibly complicated firmware ABI and user-space components. This was all done in a transparent, verifiably clean room manner using well established techniques. The code is not yet ready for end users, but we are looking to get it to end users as soon as possible.

How We Did It

Previously, I built a hypervisor to reverse engineer macOS. Now the goal became to actually do something useful with it, and what better target than writing a GPU driver. The GPU is effectively a requirement for any modern system, otherwise everything needs to be CPU rendered which is orders of magnitude slower and less power efficient. Our goal was to implement conformant OpenGL (and soon, Vulkan) drivers for the M4 Mac Mini and MacBook Neo.

Normally, building a GPU driver is an endeavor that takes years; our goal was to do it in days. It turns out that days was overly optimistic, but weeks is still a massive improvement. In those weeks we have:

  • Reverse engineered the M4, A18 Pro, and (mostly) M5 user space using only live probing, discovering hardware-supported features and instructions not emitted by Apple’s driver
  • Built a fully working user-space driver, including a new custom IR/shader compiler, command stream builder, and many more components
  • Reverse engineered, from scratch, the full AGX firmware ABI using traces from the hypervisor I previously built
  • Implemented a full Linux kernel driver for said firmware ABI

Throughout this process, we have not looked at any Apple binaries, only hardware traces (from our hypervisor) and shaders we built ourselves. For user-space graphics RE, we were careful to treat any required Apple blobs as opaque objects. We had a friend write documentation on these blobs 1 so we could write a clean room implementation ourselves (which was mostly built by just blindly trying stuff until it worked). We have published all of our experiments so that anyone can verify the provenance of our work (see the twin agx-re repos under Deliverables ).

This blog post is divided into two parts, user and kernel space. This mirrors the split in all modern GPU drivers: the kernel is responsible for interfacing with the firmware, allocating buffers, and managing scheduling, while the actual contents of those buffers and what is being scheduled are opaque. User space is responsible for actually understanding how the GPU works and filling those buffers with stuff.

Kernel Space

On Apple Silicon, the kernel driver does not interface directly with the hardware. Instead, it talks to the GPU firmware running a custom RTOS called RTKit. That means that the first step to a kernel driver is not talking to hardware, it’s figuring out the firmware ABI.

The firmware ABI was by far the most annoying part of this project, because rather than doing the sane thing of coming up with a reasonable ABI with nice interfaces, Apple essentially took a regular kernel driver, cut it in half, and then put half of it in the AGX and called it firmware, with the other half of the kernel driver communicating using shared structs in memory. Many of these structs have firmware owned fields (which we must never modify and which we must learn from reverse engineering) interleaved with host controlled fields. For an idea of how complicated the ABI is, this is what the shared memory tree looks like on the M1/M2:

The M1/M2 firmware ABI

Asahi Lina famously figured all of this out over grueling 12-hour days to build the M1/M2 kernel driver, an amazing technical accomplishment. Unfortunately, the A18 Pro firmware ABI (I started my RE work on the MacBook Neo and later pivoted to the M4 Mac Mini) is significantly more complicated than the already very complicated M1 firmware ABI:

The A18 Pro firmware ABI

What the F@!#, Apple. Note how the A18 has:

  • 1.5x as many structs
  • twice as many pointers
  • a significantly more complicated process for submitting work

There are many other issues that add friction to the RE process 2 . I did have some documentation on the firmware ABI, but it was highly incomplete and honestly was not very useful 3 .

My approach was simple and based on the approach used to successfully reverse engineer the M1/M2 machines: watch what macOS did, replay it, then try to do it ourselves, which is made possible by the hypervisor.

When I described this approach to the LLM, it took replay extremely literally: the first thing it did was wait for the first firmware visible event (these are called “kicks”), then saved a copy of the entire GPU memory state . After a reboot, it copied the saved memory state straight back into host memory, performed the kick, and saw the output pages change. It would then try to reconstruct these objects in code, following all the pointers and making sense of the contents. Over successive experiments, Codex would reduce the number of pages it copied until there was no more replayed state and everything was built from source. 4 Amazingly, I noticed Codex had good taste regarding when it should poke the hardware some more and when it should just run the hypervisor and capture the state itself.

There were three major issues, and all were caused by our inability to get a clean capture of host work:

The first issue was render work submitted after the GPU firmware started. We could prestage work before the firmware started, start the GPU, and that work would be completed as expected, but once the firmware started any work submitted would just be ACKed and retired without actually doing anything. Once the firmware has started, capturing state is much harder because everything becomes dynamic and the firmware becomes a stateful object with state you can’t easily replay.

I had to step in at this point and examine Codex’s process. It turns out it was trying to replay a capture very late in the AGX’s lifecycle, where there had already been many previous events. When I told it to choose a capture far earlier in the AGX’s lifecycle, the very first capture after firmware start, Codex was able to almost immediately discover the issue (it was missing a single byte descriptor). This took a few days.

The second, and only major blocking, issue was compute. The AGX, broadly, supports two kinds of work: compute and render. In the regular GUI path, compute work is only scheduled after a significant amount of render work was already executed. Thus, it took a long time to get a clean capture of a compute workload, and when Codex finally did it was 336 MB and impossible to replay (it tried, for a long time). It also tried to construct the objects itself by looking at the capture, and spent over a week doing this, but was ultimately unsuccessful. There was just too much nonsense to sift through. This was exacerbated by issues on my side– after getting render working, I expected that submitting compute work would be simpler (the firmware ABI for compute is indeed simpler, so I was correct here), but lost my humility and thought it would be a cakewalk that would only take a few hours. Thus, I didn’t scaffold out the task properly for the LLM.

The fix actually was given to me in another Codex session. In essence:

  1. Disable the GUI by booting into single-user mode; this means no render work would be done.
  2. Install a LaunchDaemon to run at the earliest possible point, the moment Metal (Apple’s proprietary graphics framework) became available.
  3. Run a tiny Metal program that we supplied
  4. Capture and replay this tiny, pure compute trace.

The trace was captured successfully. Within a few hours, Codex had deconstructed it, and within a few days, Codex had compute working. As for why the original compute codebase didn’t work… Codex has no idea. The working one and the broken one look very similar.

In hindsight, this should have been the strategy from the start– smallest possible capture, run in single-user mode so as not to perturb results. I learned from my mistakes here for the final issue:

Partial renders ended up being one of the hardest things to figure out. They occur when the Tiled Vertex Buffer (TVB) isn’t large enough to store the current geometry (ie, there’s just too many triangles to draw). In these cases, there are two options, and the driver needs to support both: either increase the size of the TVB, or perform a partial render, ie, render part of the geometry, then reload the buffer with the rest of the triangles, and finish the partial render. These partial renders turned out to be very, very finicky, even more so than the rest of the work because they essentially mean adding save and resume to the GPU driver.

The workflow I discovered earlier came in very handy here. Codex was able to replay one partial render transaction, and then modified our Metal shader to perform multiple partial renders (this is pretty easy by just hammering a single tile with thousands of triangles until a partial render is triggered) and then learned how to replay these. Once Codex had a successful replay, it was only a matter of time until it learned how to build it ourselves.

Building the Kernel Driver

Moving from a Python prototype driver to a fully featured Linux driver took three days, and one of those days was almost totally wasted because Codex, for some reason I still do not understand, chose to tackle partial renders first (by far the hardest task) instead of doing compute first (the easiest task). Once I told it to do compute first, everything went smoothly.

At all high level, the entire process was, basically:

  1. Rewrite the existing drm-shim in Rust following the exact same pattern; this gives us a synchronous Rust driver.
  2. Rewrite the frontend to be asynchronous; the actual GPU submission remains synchronous.
  3. Refactor the GPU submissions to be asynchronous and, instead of polling, listen for firmware events and associate work with a fence
  4. Implement some low-hanging optimizations, such as batched work submission.

This is all pretty routine engineering work that LLMs are definitely capable of.

The only notable thing I found is that Codex aggressively used the hypervisor to debug why its code didn’t work, including capturing the full address space and comparing it to known good samples. This sort of systematic debugging is why Codex is by far my favorite coding agent.

User Space

The A18 Pro user space is very different from the M1/M2; it has new descriptor formats, a new ISA, and a bunch of other new things. Aside from being a tile based deferred renderer designed to run Metal, it’s just a different GPU.

The good news is user-space RE has a very well defined process. Simply write a small Metal program, compile it, run it, see what changed, then take it apart and start fiddling with the bits until we understand what all of them do. If you’re thinking this sounds like the sort of boring, repetitive, rote work that LLMs are very good at, you would be correct.

The RE work occurred in two phases. For the first phase, I had Claude look at every possible Metal program it could find and trying to build a disassembler, assembler, and understand the format of all the other descriptors/command streams/etc required for the GPU driver. I had Claude enumerate everything, including stuff Linux can’t use (like tessellation) for completeness. This was successful, but just because Claude could disassemble and then reassemble programs doesn’t mean it knew how to build one itself. When trying to close this gap, ie, understand every instruction enough to actually be able to compile our own arbitrary programs, Claude did a horrible job and made basically zero progress.

At this point, Niklas finished his drm-shim for the M4 Mac Mini and joined me for the second phase of user-space RE. We had two different approaches to actually finishing the user-space driver:

My approach was to prioritize hardware RE, and focus on just figuring out how the hardware and all the instructions worked. Then I would write a spec and let the LLM implement it, hopefully ending with full OpenGL and Vulkan compliance. This means that most of my LLM’s time was spent writing experiments on hardware, not actually implementing Mesa code. The idea was that once I understood the hardware, everything else followed.

Niklas took a different approach, that I’d describe as “Mesa first”. Essentially, he tried to build out Mesa first and would only do RE in order to build out some functionality. His time was split between building and testing Mesa, and performing RE.

It turns out that Niklas made significantly faster progress than I did, because my agent would spend a lot of time on minor, inconsequential tasks in the name of completeness. By contrast, his agent was grounded by the need to actually build Mesa, so it used time and resources a lot more effectively. He ended up moving so much faster than me that ended up just trying to support his work by investigating any behavior he didn’t yet understand.

This was one big limitation of Codex I noticed. The best word I can think of to describe it is “pedantic”– it is extremely thorough all the time, which can be a major benefit in some scenarios, but other times it gets stuck in the weeds on some random tangent to the detriment of the overall goal.

During our RE, we found behavior that was supported by the hardware but not supported by Metal; this was found by directly messing with the bits of the different instructions and extrapolating what might exist based off what we know did exist, just like Alyssa Rosenzweig did when REing the M1/M2. This included:

  • A native single-instruction 64-bit add
  • Anisotropy to 128x (Metal caps at 16x)
  • A new mode of the matrix unit
  • 7-bit immediate support for uniform_mov

Mesa Development

There are a few things that massively work in our favor when building out user-space graphics. Most notably, the Khronos compatibility test suite (CTS) is already an exhaustive corpus of tests our driver must pass. In other words, the hardest and most sensitive part of working with LLMs, giving them good tests to ground them, is already done for us.

Additionally, Mesa already has great abstractions that make our lives significantly easier. This is what the modern OpenGL stack on Linux looks like:

Diagram of the modern Linux graphics stack.

All we have to do is translate between Gallium, Mesa’s internal API, and AGX hardware semantics. One of the biggest parts of this process is translating from NIR, Mesa’s internal IR that’s quite similar to LLVM IR, to the AGX’s proprietary ISA. As a bonus, this compiler can be reused for a future Vulkan driver.

Niklas was able to slowly iterate through OpenGL features, REing the user space as he went, until he finally achieved full OpenGL ES 3.0 compliance (the unsupported tests are optional extensions):

The OpenGL ES 3.0 conformance test suite passing on our driver.

Throughout the process, we benefited from the existing M1/M2 work: while the exact hardware semantics differ, the overall shape remains similar and thus many of the right tradeoffs/decisions were already made for us. Throughout my time building this driver, it became clear that Alyssa Rosenzweig and the others who built the M1/M2 driver are utter wizards– hats off to them!

Deliverables

Mesa: https://github.com/niklassheth/mesa

Linux Kernel Driver: https://github.com/GravityLinux/linux/gravity-m4

User-Space RE Documentation (horrible pile of LLM slop, but functional): Cody Niklas

Remaining Work

Vulkan 1.4, OpenGL 4.6, OpenGL ES 3.2, OpenCL 3.1, Direct3D 12 (via Proton), and ray tracing are all in scope. We want our driver to be as good as the best graphics drivers in the world.

Additionally, Niklas and I want to upstream all of this, but there are some significant obstacles. We used the unmodified Asahi UAPI, so there are no policy issues with Mesa upstreaming, but it needs far more testing, human review, and to be refactored into a reviewable PR. We also expect significant skepticism given that this is likely the first ever fully LLM-written GPU driver, and that our code will be held to a higher standard than human-written code. We are ready for these challenges, but they are primarily human and nontechnical, which LLMs cannot help with.

The Linux kernel driver will be an even bigger problem, since the M1/M2 driver is not yet upstream, and practically we are not in a position to change this. We think the best approach here is just to wait for that driver to be upstreamed, and then upstream our driver after M1/M2 is upstream (after all the required refactoring + review + decomposition + whatever). This may be a while unfortunately.

When Can I Use It?

Patience young grasshopper, we’re looking forward to getting this code into your hands soon enough, and the wait may be much less than you may expect. After all, the apple doesn’t fall far from the tree.

Join Us

If you’re interested in being a part of this, we invite you to join our Discord Server . Feel free to come by to discuss ideas, chat, or just hang out!

Addendum: Really Sam?

I used Codex with GPT-5.6 Sol (later GPT-6 Astra when it came out) for the kernel RE task. One of the biggest issues I hit was the overly aggressive cybersecurity restrictions (I am not enrolled in trusted access).

Ninety-nine percent of the time a simple /goal resume or “keep going” was enough to have the LLM continue (which also shows the restrictions were overly aggressive), but they still broke my unattended workflow. I coded the fastest, dumbest possible solution: a daemon that takes a screenshot every minute, diffs it against the last screenshot, and if identical (because Codex stopped making progress) types /goal resume . I accidentally left it on in some group chats:

The /goal resume daemon spamming a group chat.

It kept going.

That said, GPT-6 Astra and GPT-5.6 Sol are absolutely insane and by far the best performers at firmware ABI RE, so this hack was more than worth it.

Addendum: M4 vs A18 Pro vs M5

As far as I can tell, the user-space implementations of the M4 and A18 Pro are effectively identical, with the only difference I can find being one value is slightly larger on the M4, consistent with it having more cores. The firmware ABIs of the two differ significantly: the second RTKit coprocessor on the A18 Pro makes everything more complicated. In this way, the A18 Pro is more akin to the M5 in terms of firmware than it is to M4. The M5’s user space has some similarities to M4, with the ISA being mostly a superset, but some parts are completely different, such as its texture descriptors. The M5’s user space has been partially reverse engineered; its firmware ABI is fully reverse engineered; a prototype drm-shim has been built and thoroughly tested; and I don’t think it would take long to promote the prototype to a full Rust driver. My primary targets remain the M4 Mac Mini and MacBook Neo.

WangNet – 1.8 MB, zero-dependency Numberwang adjudication in 11 languages

Hacker News
github.com
2026-09-15 15:27:35
Comments...
Original Article

A small neural network that decides whether a number is Numberwang.

The whole model is a 1.8 MB JSON file and the inference code is about 100 lines of pure Python standard library — no PyTorch, no NumPy, nothing to install. Clone it and run it.

$ python3 numberwang.py 22
22... THAT'S NUMBERWANG!  (confidence: 99.3%)

$ python3 numberwang.py "45 - 44"
45 - 44... That's Wangernumb! Rotate the board!  (confidence: 100.0%)

$ python3 numberwang.py "hello how are you"
hello how are you... That's not even a number. It can never be Numberwang.  (confidence: 100.0%)

Usage

git clone https://github.com/GraafHenk/numberwang
cd numberwang
python3 numberwang.py 22

Run it with no arguments for an interactive session:

$ python3 numberwang.py
Welcome to Numberwang! (ctrl-c to stop playing Numberwang)
> zweiundzwanzig
zweiundzwanzig... THAT'S NUMBERWANG!  (confidence: 100.0%)
> shinty-six
shinty-six... That's not Numberwang.  (confidence: 100.0%)

Requires Python 3.8 or newer. That's the only requirement.

In your own code

from numberwang import load_model, wang_probabilities

model = load_model("model.json")
probs = wang_probabilities(model, "forty-seven")
# [p_not_numberwang, p_numberwang, p_not_a_number, p_wangernumb]

verdict = max(range(4), key=probs.__getitem__)

The four verdicts

id verdict
0 That's not Numberwang.
1 THAT'S NUMBERWANG!
2 That's not even a number. It can never be Numberwang.
3 That's Wangernumb!

What it accepts

input behaviour
42 , sixty-six , 12345 digits or words
zweiundzwanzig , veintidós , tweeëntwintig eleven languages, accents optional
5*2 , 96 divided by 2 , twelve plus four arithmetic, judged on the result
45 - 44 , double four , eins anything worth 1 or 44 rotates the board
-7 , 4.5 , £5 , 50% , 9:30 negatives, decimals, currency, units, times
XLIV , twenty-third , 22nd Roman numerals and ordinals
fortnight , vierendelen , september words built on a number, judged as that number
achtneming , often , money words that merely contain one are not numbers
shinty-six , twentington fictional numbers are numbers too
bonjour , hello how are you no numeric content — can never be Numberwang

A number's wangness is a property of the number , not the language it is said in: four , vier , quatre and cuatro all get the same verdict.

How it works

chars → Embedding(32) → Conv1d(128, k3) → ReLU
      → Conv1d(128, k3) → ReLU → global max pool
      → Linear(128) → ReLU → Linear(4) → softmax

80,804 parameters. The network reads characters directly — there is no tokenizer, no normalizer and no rules engine at inference. Digits, operators, canon verdicts and the eleven languages are all held in the weights, and model.json contains the lot.

Demo

A hosted version runs on Hugging Face Spaces. To run the same demo locally:

pip install -r requirements.txt
python3 app.py

gradio is needed only for the demo. The model itself never needs it.

Accuracy

88.9% over 486 held-out adjudications (macro-F1 0.896), against a ceiling of roughly 98% — about 2% of training labels are inverted, in accordance with long-standing adjudication practice.

class precision recall F1
not Numberwang 0.820 0.885 0.851
Numberwang 0.919 0.900 0.910
not a number 0.951 0.830 0.886
Wangernumb 0.968 0.909 0.937

Arithmetic on unseen operands is the weak spot , at 44–72%. The network memorises rather than computes, so small common expressions like 5*2 are reliable while 904 * 3 is an educated guess. If arithmetic correctness matters, evaluate the expression and hand it the result.

License

MIT — see LICENSE .

No warranty is expressed or implied as to whether any particular number is, or is not, Numberwang.

Jev: New frontier model 40-400x cheaper and 20-200x faster

Hacker News
typesafe.ai
2026-09-15 15:25:03
Comments...
Original Article

Diogo Almeida, founder, TypeSafe

Models have been superhuman at chat for years, so where is all the automation?

This has been my driving question for the last four years. At OpenAI, I helped build the methods that made language models useful at following instructions and talking with people. That work ended up as the research behind ChatGPT.  At the time, I thought maybe chat models would lead to AGI, but despite the hype it became obvious to me that there was something really big missing.

After two years in stealth, countless technical challenges, and research breakthroughs… I am beyond excited to announce that today, TypeSafe AI is releasing our first System One Model : a new class of frontier models built to make fast, structured decisions that software can use directly.

We built a new stack entirely focused on automation: with a new model architecture, parallel sampler for maximum efficiency, and training method we call Reinforcement Learning for Calibrated Decisions (RLCD).

Our first public model is Jev , available today in early access. Jev achieves similar levels of intelligence on System One tasks compared to existing LLMs, while being two orders of magnitude faster and more efficient. While Jev gives up string generation, it’s optimized for structured outputs and can’t hallucinate.

Think of Jev as a frontier-intelligence function call: unstructured state in, typed probabilistic decisions out.

Extraordinary claims require extraordinary evidence so see below for the receipts. 💅

Frontiers, Old and New

Existing LLMs

System One + Jev

Optimized with

Reinforcement Learning with Human Feedback (RLHF) / Reinforcement Learning with Verifiable Rewards (RLVR)

Reinforcement Learning for Calibrated Decisions (RLCD)

Optimizes for

Human preference: writeups and chat responses that human raters prefer.

Verifiable rewards: outputs that can be programmatically verified.

Calibrated decisions: answers with epistemically honest probabilities on System One tasks.

Inputs

Unstructured data (e.g. text) with an emphasis on sequential messages .

Unstructured data (e.g. text) with an emphasis on structured program state .

Outputs

Strings / generated text. Strings are flexible and can be anything: chat responses, code, hallucinations, refusals, or even type-safe structured values. To be used by software, responses need to be parsed + validated. There is also always some risk that the AI goes off the rails.

Type-safe structured values. Possible outputs and structure are defined in advance . The model never makes type errors. All answers are accompanied with calibrated probabilities and confidence scores.

Sampling

Sequential. Generates one token at a time, each conditioned on the last.

Parallel. Generates all outputs in a single query. Incredibly efficient and hardware-aware.

Cost

Input tokens: from $0.20 to $10 / MTok.

Output tokens: ~5x more expensive than input tokens.

Input tokens: $0.042 / MTok ($42 per billion tokens).

Output tokens: FREE (too cheap to meter).

Speed

End-to-end response time is 3 to 329 seconds for frontier models.  Fast enough for interfacing with humans, but a big bottleneck when integrated in code.

End-to-end response time is 70ms-500ms for TypeSafe. This can range from 40x-200x faster for the same levels of frontier intelligence for System One shaped queries.

Confidence

Even if prompted for a confidence estimate, models tend to be overconfident and inconsistent. If a model can do a task 95% of the time but doesn’t say when it’s in the 5%, it can’t automate that task.

Always communicates confidence and uncertainty with every output. Calibrated: higher confidence means higher accuracy. More consistent: returns similar answers for similar inputs.

Use cases

Human-in-the-loop tasks (chatbots, copilots, coding agents). General and powerful, but requires human oversight because their freedom also means they might go off the rails.


Verifiable problems (math proofs, kernel optimization). When correctness can be checked cheaply and automatically, LLMs can generate, test, and iterate until they find something that works.

Demos. The flexibility of strings allows it to be incredible for quickly making prototypes that only work sometimes.

AI-Powered Workflows / smart if-statements. Structured outputs slot into ordinary software as fuzzy decision rules: classify, route, score, extract, or branch where hand-written logic is too brittle. The surrounding code constrains their freedom, making them easier to compose into reliable systems.

Map-reducing over big data. Turn petabytes of data into features and insights.

Real-time applications. 100ms speeds means you can use AI in your applications where UX is critical. Verify everything. Score, judge, verify, guardrail, and detect jailbreaks of LLM prompts, reasoning traces, and/or outputs.

Evidence / Technical Results

We love skeptics, and are skeptics ourselves.

There are some claims you can easily verify:

  • Speed per call: We truly are that fast, though our published evals are generally run from our laptops on the West Coast (this is where our service is currently based).

  • Cost per call: We make our pricing transparent. We can’t prove it isn’t subsidized; we’ll need the long-term to prove the sustainability of our pricing (which we expect to go down, not up).

  • No type errors : This would be an easy thing to falsify with just a single counter-example, but it is mathematically impossible.

For our bolder claims, we want to provide as much nuance as we can.

Side-by-side demonstration

Our side-by-side demo shows a key difference between our models and LLMs: Jev outputs all probabilities in parallel instead of autoregressively generating by token. Strings are extremely powerful and general, but costly. “Giving up” strings actually gives us a lot of superpowers!

Nuance:

  • For people with early access to TypeSafe, here is the actual query .

    • The query is highly simplified and questions were chosen to have descriptive, human-readable keys so that the output on the screen is understandable.

    • The state is also a short, dense, and detailed paragraph, to emphasize the difference in sampling methodology. The relatively shorter input paints our model in an advantageous light.

  • For the keen eyed, for the recorded run, the only disagreement with GPT-5.6 Terra is on “Churn likelihood level”. The actual answer seems genuinely ambiguous to us.

  • We used GPT-5.6 Terra with default reasoning for this example, because we’ve found it to be the most comparable at intelligence to Jev on average.

  • Fun fact: a similar demo was what convinced us to go all-in in the direction of System One Models!

Workflow evals

We made a new type of evaluation to measure how well AI works within code. We don’t optimize for a ground truth classification orand allow the harness and model to change (potentially allowing for overfitting via harness engineering). Instead, we assume there is a correct compute graph (a “workflow” represented in code) and use the predictions of the largest, smartest, and most expensive external models as reference probabilities.

Rephrased: every model gets the same workflow. We test how they compare to the average of the smartest models (in this case, Astra and Fable).

Jev is off the charts – owning the Pareto frontier for almost 2 orders of magnitude. We also compare to models with a generated prompt doing all the logic in their chain-of-thought, but this tends to do significantly worse than using the workflow itself.

Note that the calls here are significantly more complex than the side-by-side demonstration above. That’s because they’re more representative of the types of production workloads needed for true business automation. Below is the simplest of the 4 workflows we’re publishing:

The most reliable real-world workflows tend to have many independent, decomposed questions, with fine-grained behavior that’s dependent on probabilities instead of discrete decisions. The end result is discrete branching, but how we get to a final answer involves a lot of domain-specific engineering that needs to be done highly consistently.

See our workflow evals site for all the details: examples, disagreements, full queries, and each workflow.

Nuance:

  • This is where the claims of 193.6x faster, 444.6x cheaper on our home page comes from, and we expect that these are on the higher end of real world gains.

  • These content of these workflows were not deliberately chosen nor constructed to make our model look good, and are not in our training distribution. However, they were made by individuals on our model capabilities team, so some bias could exist.

  • We use the average of GPT-6 Astra and Fable 5.1 as the reference answer, which biases answers towards OpenAI and Anthropic’s models. We likely underestimate the relative performance of our model and DeepSeek’s models.

  • The LLMs use our System One LLM wrapper, which constrains LLMs to output structured decisions compatible with our API. We have found this to be the most accurate way to get decisions from LLMs, but this tends to be slower and more expensive than giving decisions without probabilities.

Hallucination and Type-safety

Hallucination and type-safety are intrinsically related, and we think the latter is table stakes for automation. Having a hallucinated tool call is inconvenient in an agent, but is an absolute deal-breaker if it’s part of a system with latency guarantees or it’s buried several layers deep in a dependency chain. Existing models, no matter how smart , still hallucinate and have type errors.

Nuance:

  • The numbers for LLMs are from OpenRouter i.e., there almost certainly is bias here: more complex queries might be routed to better models.

  • Our number is not empirical. Schema matching is guaranteed, thus we can confidently add 0% into the plots.

Fun Demos

Perhaps the most exciting part of our work is enabling new use cases. We have a lot more to show you, but here are a couple of the team’s favorites:

Doom

We love how this doomo doomonstrates real-time intelligence and what can be doone with code + AI. The engineer behind it was worried about making 10 queries a second (which ends up costing ~$7/hour), but the rest of us agreed that was lower than expected! This is so fun we intend to not only release an in-depth walkthrough, but also host some events to hack on this.

Nuance:

  • The demo is on structured state as a data structure with text, not on images (yet…)

  • A non-AI doom bot could play better, but we wanted a bot that was reactive to different representations of game state, and most importantly… following instructions was cool as heck!

Wikiracing

The objective of the game is to start on one Wikipedia page and reach a specific other Wikipedia page using only links you come across while traversing. Each step can mean choosing between hundreds to thousands of links! It’s a great playground for demonstrating not just intelligence-per-second, but also the compounding benefits of not hallucinating with high-cardinality choices.

Nuance:

  • As far as we know, it was completely random that both the 2nd and 3rd challenges started with “Rubber Duck.” The author only noticed when the team pointed it out.

  • Our speedups here tend to be a lot less than in previous demos. That’s because this is against the non-reasoning modes of the models (except Astra which was set to the lowest reasoning setting). This is also why Jev tended to finish in fewer steps (a sign of greater intelligence). This was to make the demo more bearable to watch. The LLMs look much worse at this task than with reasoning enabled.

  • Jev supports a cardinality up to 255. For the higher cardinality choices, we do a 2 stage-system of scoring independently then making an explicit choice, hence the occassional slowdown.

What’s next

We’re still in Jev’s early days. We have a lot more in the pipeline and are so excited to keep on shipping 🔥.

Today, we are opening early access and bringing developers off the waitlist as quickly as we can. We want to hear which decisions you need to automate, where Jev works, and where it falls short. Tell us what sci-fi you want to build!!

We started TypeSafe because we believe that AI needs an interface software could depend on. We can't wait to see new use cases continuously diffuse through the community and economy.

We Give A FAQ

Where do the names “System One Models” and “Jev” come from?

We were inspired by Daniel Kahneman, Thinking, Fast and Slow . The model class name draws on the distinction between fast, intuitive System 1 thinking and slow, deliberate System 2 reasoning.

“System 1 thinking” has also implied error-prone. For reasons we will get into in the future, we believe System One Models can be made more reliable than its alternatives.

We named Jev after William Stanley Jevons. We expect machine intelligence to follow a similar path to coal, after steam-engine efficiency led to an increase in demand. Every order of magnitude drop in the cost of intelligence unlocks orders of magnitude more use cases.

Why was a new training algorithm needed?

What use cases is Jev good for?

Is Jev just a smaller LLM?

How does Jev perform against public benchmarks?

Where does our training data come from?

These are results are kinda crazy - how is it possible?

Bernie Sanders says Congress has been ‘asleep at the wheel’ over AI – video

Guardian
www.theguardian.com
2026-09-15 15:11:15
Bernie Sanders criticised the advancement of artificial intelligence at the Pro-Human Assembly in Washington Tuesday, warning that this version of AI is 'the least capable version of AI that we will ever have'. The independent senator from Vermont was speaking at the conference alongside a number of...
Original Article

Bernie Sanders criticised the advancement of artificial intelligence at the Pro-Human Assembly in Washington Tuesday, warning that this version of AI is 'the least capable version of AI that we will ever have'. The independent senator from Vermont was speaking at the conference alongside a number of AI sceptics, including Steve Bannon

Learning to solve hard problems in RL for LLMs by never giving up

Hacker News
mnoukhov.github.io
2026-09-15 15:07:38
Comments...
Original Article

Learning to Solve Hard Problems in RL for LLMs by Never Giving Up

Sep 15, 2026

Table of Contents

This is a blog post for my recent paper on RL post-training of LLMs: introducing the Matthew Effect and proposing to solve it with Never Give Up. It is presented interactively and less formally, more like how I give the talk. For a deeper, more technical dive, check out the paper on arxiv and code on github .

What is your eval actually measuring? #

Every good RL practitioner has no doubt seen an eval curve go up. Here is the AIME 2025 eval during our RL training of Olmo 3.1 RL-Zero Math (1) see Olmo 3.1 blog post and arxiv

Training Olmo 3 7B base with RL on Dolci RL-Zero math improves its overall math ability. Or does it?

What does this curve really mean?

Our eval is an average over 30 AIME questions. Let’s break those 30 questions down into 3 levels of difficulty. Every question that our initial, pre-RL model gets 0 for pass@32 will be labelled “hard”. The other questions we’ll divide evenly by into “medium” and “easy” based on their pass-rates. So our initial pass@1 averages will be 0%, 3.8%, 22.7% for our subsets. How do you think performance on each subset will evolve?

Our AIME evals during Olmo 3.1 RL-Zero split into three levels of difficulty by their initial accuracy. Easy AIME problems improve drastically but problems that start with pass@32=0 mostly end with pass@32=0!

Averaging our AIME eval was hiding something important: the majority of our improvements are coming from the easiest problems going from somewhat solved to mostly solved. The hardest problems are barely improving. This is clearly visible if you look at how each example’s solve rate changes over time (see plot in the margin). Heatmap of solve rate for each AIME evaluation example across training steps, grouped by initial difficulty. Accuracy of each AIME eval example over training. We order examples by difficulty from top (initial model pass@32=0) to bottom (initial model pass@1 > 30%). The hardest examples (top rows) barely improve over training. The model mainly learns to better solve easy and medium-difficulty examples that were already reasonably-well solved. We call this discrepancy the Matthew Effect . But this is for math RL on LLMs. What about other domains?

We evaluate code RL and agentic RL using Deepcoder and DeepSWE , two nice open-source projects that released models and logs. We can use the initial model to split each benchmark into difficulty buckets (Deepseek-R1-Distilled-Qwen-14B on LCBv6) or we can use existing task length/difficulty labels (SWEBench).

The gains from RL are proportional to how easy the problems are. We connect this bias to a similar phenomenon in network science and economics, the Matthew Effect (2) Merton (1968) , also see Wikipedia , generally summarized as “the rich get richer”.

We therefore propose The Matthew Effect in RL for LLMs

RL improves performance on a task in proportion to a model’s initial competence—making easy tasks easier while hard tasks often remain difficult.

What causes the Matthew Effect? #

You might assume the issue has to do with GRPO. If we just don’t get a correct answer to our problem in our $k$ sampled completions, then we don’t get any gradient and can’t improve on this problem. (3) Xiong et al (2025) call this signal loss One possible answer is to sample more completions i.e. larger $k$ . (4) Other approaches include using priveleged information and curriculum learning . These are generally complimentary to our approach.

To test this out, we train Qwen 2.5 0.5B Instruct with GRPO on GSM8k platinum and test on the same. We split our dataset into difficulty levels using initial pass@1: easy (25%), medium (10%), hard (5%), and extra-hard (0%) problems. We vary $k \in \{4, 8, 16, 32\}$ but keep batch size fixed.

Smaller k solves harder problems than bigger k . Our GSM8k eval, split by initial difficulty, as previously. Surprisingly, $n=64$ prompts and $k=4$ completions is a better setting for solving hard problems than $n=8$ prompts and $k=32$ completions.

It turns out that smaller $k=4$ is actually best! Why is this happening?

Lets look at what our training batch is actually composed of. Since we filter any prompt whose completions are all correct or incorrect, our training batch must always be composed of problems with some completions right, some wrong. We plot what percentage of our batch is our easy subset and our extra hard subset and how this changes over time.

Larger $k$ increases the chance of finding a rare correct solution to a very hard problem. So, naively, we expect it to have more hard problems in the batch. The issue is that larger $k$ also increases the chances of finding a rare incorrect solution to an easy problem.

GSM8k Platinum, prompt difficulty in training batch . Tracking the actual prompts that make it into our training batch, by difficulty. $k=32$ solves more hard problems at the beginning but there’s an inflection point at 200 steps after which $k=4$ overtakes it.

Early in training, $k=32$ finds rare solutions to hard problems. But after the inflection point around step 200, $k=4$ does better. $k=4$ filters any problem that is solved in $4/4$ completions. In contrast, for $k=32$ to filter the same problem, it must be solved much more: $32/32$ . $k=4$ ends up spending much less compute on easy problem, especially when they get a rare incorrect solution. The inflection point is when the benefit of finding rare correct answers to hard questions is outweighted by wasting compute training on rare incorrect answers to easy questions.

Because of our asynchronous RL for LLMs setup (5) Async RLHF (Noukhovitch et al, 2025) is a blatant self-citation but also the first async RL for LLMs paper. See also PipelineRL (Piche et al, 2025) , all the compute we save filtering easy problems is used to train on harder problems. We argue the issue behind the Matthew Effect isn’t just undersampling for hard problems, but spending too much compute on easy problems. (6) In contrast to signal loss, we term this signal efficiency

Never Give Up on hard problems #

Our goal is therefore to only use small $k$ for easy problems but have large $k$ for hard problems. We propose a simple, but effective method of adapting asynchronous RL sampling: Never Give Up . We start sampling some small amount $k$ . If a prompt is solved within the first $k$ completions, train on it! If a prompt is fully solved in $k/k$ completions, then we can easily and quickly filter it.

The tricky part is if all completions are wrong. With probability $p$ , we never give up and add the prompt back to our generator in order to sample $k$ more completions. We keep track of our old completions and when we do solve the problem, train on our whole $k * \text{rounds of NGU}$ completions. This creates a geometric distribution for the number of samples we take: if we never solve the prompt, we expect to take $\frac{ \ \ k}{1-p}$ samples, in expectation.

This method is implicitly adaptive. Whereas curriculum learning pre-sets the difficulty of a problem, we find that online, adaptive methods do better as easy problems can become more difficulty over training and vice-versa. On GSM8k, $k=4$ with NGU $p=0.9$ outperforms all values of standard GRPO with varied $k$ . This is especially evident on the hardest subset.

Never Give Up with k=4 outperforms all possible values of k . On GSM8k platinum, NGU with k=4 solve more hard problems than k=4 with the same compute, without degrading performance on easy problems.

It does this by achieving the best of large $k$ early in training and small $k$ late in training.

Never Give Up gets the best of both k=4 and k=32 . NGU allows $k=4$ to keep retrying hard problems, solving them as well as $k=32$ early in training. NGU doesn’t overspend compute on easy problems, filtering them as fast as $k=4$ late in training.

Async RL staleness and tricks for NGU #

Astute readers might already see a downside to the method: stale completions. This section introduces two tricks for dealing with staleness, but its not necessary to the main message so feel free to skip it.

If we take multiple rounds of NGU to get one correct completion, our initial $k$ completions will be pretty stale by the time we train on them. Stale negatives are known to be bad for LLMs and RL (7) Async RLHF argues that data staleness slows down training, this was also true for deep RL . Le Roux et al (2025) show that stale negatives are particularly bad. so its important to filter completions to be below some age threshold.

$T=4$ wins out but this leaves another issue: our GRPO baseline. Just because we don’t train on a stale completion doesn’t mean we shouldn’t use it in our GRPO baseline. Suppose we have 4 stale negative completions, 3 new negatives and just 1 new positive. We should treat our positive as the rare phenomenon it is and set our GRPO baseline to $\frac{1}{8}$ . But if we only train on the newest 4 completions, our total group’s reward becomes non-zero $\frac{7}{8} - \frac{1}{8} - \frac{1}{8} - \frac{1}{8} = \frac{3}{8}$ . Our options are to ignore the filtered completions from our baseline ( ignore ), to leave the baseline non-zero ( no rescale ), or to anchor the positive and rescale the negative advantages by $\frac{7}{3}$ to maintain total reward 0 ( anchor pos ).

Overall, it makes sense to use all the samples you have for your GRPO baseline, even if you’re not training on them. (8) This baseline + rescaling may be generally useful for async RL if there’s filtering of samples for being too off-policy.

NGU at a bigger scale: Math #

We scale up to a bigger math RL setup: DeepScaler with Qwen 3 4B base. (9) generally following the setup of Li et al (2025) On top of a strong GRPO $k=16$ baseline, NGU further improves performance, especially on the hardest subsets of our AIME + BRUMO 2025 eval.

NGU outperforms GRPO when scaling up to a realistic math task, DeepScaler. Final evals on a combination of AIME 2025 and BRUMO 2025 math datasets after training with Qwen 3 4B base for ~120 H100 hours. As previously, the evals are split by initial model’s pass@32 (i.e. difficulty) to show that NGU improves on the hardest problems.

The Matthew Effect still persists, but we can mitigate it; NGU helps solve harder questions without really degrading on easier ones.

NGU on a different scale: Code #

Code RL is fundamentally different from math because math usually has binary verifier: right or wrong. A coding problem has many tests and the tests can vary from easy and difficult within a single coding problem. Math problems are either easy or hard. Solving a single coding problem means solving both easy and hard tests.

We look at a particularly tough setup: Manufactoria . (10) following the benchmark setup by Sun et al (2025) Standard GRPO improves performance but eventually stagnates: improving on some tests but failing to pass all tests. Splitting the tests by difficulty, we see a clear Matthew Effect.

The Matthew Effect can explain stagnating code RL in Manufactoria . We combine all tests from all problems and split them by again by initial model’s pass@32 difficulty, as before. RL improves easy tests to nearly fully solved. Some hard tests are solved as well. But mostly, training signal is oscillating medium-difficulty tests.

Easy tests are nearly fully solved and improvement on hard tests stagnates. GRPO dedicates the majority of training signal to repeatedly revisiting partially-solved medium tests and oscillates between solving them slightly more or less. (11) We can see this as just another issue of signal efficiency , but for code RL.

If our first $k$ completions all pass $\frac{7}{12}$ tests, then Never Give Up won’t accept the next $k$ completions unless they pass more than $\frac{7}{12}$ , pushing the model to iteratively do better. Where standard GRPO stalls, GRPO + NGU keeps solving harder and harder tests until it starts to fully pass all tests for a given problem.

NGU allows RL to fully solve coding problems in Manufactoria. Overall performance ( combined ) stalls in standard GRPO but not in NGU. That’s because NGU continues improving on the hardest subset of tests ( hard ) and then starts pass all the tests for some eval problems ( all-tests ).

The Matthew Effect is a Primacy Bias, sort of #

Some very astute readers may have noticed that the Matthew Effect resembles a primacy bias where LLMs are predisposed to solve certain problems according to their initial state. This intuitively connects the Matthew Effect to the Primacy Bias in Deep RL (12) Nikishin, Schwarzer, D’Oro et al, (2022) where deep RL training runs could be derailed due to bad early samples. This was due, in part, to issues of plasticity in neural networks trained with RL.

Could it be that the Matthew Effect in RL for LLMs caused by plasticity? In short, no.

We start from a post-GRPO checkpoint at 6000 steps that has been stagnant for at least 3000 of those steps. We can then either train with a single reward for passing all tests ( all-tests ) (13) This was how Sun et al (2025) originally got around the issue of stagnation on Manufactoria. or keep our per-test reward and just add NGU ( per-test NGU ).

The Matthew Effect is not caused by issues of platicity, using code RL on Manufactoria . Continuing from a very stagnant checkpoint, we can recover performance either by switching to a reward only if all-tests pass, or using NGU with the original partial reward per-test passed.

Both methods recover strong performance, even after spending a while on suboptimal data. This demonstrates that plasticity is not a major issue and LLMs can generally recover from early bad samples.

Limitations #

The intuition behind Never Give Up is that our RL training can reallocate compute by quickly filtering easy problems. So if a task leans heavily towards very difficult problems, Never Give Up will likely not be effective. NGU’s sample, wait, sample more process can take longer to finish a whole group of completions as compared to sampling $\frac{k}{1-p}$ from the start. This means your early samples will be more off-policy than necessary, which means a worse learning signal and slower learning speed. But if you already know a decent $k$ for your data distribution, NGU can likely be effective!

Conclusion #

We have highlighted the Matthew Effect in RL for LLMs, and shown that standard RL results in disproportionately poor performance on the hardest problems. This demonstrates how simple scalar values may not be sufficient for accurate evaluations of LLMs. Hopefully, it inspires you to dig into your evals and look at more dense evaluation signals.

Our proposed solution, Never Give Up , represents a simple method for better allocating compute. The trick is really just reducing the compute spent on easy problems, which reallocates it towards harder problems. But it does seem to show substantial improvements on difficult tasks. Future work should examine more complex multi-step, agentic environments and try to gain a deeper understanding of how RL actually changes our model’s distribution, in practice.

Acknowledgements #

Thanks so much to my coauthors: Hamish, Nathan, and Aaron! And all my friends who I annoyed for advice during the project: Costa, Sam, Finbarr, Dima, and Adrien. Hamish, Nathan, and Adrien kindly gave me feedback on this blog post. Thanks to Ai2 for giving me compute, some really nice stickers, and great colleagues.

All graphs here made with plotly thanks to my robot friends Sonnet and Sol.

Citation #

@misc{noukhovitch_ngu_2026,
	title = {Learning to Solve Hard Problems in RL for LLMs by Never Giving Up},
	url = {https://arxiv.org/abs/2609.13443},
	author = {Noukhovitch, Michael and Ivison, Hamish and Lambert, Nathan and Courville, Aaron},
	month = sep,
	year = {2026},
}

Back to all blog posts

FT: ‘Steve Bannon and Bernie Sanders Unite in AI Safety Call’

Daring Fireball
www.ft.com
2026-09-15 15:01:30
Joe Miller, reporting for the Financial Times (via Political Wire): Veteran socialist Bernie Sanders and rightwing activist Steve Bannon are joining forces to call for greater guardrails on AI, even as Donald Trump insists concerns about the technology are a hoax that will help China overtake th...
Original Article

For help please visit help.ft.com . We apologise for any inconvenience.

The following information can help our support team to resolve this issue.

Error Code
CG000 / 403
Request ID
a3b9ed511be3f9a7

A Lone Juror Hijacked Deliberations. This Time, a Man Faces Execution.

Intercept
theintercept.com
2026-09-15 14:48:27
While many fixated on the man who refused to acquit Lindsay Clancy, Georgia is set to kill Stacey Humphreys despite “extreme juror misconduct.” The post A Lone Juror Hijacked Deliberations. This Time, a Man Faces Execution. appeared first on The Intercept....
Original Article

More than a week after the Lindsay Clancy trial ended in a hung jury — with a lone holdout refusing to acquit the 36-year-old for killing her young children — the media has continued to probe how one man forced a mistrial in the triple-murder case. “I hope that guy can sleep well at night,” Clancy’s defense attorney told reporters. “Whatever his agenda was, he stole seven weeks of the life of these other jurors.”

Commentators have been strikingly sympathetic toward Clancy, showing compassion for her struggle with severe postpartum mental illness. Meanwhile, the unnamed juror, who is Black, has been widely criticized for allegedly violating his duty to follow the law. The press has dug into his own criminal history, from reports of domestic abuse, which were dismissed , to failure to pay rent .

The attention devoted to the Clancy trial — and the wave of anger over the juror’s intransigence — stands in sharp contrast with the silence over a different murder case, which is about to culminate in an execution.

At the Georgia death penalty trial of Stacey Humphreys, who was convicted of committing a double murder, a lone juror “appears to have singlehandedly changed the verdict from life without parole to death,” U.S. Supreme Court Justice Sonia Sotomayor wrote last year. The woman’s actions, she concluded, amounted to “extreme juror misconduct.” Yet the high court has refused to consider Humphreys’s case — and media attention on the upcoming execution has been virtually nonexistent.

“The whole legal commentariat weighed in on what happens when a juror didn’t follow the rules in the Clancy trial,” organizer Hannah Riley Fernandez, director of programming at the Center for Just Journalism, wrote on social media, “meanwhile GA is about to kill someone whose juror did the same & MUCH more.”

Humphreys was sentenced to die in 2007 for murdering two real estate agents, 21-year-old Lori Brown and 33-year-old Cyndi Williams, at a model home just outside Atlanta. Pretrial publicity led to a change of venue, and the jury was sequestered over the course of the monthlong trial. The local sheriff’s department escorted jurors between a Holiday Inn and the courthouse.

The story of what happened in the jury room is contained in years of legal filings and affidavits signed by investigators and jurors themselves. The jury foreperson was repeatedly interviewed by Humphreys’s defense team and testified in court. She described how the lone holdout, Linda Chancey, signaled her intentions even before the jury had voted to convict Humphreys, announcing “something along the lines of ‘he’s guilty and he deserves to die.’”

Most of the jurors were inclined to agree with Chancey at first. But at Humphreys’s sentencing trial, defense lawyers revealed their client’s harrowing upbringing. From the time he was a toddler, witnesses said, Humphreys had been brutally abused by his parents, leading to psychological problems that indelibly shaped the rest of his life. “This testimony was impactful to the jurors tasked with balancing Stacey’s terrible crime and the rage-filled, abusive household where he grew up,” lawyers later wrote in his clemency petition.

Juror affidavits described a sense of grief for family members on both sides. One said she’d been especially struck by the testimony of Humphreys’s older sister Dayna, who recalled how her brother “took most of the beatings for her.” Despite an initial internal vote in which several jurors favored a death sentence, they eventually agreed that he could be sufficiently punished without being executed. On the second day of deliberations, 11 jurors voted for life without parole. Only Chancey was opposed.

After hours of additional deliberation, the foreperson wrote a note to the trial judge saying that they were “unable to come to a unanimous decision on either death or life imprisonment without parole.” Under Georgia law, a judge faced with a non-unanimous decision is supposed to dismiss the jury and sentence a defendant to either life or life without parole. But Chancey insisted on editing the note, revising it to include the word “currently” in two separate places, which left the impression that jurors might eventually agree. Upon receiving the note, the trial judge directed jurors to keep deliberating.

After that, a member of the jury later said in an affidavit, Chancey “snapped.” Fellow jurors said she screamed and threw photos of the deceased victims at them, demanding to know whether they “want this to happen to someone you know.” Things got so contentious that the foreperson sent another note asking to be removed from the case due to Chancey’s “hostile” behavior. But the judge again directed them to keep working.

Chancey did not respond to The Intercept’s requests for comment.

Chancey also revealed something to her fellow jurors during deliberations that she had kept hidden from the court during voir dire. While she’d previously disclosed that she had been the victim of an attempted armed robbery of her home but that she’d escaped before the perpetrator was able to get inside, she later told fellow jurors that he assaulted her in her bed. Had Chancey revealed this during jury selection, lawyers have argued, she would almost certainly been struck from the panel.

Instead, Chancey was seated on the jury and hijacked the deliberations. According to other jurors, she said that they had to vote unanimously on a sentence or Humphreys might eventually walk free. Confusing instructions from the judge deepened this misimpression, according to the foreperson. Along with the rest, she ultimately capitulated and changed her vote to death. “I cried the entire time,” she said.

Humphreys, now 52, is scheduled to die by lethal injection on September 16 at 7 p.m. He is one of two people set to be executed this week, despite the fact that a majority of their trial jurors wished to show mercy. On September 17, Alabama plans to kill Jeffery Lee, whose jury voted 7 to 5 to sentence him to life without parole. At the time of Lee’s trial, Alabama judges had the power to override a jury’s decision. Despite the jury’s vote, Lee was sentenced to die.

Several national outlets have covered Lee’s plight . But Humphreys’s looming execution remains overlooked. One reason may be that the legal issues are technical and complex — a “procedural thicket,” as Sotomayor described it, that has prevented any court from addressing the disastrous deliberations at Humphreys’s trial.

In Georgia and other states, courts are not supposed to allow juror affidavits to undermine a verdict, with some narrow exceptions. Humphreys’s lawyers learned about Chancey’s behavior soon after the trial and obtained affidavits anyway, seeking to challenge the death sentence on direct appeal. But rather than argue that juror misconduct violated his right to a fair trial, they argued that the judge’s instruction to keep deliberating was coercive.

When state post-conviction lawyers later sought to argue that Chancey committed misconduct, Georgia courts ruled that it was too late: Because Humphreys had never sought to argue the claim, his attorneys were now barred from doing so. Similar procedural barriers then prevented Humphreys’s federal legal team from bringing the challenge into federal court.

Humphreys was scheduled to be executed last December , but was temporarily spared amid a separate legal fight over alleged conflicts of interest by members of Georgia’s Board of Pardons and Paroles. Among the five members with the power to decide whether Humphrey would live or die were two people involved in the trial. One had worked as a victims’ advocate; another was the former sheriff of the county where the trial took place.

An Atlanta judge agreed that the board member who had worked as a victims’ advocate had a conflict of interest but disagreed about the ex-sheriff. He ultimately concluded that the clemency board could simply move forward without the former. Humphreys’s lawyers describe this as both unprecedented in Georgia and unfair to their client. Humphreys “will not only be required to convince three out of four presiding Board members,” they argue, “he will be judged by the very Board members that only last December were adverse parties in his legal action.”

As Humphreys’s lawyers prepared for his clemency hearing, scheduled for the eve of his execution, his legal team pursued an array of last-minute avenues to save his life. Georgia law allows prisoners one chance to file an “extraordinary motion for a new trial,” which the defense team submitted to argue for a resentencing trial.

In advance of a hearing on the matter last week, the lawyers submitted an unusual collection of affidavits. The documents described Chancey’s controversial role on a public committee assembled in 2020 to handle a dispute over a local Confederate monument, which Chancey defended. With the Covid pandemic underway, meetings were public and held via Zoom; one former mayor of the city recalled receiving “numerous emails from concerned members of the community who were honestly shocked and horrified by her behavior.” A historian said in one affidavit that she was insulting toward him and others, questioning their qualifications and derailing attempts at civil debate. “Because of her,” he wrote, “the meetings devolved into a shouting match.”

At a hearing on September 10, a state lawyer slammed the affidavits as an attempt at “character assassination,” while a defense attorney said they showed a pattern of “failing to deliberate in good faith.” The judge said he would not consider the affidavits but commended the lawyers’ efforts on behalf of their client. He briefly addressed Humphreys, who was watching from the prison. “I hope you realize what wonderful people you have working for you,” the judge said. In a written order, he rejected the motion for a new sentencing trial.

If Humphreys is executed on Wednesday night, it will be the end of a long road that has proven traumatic for countless people — including the jury foreperson, who took the stand at last week’s hearing. The trial left her disillusioned with the justice system, she testified. She said she’d sought guidance from the trial judge to no avail and remained devastated by her own vote 20 years ago. “I felt I had failed in many, many ways.”

But a state lawyer argued that one should expect emotions to run high in a death penalty trial. “I hope people are fighting,” she said. “I mean, you can’t even get 12 people to agree on a pizza topping, but you’re talking about life or death here.”

“Sometimes,” she said, “you do have a very strong personality that can, you know, rule the day.”

Chop Up Your Books

Hacker News
attainablefelicity.mattkirkland.com
2026-09-15 14:45:31
Comments...
Original Article

Chop up your books

This is my appeal to readers everywhere: you should take a knife to your books.

(And no, not in the sense that the destructive AI-scanners do.)

Like apparently everybody else, my book club recently picked out Lonesome Dove . I’m not a Western guy, but it’s clear that this pulitzer-winner earned it. It’s good.

But come on: this is an 850+ page paperback! It is what I call Too Big.

This book is so big

It’s going to tire out your hands to hold up an 850-page book for the time it takes to read an 850-page book. If you read in bed, it’s going to tire your arms out, trying to hold this giant tome over your head. If you want to take this book on a plane or bus, it’s going to take up half of your bag.

So, I would like to recommend you to a practice I call Chop That Book Up Into Reasonable Sizes .

Ahh look, reasonably sized volumes

It takes a few minutes and very few tools. You also can enjoy reading reasonably-sized volumes of big books.

At the risk of parroting ‘you can just do things’, I’m telling you: You Can Just chop up your book. Nobody will call the cops. Authors don’t mind! (well, I don’t think so, and I wouldn’t mind if you chopped up my book , which I freely admit is also Too Big).

Here’s what I do when the book is Too Big:

  1. Buy a copy. Don’t do this with library books.
  2. Paperbacks are easiest but hardbacks work fine too. Think about the format you like to read and look at its pages. Do you like the type sizing? The margins?
  3. Find the natural break points. Lonesome Dove is a great case here; it’s divided into three Parts, and each Part makes a great smaller volume. But otherwise you’re looking for chapter breaks.
  4. Crack that spine. Bend the book alllllll the way open at the first break point. Manhandle it. If the book is perfect-bound (which means the pages are glued together along the spine, most paperbacks are), you can bend the spine backwards enough to see the glue strip. If you’ve got a hardback that’s actually stitched together, then look for a break between signatures (those are the groupings of pages that are stitched together). Signatures are still going to be glued together in most cases. Here’s a comparison of binding types .
  5. X-acto that baby. Carefully slice between the sections, right into the glue. Bookbinders glue is great stuff - you can slice into it neatly with a good sharp blade, but you won’t mess up the glue’s grip on surrounding pages.
  6. Voila: you have volumes. Next you’ll want to bind it in some new ersatz cover. If you try to carry around just the section of the book without any cover, you will soon learn what covers are for! Individual pages will snag, rip, and peel off. Trust me, you want a new cover.
  7. You can use anything, but I recommend a manila folder. These are great: firm enough to protect the book block (the actual pages), but cheap and disposable feeling. Fold a manila folder around your new smaller volume. Make sharp creases. Trim it to size with your x-acto blade.
  8. Then glue it on! You can get bookbinders glue, but honestly Elmers will work just fine. You’re not binding this book to make an heirloom: you’re rebinding it for your own convenience. Smear a line of glue in the new spine, and use binder clips will hold the manila folder in place. Let it dry.
  9. Label it! I think a bold sharpie does the job here. I’ve had books where I gave it more detail, but I love the unpretentiousness of a marker.
  10. Enjoy your reasonably-sized book .

Subnormal floating-point numbers are expensive… on Intel processors

Lobsters
lemire.me
2026-09-15 14:41:00
Comments...
Original Article

We represent floating-point numbers using the IEEE standard. For very small numbers, the standard uses special subnormal numbers. Unfortunately, they have a reputation of making operations slow. Thus video game programmers and machine learning specialists sometimes avoid computing with subnormal numbers for performance.

How slow are they? Let me measure. I wrote a small C++ benchmark with a few kernels over arrays of 16384 values (small enough to fit in cache):

  • multiply each value by 0.75,
  • add two arrays,
  • divide each value by 3,
  • multiply normal values by a tiny constant (2 -1030 ) so that the inputs are normal but the outputs are subnormal,
  • a dependent chain x *= 0.9999 repeated 16384 times.

For each kernel, I feed either normal values (in [0.5, 1) ), subnormal values, or normal values where one value in a hundred is subnormal. The compiler is allowed to autovectorize the array computations. I use GCC 15 with -O3 -march=native on Linux and Apple clang 17 with the same flags on macOS. I also checked with clang 21 on Linux to make sure.

I ran the benchmark on five processors:

  • Intel Xeon 6975P-C (Granite Rapids), on an AWS c8i.xlarge instance,
  • Intel Xeon Gold 6548N (Emerald Rapids), a server in my lab,
  • AMD EPYC 9R45 (Zen 5), on an AWS c8a.xlarge instance,
  • AWS Graviton 5 (Arm Neoverse V3), on a c9g.xlarge instance,
  • Apple M4 Max.

Here are the results for double values, in nanoseconds per element (or per step).

Intel Granite Rapids

kernel normal 1% subnormal subnormal
multiply by 0.75 0.17 0.44 8.35
add two arrays 0.20 0.18 0.18
divide by 3 0.51 0.85 9.38
normal in, subnormal out 0.17 8.58
dependent chain 0.77 32.69

Intel Emerald Rapids

kernel normal 1% subnormal subnormal
multiply by 0.75 0.21 0.49 9.25
add two arrays 0.23 0.25 0.25
divide by 3 0.57 0.94 10.40
normal in, subnormal out 0.21 9.27
dependent chain 1.14 36.51

AMD Zen 5

kernel normal 1% subnormal subnormal
multiply by 0.75 0.07 0.10 0.08
add two arrays 0.09 0.09 0.09
divide by 3 0.11 0.24 0.25
normal in, subnormal out 0.07 0.07
dependent chain 0.66 0.88

AWS Graviton 5

kernel normal 1% subnormal subnormal
multiply by 0.75 0.17 0.17 0.16
add two arrays 0.19 0.20 0.20
divide by 3 0.30 0.30 0.30
normal in, subnormal out 0.17 0.17
dependent chain 0.91 0.91

Apple M4 Max

kernel normal 1% subnormal subnormal
multiply by 0.75 0.06 0.06 0.06
add two arrays 0.12 0.12 0.12
divide by 3 0.11 0.11 0.11
normal in, subnormal out 0.06 0.06
dependent chain 0.72 0.75

On Intel processors, a multiplication involving a subnormal number is about 45 to 50 times slower than a multiplication over normal numbers. A division is 18 times slower. The dependent chain, where each multiplication waits for the previous one, goes from about 1 ns to over 30 ns per step. A normal multiplication in the dependent chain has a latency of 4 cycles. With a subnormal, it has a latency of 128 cycles. It does not matter whether the subnormal is an input or an output: multiplying normal numbers into a subnormal result is just as slow as multiplying subnormal numbers. The exception is additions and subtractions: they run at full speed. Even if subnormals are rare (1%), the cost on Intel can be significant because when the compiler vectorizes the computation, a single subnormal can slow down a whole block of computations.

AMD does much better. On Zen 5, multiplications and additions run at full speed regardless of the inputs. The dependent multiplication chain is a third slower (0.66 ns to 0.88 ns per step): the multiplier needs an extra cycle or so to handle a subnormal. Divisions are about twice as slow. Interestingly, with divisions, having one subnormal in a hundred is almost as slow as having all subnormals. The two Arm processors, the Graviton 5 and the Apple M4 Max, do not care at all. Subnormal numbers are handled at full speed.

Thus it appears that on the latest AMD and ARM processors subnormals might not be a concern. But they remain very much a performance issue under Intel processors.

My source code is available .

Published by

From Intern to Software Architect

Lobsters
chauhankiran.blogspot.com
2026-09-15 14:38:43
Comments...
Original Article

From Intern to Software Architect

You start your journey as an intern or junior software engineer. At this level, you care about the syntax and language constructs such as defining variables or constants correctly, writing a function that might be reusable, or constructing conditions correctly. Let's call this level - 1.

As you start to become more familiar with the language and code, your focus shifts towards the program files. Now, you start to care about files instead of language constructs as you're comfortable with syntax and language constructs. You can now connect the files across a bug fix or a change that you need to make. You're now at level - 2.

As you become more experienced, you now start to care about a module - not just a set of files but a logically related set of files. Previously, it was a set of program files only. But now you start to see program files as a module. You know the connection between files and can reason about them. You reach level - 3.

Next, you continue your journey and start seeing multiple modules all together as an app. You are not seeing the language constructs, you're not seeing a set of files, you're not seeing a module, but a set of modules all together as a whole app. You're now caring about the whole app. When you review the PRs, you are adding the whole app context to the review before you approve and merge. You're now at level - 4.

As you gain more experience, you start to see a bigger picture than the app. You're now seeing things outside of the app such as services, proxies, DNS, security, firewalls, and many surrounding services. You now not only care about the app but about these services as well, which are required for your app to be alive. You now know the exact details about how a request comes into the app and goes out through all these services. You're now at level - 5.

Your journey continues and you start to connect with other teams who manage other apps that make up the whole product. You start to see a bigger picture than the app and services. You know how apps live together, how they communicate, how they are monitored, and much more. You're now at level - 6.

I'm going to stop here at level - 6 as I think this is where the role of a software architect becomes clear and in full view. I'm not saying that the level steps stop here. But this is where I want to stop for this article discussion.

In summary, you started with syntax and reached the level where you care more about app servers, proxies, communication, security, and so on.

---

Although I've used labels level 1 - 6, you can give a position you like against the levels based on the hierarchy you have seen in organizations. For example,

Level - 1 = Intern
Level - 2 = Jr. Software Engineer
Level - 3 = Software Engineer
Level - 4 = Sr. Software Engineer
Level - 5 = Principal Software Engineer
Level - 6 = Architect

Or you can combine some levels against the same position.

Level - 1, 2 = Jr. Software Engineer
Level - 3, 4 = Software Engineer
Level - 5 = Lead Software Engineer
Level - 6 = Architect

Or you can introduce more levels. For example,

Level - 3 can be divided into two parts as Software Engineer and Sr. Software Engineer, where you write software engineering with design patterns as an effective solution in the later position.

---

Now, let's take a top-down look.

At level - 6, the architecture is created so well that at level - 5 you don't have to worry about other apps. At level - 5, the app is set up very well so that at level - 4 you don't have to worry about surrounding services. At level - 4, the app is created and maintained very well so that at level - 3, you don't have to worry about other modules within the app. This continues at level - 2, where the code is crafted so well that you don't have to worry at level - 1 that your minor code change will throw a module error or bring down the app or crash the product!

Layers of abstraction are added so well that at the given level, only the necessary context is required. When you're at level - 6, you don't have to care about the code of the app or module because it is taken care of at levels - 3 and 4. Similarly, at level - 2, you don't have to worry about other parts of the product as they are taken care of at a higher level. You just have to worry about a set of files.

---

With the advancement of AI, the border between these levels has started to become blurred. Not between levels but between the whole range of levels. You, as level - 2, are expected to work on levels - 1 to 4, or level - 4 is required to work on levels - 3 to 6. Geniuses can jump between these levels, but lack the knowledge that only comes with experience.


California: Tell the Governor to Stand Up for Net Neutrality, Affordability, and Public Safety

Electronic Frontier Foundation
www.eff.org
2026-09-15 14:35:24
The federal government has inserted a provision into a funding deal with the state of California that would make the state abandon its gold standard net neutrality law, broadband affordability laws, and public safety protections. Doing so would be a huge step back for California, and would actually ...
Original Article

The federal government has inserted a provision into a funding deal with the state of California that would make the state abandon its gold standard net neutrality law, broadband affordability laws, and public safety protections. Doing so would be a huge step back for California, and would actually end up being more expensive for Californians in the long run. Tell the governor to reject this provision before accepting these funds from the federal government.

Take Action

Tell the Governor to Stand Up for Net Neutrality, Affordability, and Public Safety

On August 31, the National Telecommunications and Information Administration announced it would be awarding California $1.4 billion to expand broadband connectivity in the state. In that deal is a provision that says that California agrees to not enforce any law, order, or policy that imposes any sort of restriction on internet service providers (ISPs). These ISPs will get awarded the funding in order to connect Californians they have neglected for years. The ban on enforcing our laws would last 14 years . This is disastrous for a lot of reasons.

First, California is one of the only states with a strong state net neutrality law . Recreating much of the FCC’s Open Internet Order, the law prevents ISPs from blocking, throttling, zero rating, and instituting paid prioritization on internet service. Put another way, the law ensures that users, not companies, decide how they can see on the internet. If California is not allowed to enforce our gold standard law, there will be little stopping ISPs from controlling how everyone experiences the internet.

Second, California has a number of affordability protections that would also fall under this agreement. For example, when the state approved the merger of Verizon and Frontier earlier this year, it required the new merged company to offer a $20 internet plan to low-income Californians—saving Californians billions of dollars over the next decade. Just this year the California Public Utilities Commission found that the average cost of broadband across four major urban markets (San Mateo, Oakland, Los Angeles, and San Diego) was $51 per month. In 2023, Consumer Reports found that 84% of American consumers pay at least $50 per month, with many paying more. That $30 difference per month—which is likely to actually be more—makes all the difference for low-income Californians. It is how Californians will save billions from this merger requirement. In contrast, $1.4 billion in new connectivity and infrastructure doesn't matter if the most vulnerable Californians cannot afford it. Eviscerations of this and the net neutrality protections will, ultimately, cost Californians more than they will get.

Third, this deal will impact public safety. The same California net neutrality law which protects consumers also ensures reliable service for first responders during emergencies by banning throttling. In 2018, Verizon throttled, or slowed down, the service of firefighters as they were battling what was, at the time, the largest wildfire in California history. In reaction, fire departments came out in support of what would become California’s net neutrality law. If California cannot enforce its net neutrality law it will leave its first responders in a weaker position as natural disasters only become more intense.

Most people do not have a choice in ISP as it is. California’s net neutrality law is one of the few things protecting Californians from the whims of these monopolistic giants. Californians should not give up our few hard-won protections in return for a hand out to these behemoths. Tell Governor Newsom to reject this provision before he accepts these funds from the federal government.

Take Action

Tell the Governor to Stand Up for Net Neutrality, Affordability, and Public Safety

PlayBook: A Programmable Paper Notebook

Lobsters
www.youtube.com
2026-09-15 14:24:41
Comments...

We got admin access to Baseten's production GitHub in 25 minutes

Hacker News
www.strix.ai
2026-09-15 14:11:24
Comments...
Original Article

We were about to trust Baseten with our own and our customers’ data. So to be safe, we ran Strix to ensure they were secure first. About 25 minutes later, it had a live GitHub token with repository-level admin rights on internal Baseten repos.

We build Strix , an autonomous hacking agent, which of course means we need (cheap and fast) inference. We were exploring our options, and Baseten is one of the obvious choices. It's a great product, they're valued at $13 billion , and a lot of serious companies depend on them.

But... we're a security company. Before we give a third party our data, models, or code, we scan them. We would much rather find a problem and help get it fixed before we start depending on that service (we do this with almost all of our vendors and have a high rate of finding serious issues).

So... we pointed Strix at *.baseten.co and let it run without credentials or source code.

It came back with an active GitHub personal access token for basetenbot . That token had admin and push access to Baseten's main product repo, the GitOps repo that drives their clusters, and their Homebrew tap , plus read/write access to other private repositories including specific repos per customers.

The image build dated to March 2023 , and the token still worked when we found it in July 2026.

But before we get into the details, let's give some kudos to Baseten's security team. They confirmed the issue as critical, locked down the registry project, and rotated the token by the next afternoon. They were professional and very quick to deal with it (which is often not the case in these situations).

How Strix actually found it

Strix starts the way any good pentest does: recon. Many times the most severe vulnerability on your infra may be on a service on a subdomain you forgot about (which is why we always recommend black box testing alongside testing with code access)! So Strix enumerated hosts, looked through certificate logs, mapped the full surface. Eventually, it found a Harbor registry at gcp-us-east4-zlw.registry.baseten.co .

Harbor stores container images and groups repositories into projects. One of these projects was public. Without any token or auth, Strix could list repositories, obtain anonymous pull tokens, and download the actual image manifests and blobs. That included an image called baseten/baseten-app .

At this point, it would be easy to report an exposed registry and move on. But companies do intentionally publish images, and Strix never wants to report a false positive. The important question is what impact the images being exposed have.

Strix decided to pull an image and see what was inside. Here's that part of the run as it appears in Strix (yes, the agent's own writing sounds like AI, for obvious reasons):

Harbor Exposure Impact Review

Thinking

If those Harbor projects are not meant to be public, this is a real exposure, not metadata noise. The chain is what matters: enumerate public projects, read artifact metadata, mint an anonymous service/token scoped to repository:baseten/baseten-app:pull , pull the manifest, then fetch the blob through a signed storage redirect. That proves real anonymous pull access. The way to make triage take it seriously is to prove one thing from a pulled image: proprietary code, internal binaries, hardcoded credentials, or internal hostnames.

The first promising hit was a pair of AWS keys in baseten/baseten-app . Strix tried a read-only sts:GetCallerIdentity call, which tells you what account a credential belongs to. The response was InvalidClientTokenId .

That key was dead, so Strix kept looking.

Then, a token that actually worked

It pulled the layers, ran TruffleHog (shout out to our open-source security friends!), and inspected the image config directly. And there it was: a classic GitHub personal access token, sitting in history[].created_by .

I am not a Docker runtime expert, but luckily Strix is (thanks to it having pretty much all of human knowledge at its disposal). So it knew that that field records how a build step was created. In this case, it contained a RUN command with the value of GITHUB_TOKEN expanded directly into it.

Strix used the token for a read-only GET /user request to GitHub and… VOILÀ . 200 , with the account name basetenbot .

The token in the Docker build history, followed by GitHub identifying it as basetenbot. The credential is redacted.
The token in the Docker build history, followed by GitHub identifying it as basetenbot. The credential is redacted. Open image for full size.

Notice where the token was found. As I learned, a Docker image has filesystem layers, but it also has a config containing information about the image and its build history. That config is downloadable along with the image. Cleaning up a credential file doesn't help if the build history still contains another copy of the token.

And this one still worked more than three years later.

Okay, what can basetenbot do?

Job's not finished.

A live token is interesting, but obviously the permissions matter. This token could have 0 permissions and thus 0 impact. So Strix checked the account and its organization membership. GitHub returned X-OAuth-Scopes: repo , and the account belonged to basetenlabs .

GitHub returned repo scope for basetenbot and listed basetenlabs as its organization.
GitHub returned repo scope for basetenbot and listed basetenlabs as its organization. Open image for full size.

Then it checked the individual repository permissions, again using read-only requests:

Repository Access
basetenlabs/baseten admin: true , push: true
basetenlabs/flux-cd admin: true , push: true
basetenlabs/homebrew-tap admin: true , push: true
basetenlabs/release-platform Private, read/write
basetenlabs/basevibe Private, read/write
basetenlabs/trainers Private, read/write
basetenlabs/baseten-dbt Private, read/write

This is an insane amount of access to leave in a publicly downloadable image.

basetenlabs/baseten is the product. Someone with this token had admin and push permissions on the main source code repository for an inference platform. They could tamper with the code other companies rely on to run their models. We were considering sending our own code and models to this company, which is exactly why we do these checks in the first place.

basetenlabs/flux-cd is arguably even scarier. Flux is GitOps: the repository contains the desired state of the clusters, and Flux applies that state to the infrastructure. Admin access here creates a route from a leaked build token to changes in production infrastructure.

basetenlabs/homebrew-tap is how their CLI gets onto developer machines. Tampering with the distribution channel could turn this into a supply chain attack against people installing Baseten's tooling.

And then there was basetenlabs/fde . A listing of that private repo showed a top-level customers/ directory, with subdirectory after subdirectory named after Baseten customers.

At that point, we had enough to report and be confident this was not a false positive. We didn't clone the customer repo, push anything, or change any configuration. We stopped there and wrote the disclosure email immediately.

How does a token end up there?

The build history was timestamped. The step containing the token ran on March 3, 2023 . This was an old build credential that still had all of that access when we tested it in July 2026.

The underlying mistake is pretty familiar. A build needed to fetch private dependencies from GitHub, so somebody passed a token in as a build argument. The relevant pattern looked like this:

1 ARG GITHUB_TOKEN
2 RUN GITHUB_TOKEN= ${GITHUB_TOKEN} bash -c '\
3 if [[ "${GITHUB_TOKEN}" != "" ]]; then \
4 git config --global --add \
5 url."https://${GITHUB_TOKEN}@github.com/".insteadOf "git@github.com:"; \
6 fi'

I can see how someone ends up writing this. You need a private dependency, you pass in the token, Git authenticates, and the build works. But Docker can record that build argument in the image's metadata and history. In this case, it recorded the actual token value. Docker explicitly warns about this .

There is also a second problem with this pattern: git config --global writes the authenticated URL into Git's configuration file. Even if you change how the token gets into the build, you still need to avoid saving it into the image.

The fix is to use a BuildKit secret mount and temporary authentication that doesn't persist the credential. Then inspect both the image's layers and its history. And revoke the old token! Changing the Dockerfile doesn't do anything about an image that someone already downloaded.

What Strix did on its own

Baseten has a responsive security team and already uses AI security tooling . Still, this token from a 2023 build had admin access to their product and deployment repos when we found it.

It's easy to focus on the application and the source repositories, and forget about an old container image. Even if you scan the image's files, you still need to check its build history.

What I like about this scan is that Strix kept following the finding. It found a registry, checked whether it could actually pull an image, tested a credential and found it was dead, found another credential in the build history, and checked what that one could access.

We hadn't told it to look for Harbor or given it any hints about a token. It worked through the whole thing autonomously in about 25 minutes.

This is why we're building Strix. AI-powered attacks have been getting super scary in the past few weeks, and we believe the only way to defend yourself is to constantly be hacking yourself to find these issues (because there will always be issues) before the bad guys do.

Disclosure

Baseten handled this well. The timeline was:

  • July 13, 11:10 PM: I reported the live basetenbot token, the public Harbor project, and the repository permissions.
  • July 14, morning: Baseten made the Harbor project private. I flagged that the token itself still worked.
  • July 14, 4:34 PM: Anton from Baseten Security confirmed the issue as critical and said they had made the Harbor project private and rotated the token. He also asked us to securely delete the images we'd pulled.
  • July 14, 5:05 PM: We confirmed deletion and sent over two lower-severity findings from the same scan.
  • July 17: Baseten closed out the remaining findings.
  • September: We let Baseten know we planned to disclose the finding publicly and sent them a draft of this post.

They also sent us some T-shirts and sweatshirts as a thank-you for finding this critical bug.

Go check your old images

If you run containers and use GitHub, this is worth checking in your own infrastructure:

  1. See what someone can pull without logging in, including old tags and projects you haven't thought about in a while.
  2. Read the build history with docker history --no-trunc , or inspect the config blob's history[].created_by fields. Check the layers too.
  3. Get secrets out of build arguments. Use secret mounts, and make sure the commands consuming those secrets don't write them back into the image.
  4. Check what your build tokens can actually do. Fetching a dependency needs read access to that dependency. Giving that token admin on your product and deployment repos makes a leak much worse. Limit the permissions and give it an expiry.

And run something like Strix against your own systems. This whole scan started because we wanted to use an inference provider. We gave it a domain and got back a critical vulnerability that Baseten could act on the next morning.

AI attackers can follow these same paths. If an agent can find a live admin token in an old image in 25 minutes, you want yours to find it first.

Book a demo →

Hugging Face is billing OpenAI $100M for hacking it

Hacker News
thenextweb.com
2026-09-15 13:56:10
Comments...
Original Article

Companies that get hacked usually issue a statement and move on. Clément Delangue has issued an invoice.

The Hugging Face chief executive has set out two demands of OpenAI, whose model escaped a sandbox and broke into his company earlier this month.

Neither demand is a lawsuit. Both are unusual.

What he is asking for

The first request is disclosure. Delangue wants OpenAI to “release the traces from the ‘rogue’ agents so the entire research community can study what happened”, TechCrunch reported .

He calls this radical transparency. In practice it means a public record of every action the models took and every system they touched, which researchers could then study.

The second request has a price on it. Delangue wants OpenAI to commit “$100 million worth of computing power” so the Hugging Face community can build cyber defences.

The wording matters. He is not asking for cash. He is asking the company that caused the incident to pay in the one currency it has most of.

“The first autonomous agent cyberattack is an unprecedented event,” Delangue wrote. “It deserves an unprecedented response!”

His first public reaction was less formal. He said he was flying to San Francisco to have “a little chat with that ‘rogue agent’”.

What happened to Hugging Face

OpenAI admitted on 21 July that its own models were responsible. Two were involved, GPT-5.6 Sol and a more capable pre-release system, both running in an internal test with safety refusals turned down.

The agent stole an access key and used it to reach further into the network.

It was not the only OpenAI model behaving that way this month. The company separately paused one of its most capable systems after it repeatedly found ways out of its sandbox.

Then came the part that turned an embarrassing incident into an industry argument. When Hugging Face tried to investigate, analysing the intrusion meant submitting the attacker’s own code to commercial AI tools. Those tools refused, unable to tell an attacker from a victim.

So Hugging Face ran an open Chinese model on its own servers instead. GLM 5.2, built by Z.ai, reviewed more than 17,000 actions and helped contain the breach.

The word doing the heavy lifting

Delangue calls this the first autonomous agent cyberattack. That framing is what makes the $100mn demand coherent, and it is contested.

Security researchers have pointed at human error instead , specifically OpenAI’s apparent failure to properly configure a test environment that was meant to be fully isolated.

The distinction decides what OpenAI owes. If a machine escaped on its own, the whole field has a new problem and the industry needs new tools. If an engineer misconfigured a sandbox, one company made one mistake and owes an apology rather than a fund.

Delangue is arguing for the first reading. It is also the more expensive one for OpenAI.

Why the timing is awkward

A day after Delangue posted his demands, Nvidia launched the Open Secure AI Alliance , an industry group built on the argument that defenders need open models they can run themselves.

Hugging Face is a founding member. OpenAI is not.

Read the two things together and the alignment is hard to miss. Delangue asked for compute to build defences “with the best open and closed models”. Nvidia’s announcement says the world needs both closed and open models. He was making the alliance’s case a day before the alliance existed.

That gives the demand a second life. It is no longer only one company asking another for money. It is a member of a 37-strong coalition asking a non-member to fund the coalition’s work.

Whether anything happens

OpenAI has not publicly committed to releasing the traces or to the compute.

It has little obvious incentive to do either. Publishing full execution traces of a model that broke containment would hand competitors and researchers a detailed map of how its systems behave when guardrails come down. Paying $100mn would set a price for a category of accident that is likely to happen again.

There is also no mechanism forcing it. Delangue has not sued, and no regulator has ordered disclosure, though Congress responded to the breach with a proposed kill-switch bill.

What he has instead is the argument, and the fact that his company had to reach for a Chinese model to clean up after an American one.

That detail has already done more to shift the open-weights debate in Washington than any lobbying document. The bill may go unpaid. The example will not go away.

★ Thoughts and Observations on Apple’s ‘Surprise and Shine’ Event; the Announcements of the iPhones 18 Pro, AirPods 5, Apple Watches Series 12 and Ultra 4, and the iPhone Duo; and the Dawn of the Ternus, John Ternus Era at Apple

Daring Fireball
daringfireball.net
2026-09-15 13:54:09
The keynote, the products, and a new era....
Original Article

The Keynote

I loved the keynote ’s “You want a great opening scene?” opening, which ran through a series of genre-inspired hook-you-from-the-first-shot pretend movie openings. Apple started with a title card that said “The following was shot on iPhone”, but the whole keynote — including the fun fake-movie openings — was shot on iPhone 18 Pros. Apple started shooting its keynote movies using iPhone Pros in October 2023, with the “Scary Fast” Mac event . That first one was shot on the then-just-released iPhone 15 Pro, the first iPhone Pro generation to support the necessary pro video features to enable Apple-level production quality (e.g. ProRes recording and Apple Log encoding). 1 For the next few years, Apple shot each of its keynotes using whatever the current iPhone Pro model was. But last year, with the September 2025 “Awe Dropping” event , they shot the keynote using the as-yet-unreleased iPhone 17 Pro itself — a bit of a risk given that it was running mid-summer iOS 26 beta software. They did the same this year, shooting the entire keynote, including the prelude fake movies, using iPhone 18 Pros. But, they couldn’t say that it was the 18 Pro on the title card because the 18 Pro wasn’t announced until later in the keynote.

There was at least one report from an ill-informed know-it-all who claimed, with odd certainty given how often he’s wrong, that Tim Cook would not appear in the keynote. But the last segment of the keynote’s cold opening gave us Cook, Tim Cook, framed in an obvious homage to the opening of 2012’s Skyfall :

Daniel Craig in “Skyfall”.

Tim Cook in a “Skyfall” homage at the beginning of the keynote.

I’ve stolen those comparison frames from this splendid blog post by Vaziri, Todd Vaziri , in which he points out a few other Bond homages in this keynote — including using the new variable aperture on the 18 Pro’s main camera as a reference to the Bond title-sequence gun barrel. Fun stuff all around, and a perfect way to commemorate the Cook-to-Ternus passing of the torch. 2 Also, the variety of visual styles in these mini movies really showed off the capabilities of the iPhone 18 Pro as a cinematic camera.

The little Cook / Bond homage that closes the opening segment starts with a voiceover from Cook himself ( starting at 2m:07s ):

Forget all that. Keep it classic. Open on an aerial shot, high above the California coast. A car drives down the highway. Then it disappears into a tunnel. Cut to an interior. Camera follows a mysterious figure, before revealing our main character...

No no no no no. Not me. That’s your guy. That’s your opener.

It’s a perfect segue, and Cook’s line reading is sublime. Cook’s on-screen persona improved a lot in the 15 years he served as CEO and keynote emcee, but even at his best he always came across as a bit, well, stiff. Voicing this introduction of John Ternus, on the other hand, he sounds cool as hell — relaxed, certain, and comfortable. I think the chairman role suits him better than CEO did. He’s a natural born éminence grise . And if this was Cook’s last appearance in a keynote, he went out on top.

Ternus’s Turn

For those of us invited to Apple Park, Ternus actually took the stage in person at 9:58 am, for a brief in-person greeting and introduction. What I recall isn’t anything he said but how he was greeted, which was with tremendous enthusiasm. The annual iPhone events always have a unique charge to them. WWDC is a far bigger event attendee-count-wise, but the September iPhone event is sharper, higher pressure. The exclusivity heightens the stakes — Apple only invites as many people as fit in Steve Jobs Theater: 1,000 . The new era begins energy was palpable, if subtle. In the morning meet-and-greet coffee-and-apple-tarts mingling in and outside the Steve Jobs Theater upstairs atrium, attendees were genuinely unsure what to expect. A few were genuinely wondering if Ternus would bring back live-on-stage keynotes. (The apple tarts were supposedly made using apples grown on campus, and were quite good.)

In person, Ternus wore a dark t-shirt and jacket. In the keynote video, a burgundy shirt that clearly served as a reference to burgundy as the color of the year. (It’s hard to imagine Ternus in crayon orange shirt if he’d taken the top job a year prior.)

I think Ternus came across fine as emcee. Not great, but fine. He’s comfortable but I think the role still feels like a new pair of shoes he hasn’t yet broken in. Ternus’s opening line in the video was “Hello, and welcome to Apple Park”. I think that’s what he said on stage too. They’re leaving “Good morning!” as Cook’s signature, as they should.

Before any product announcements, Ternus laid out his theory of an “intelligent personal hub” for the AI era, making the case that if one were to design such a device from the ground up, starting today, you’d wind up with something exactly like an iPhone — with you all the time, with a great display, ubiquitous networking, great cameras and mics, and long battery life. The cynical take would be that this is an “ If all you have is a hammer, everything looks like a nail ” guiding doctrine. Apple is not a leading AI model company, is completely sitting out the capex data center rat race, and so of course they’re going to argue that the most popular product they make — the iPhone — is the central device of the AI era. But, well, I think it’s true. You need devices to run AI. I wrote an entire column about this back in May — “ AI Is Technology, Not a Product ” — responding to Steven Levy’s argument that Ternus “needs to launch a killer AI product”.

Levy, in Wired, wrote:

By the end of this decade, it’s unlikely that people will swipe on their phones to tap on Uber or Lyft. They will just tell their always-on AI agent to get them home. Or that agent will have already figured out where they need to go, and the car will be waiting without the friction of a request. “There’s an app for that,” may be replaced by “Let the agent do that.”

I wrote:

Actual products have to be real. Actual experiences have to rely on actual products. How exactly in Levy’s end-of-this-decade scenario will we tell our “always-on AI agent” to get us home? What microphone is listening to the command? What speaker is telling us the request was understood and acted upon? What screen do we look at to see how far away the hailed car is? I’d bet a pretty large sum of money that in 2030, when someone hails a ride-share vehicle to take them home, the most common product they’ll use to do that will be their phone. Whether they’re doing it via a verbal command issued to an “always-on AI agent” or good old tapping and swiping, it’ll be a phone.

Ternus’s “intelligent personal hub” doctrine seems very much in line with my retort to Levy’s not-entirely-thought-through fantasy. It also sounds an awful lot like a modern-day reimagining of a former Apple CEO’s “digital hub” strategy for the Mac. I highly recommend you take 8 minutes of your time, right now, and watch Steve Jobs’s introduction of the digital hub strategy from January 2001’s Macworld Expo. History doesn’t repeat but it does echo, and Ternus’s “intelligent personal hub” sure sounds exactly like Jobs’s “digital hub” echoing a quarter century later. If Ternus’s “intelligent personal hub” strategy works out like Jobs’s “digital hub” one did a quarter century ago, Apple’s going to be alright.

iPhone 18 Pro

Heretofore, to my recollection, the iPhone Pro always came last in the September keynotes (including the years when the higher-tier iPhones didn’t yet have the word “Pro” in their names, like the XS in 2018 and X in 2017). This year the 18 Pro came first. This worked both as the immediate product announcement after Ternus’s expression of Apple’s intelligent personal hub strategy, and to clear the final act of the keynote for the much-anticipated Duo. (It’s somewhat telling that the Air didn’t get that cleanup spot in the announcement batting order a year ago. 3 )

Thoughts on the colors, based on my hands-on experience:

  • Black: Truly deep black. Reminds me a lot of the flat black iPhone 7 (not to be confused with the jet black iPhone 7 — what a year for black that was ).
  • Silver: Looks good. Not sure how it’s different from last year’s silver, because I had no way to compare side-by-side.
  • Glacier: Nice. I think this will be popular. It’s definitely blue, not silver / gray (which you’d hope would be true in general, but especially alongside an explicit silver colorway).
  • Burgundy: Last year the iPhone 17 Pro only came in three colors: silver, deep blue, and cosmic orange. I sort of thought of the strategy as “light”, “dark”, and “special”. But this year there are two specials, glacier and burgundy, but burgundy seems more special. Last year cosmic orange was clearly the color of the year. It was the most-used by far in Apple’s advertising, and I suspect it was the most popular sales-wise. Burgundy is that color this year. I don’t like it nearly as much, and I don’t think it will prove to be as popular or as iconic as cosmic orange. The cosmic orange iPhone 17 Pro seems set to go down as one of the most iconic iPhone colors ever made. I don’t think that will be true for the burgundy iPhone 18 Pro.

Pricing:

256 GB 512 GB 1 TB 2 TB
18 Pro $1,200 $1,400 $1,800 $2,400
18 Pro Max $1,300 $1,500 $1,900 $2,500

Compared to the 17 Pro one year ago : 4

256 GB 512 GB 1 TB 2 TB
17 Pro $1,100 $1,300 $1,500
17 Pro Max $1,200 $1,400 $1,600 $2,000

So:

  • Max remains $100 extra.
  • 2 TB is now available on the regular-size Pro; last year it was exclusive to the Max.
  • 256 and 512 GB storage options are only $100 more expensive this year.
  • 1 TB costs $300 more this year.
  • 2 TB costs $500 more.

256 and 512 GB are by far the most popular sizes (and I think 256 in particular far outsells 512 — the base size truly is large enough for the majority of users), so for most 18 Pro buyers, the RAM/SSD shortage is only resulting in a $100 year-over-year price increase. The higher storage tier prices aren’t too bad, all things considered.

It’s worth pointing out here that coincident with the announcement of the iPhones 18 Pro and Duo on Wednesday, Apple raised the prices of all existing iPhones in its lineup by the same amounts, depending on storage — $100 for models with 128, 256, or 512 GB storage; and $300 for the iPhone Air with 1 TB.

Technically, the iPhone 18 Pro mostly offers three new things compared to the 17 Pro: the A20 Pro SoC (faster cores, two Neural Engines instead of one, and 50% faster memory bandwidth); variable aperture on the 1× main camera, ranging from ƒ1.48 to ƒ4.0; and an all-new thermal management system inside, including a much larger vapor chamber. The A20 Pro means peak performance is faster; the new thermal system allows it to maintain peak performance longer.

In a curious decision, the regular-sized 18 Pro models ship with Apple’s C2 cellular modem worldwide, but the 18 Pro Max models use a Qualcomm modem in the U.S. , and the C2 everywhere else . Apple’s C-series modems are power efficiency marvels, with (in my experience) just as good reception and speed. I’d be just ever so slightly bummed about this if I were a Pro Max fan in the US.

That said, both the 18 Pro and Pro Max models continue to get slightly longer battery life in the US models than worldwide models , because the US models are eSIM-only, but most (all?) models elsewhere still have a physical SIM tray. The US models use the space occupied by the SIM tray in international models to make the batteries physically larger. Going by Apple’s new “typical use” tech spec, the US Pro / Pro Max models are rated for 24 / 30 hours respectively; the international models with a physical SIM tray for 23 / 29. Not a big difference, but not nothing.

In line with my belief that modern iPhones are best thought of as cameras with phones, not phones with cameras, most of the 18 Pro’s year-over-year improvements (and the features that set the Pro models apart from all other iPhones, including the Duo) are related to photography — both still and video:

  • Photographic Styles is now up to version 3, adding new controls for “texture”, film-look, and grain. One stated goal of this is to enable shooting images with a less processed look; on the other end of the aesthetic spectrum, though, I think it will also enable shooting with a more processed look, with the addition of “glow” and “soft skin” as texture alternatives alongside “film” (which is the only texture with the new grain option). The other, and default, texture option is “standard”, which effectively means no texture processing.

  • Apple Reference Image is a new shooting mode (main 1× camera only, because it’s tied to new features on that sensor) that performs pixel-by-pixel signing of the sensor data and embeds that signature in the file. After capture, reference images can be sent to Private Cloud Compute servers for verification and comparison to the original image sensor data. To capture images in Apple Reference format, you must shoot them in that mode, and you must enable that shooting mode first. The embedded sensor data doesn’t make files that much larger, but the sensor data does uniquely identify your iPhone. That’s part of the verification. When you share a photo captured in Apple Reference Image format, you have the option to share a plain version without the embedded verification data. Apple Reference Image capture is not available in the EU or China, the world’s two largest bastions of freedom, but according to an Apple footnote , users in the EU “with iOS 27, iPadOS 27, and macOS 27 will be able to develop and view reference images.”

  • Cinematic Mode can now be applied after shooting a regular video; on previous iPhones the Cinematic Mode effect was only available via a specific shooting mode.

To the layman’s eye, though, the 18 Pro just looks like it comes in new colors. That’s not a complaint, just a fact. Oh, but the Face ID infrared camera is now underneath pixels on the display, so the Dynamic Island cutout is now smaller. So the part of the Dynamic Island that is always black is smaller, but because there are now more display pixels next to it, the 18 Pro Dynamic Island can now show three active Live Activities, up from two. Apple is getting closer and closer each year to achieving a no-notch / no-cutout display.

AirPods 5

Just like with AirPods 4, Apple continues to make two different products under the same name. For $130, AirPods 5 now have active noise cancellation (previously reserved for the higher-priced tier of AirPods 4), including Transparency and Adaptive modes. For $150, you get AirPods 5 with a wireless charging case (using MagSafe, Qi, or Apple Watch pucks), volume control by swiping up / down on the earbud stems, and longer battery life. It’s a much better product for just $20 extra, but it must have worked out well for Apple with a similar split with AirPods 4. I honestly can’t see why anyone should buy the $130 models, but someone must be buying them.

Apple provided me with a pair (the $150 model) to review, with no embargo. I wore them on the cross-country plane ride home, and compared them to my own AirPods Pro 3 for noise cancellation. It’s hard to make a direct comparison of the overall acoustic quality, but solely in terms of reducing the noise of the airplane cabin while trying to work, I found the new AirPods 5 with full noise cancellation to be roughly equivalent to the AirPods Pro 3 in Transparency mode. That’s pretty good, and makes the ambient noise level in an airplane much more pleasant. But the full noise cancellation mode of the AirPods Pro 3 is an entire step change in noise reduction that the open-ear AirPods 5 cannot match.

I feel bad for people who despise airplane cabin noise but whose ears aren’t comfortable with closed-ear fit earbuds like AirPods Pro. But the noise reduction quality of the AirPods 5 seems impossibly good given their open-ear design. I don’t find AirPods Pro uncomfortable, but I do find regular AirPods more comfortable, especially for long stretches like hours-long flights. I’ll take the small trade-off in comfort for the big improvement in audio quality and noise cancellation with the AirPods Pro, though. With AirPods Pro you can truly forget you’re on an airplane at all if you’re watching a movie with Vision Pro or even on a MacBook or iPad display in a dark cabin. 5

Apple Watch Series 12 and Ultra 4

The big new health thing is a next-generation heart sensor that takes readings every 5 seconds — 60× as frequent as with Series 11 / Ultra 3 a year ago. Apple commissioned a study comparing their new watches against a slew of competing heart rate monitoring devices, and released a paper with the results , claiming that Apple Watch was the most accurate. That’s an Apple-commissioned, Apple-released study, so take it with a grain of salt, but it looks solid to me and I’m unaware of any other studies showing otherwise. (And if I’m reading the results correctly, rings like Oura’s are crummy heart rate monitors.)

Ultra 4 looks the same as Ultras 1–3 (although Ultra 1 only came in natural titanium, not black). Series 12 comes in some tweaked colors in matte aluminum ($400) and polished titanium ($700), and for the first time since Series 5 seven years ago, brings back ceramic, in two colors: pearl white and night blue ($900). Those prices are for the smaller 42mm size; add $50 to each of them for the larger 46mm size. As has been true for every Series model to date, only the higher-priced materials come with sapphire crystal displays; the entry-model aluminum models still use Ion-X glass. But now those glass displays offer Ceramic Shield 2, which treatment has proven to be a tremendous boon to scratch resistance on iPhones.

The S11 chip powering the new watches enables two very interesting, and instantly controversial Apple Intelligence features :

  • Live Rewind is a feature that allows you to double-click the digital crown to bring up a transcript of the last 15 seconds of conversation around you. Apple claims, somewhat preposterously, that audio isn’t being continuously recorded, when it clearly is. What they mean is that audio is only being cached for 15 seconds, continuously. There is no way to set Live Rewind to transcribe more (or less, for that matter) than the last 15 seconds of audio.

  • Siri Recap is a feature that produces Apple Intelligence-generated summaries of your conversations throughout the day, while the feature is enabled on your Apple Watch. These are not transcripts, just bullet-point style summaries. You can set Siri Recap to only be enabled in certain times (like work hours), and you can toggle it on and off in Control Center at any time. But when it’s on, that’s the only control you have over it. You can’t manually start / stop conversations to record, like you do when recording something with Voice Memos. You just turn Siri Recap on, and Siri generates summaries when it thinks you’ve had a conversation worth summarizing. It’s entirely and exclusively automatic, when the mode is enabled.

Neither of these features are available in WatchOS 27.0. They’re both described as coming “in beta later this year”. I asked Apple reps if that meant 27.1, and they wouldn’t answer other than to repeat “later this year”, but I thought I saw a smile. Both features are English-only for now.

Some people are objecting to the very existence of these features as privacy intrusive. I think they’re pretty reasonably thought out. There are surveillance cameras all over the place now. I was walking home from dinner here in Philadelphia a few weeks ago with a friend visiting from out of town, talking about this, and he stopped midway between the restaurant and my house and asked me to count how many cameras we were on at that spot. I think we counted seven or eight. Who knows how many we missed? I’m not saying that’s good or bad, it’s just the world today. So too it’s soon going to be with personal devices recording audio. This is inevitable. I’m glad to see Apple ship features like this early, with pretty limited scope, to provide good examples before other companies ship devices that record and save audio continuously throughout the day. I think it’s better for Apple to lead with privacy-minded versions of ambient transcripts and AI-generated conversation summaries than to follow after other products have hit the market with all-day recordings and whole-life transcripts.

Think back to Apple’s announcement of AirTags. A lot of people had a perfectly reasonable “ Hey I don’t know if it’s a good idea for a product like this to even exist ” initial reaction. But having Apple lead the way actually proved good. I suspect Live Rewind and Siri Recap will turn out like that.

It’s also interesting to think about why these two features are only on Apple Watch, not iPhone. I think it’s because once an Apple Watch is on your wrist and unlocked, it remains a “trusted device” until you take the watch off. An iPhone is only a trusted device while it’s unlocked. It’s locked, and thus untrusted, while it’s in your pocket or bag, or even just sitting on a desk. I leave my iPhone unattended in our kitchen all the time. I’m often not in the room for conversations that take place in the kitchen while my iPhone is there, sitting on the counter. I shouldn’t get summaries of those conversations. And, thinking more nefariously, I shouldn’t be able to hide my iPhone on or near someone’s desk, or in a conference room, or in a car, or anywhere else to get Siri Recap summaries of conversations where I’m not present. Making this an Apple Watch exclusive feature avoids that. Thus, I think Apple Watch can be trusted to “listen” ambiently, all the time while it’s unlocked on the wearer’s wrist, in ways that even an iPhone can’t.

iPhone Duo

Ternus broke out the hallowed “But actually, there is one more thing...” to introduce the Duo, which line — to circle back to the keynote’s intro — is for Apple CEOs what “Vodka martini, shaken not stirred” is for actors playing James Bond. 6

Pre-orders for the Duo don’t start until October 16, and it doesn’t ship until October 23. In my experience, Apple never seeds review units for any product until a few days before pre-orders open, so we’re not going to hear or see anything more about the Duo until those review embargoes lift on October 20 or 21, I bet.

Duo is available in only two colors: star white (polished silver) and night sky (a deep blue-black). As predicted, the Duo is expensive. I’ll put it alongside the 18 Pro models for comparison:

256 GB 512 GB 1 TB 2 TB
18 Pro $1,200 $1,400 $1,800 $2,400
18 Pro Max $1,300 $1,500 $1,900 $2,500
Duo $2,000 $2,200 $2,600 $3,200

Effectively, it’s $800 more than an 18 Pro and $700 more than an 18 Pro Max.

Key facts:

  • It has no Face ID sensor, either open or closed. The only authentication is an iPad Air-style Touch ID side button.

  • It has a Camera Control button, but does not have an Action button.

  • The interior display has a matte nano-texture display. It looks great and apparently this is a key element of Apple’s efforts to make the crease as unnoticeable as possible. By next year I expect all competing foldables to have matte interior displays and 1.4 : 1 aspect ratio displays.

  • It is very satisfying to open and close.

  • The outer glossy screen has a “Center Stage” 12 megapixel front-facing camera, that seems equivalent if not identical to the Center Stage front cameras in the iPhone 17 generation phones. Year-ago tech but still high quality. There’s a circular hole punch in the corner of the display for this camera.

  • The inner matte screen has an under-the-display “FaceTime” camera for which Apple hasn’t even given a megapixel count. Clearly this is a lesser camera — the question is how much lesser. The idea presumably is that this camera is for use in Zoom or FaceTime apps, where you might want to use the bigger inner display to view the meeting, and don’t care as much about your own image quality. If the Duo is opened, and you care about image quality for self portraits, you can shoot using the main camera and use the outer display to frame yourself. When not using the inner FaceTime camera, there is no visible cutout showing where it goes. If you look closely at the display however, you can see where it is. Under-display cameras aren’t magic.

  • The regular cameras are a 1× / 2× main, and a 0.5× ultra wide. I suspect the main 1× camera is very similar optically to that of the iPhone Air, but it has A20 Pro features like Photographic Styles 3. (The Air only has last year’s Photographic Styles 2.)

The initial introduction of the Duo was handled by two keynote newcomers: industrial design VP Molly Anderson , and human interface VP Steve Lemay . A hardware / software design duo ( ahem ), if you will. What made it worth having Anderson and Lemay present together is that the iPhone Duo exemplifies Apple’s integrated approach to product design. Are they a hardware company that writes their own software or a software company that makes their own hardware? The answer is yes. 7 The defining aspect of the Duo isn’t that it folds open and closed — we’ve seen almost a decade of Android phones that do that — but its aspect ratio, roughly 1.4 : 1. Because that’s effectively the square root of 2, doubling the outer display’s shorter dimension when you unfold it produces an inner display with roughly the same aspect ratio. That’s elegant and satisfying conceptually. And I think in practice it will feel right, too.

Heretofore the iPhone has only had three display aspect ratios:

  1. The original iPhone (2007) display was precisely 3 : 2 (480⁠ ⁠×⁠ ⁠320 pixels), and this remained true through the iPhone 4 and 4S (960⁠ ⁠×⁠ ⁠640 pixels, 2× retina).

  2. Then the iPhone 5 (2012) display size went from 3.5 inches to 4 inches, but added all the extra pixels on the long dimension. Held vertically, it got taller, but not at all wider, changing the aspect ratio to 16 : 9 (roughly 1.77 : 1). This 16 : 9 aspect ratio held for all of the 5, 6, 7, and 8 generation iPhones, including the Plus models.

  3. The iPhone X (2017) introduced the “all-screen” era. The actual device hardware didn’t change size or aspect ratio by much , but eliminating the chin (with Home button) and forehead and filling those areas with the screen, the display aspect ratio went to roughly 2.17 : 1, and every new model since then, including the new iPhone 18 Pro and Pro Max, has maintained that aspect ratio. 8

Thus, over 20 years, iPhone displays (and thus all phones, since all the other phones mostly just copy iPhones) have only gotten more elongated: from 1.5 : 1 to 1.77 : 1 to 2.17 : 1. Now, though, the iPhone Duo goes squatter than the original iPhone for the first time, to 1.4 : 1 on both the outer and inner displays. 9 That’s a noticeable change from the original iPhone, but it’s a downright striking change from every iPhone released in the last 10 years.

This aspect ratio is so different that it demands a different UI layout for the Duo to have any chance at being a good experience. That’s exactly what Apple has done, and what Steve Lemay was there in the keynote to unveil. A lot of UI chrome that previously was at the top or bottom of the display is now on the right — the dock on the home screen, the toolbars in apps, etc. And these elements are always on the right. You can’t turn the Duo upside down to move them to the left, nor is there an accessibility setting to move them to the left. This layout puts these controls on the right not because 90 percent of people are right-handed and Apple thinks lefties can get bent, but, I think (and so I’ve been told in conversations with Cupertinoites), because that’s the nature of a book-like shape. The arrow keys on keyboards always fall on the right, publishers don’t print left-handed books, and carmakers don’t make left-handed steering wheels. The right-side bias in the Duo iOS layout is like that.

I’ve only spent a few minutes, in total, playing with the Duo, so I really can’t say whether it succeeds or not. But I will say that it’s weird . Not saying it’s weird good or weird bad — just weird different . In some ways iOS on the Duo feels more different from iOS on all other post-iPhone-X phones than iOS on the original iPad felt from iOS on the iPhones of that time. The original iPad just felt like a much bigger iPhone. The Duo feels like a different layout entirely, in every single app that I had time to poke around with.

Is the Duo good? I do not know. Is it different? Very much.

Here’s the thing. If you take a 2 : 1 or even 2.2 : 1 aspect ratio phone display and turn that into a book-like foldable, the inner screen comes out square — or so close to square that we might as well just call it square. Nobody makes any device of any sort that just has a square display. Square is a terrible aspect ratio for a phone or a tablet or a laptop or a desktop display. The only square displays on the market are the inner displays for Android foldables from the last decade. Square being a shitty aspect ratio is half the reason almost no one has bought those things . The other half of the reason is that no developers truly adapted software to take advantage of the double-area inner displays. Google didn’t do it, Samsung didn’t do it, and since neither of them bothered to do it, of course third-party developers didn’t do it either. Nothing adapts to the inner display on Android foldables — it just expands to fill the space.

Apple’s insight to go with 1.4 : 1 on the outside and thus also 1.4 : 1 on the inside gives the Duo a chance. Maybe this will be a huge hit. But maybe not? There are no 5-inch phone displays with aspect ratios anywhere near as squat as the Duo’s. That’s a tablet aspect ratio. The current A17 Pro iPad Mini has a 2266⁠ ⁠×⁠ ⁠1488 pixel display: roughly 1.5 : 1. The 11-inch iPad Air is 1.4 : 1 and the 13-inch Air is 1.33 : 1. So I think the Duo’s inner display is going to feel just right.

But that outer display? I don’t know. Even in the best case, it’s going to be no better than a tolerable compromise. If it were actually a good idea there would be non-folding phones with 1.4 : 1-ish squat displays, and there aren’t any such phones. And even if the Duo proves to be a hit, I don’t think there will be. If you only have one display you don’t want it to be the size and shape of the Duo’s outer display. So the questions are: How tolerable has Apple’s UI redesign made it? And how will Duo users split their time between using it closed vs. using it open? Someone who spends more of their time using it open might absolutely love it with a “you’ll take it away only from my cold dead hands” fervor. Someone who winds up mostly using the outer display because so many of their iPhone interactions are quick take it out → use it for a bit to check something, jot something down, or snap a quick photo → put it back in pocket / bag situations, might wind up regretting it.

Given the design brief of creating a book-shaped foldable phone, I feel quite certain already that Apple has nailed it with the Duo. But having only seen the keynote spiel and poked around with one in hand for a few minutes Wednesday afternoon, I think it remains an open question whether a book-shaped foldable phone is a good idea in the first place. Folks from Apple who have actually spent time carrying the Duo seem genuinely enthused. I remain highly skeptical about its appeal for me personally, and somewhat skeptical about its appeal broadly. That’s OK if it’s not suited for me, but is suited for many (but not most) people. No one in the keynote described the iPhone Duo as the future of iPhone, like they did when introducing the iPhone X in 2017. I just wonder if the fundamental idea appeals to all that many people at all. We’ll find out starting in late October.

Regarding some specific Duo details:

  • The lack of Face ID is a serious sad-trombone bummer, and in my opinion could sink the first-generation model. Side-button Touch ID is OK, but OK is not good , and even good is not insanely great . Face ID is insanely great. For whatever reason, the only iPads with Face ID are the iPad Pro models. Every time I review an iPad Air or iPad Mini, my biggest gripe is the lack of Face ID. I complained about it reviewing the iPad Air in 2022 and the iPad Mini in 2021 , and in the five years since then my annoyance has only grown. With Face ID, so long as you’re looking at the screen as you unlock the device, it’s like not having it locked at all. It just unlocks, seemingly automatically. With Touch ID you must think about putting one of your registered fingers on the button every single time you unlock it. It never ever feels automatic. I feel quite certain Apple tried to fit a Face ID sensor on the outer Duo display and could not make the engineering work.

  • I’m much more concerned about convenience than security, but Face ID is 20× more secure than Touch ID. That’s according to Apple’s own Apple Platform Security whitepaper, updated just last month, which states:

    The probability that a random person in the population could unlock a user’s iPad, iPhone, or Apple Vision Pro is less than 1 in 1 million with Optic ID or Face ID — including when Face ID with a mask is turned on. For a user’s iPad, iPhone, Mac models with Touch ID, and those paired with a Magic Keyboard with Touch ID, it’s less than 1 in 50,000. This probability increases with multiple enrolled fingerprints (up to 1 in 10,000 with five fingerprints) or appearances for Face ID (up to 1 in 500,000 with two appearances).

  • As a sign of how inconvenient the lack of Face ID is, Apple is promoting the fact that the iPhone Duo can be unlocked by your Apple Watch, like a Mac. No other iPhone needs such a crutch. 10 Based on my experience with Touch ID iPads, wearing an Apple Watch all the time to unlock the Duo will feel essential. I don’t wear an Apple Watch all the time, and don’t want to start wearing an Apple Watch all the time, so I suspect this alone will keep me from switching to an iPhone Duo personally. On the other hand, for people who do wear an Apple Watch all the time and are happy to do so, the Duo’s lack of Face ID might prove to be only an occasional inconvenience rather than a constant annoyance. 11

  • Contrary to some speculation , the Duo does support MagSafe. (Samsung’s latest, the Galaxy Z Fold8 and Fold8 Ultra, support inductive Qi charging but don’t have magnets, so you need to use a case or something to get chargers to snap into place.)

  • Closed, the Duo has — per Molly Anderson’s dulcet keynote description — “a distinctive asymmetric shape, with two rounded corners, that invites you to open it.” Expect this to be copied by every other foldable phone in 2027, and then for Android dorks to argue that it’s a totally obvious design. (Same for the matte interior display.)

  • Apple rates the Duo as IP68 for dust and water resistance, with exactly the same description as the iPhones 18 Pro (and all other iPhone models currently in the lineup): “Rated IP68 (maximum depth of 6 meters up to 30 minutes) under IEC standard 60529”. Samsung’s new Z Fold8 is only IP48 . With these IEC ratings the second numeral is for water. The first numeral is for “solid foreign agents” (read: dirt and dust). iPhone Duo’s IP6 x rating means “dust-tight”. The Z Fold8’s IP4 x rating isn’t even “dust-protected” (that would be IP5 x ) but instead only protection against particles 1mm or larger. 1mm is pretty big for a piece of grit. That doesn’t mean it’s susceptible to ingress from every particle smaller than 1mm, but it’s not good. Pocket lint is a real thing and people put their phones in their pockets.

I’m Trying So Hard Not to Be Insufferable About Predicting the ‘Duo’ Name That I’ve Put This Entire Section 6,000 Words Deep Into the Column

Yours truly, back on April 14 :

I have no inside knowledge about what Apple plans to name this device, but I’ll eat my proverbial hat if they name it “iPhone Fold”. That name is so dumb it’s what Samsung calls their foldables. You don’t name a device for what it does, you name it for what it connotes. A good name conveys feeling, not just function. “iPhone Ultra” or “iPhone Max” would both work, and Ultra sounds more luxe. So while unsurprising, that’s probably the best bet, even without the reliable word of Mr. Digital Chat Station.

But if you want my take on a wildcard name, one with some history , how about “iPhone Duo”?

My thinking re: “Duo” seemingly jibed with that of Apple’s crack marketing team:

  • It’s two devices in one.
  • The early 1990s PowerBook Duo (and corresponding Duo Dock ) were extraordinary Macs that were ahead of their time.
  • It’s not ultra , if we take ultra to mean “better than what’s less than ultra in every way”. The iPhone Duo is different — quite different — from the iPhones Pro but, to name one conspicuous example, the iPhones Pro have better camera systems. (The Duo is ultra-priced, but one can also say it’s double-priced.)
  • Duo looks and sounds so good alongside Neo .

When I suggested “Duo” in April and again the day before the event , the number one objection was that Microsoft had used the name “Duo” for one of its many forgettable Surface products. The half dozen or so readers who argued thus might be the only people alive who remember this product. No one cares. Microsoft also had a Surface Neo , and no one cares about that one either. Even if Apple hadn’t offered a line of (successful, memorable) PowerBook Duos from 30 years ago, it wouldn’t matter that Microsoft had used “Duo” in 2020. For chrissakes Cisco had the rights to the name “iPhone” in 2007 and had been making products with the name since 1998.

I love the name Duo for this iPhone. It just feels right. And, just five days in, I already find myself saying and writing just “Duo”, without “iPhone”, when referring to it.

Miscellaneous

  • Too many cooks spoil the stew. It was very fun and a little touching to have the Cook hand things off to Ternus at the start, but I think there were too many presenters in the keynote. Duo was effectively introduced three or four separate times. First by the Anderson / Lemay ( I’m going to pun it again ) duo, then Joz, then Craig Federighi (covering much of the same software ground Lemay did), then Johny Srouji (covering much of the same ground Anderson did). Maybe the thinking was that everyone wanted a part in Ternus’s debut keynote. Maybe this is just the way of the future — many small segments, rather than one continuous on-stage live monologue. It didn’t make the keynote run overly long (running time was just 1h:17m), so maybe I’m all wet here. But I think what I wanted was to see Ternus do more of the explaining — not just the what but the why .

  • Apple’s YouTube video of the entire keynote has over 49 million views, and that doesn’t count the views from Apple’s own hosted version, presented on Apple.com and Apple TV. Apple has 12 other shorter keynote-related videos on YouTube, including “ Introducing the new iPhone 18 Pro ” with 18 million views, and “ Introducing the new iPhone Duo ” with 28 million views. For comparison, this year’s WWDC keynote on YouTube has 8.9 million views. 9 million views is a hell of a view count for the introduction of operating systems, but the iPhone remains king — not just for Apple, but the industry. It remains the Super Bowl of the annual tech calendar.

  • For an even better comparison, in the full version of that 2001 Macworld Expo keynote that I suggested you watch earlier in this column — the one where Steve Jobs introduced the Mac-as-digital-hub strategy — when Jobs takes the stage, he’s beaming with pride when he says there are “tens of thousands” of people watching the stream live. Both streaming and Apple have come a long way in 25 years.

An Update on Wayback Machine Access

Hacker News
blog.archive.org
2026-09-15 13:52:18
Comments...
Original Article

We’ve heard you: “Fix the Wayback Machine!”

Here’s what’s going on. The Internet Archive’s Wayback Machine has been hit by waves of high-volume automated traffic, and we’ve put protections in place to keep the service running. One recent change: we rewrote the message you see when a request is blocked with a 429 error — the HTTP code that means “too many requests.”

Those protections sometimes catch real people by mistake. If that’s happened to you, we’re sorry, and we appreciate your patience while we work to reduce the errors.

We’re getting better at telling abusive bots apart from the people who depend on the Wayback Machine every day. If you think you were blocked in error, email info@archive.org with your operating system, browser, and IP address, and we’ll look into it.

Gemini 3.8 Live and 3.8 Live Extended Thinking

Hacker News
blog.google
2026-09-15 13:38:18
Comments...
Original Article

Gemini 3.8 Live and Gemini 3.8 Live Extended Thinking are our most advanced live dialogue models yet. Major upgrades in intelligence and parallel reasoning make them more intuitive to collaborate with and use to execute complex tasks using your voice.


Malini Jaganathan

Member of Technical Staff, on behalf of the Gemini Audio Team


Text "Introducing Gemini 3.8 Live and 3.8 Live Extended Thinking" with the Gemini Spark, all on a light blue background

Your browser does not support the audio element.

Listen to article

[[duration]] minutes

This content is generated by Google AI. Generative AI is experimental

Today, we’re introducing two new models that bring advancements in near real-time reasoning to more effectively enable voice agents and make conversing with AI feel more intuitive and intelligent.

  • Gemini 3.8 Live : Built for scale and cost efficiency, combining conversational intelligence with fluid dialogue and visual grounding.
  • Gemini 3.8 Live Extended Thinking : Built for high-complexity tasks, with increased intelligence and multi-step reasoning.

For developers and enterprises, these models deliver the building blocks for reliable, production-ready voice agents. They also make speaking with Gemini across the Gemini app, Google Workspace, and Search more fluid and collaborative — helping you tackle complex tasks using just your voice.

Experience more fluid, intelligent conversations

Gemini 3.8 Live Extended Thinking provides enterprise-grade task completion and intelligence, capturing the #1 overall spot on Artificial Analysis' Speech to Speech Quality Index (82.6), and leads in agentic task completion with 68.6% on τ -Voice and 35.1% on Sierra’s τ -Voice-banking benchmark. It also provides strong reasoning capabilities, scoring 97.7% on Big Bench Audio, while maintaining a highly competitive price point compared to other frontier models.

Gemini 3.8 Live has shown a high preference among users, securing a second place in the Speech Agent Arena . In addition to this performance, it remains highly cost-effective — providing developers and enterprises with a capable and efficient model built for scale.

a chart showing Artificial Analysis Speech to Speech Index

A chart showing Artificial Analysis agentic performance

A chart showing Sierra

A chart showing Artificial Analysis cost per hour of input audio

On ServiceNow’s EVA-Bench , a benchmark for evaluating voice agents, our models push the Pareto Frontier for complex workflows by successfully balancing accuracy with conversational quality.

Note: This was run on the Live API on Gemini Enterprise Agent Platform.

A chart showing EVA Bench Experience to Task Completion

Gemini 3.8 Live processes visual inputs in near real-time, enriching conversations with context for more helpful responses. It automatically detects and transitions between 97 supported languages mid-conversation. It executes tools and API calls in the background while continuing the conversation, so the model can acknowledge requests and keep chatting while tasks finish in the background.

For tasks that require deeper reasoning, 3.8 Live Extended Thinking reasons and speaks simultaneously. It delivers increased intelligence for complex workflows while maintaining an uninterrupted conversational flow — using early verbal cues like “Let me check that…” to acknowledge prompts naturally, and live progress narration to walk users through multi-step background tasks as they progress.

Across Google Workspace and Search, our Live models deliver more intuitive, collaborative experiences — especially when tackling your most complex tasks.

Get step-by-step, real-time troubleshooting help powered by Gemini 3.8 Live — right inside Search Live.

Empowering the developer and enterprise voice ecosystem

By using the Gemini Live API , developer platforms such as Agora , Fishjam , LangChain , LiveKit , Pipecat , Vercel , and Vision Agents enable developers to build and deploy high-performance voice-driven interfaces with ease. These platforms manage complex real-time media streaming infrastructure behind the scenes, allowing developers to focus entirely on crafting the user experience.

We’re also partnering with companies like Salesforce, Genspark, and Lumeris who are excited about 3.8 Live and 3.8 Live Extended Thinking, highlighting its impressive latency, fluidity, and tool-calling capabilities.

Salesforce quote

11Sight Quote

Equal AI Quote

ServiceNow quote

Genspark quote

Lenskart quote

Lumeris quote

Agora quote

Ambr AI quote

LiveKit Quote

Casuu quote

Ensure transparency with SynthID watermarking

All audio generated by our AI products is watermarked with SynthID . This imperceptible watermark is woven directly into the audio output, ensuring AI-generated content remains detectable to help prevent misinformation. For details on our approach to safety and responsibility, review the model card .

Start using our latest Gemini Audio models:

3.8 Live is rolling out starting today:

3.8 Live Extended Thinking is rolling out starting today:

Get the latest news from Google in your inbox

Sign up for our newsletters with product updates, event information, special offers, and more.

Your information will be used in accordance with Google's privacy policy. You may opt out at any time.

Dystopian Surveillance Is Becoming a Reality

Hacker News
dallincrump.com
2026-09-15 13:37:36
Comments...
Original Article

Apple recently announced an upcoming feature for their new Apple Watches called “Audio Intelligence,” which will always be listening to and transcribing conversations in the background and summarizing them at the end of the day. While Apple have implemented some protections and safeguards to protect the privacy of the watch owner, anyone who talks to someone who has an Apple Watch with “Audio Intelligence” enabled could potentially be recorded without their knowledge or consent.

I can see how such a feature could be genuinely useful. Keeping a personal journal, keeping track of what was discussed in business meetings, etc. But I think the privacy implications are serious.

I don’t like the idea that anything I say in a public setting or in a conversation with others could end up in a transcript summary on someone’s personal device. But this is just the latest in a long line of concerns about surveillance as the devices we use become more and more technologically advanced.

In truth, there have been concerns for many years about smartphones, smart speakers like the Amazon Echo, and even smart TVs listening in on us without our knowledge or consent. I've lost track of how many times I've experienced or heard others relate their experiences of how they were discussing a particular topic with a friend or family member and then as soon as they get on Facebook or another social media platform on their smartphone, they see posts and ads featuring the very topic they were discussing.

Meta's AI Glasses have been causing a stir because people who wear them are wearing video cameras that can record everything – and everyone – they see.

OpenAI is developing a wearable device, designed by Johnny Ive of Apple fame, that will no doubt be constantly listening, recording, and transmitting data to OpenAI's servers and who knows where else.

And whether we personally own or use any of these devices ourselves, chances are we will be interacting increasingly more with people who do.

For all the controversy around Flock cameras being installed everywhere, eventually they might become obsolete. Because the surveillance devices won't need to be on top of poles strategically posted in public places. The surveillance devices will be hanging around our necks, in our earbuds, built into our glasses. We will be wearing them, carrying them in their pockets, driving them, etc.

This goes far beyond the breadth and scope of the telescreens George Orwell envisioned in his dystopian novel 1984 . And that story did not end well, either.

# 100DaysToOffload (No. 167) # tech # privacy

Why I'm still bearish on LLMs after Navier-Stokes

Hacker News
dank.systems
2026-09-15 13:37:12
Comments...
Original Article

[ thank you to claude fable 5.1, holden saberhagen, gabriel kammer, andres erbsen, alice mckean, and tristan wylde-larue for comments on this post ]

i'll begin with a few theses for the reader to chew on:

  1. the frontier labs are priced according to the narrative that they have produced or will in the very near future produce a fully automated drop-in replacement for most knowledge workers, but current frontier models need laborious oversight and guardrails on even the simplest tasks. one misled by the headline shows of force (navier-stokes, freebsd RCEs, the huggingface incident) and frontier lab rhetoric into believing meaningful autonomy has been achieved need only look at the software firms continuing to employ and hire bottom quartile software engineers who would score far below the models they supervise on the benchmarks du jour.
  2. the models generalize well only on tasks within a small neighborhood of the specific tasks they've been trained on, and even then with severe caveats. the frontier labs have developed a general recipe to teach models almost any specific task enjoying clearly defined levels of task performance; many tasks are covered in the training data; but even small perturbations within a covered class of task result in outright failure or reward hacking.
  3. the present problem of reward hacking can be solved only by rigorous specification by domain experts. the time of domain experts is expensive. rigorous specification is itself a skill, demanding its own expertise outside of a given problem domain. even many skilled software engineers are bad at it. for the vast majority of domains, the intersection of domain experts and specification experts is ludicrously small.
  4. the labor costs of rigorous specification can greatly exceed that of direct implementation of an informal specification. the hardware engineering world presents a great case study on this, where a typical CPU project anecdotally has about three times as many specification and validation engineers as design engineers and a 5:1 ratio is not unheard of . even worse, many tasks don't admit a convenient spec-and-forget regime where you write a specification once and continuously implement against it: rigorous formal specifications frequently evolve in conversation with insights derived from discoveries made while implementing according to the informal specification. for tasks that enjoy high level one-and-done specifications (say an executable ISA specification for a family of CPU architectures) the costs of verification against such high level specifications are insurmountable with current technology, necessitating the use of lower level specifications that are both more expensive to construct and far more fragile to design flux.
  5. navier-stokes and statements in pure mathematics like it are the absolute best case scenario for agentic work against rigorous specification. the theorem statement itself is already a rigorous specification. it has undergone decades of auditing by the mathematical community and its rendering in lean is a straightforward translation defined in terms of battle-tested mathematical objects from mathlib. the verifier, the lean theorem prover, has been extensively audited and specifically designed to avoid the types of unsoundness that would make it vulnerable to reward hacks. even lean and theorem provers like it are not invulnerable: soundness bugs have allowed LLMs to launder bogus proofs through the proof kernel before and it is not improbable that more such bugs exist. this is the rosiest setup; the vast majority of human knowledge work does not look like this. i'll comment below on the few areas of knowledge work that do resemble pure mathematics in this respect.
  6. the best alternative to rigorous specification is human review. human review doesn't scale well to the volumes of output produced by language models. to make matters worse, even expert human review is extremely vulnerable to reward hacking: consider the xz backdoor and the infamous UMN hypocrite commits that landed in linux. if human review remains a critical part of the agentic production loop, the pace of production is necessarily bottlenecked by factors like the limits of human time and attention; it is a total non-starter for the country full of geniuses in a datacenter frontier lab CEOs would have you believe is perpetually just a few more months out.

taken together, it appears that for most domains LLMs will continue to look like a cracked intern: quick and effective in the hands of an adult but not given run of the place. most firms will not be able to adopt fully autonomous AI, not for problems of skill issue or lagging technology diffusion but rather for structural reasons seemingly endemic to current architectures. the classes of firms that can accept the use of fully autonomous LLMs are few, by my count just three:

  1. those who can accept failure cheaply: firms that would otherwise hire interns, firms involved in rapid prototyping work, etc.
  2. those who need done a small set of narrowly defined tasks with existing clear guardrails: repetitive physical labor in a controlled environment, call center and customer service chat work, etc.
  3. those that can accept or already do by nature the costs of rigorous specification and validation: chip design, drug discovery, and other domains where failure on deployment is an existential concern.

the first two classes are price sensitive and arguably don't need the jump in reasoning quality you see going from cheap to frontier models. most of these firms will be best served by open models running on cheap hardware, perhaps even locally at the site of use. for the first and third classes, the type of fuzzy combinatorial search that has produced headline results in mathematics and security research seems more sensitive to agentic swarm width than reasoning capacity: see small open models reproducing the mythos CVEs that drove the spring 2026 hype cycle. if that is indeed true, there is even greater reason to use cheap open models that enable you to run the same workload with wider swarms.

the third class of firms might still use frontier models, though it's not totally clear that their work couldn't be done with cheap models like deepseek v4.1 flash, and the swarm width advantage i hypothesized above gives them all the more reason to push for cheaper models. another interesting property of firms of this class is that they are generally very secretive about their IP and probably aren't overjoyed about shipping it all to anthropic and openai even with supposed agreements to not train on user data .

now, you might propose that even if the frontier labs are cooked, the data center full of brainlets scenario drives just as much AI compute as an artificial superintelligence scenario. the difference is that the data center full of geniuses is self-driving and limited only by how much compute it can consume while the brainlet swarms will be heavily bottlenecked by their human orchestrators. my personal bet is that the blast radius will go far beyond the frontier labs.

Trying to Make a Loop Auto-Vectorize

Lobsters
jsgroth.dev
2026-09-15 13:29:43
Comments...
Original Article

This post assumes at least a vague level of familiarity with the concept of SIMD CPU instructions (x86 SSE/AVX and ARM Neon), where the CPU can perform a single operation on multiple scalar values simultaneously. There is a lot of x86 assembly here to demonstrate what’s happening but you don’t have to be able to read it.

This is not a super deep dive, just a writeup on something I found sort of interesting. All assembly was generated using Compiler Explorer with the Rust 1.98.0 stable compiler (because I wrote most of this before 1.98.1 was out, you should definitely be on that instead of 1.98.0 if you’re not already).

In the context of compilers, auto-vectorization is when the compiler uses vectorized SIMD CPU instructions to implement high-level code that only operates on scalar values, and in a way that provably behaves identically to a non-vectorized implementation. In many data processing contexts this can dramatically improve performance…when it happens.

I’ll note up front that if you want to be 100% sure that a piece of code is using vectorized CPU instructions, you can’t rely on auto-vectorization at all unless you’re willing to look at the compiled assembly every time the code changes. You need to use either compiler intrinsics or some library that wraps them with a higher-level interface (or raw assembly if you want to be really sure what the CPU is executing, as ffmpeg does ). In many use cases this requires changing larger amounts of code to operate on vector values instead of scalar values. That is also an interesting topic! But not what this post is about.

With that out of the way…

Take this very simple Rust function that computes a sum of products over two float arrays:

1
2
3
4
5
6
7
8
use std::iter;

const LEN: usize = 1600;

fn dot(a: &[f32; LEN], b: &[f32; LEN]) -> f32 {
    iter::zip(a, b)
        .fold(0.0, |sum, (&a, &b)| sum + a * b)
}

(I made the size a constant so that the compiler will omit length and bounds checks, and I made it large so that the compiler won’t fully unroll the loop.)

This should be trivial for a compiler to auto-vectorize, but. Here’s that compiled with target-cpu=x86-64 and opt-level=3 (the default for --release builds):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dot:
        xorps   xmm0, xmm0
        mov     eax, 4
.LBB0_1:
        movss   xmm1, dword ptr [rdi + 4*rax - 16]
        movss   xmm2, dword ptr [rdi + 4*rax - 12]
        mulss   xmm1, dword ptr [rsi + 4*rax - 16]
        mulss   xmm2, dword ptr [rsi + 4*rax - 12]
        addss   xmm1, xmm0
        movss   xmm3, dword ptr [rdi + 4*rax - 8]
        mulss   xmm3, dword ptr [rsi + 4*rax - 8]
        addss   xmm2, xmm1
        movss   xmm1, dword ptr [rdi + 4*rax - 4]
        mulss   xmm1, dword ptr [rsi + 4*rax - 4]
        addss   xmm3, xmm2
        movss   xmm0, dword ptr [rdi + 4*rax]
        mulss   xmm0, dword ptr [rsi + 4*rax]
        addss   xmm1, xmm3
        addss   xmm0, xmm1
        add     rax, 5
        cmp     rax, 1604
        jne     .LBB0_1
        ret

It’s using SSE instructions and 128-bit vector registers ( xmm0 , xmm1 , etc), but it’s only using 32-bit scalar arithmetic instructions ( addss , mulss ) and 32-bit scalar load instructions ( movss ). This isn’t vectorized at all! Those SSE scalar instructions take vector registers as operands, but they only operate on the lowest 32 bits.

The x86-64 target includes SSE and SSE2 as baseline features, so the compiler could use 128-bit / f32x4 vector instructions here, but it’s choosing not to. ( x86-64 does not enable SSE3 or SSE4 by default because the earliest amd64/x86_64 CPUs don’t support them, but those aren’t needed to vectorize this.)

Does enabling AVX instructions make any difference?

1
2
3
4
5
6
7
8
9
use std::iter;

const LEN: usize = 1600;

#[target_feature(enable = "avx2,fma")]
fn dot_avx(a: &[f32; LEN], b: &[f32; LEN]) -> f32 {
    iter::zip(a, b)
        .fold(0.0, |sum, (&a, &b)| sum + a * b)
}

(I’m enabling AVX2+FMA instead of AVX because those just add more instructions on top of AVX, and nowadays most CPUs that support AVX will also support AVX2+FMA. AVX2 isn’t useful in this example but FMA definitely is!)

Compiled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dot_avx:
        vxorps  xmm0, xmm0, xmm0
        mov     eax, 4
.LBB0_1:
        vmovss  xmm1, dword ptr [rdi + 4*rax - 16]
        vmovss  xmm2, dword ptr [rdi + 4*rax - 12]
        vmulss  xmm1, xmm1, dword ptr [rsi + 4*rax - 16]
        vmulss  xmm2, xmm2, dword ptr [rsi + 4*rax - 12]
        vaddss  xmm0, xmm0, xmm1
        vmovss  xmm1, dword ptr [rdi + 4*rax - 8]
        vmulss  xmm1, xmm1, dword ptr [rsi + 4*rax - 8]
        vaddss  xmm0, xmm0, xmm2
        vmovss  xmm2, dword ptr [rdi + 4*rax - 4]
        vmulss  xmm2, xmm2, dword ptr [rsi + 4*rax - 4]
        vaddss  xmm0, xmm0, xmm1
        vmovss  xmm1, dword ptr [rdi + 4*rax]
        vmulss  xmm1, xmm1, dword ptr [rsi + 4*rax]
        vaddss  xmm0, xmm0, xmm2
        vaddss  xmm0, xmm0, xmm1
        add     rax, 5
        cmp     rax, 1604
        jne     .LBB0_1
        ret

…They’re the same picture code! It’s using AVX’s newer VEX instruction encoding instead of the legacy SSE encoding (hence why most of the instructions start with v and the add/mul instructions have 3 operands instead of 2), but functionally this is exactly the same code as before except it only uses 3 vector registers instead of 4.

Does rewriting the function to use a manual loop instead of iter::zip and fold look any better?

1
2
3
4
5
6
7
8
9
const LEN: usize = 1600;

fn dot_manual_loop(a: &[f32; LEN], b: &[f32; LEN]) -> f32 {
    let mut sum = 0.0;
    for i in 0..LEN {
        sum += a[i] * b[i];
    }
    sum
}

I’m not going to paste the assembly here because the answer is no, no it does not. This compiles to the exact same assembly as the first version.

The main problem here is that the compiler can’t safely reorder floating-point arithmetic operations because IEEE 754 floating-point arithmetic is not associative, i.e. (a + b) + c is not necessarily equal to a + (b + c) . They’ll generally be equal within some margin of error, but due to floating-point rounding semantics they may not be exactly equal.

The natural way to 128-bit vectorize this function is to process the input arrays in chunks of 4 while accumulating into an f32x4 vector, then sum over the accumulator vector at the end. This is impossible to do without reordering addition operations, which the compiler will not do because it might change the final result for some inputs.

To prove that this is a float-specific issue, here’s an integer version of the same function (with SSE4 enabled because it makes the generated code shorter and simpler):

1
2
3
4
5
6
7
8
9
use std::iter;  

const LEN: usize = 1600;

#[target_feature(enable = "sse4.2")]
fn dot_int(a: &[i32; LEN], b: &[i32; LEN]) -> i32 {
    iter::zip(a, b)
        .fold(0, |sum, (&a, &b)| sum + a * b)
}

Compiled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
dot_int:
        pxor    xmm0, xmm0
        mov     eax, 12
        pxor    xmm1, xmm1
.LBB0_1:
        movdqu  xmm2, xmmword ptr [rdi + 4*rax - 48]
        movdqu  xmm3, xmmword ptr [rdi + 4*rax - 32]
        movdqu  xmm4, xmmword ptr [rdi + 4*rax - 16]
        movdqu  xmm5, xmmword ptr [rdi + 4*rax]
        movdqu  xmm6, xmmword ptr [rsi + 4*rax - 48]
        pmulld  xmm6, xmm2
        paddd   xmm6, xmm1
        movdqu  xmm2, xmmword ptr [rsi + 4*rax - 32]
        pmulld  xmm2, xmm3
        paddd   xmm2, xmm0
        movdqu  xmm1, xmmword ptr [rsi + 4*rax - 16]
        pmulld  xmm1, xmm4
        paddd   xmm1, xmm6
        movdqu  xmm0, xmmword ptr [rsi + 4*rax]
        pmulld  xmm0, xmm5
        paddd   xmm0, xmm2
        add     rax, 16
        cmp     rax, 1612
        jne     .LBB0_1
        paddd   xmm0, xmm1
        pshufd  xmm1, xmm0, 238
        paddd   xmm1, xmm0
        pshufd  xmm0, xmm1, 85
        paddd   xmm0, xmm1
        movd    eax, xmm0
        ret

I’m not going to go into detail on what exactly this is doing, but it did auto-vectorize! paddd and pmulld are i32 vector arithmetic instructions, and movdqu is an i32 vector load instruction. It’s using xmm* registers so the vector size is 128-bit / i32x4.

Back to the float version, what if the function chunks manually, so that the compiler can theoretically use vector instructions without needing to reorder anything?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use std::{array, iter};

const LEN: usize = 1600;

fn dot_chunked(a: &[f32; LEN], b: &[f32; LEN]) -> f32 {
    // This implementation is only correct if the len is a multiple of 4
    assert!(LEN.is_multiple_of(4));

    let a_chunked = a.as_chunks::<4>().0;
    let b_chunked = b.as_chunks::<4>().0;

    let sums = iter::zip(a_chunked, b_chunked)
        .fold([0.0; 4], |sums, (a_chunk, b_chunk)| {
            array::from_fn(|i| sums[i] + a_chunk[i] * b_chunk[i])
        });

    sums.into_iter().sum()
}

Compiled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dot_chunked:
        xorps   xmm0, xmm0
        mov     eax, 16
.LBB0_1:
        movups  xmm1, xmmword ptr [rdi + rax - 16]
        movups  xmm2, xmmword ptr [rdi + rax]
        movups  xmm3, xmmword ptr [rsi + rax - 16]
        mulps   xmm3, xmm1
        addps   xmm3, xmm0
        movups  xmm0, xmmword ptr [rsi + rax]
        mulps   xmm0, xmm2
        addps   xmm0, xmm3
        add     rax, 32
        cmp     rax, 6416
        jne     .LBB0_1
        movaps  xmm1, xmm0
        shufps  xmm1, xmm0, 85
        addss   xmm1, xmm0
        movaps  xmm2, xmm0
        unpckhpd        xmm2, xmm0
        addss   xmm2, xmm1
        shufps  xmm0, xmm0, 255
        addss   xmm0, xmm2
        ret

Oh hey, it auto-vectorized!

It’s using f32 vector arithmetic instructions ( addps , mulps ) instead of scalar ( addss , mulss ), and it’s also using f32 vector load instructions ( movups ). Everything after the jne is to reduce the final f32x4 accumulator to a single scalar f32, so scalar addss instructions are expected there. (Along with some fun instructions like shufps and unpckhpd )

This is better than needing to use x86_64 intrinsics, but it isn’t ideal. Manually iterating in chunks of 4 prevents the compiler from taking advantage of larger vector sizes if they’re available, such as AVX’s 256-bit vectors, which can hold and operate on f32x8 values. Here’s that same function compiled with AVX2+FMA enabled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
dot_chunked_avx:
        vxorps  xmm0, xmm0, xmm0
        mov     eax, 16
.LBB0_1:
        vmovups xmm1, xmmword ptr [rdi + rax - 16]
        vmovups xmm2, xmmword ptr [rdi + rax]
        vmulps  xmm1, xmm1, xmmword ptr [rsi + rax - 16]
        vmulps  xmm2, xmm2, xmmword ptr [rsi + rax]
        vaddps  xmm0, xmm0, xmm1
        vaddps  xmm0, xmm0, xmm2
        add     rax, 32
        cmp     rax, 6416
        jne     .LBB0_1
        vmovshdup       xmm1, xmm0
        vaddss  xmm1, xmm0, xmm1
        vshufpd xmm2, xmm0, xmm0, 1
        vaddss  xmm1, xmm1, xmm2
        vshufps xmm0, xmm0, xmm0, 255
        vaddss  xmm0, xmm1, xmm0
        ret

VEX encoding allows the compiler to do the same thing in fewer instructions (also SSE3’s movshdup ), but it’s still using 128-bit vector registers ( xmm* ) even though 256-bit vectors are available. It’s also not using any 128-bit FMA instructions, I believe because FMA has slightly different rounding behavior than separate mul+add instructions, so using FMA instructions here might change the result.

Some compilers have flags to enable float optimizations that are not strictly safe, such as GCC’s -funsafe-math-optimizations , or the maybe better-known -ffast-math which is a superset. Among other things, this includes -fassociative-math which allows the compiler to assume that float arithmetic is associative so that it can reorder float arithmetic operations. This may change the result for some inputs (hence an “unsafe” optimization) but generally enables the compiler to generate more performant code.

Rust used to not have any reasonable way to do anything like this on stable (for my own definition of reasonable), but now it does! Rust 1.98 stabilized algebraic operators for floats . These allow you to declare per-operation that you’re okay with the compiler making optimizations that may change the result as long as the optimized code is algebraically equivalent to the original. Yes, this includes potentially reordering operations.

Let’s try this out:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::iter;

const LEN: usize = 1600;

fn dot_algebraic(a: &[f32; LEN], b: &[f32; LEN]) -> f32 {
    iter::zip(a, b)
        .fold(0.0, |sum, (&a, &b)| {
            sum.algebraic_add(a.algebraic_mul(b))
        })
}

The syntax is very Java-like, but if you were using these frequently then you could implement something like the standard library’s Wrapping<T> newtype, where you’d overload the arithmetic operators to delegate to the algebraic_* functions. (I’m slightly surprised this isn’t already in std, but I suppose it was easier to ship the core functionality by itself before shipping any conveniences on top of it.)

Compiled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
dot_algebraic:
        xorps   xmm0, xmm0
        mov     eax, 12
        xorps   xmm1, xmm1
.LBB0_1:
        movups  xmm2, xmmword ptr [rdi + 4*rax - 48]
        movups  xmm3, xmmword ptr [rdi + 4*rax - 32]
        movups  xmm4, xmmword ptr [rdi + 4*rax - 16]
        movups  xmm5, xmmword ptr [rdi + 4*rax]
        movups  xmm6, xmmword ptr [rsi + 4*rax - 48]
        mulps   xmm6, xmm2
        addps   xmm6, xmm1
        movups  xmm2, xmmword ptr [rsi + 4*rax - 32]
        mulps   xmm2, xmm3
        addps   xmm2, xmm0
        movups  xmm1, xmmword ptr [rsi + 4*rax - 16]
        mulps   xmm1, xmm4
        addps   xmm1, xmm6
        movups  xmm0, xmmword ptr [rsi + 4*rax]
        mulps   xmm0, xmm5
        addps   xmm0, xmm2
        add     rax, 16
        cmp     rax, 1612
        jne     .LBB0_1
        addps   xmm0, xmm1
        movaps  xmm1, xmm0
        unpckhpd        xmm1, xmm0
        addps   xmm1, xmm0
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85
        addss   xmm0, xmm1
        ret

This is a lot more code than the manually chunked version, but it definitely auto-vectorized!

The manually chunked version did two f32x4 multiply+add operations per loop iteration. This one does four, and it’s also accumulating into two different f32x4 accumulators ( xmm0 and xmm1 ) that it adds together right after the loop ends. From my not-super-scientific testing on a Zen 4 CPU, this is actually noticeably faster than the manually chunked version! Presumably using two accumulators is friendlier to the CPU’s out-of-order execution hardware. (You may have noticed that the auto-vectorized i32 version above also used two accumulators.)

Now with AVX2+FMA enabled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
dot_algebraic_avx:
        vxorps  xmm0, xmm0, xmm0
        mov     eax, 24
        vxorps  xmm1, xmm1, xmm1
        vxorps  xmm2, xmm2, xmm2
        vxorps  xmm3, xmm3, xmm3
.LBB0_1:
        vmovups ymm4, ymmword ptr [rsi + 4*rax - 96]
        vmovups ymm5, ymmword ptr [rsi + 4*rax - 64]
        vmovups ymm6, ymmword ptr [rsi + 4*rax - 32]
        vmovups ymm7, ymmword ptr [rsi + 4*rax]
        vfmadd231ps     ymm0, ymm4, ymmword ptr [rdi + 4*rax - 96]
        vfmadd231ps     ymm1, ymm5, ymmword ptr [rdi + 4*rax - 64]
        vfmadd231ps     ymm2, ymm6, ymmword ptr [rdi + 4*rax - 32]
        vfmadd231ps     ymm3, ymm7, ymmword ptr [rdi + 4*rax]
        add     rax, 32
        cmp     rax, 1624
        jne     .LBB0_1
        vaddps  ymm0, ymm1, ymm0
        vaddps  ymm1, ymm3, ymm2
        vaddps  ymm0, ymm1, ymm0
        vextractf128    xmm1, ymm0, 1
        vaddps  xmm0, xmm0, xmm1
        vshufpd xmm1, xmm0, xmm0, 1
        vaddps  xmm0, xmm0, xmm1
        vmovshdup       xmm1, xmm0
        vaddss  xmm0, xmm0, xmm1
        vzeroupper
        ret

It’s now using 256-bit / f32x8 vectors ( ymm* registers) and fused multiply-add instructions ( vfmadd*ps ), with no code changes other than sticking #[target_feature(enable = "avx2,fma")] on top of the function!

Similar to the 128-bit SSE version, this one does four f32x8 FMA operations per loop iteration, but it accumulates each into a separate f32x8 accumulator rather than only using two accumulators. I’m not sure why, probably some heuristic on optimizing for AVX vs. SSE.

Just for fun, here’s a version that attempts to use x86_64 AVX intrinsics (yeah it’s ugly):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use std::arch::x86_64::*;
use std::mem::transmute;

const LEN: usize = 1600;

#[target_feature(enable = "avx2,fma")]
fn dot_intrinsics(a: &[f32; LEN], b: &[f32; LEN]) -> f32 {
    let mut sum = _mm256_setzero_ps();

    // 256-bit / f32x8 vectors
    // Similar to the manually chunked version, this is only correct when len is a multiple of 8
    assert!(LEN.is_multiple_of(8));

    for i in (0..LEN).step_by(8) {
        let a_chunk = unsafe { _mm256_loadu_ps(a.as_ptr().add(i)) };
        let b_chunk = unsafe { _mm256_loadu_ps(b.as_ptr().add(i)) };
        sum = _mm256_fmadd_ps(a_chunk, b_chunk, sum);
    }

    // In AVX-512 you could replace everything after this comment with _mm512_reduce_add_ps(sum), but alas...
    let upper128 = _mm256_extractf128_ps::<1>(sum);
    let lower128 = _mm256_castps256_ps128(sum);
    let sum128 = _mm_add_ps(upper128, lower128);

    // 0 1 2 3 -> 1 0 3 2
    let shuffled128 = _mm_shuffle_ps::<0b10_11_00_01>(sum128, sum128);
    let sum64 = _mm_add_ps(sum128, shuffled128);

    let scalars: [f32; 4] = unsafe { transmute(sum64) };
    scalars[0] + scalars[2]
}

Compiled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
dot_intrinsics:
        vxorps  xmm0, xmm0, xmm0
        mov     eax, 128
.LBB0_1:
        vmovups ymm1, ymmword ptr [rdi + rax - 128]
        vmovups ymm2, ymmword ptr [rdi + rax - 96]
        vmovups ymm3, ymmword ptr [rdi + rax - 64]
        vfmadd132ps     ymm1, ymm0, ymmword ptr [rsi + rax - 128]
        vfmadd231ps     ymm1, ymm2, ymmword ptr [rsi + rax - 96]
        vfmadd231ps     ymm1, ymm3, ymmword ptr [rsi + rax - 64]
        vmovups ymm0, ymmword ptr [rdi + rax - 32]
        vfmadd231ps     ymm1, ymm0, ymmword ptr [rsi + rax - 32]
        vmovups ymm0, ymmword ptr [rdi + rax]
        vfmadd132ps     ymm0, ymm1, ymmword ptr [rsi + rax]
        add     rax, 160
        cmp     rax, 6528
        jne     .LBB0_1
        vextractf128    xmm1, ymm0, 1
        vaddps  xmm0, xmm1, xmm0
        vmovshdup       xmm1, xmm0
        vaddps  xmm0, xmm0, xmm1
        vshufpd xmm1, xmm0, xmm0, 1
        vaddss  xmm0, xmm0, xmm1
        vzeroupper
        ret

It’s similar to the auto-vectorized version, but each loop iteration does five f32x8 FMA operations into a single f32x8 accumulator. Pretty direct translation of the Rust code, only unrolling the loop a bit.

For me, this performs significantly worse than the auto-vectorized AVX version, actually even a little worse than the auto-vectorized 128-bit SSE version! This seems to be caused entirely by this version using only one accumulator (because that’s what the Rust code said to do) while the auto-vectorized versions use multiple accumulators. Yay out-of-order execution (I assume).

And…that’s all I’ve got here! I’m not entirely sure this was a useful excursion, but I found it interesting, particularly that auto-vectorization did much better than my naive intrinsics implementation (when the compiler is allowed to reorder additions).

Postscript: Aligned Loads

Whether you’re manually vectorizing or auto-vectorizing, you can get a noticeable performance boost by guaranteeing that vector loads/stores are always on a vector size boundary, if possible. The compiler will generally use aligned load instructions ( vmovaps ) instead of unaligned ( vmovups ) if it can guarantee the address is aligned, but on modern CPUs unaligned loads from aligned addresses perform just as well as aligned load instructions, so aligning the data improves performance regardless of whether the compiler emits aligned load instructions.

In Rust, you can accomplish this by making a newtype and sticking #[repr(align(N))] on top of it, where N is in bytes:

1
2
#[repr(align(32))] // Align all values to a 32-byte / 256-bit boundary to be AVX-friendly
struct AlignedF32Array<const LEN: usize>([f32; LEN]);

Then you could implement Deref so that you can pass an &AlignedF32Array<LEN> to any functions that expect a &[f32; LEN] , or you could make functions take &AlignedF32Array<LEN> as a parameter if you want to guarantee at compile time that the data is aligned.

Postscript 2: AVX-512

I didn’t paste an auto-vectorized AVX-512 version because it looks almost exactly the same as the AVX2+FMA version, only using 512-bit / f32x16 vectors ( zmm* registers) and with 2 extra instructions at the end to add the 256-bit halves together ( vextractf64x4 followed by vaddps ).

Using 512-bit vectors may not improve performance much if at all depending on the CPU, and on some CPUs it may even worsen performance. Intel Skylake-X seems to be particularly problematic given that the compiler will not use 512-bit vectors at all here when optimizing for target-cpu=x86-64-v4 , which is roughly Skylake-X as a baseline.

On my AMD Zen 4 CPU, 512-bit vector performance is ~equal to 256-bit on the same total amount of data, which is unsurprising given that Zen 4 implements 512-bit vector operations by double pumping its 256-bit vector units. I’ve read that Zen 5 has a decently performant 512-bit vector implementation but I don’t have one to test on.

All that said, on CPUs that support it, AVX-512 (or “ AVX10.1 ” as Intel is rebranding it) is still useful even if you never use 512-bit vectors. Some of the new instructions are really nice to have (e.g. masked loads/stores) and you get twice as many vector registers for all vector sizes, so AVX-512 can improve performance of 256-bit vector code that can take advantage of the new functionality (i.e. not this trivial example).

GEFS on OpenBSD: A Early Preview

Hacker News
marc.info
2026-09-15 13:12:19
Comments...
Original Article
[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openbsd-tech
Subject:    GEFS on OpenBSD: A very early preview
From:       ori () eigenstate ! org
Date:       2026-09-15 15:45:05
Message-ID: EBB049B251DCF60A8C9A0575F6FC7DC9 () eigenstate ! org
[Download RAW message or body]

Before anyone asks: I am *not* proposing to put
this in tree for a while yet.

What is it
----------
So, as people may have seen at EuroBSD, I've got a
rough, buggy, issue-filled preview of GEFS running
on OpenBSD. The port is NOT ready for production,
and data loss is currently expected, especially on
error.

However, the port has reached the state where (with
some exceptions) the remaining problems are reasonably
understood, and people can poke at them.

For those who haven't watched my talk, GEFS is a
new, crash-safe, snapshotting, copy on write FS
that I wrote for 9front, and which I am in the
process of moving to OpenBSD. The file system is
described in full here:

	https://orib.dev/gefs.pdf

It's under 9,000 lines of code in kernel at the
moment, though I expect it'll grow a bit over time,
since there's a good deal missing.

Porting
-------
I'm approaching this as a proper, in-kernel file
system that keeps the data structure and fiddly
code fairly in sync between Plan 9 and OpenBSD,
but the code itself is copied and pasted. My goal
is that it will continue to rhyme, so that fixes
can be shared, but the requirements of the two
environments means that the code probably won't
be ported, and trying to shove in some abstraction
layer seems like a bad idea.

Problems
--------
So, IMO, the biggest of the remaining problems:

Consistency Protocol:
  This is the big unknown for me; A write to the
  superblock needs to come after all writes in
  the snapshot, but as long as it is written after
  the blocks have hit disk, it's safe. There are
  also a couple of fixes to backport from 9Front
  on the write ordering.

Error handling and porting brain damage:
  Error handling is largely commented out. I'm
  going to have to go through the error handling
  paths bit by bit and convert it with care; the
  approach to error handling I took on 9front is
  not acceptable for OpenBSD. There's a few other
  things, like some globals being used that would
  prevent more than one mount at a time.

Userspace tools:
  At least, I assume that telling people to boot
  9front in a VM to create or fsck is not a good
  long term solution. Snapshot management and the
  associated ioctls will also go here.

Posix persnicketies:
  The system that this was ported from was not
  a posix system, so there are likely to be many
  places where we don't quite get the constraints
  right.

Regress:
  There's a small test suite for Plan 9. There's
  none for OpenBSD.

Hardlinks:
  Easy to implement, but needs a refcount file
  by file.

Kqueue:
  Easy to implement, but needs a refcount file
  by file.

NFS:
  Has some ugly hooks, not sure what would be
  needed to add them.

Other missing pieces:
  Things like bootloader support, adding boot
  environments, quotas, and other niceties; this
  is well  down the list. Quotas, in particular,
  would allow LLVM to continue in its attempt to
  grow to the size of the known universe without
  us preallocating a /usr/obj of that size.

I'm sure I've forgotten some.

Right now: I'm not looking to fix KNF; as things
get touched, I'll start to move them over, but I
would prefer to keep the code closer with the
original until it's closer to ready for upstreaming.

There are also a few non-fatal issues independent
of 9front and OpenBSD, such as slow deletions of
large files; I've got ideas to improve them.

Getting it
----------

as one patch:

  https;//orib.dev/gefs.diff

The git repo is hidden on shithub, and the
server is tiny and will get overloaded if
people clone fresh repos from it. Instead,
get a copy of the initial repo from github,
and pull only the updates.

  # first, get a copy of the upstream from
  # somewhere else
  git clone https://github.com/openbsd/src
  cd src

  # now, add my server, and get just the diffs
  git remote add gefs git://shithub.us/ori/openbsd
  git fetch gefs
  git checkout -b gefs gefs/gefs

Note, I will be rebasing and force-pushing
the branch occasionally in order to keep up
with openbsd current; commit ids will change.

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic

Performance Improvements in .NET 11

Lobsters
devblogs.microsoft.com
2026-09-15 13:03:47
As is tradition with an imminent new release of dotnet, Stephen Toub has done a long writeup of what this means with regards to performance improvements. Comments...
Original Article

Before television shows like The Office and Parks and Recreation cemented the mockumentary in the minds of millions, there was Christopher Guest. He didn’t invent the genre, but he’s widely recognized as one of its most influential practitioners, and for my money, there’s none better. I’ve watched Waiting for Guffman and Best in Show more times than I can count. But the one that has stuck with me the most, the one I quote at the slightest provocation, is This Is Spinal Tap .

If you’ve seen it you already know where this is going (and if you haven’t, you now have weekend plans). The film is a fictional documentary about an aging English rock band named Spinal Tap, whose members are everything we picture when we picture over-the-top rock stars. In one of its more memorable scenes, the guitarist (Nigel) gives the filmmaker (Marty) a tour of his most prized gear, in particular showing off an amplifier unlike any other: its dials don’t stop at ten. That leads to what might be the single most quoted exchange in the entire movie:

Nigel: “You see, most blokes, you know, will be playing at ten. You’re on ten here, all the way up, all the way up, all the way up, you’re on ten on your guitar. Where can you go from there? Where?”

Marty: “I don’t know.”

Nigel: “Nowhere. Exactly. What we do is, if we need that extra push over the cliff, you know what we do?”

Marty: “Put it up to eleven?”

Nigel: “Eleven. Exactly. One louder.”

This is .NET 11. It’s one louder, with another year’s worth of performance work having gone into making the runtime and libraries that much faster. Of course, the premise of Nigel’s special amplifier is ludicrous, as is exemplified in the subsequent few lines of dialog:

Marty: “Why don’t you just make ten louder and make ten be the top number and make that a little louder?”

Nigel: (pauses) “…these go to eleven.”

In contrast, .NET 11 is actually one higher, one louder. The sections that follow are full of real improvements. A bounds check removed, an allocation that no longer happens, a lock that isn’t taken, a loop that runs in fewer cycles than it did a year ago, a comparison folded to a constant here, a redundant check hoisted out of a loop there, a couple of instructions fused into one, a syscall sidestepped, an array copy handed off to SIMD, and on and on. That’s how real performance work goes, accumulating gain after gain, each compounding on the last, until the whole thing is measurably, provably louder. And so, in this post, as I’ve done in past years with .NET 10 , .NET 9 , .NET 8 , .NET 7 , .NET 6 , .NET 5 , .NET Core 3.0 , .NET Core 2.1 , and .NET Core 2.0 before it, we’ll take an unhurried tour through hundreds of them.

This is a long one. It’s meant to be. Grab your hot beverage of choice, settle in, and let’s turn it up.

Benchmarking Setup

As in previous years, the post is chock full of micro-benchmarks that demonstrate the individual improvements. Almost all of them use BenchmarkDotNet , and each is written to be self-contained so you can try it out yourself.

Start by ensuring you have both .NET 10 and .NET 11 installed (most of the benchmarks compare the same code running on both versions) and create a new console project in a fresh benchmarks directory:

dotnet new console -o benchmarks
cd benchmarks

Replace the contents of the generated benchmarks.csproj with the following, which multi-targets both versions so that BenchmarkDotNet can build for each:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net11.0;net10.0</TargetFrameworks>
    <LangVersion>preview</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <ServerGarbageCollection>true</ServerGarbageCollection>
    <SystemPackageVersion Condition="'$(TargetFramework)' == 'net10.0'">10.0.12</SystemPackageVersion>
    <SystemPackageVersion Condition="'$(TargetFramework)' == 'net11.0'">11.0.0-rc.1.26425.128</SystemPackageVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.16.0-preview.1" />
    <PackageReference Include="System.IO.Hashing" Version="$(SystemPackageVersion)" />
    <PackageReference Include="System.Runtime.Caching" Version="$(SystemPackageVersion)" />
    <PackageReference Include="System.Numerics.Tensors" Version="$(SystemPackageVersion)" />
  </ItemGroup>

</Project>

For a given benchmark to test, copy its complete contents over everything in Program.cs and then run it. Each benchmark includes as a comment at the top the exact command to use. In most cases, it’s:

dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

which builds in Release and runs the benchmark against both .NET 10 and .NET 11, emitting a side-by-side comparison. The other common form, used when a benchmark is comparing two coding approaches on a single runtime (rather than the same code across two runtimes) is:

dotnet run -c Release -f net11.0 --filter "*"

The usual disclaimer applies: these are micro-benchmarks, many measuring operations so short that a blink would miss them. Your results will vary with your hardware, OS, runtime configuration, what else your machine happens to be doing at that exact moment, and whether Mercury is in retrograde.

Every line of managed code ultimately ends up at the just-in-time compiler, so let’s start there.

JIT

Of all the places to improve .NET’s performance, few have as broad an impact as the just-in-time (JIT) compiler. C#, F#, and Visual Basic are typically compiled first to intermediate language (IL), and the JIT ultimately turns that IL into the native instructions the CPU executes. A JIT improvement can therefore benefit application and library code wherever the optimized pattern occurs, often with no source changes or recompilation of the application itself. Even removing a single instruction or proving one check unnecessary can add up when the code is on a very hot path.

Deabstraction

We as developers love our abstractions. They let us write clean, reusable, object-oriented code, but we don’t want to pay for every abstraction at run time. The runtime can often undo an abstraction when it proves the effects aren’t observable. It can look at a virtual call and determine which concrete method it’ll invoke, look at a heap allocation and recognize that the object never leaves the current stack frame, or look at an interface cast and reuse a type fact already established earlier in the method. This process is called “deabstraction.” .NET has improved steadily in this area for years, and that continues in .NET 11.

Every time you write interface in C#, you’re creating a contract, a promise that any type implementing that interface can be substituted for any other. That flexibility is enormously valuable because, for example, it’s what lets us write IEnumerable<T> and have it work equally well over arrays, lists, other collections, LINQ, custom iterators, and so on. But the CPU doesn’t know anything about these contracts; it just knows how to execute instructions. Turning “call whatever method this interface reference points to” into actual machine instructions requires special machinery. Consider this example:

// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private Animal _animal = Environment.TickCount >= 0 ? new Dog() : new Cat();

    [Benchmark]
    public int Speak() => _animal.Speak();

    public abstract class Animal
    {
        public abstract int Speak();
    }

    private sealed class Dog : Animal
    {
        [MethodImpl(MethodImplOptions.NoInlining)]
        public override int Speak() => 1;
    }

    private sealed class Cat : Animal
    {
        [MethodImpl(MethodImplOptions.NoInlining)]
        public override int Speak() => 2;
    }
}

At compile time, all else equal, the JIT doesn’t know whether _animal is a Dog or a Cat . It generates code that loads the instance’s “method table pointer” (its object type handle), sometimes called a “vtable pointer”, stored at the beginning of every .NET object, indexes into the method table at the known slot for Speak , and calls the function pointer found there:

; x64
mov     rcx, [rcx+8]   ; load _animal
mov     rax, [rcx]     ; load method table
mov     rax, [rax+40]  ; load vtable chunk
call    qword ptr [rax+20]

For this one call to Speak , we pay three dependent memory dereferences and an indirect call because the processor doesn’t know for certain in advance where the call is going (it might guess, or “speculatively execute”, but it has to be prepared for the possibility it was wrong), and because the call target is indirect, the JIT can’t inline the callee. Whatever Speak does, its code can’t be folded into the calling method.

That’s a performance problem. Those indirections have overhead, but the bigger cost is the lost opportunity to inline. Inlining not only saves function call overhead, more importantly it opens the callee’s code up to the same optimizations that are operating on the caller, such as constant propagation, dead code elimination, bounds check elimination, further devirtualization, etc. That means a series of small virtual calls that each look innocent can, when devirtualized and inlined, collapse into a handful of instructions that would be unrecognizable and way cheaper when compared to the original source code. Without inlining, each callee is an opaque box; with it, the JIT can see through the layers.

We as .NET developers constantly rely on the JIT’s sophisticated heuristics for inlining that weigh the IL size of the callee, the exact work the callee is performing, the call frequency of the method, the expected benefit from constant arguments, and dozens of other factors. For virtual calls, the JIT needs to know what the actual target of the call will be; it needs to “devirtualize”. In some cases, it can determine that statically, where it has exact-type knowledge. For example, if the JIT can prove that animal is always a Dog , whether because it was just allocated with new Dog() :

Animal animal = GetSomeAnimal();
animal.Speak();
...
static Animal GetSomeAnimal() => new Dog(); // inlineable

or because the variable’s type is a sealed class:

Dog animal = GetSomeAnimal();
animal.Speak();
...
sealed class Dog { ... } // impossible for `animal` to be anything other than a `Dog`

or with NativeAOT and whole-program compilation, if it sees that Animal is abstract and the only type in the whole application that derives from Animal is Dog :

Animal animal = GetSomeAnimal();
animal.Speak();
...
abstract class Animal { ... }
class Dog : Animal { ... } // no other such derived type

or other such validation, it can emit a call to Dog.Speak() directly, and the inliner can take its shot.

But for other cases where it can’t prove this with static analysis, the JIT turns to profile-guided optimization (PGO). PGO sounds fancy, but it’s conceptually simple. With “tiered compilation”, when a method is first invoked, it can be compiled “just in time” with few-to-no optimizations (this is referred to as Tier 0). The JIT can include in this compilation additional probes (think “printf debugging”) that let it track a bunch of interesting information about the nature of the code, recording what actually happens when it runs: which branches are taken, what are the concrete types that show up at virtual call sites or cast attempts, and so on. If the method is invoked enough or loops enough times, the runtime can ask the JIT to produce a new optimized version (referred to as Tier 1). That compilation can then factor in all of the learnings gathered as part of that profiling.

The JIT, of course, still needs to generate code that’s always correct. Even if a dynamic profile says animal was Dog 100% of the time, that doesn’t guarantee it’ll always be Dog in the future; it could be that the first 1000 calls passed in a Dog but the 1001st call is going to pass in Dolphin . How can the JIT incorporate this learning then? By emitting a run-time check. The Dog path can get a direct call, which may then be inlinable, and the other path keeps the original virtual call as the fallback. The speed comes from making the common case tiny, while correctness comes from leaving the uncommon case intact.

// Approximately what the JIT generates
if (animal?.GetType() == typeof(Dog))
{
    ((Dog)animal).Speak();  // devirtualized, inlinable
}
else
{
    animal.Speak(); // original virtual call, hopefully rare
}

This “guess and verify” pattern, called “guarded devirtualization” (GDV), accounts for many of the biggest throughput wins in real workloads. It’s applicable not only to virtual dispatch but also to interface dispatch, which also happens to be a bit more expensive than virtual dispatch because a type can implement any number of interfaces and that means the interface slots don’t simply map to fixed vtable positions.

Deabstraction can also make object creation more efficient when it reveals what kind of object is involved. In general, objects in .NET are allocated on the garbage collected heap, tracked by the garbage collector (GC), and collected when no longer reachable. Heap allocation is typically fast, often effectively just bumping a pointer. However, when there’s not enough space available to bump the pointer, it can get much more expensive, including needing to incur a garbage collection. Every allocated object also effectively incurs the amortized cost of all collections, as every allocated object eventually needs to be cleaned up.

“Escape analysis” is the compiler technique that lets us ask whether this object ever “escapes” the current method. If an object reference to a newly allocated object provably doesn’t escape, then the JIT can more efficiently allocate it. It needn’t store it on the GC heap, because nothing could possibly need to reference that object again, so it can instead allocate the object on the stack, making both allocation and cleanup essentially free. Stack allocation is even faster than heap bump-pointer allocation; it’s just decrementing the stack pointer, which is typically already in a register. And more importantly it means zero GC impact, because the stack frame is freed atomically on function return.

The JIT’s been progressively expanding escape analysis over the past several .NET releases, with .NET 9 and 10 seeing significant investments in stack-allocating delegates and closures, Nullable<T> temporaries, and small helper objects. The key theme is that every false positive escape, every time the JIT incorrectly concludes an object may escape when it really doesn’t, represents a heap allocation that could have been avoided, and we want to whittle away at that false positive list. In .NET 11, the JIT trims that list in several ways.

We’ll start with nullable boxing. Consider this benchmark:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private int? _nullableNull;
    private int? _nullableValue = 42;

    [Benchmark]
    public object? BoxNullableNull() => (object?)_nullableNull;

    [Benchmark]
    public object? BoxNullableValue() => (object?)_nullableValue;

    [Benchmark]
    public string? FormatNullableInt() => Format(_nullableValue);

    private static string? Format<T>(T value)
    {
        if (value is IFormattable formattable)
            return formattable.ToString(null, null);

        return null;
    }
}
Method Runtime Mean Ratio Allocated Alloc Ratio
BoxNullableNull .NET 10.0 2.095 ns 1.00
BoxNullableNull .NET 11.0 1.764 ns 0.84
BoxNullableValue .NET 10.0 9.213 ns 1.00 24 B 1.00
BoxNullableValue .NET 11.0 4.126 ns 0.45 24 B 1.00
FormatNullableInt .NET 10.0 9.583 ns 1.00 24 B 1.00
FormatNullableInt .NET 11.0 1.987 ns 0.21 0

dotnet/runtime#122167 expands nullable boxing inside the JIT, exposing the temporary box to escape analysis; previously, a runtime helper hid it. For a null input, there’s no allocation on either version, because nothing gets boxed. And on both versions, BoxNullableValue returns the boxed object, meaning the object escapes, so the 24-byte allocation remains. However, for FormatNullableInt , the JIT in .NET 11 can now see that the temporary 24-byte box doesn’t escape and eliminates that heap allocation entirely.

Escape analysis improved further for enumerators, through a mechanism called Conditional Escape Analysis (CEA). Support for CEA was introduced in .NET 10, but .NET 11 extends the set of patterns that this analysis can safely recognize. The existing escape analysis asks whether a reference created by an allocation can flow somewhere the JIT can no longer track, such as an unknown call. If it can, the object must remain on the heap. That analysis is necessarily conservative and largely flow-insensitive: if an object might be passed to an interface call on any path, it doesn’t try to prove that the path containing that call is mutually exclusive with the path containing the allocation.

Unfortunately, that’s exactly what GDV produces when it optimizes a foreach over an IEnumerable<T> . As noted earlier, GDV turns an interface call into a type check with two branches: a fast branch for the likely collection type and a fallback branch containing the original interface call. Devirtualization and inlining along the fast branch will often reveal an enumerator allocation for the collection type, while later enumerator guards retain fallback calls such as IEnumerator<T>.MoveNext . The existing analysis sees those calls and concludes that the locally allocated enumerator might escape. CEA instead records the relationship between the fast-path allocation and the enumerator local tested by the later guards. If every apparent escape occurs only behind a failed type check, the JIT can clone the region into a hot version where those checks are known to succeed. In that clone, the object can’t reach the fallback calls, so it can be stack-allocated and often promoted into separate scalar locals. The original region remains as the general slow path.

One case .NET 10 didn’t handle, though, was a GetEnumerator() implementation that returns the result of another GetEnumerator() call. A collection expression converted to IEnumerable<int> , for example, uses a compiler-generated read-only-array wrapper with exactly this structure: the wrapper’s GetEnumerator() delegates to the underlying array’s GetEnumerator . With dotnet/runtime#122946 , the JIT in .NET 11 handles this “chaining”:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly IEnumerable<int> s_readOnlyStatic = [1, 2, 3, 4, 5];
    private readonly IEnumerable<int> _readOnlyInstance = [1, 2, 3, 4, 5];

    [Benchmark]
    public int ReadOnlyStatic()
    {
        int sum = 0;
        foreach (int item in s_readOnlyStatic) sum += item;
        return sum;
    }

    [Benchmark]
    public int ReadOnlyInstance()
    {
        int sum = 0;
        foreach (int item in _readOnlyInstance) sum += item;
        return sum;
    }
}
Method Runtime Mean Ratio Allocated Alloc Ratio
ReadOnlyStatic .NET 10.0 2.665 ns 1.00
ReadOnlyStatic .NET 11.0 2.666 ns 1.00
ReadOnlyInstance .NET 10.0 13.874 ns 1.00 32 B 1.00
ReadOnlyInstance .NET 11.0 2.674 ns 0.19 0

ReadOnlyStatic , whose static readonly field the JIT can effectively treat as a constant, was already optimized in .NET 10. In .NET 11, the instance-field case also loses its 32-byte enumerator allocation and converges on the same throughput.

dotnet/runtime#121918 from @MichalPetryka fixes another way an address could unnecessarily make an object appear to escape. The IL constrained. prefix lets one generic callvirt sequence work for both value types and reference types: it can avoid boxing a value type, while for a reference type it dereferences the receiver and performs normal virtual dispatch. ObjectEqualityComparer<T>.Equals , used in the following benchmark by EqualityComparer<T>.Default , contains such a call to value.Equals(other) . The receiver was represented as an indirect read through the address of a local. Merely taking that address marked the local as exposed, preventing the newly allocated Value from being considered for stack allocation. The receiver is now represented as a direct value load instead, and the 24-byte heap allocation disappears.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections.Generic;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly Value s_other = new(42);

    [Benchmark]
    public bool Equals() => EqualityComparer<Value>.Default.Equals(new Value(42), s_other);

    private sealed class Value(int value)
    {
        private readonly int _value = value;

        public override bool Equals(object? obj) => obj is Value other && _value == other._value;

        public override int GetHashCode() => _value;
    }
}
Method Runtime Mean Ratio Allocated Alloc Ratio
Equals .NET 10.0 3.874 ns 1.00 24 B 1.00
Equals .NET 11.0 1.786 ns 0.46 0

While CEA can move a non-escaping object off the GC heap, sometimes the JIT can go further and prove an allocation need not exist at all. Generic code provides a common source of such opportunities through boxing. For example, the ArgumentNullException.ThrowIfNull method accepts an object value . That means when you have a method like this:

static void Test<T>(T value)
{
    ArgumentNullException.ThrowIfNull(value);
    ...
}

when T is constrained to a non-nullable struct, boxing is incurred, in order to pass value as object . ThrowIfNull here is a nop if value is non- null (since the method is simply if (value is null) Throw(); ), and previous releases successfully optimized away that boxing in optimized code. However, in Tier 0, that optimization wasn’t applied, and ThrowIfNull would end up allocating. While this wouldn’t negatively impact steady-state throughput, it would lead to annoying noise in profiling, as well as additional overhead during startup, where such use wasn’t yet promoted out of Tier 0. In .NET 11, dotnet/runtime#129392 adds support for this in Tier 0 as well.

On the virtual-dispatch side, multiple PRs contribute to improving generic virtual methods (GVMs). dotnet/runtime#120866 from @hez2010 stops eagerly spilling ldvirtftn call targets into a temporary, and lets generic virtual target resolution move ahead of argument setup when legal. dotnet/runtime#122023 from @hez2010 then enables the JIT to devirtualize non-shared GVMs, carrying the generic context needed to turn the indirect dispatch into a direct, and potentially inlineable, call. And dotnet/runtime#128702 from @hez2010 extends that support to shared GVMs and default interface implementations that require an instantiating stub. These optimizations can increase total code size when the newly direct calls are inlined, but that’s generally the desired trade: more of the actual work becomes visible to the optimizer. Consider the following benchmark:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Benchmark]
    public int NonShared() => ((IProcessor)new Processor()).SizeOf(42);

    [Benchmark]
    public int Shared() => ((IProcessor)new Processor()).SizeOf("hello");

    private interface IProcessor
    {
        int SizeOf<T>(T value);
    }

    private sealed class Processor : IProcessor
    {
        public int SizeOf<T>(T value) => Unsafe.SizeOf<T>();
    }
}

Casting a freshly allocated Processor to IProcessor incurs an interface generic virtual call in the IL, but the JIT is now able to see the receiver’s exact type, even in the shared string case, such that .NET 11 devirtualizes and inlines both calls. That in turn exposes Unsafe.SizeOf<T>() as a constant and proves that the short-lived Processor doesn’t need to be allocated at all.

Method Runtime Mean Ratio Allocated Alloc Ratio
NonShared .NET 10.0 6.678 ns 1.00 24 B 1.00
NonShared .NET 11.0 1.764 ns 0.26 0
Shared .NET 10.0 7.166 ns 1.00 24 B 1.00
Shared .NET 11.0 1.764 ns 0.25 0

Building on that, dotnet/runtime#123183 from @hez2010 enables ReadyToRun compilation to resolve and devirtualize more non-shared generic virtual calls that would otherwise remain indirect, and dotnet/runtime#130202 from @hez2010 extends that support to NativeAOT. NativeAOT represents some generic virtual targets as “fat pointers” (pointers that are more than just an address, typically an address and associated metadata, and that in this case carry both a code address and generic context); by deferring that transformation until after exact-type devirtualization has had a chance to run, the JIT can turn an interface call site with a single known target to a non-shared GVM into a direct call that may then be inlined.

Type information also needs to survive the transformations the JIT performs internally. If the JIT spills a reference expression into a temporary while restructuring a tree, losing the expression’s exact class information can turn a call that was devirtualizable back into an opaque virtual call. That’s what happens here in .NET 10: Value gets boxed and SetValue is invoked through IValue . dotnet/runtime#128485 from @hez2010 preserves the class handle and exactness on the temporary. With that information still available, .NET 11 devirtualizes and inlines the call, eliminating the box and its 24-byte allocation.

Separately, dotnet/runtime#127433 relaxes the inliner’s budget heuristics for callees on [Intrinsic] types like Span and Vector . These types intentionally expose many small, composable methods that serve as gateways to JIT-recognized operations. If a wrapper remains as a call, the caller pays the call overhead and optimizations around it see an opaque boundary. If it inlines, the importer can replace its body with an intrinsic node and optimize that node together with the surrounding indexing, bounds checks, and vector operations. Giving such wrappers more favorable budgeting therefore keeps more of them inlineable and exposes more of the actual operation to the rest of the optimizer.

One of the core abstraction-enabling mechanisms in .NET is delegates: they let us pass around objects representing functions to be invoked, carrying with them associated required state. Deabstraction enables avoiding paying for the overheads associated with delegates in some cases. For the rest, we still want those delegates to be as cheap as possible. dotnet/runtime#99200 from @MichalPetryka simplifies CoreCLR’s delegate representation, removing one pointer-sized field from every delegate object. That saves 8 bytes per delegate in a 64-bit CoreCLR process. dotnet/runtime#129304 from @MichalPetryka improves Native AOT’s delegate layout separately by reordering its existing four fields so related values are adjacent. The updated layouts also give equality and hash-code operations more direct access to the method identity they need.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly Target s_target = new();
    private static readonly Func<int> s_first = s_target.GetValue;
    private static readonly Func<int> s_second = s_target.GetValue;

    [Benchmark]
    public Func<int> ClosedInstance() => s_target.GetValue;

    [Benchmark]
    public bool DelegateEquals() => s_first.Equals(s_second);

    [Benchmark]
    public int DelegateGetHashCode() => s_first.GetHashCode();

    private sealed class Target
    {
        public int GetValue() => 42;
    }
}
Method Runtime Mean Ratio Allocated Alloc Ratio
ClosedInstance .NET 10.0 7.395 ns 1.00 64 B 1.00
ClosedInstance .NET 11.0 6.844 ns 0.93 56 B 0.88
DelegateEquals .NET 10.0 3.254 ns 1.00
DelegateEquals .NET 11.0 2.215 ns 0.68
DelegateGetHashCode .NET 10.0 5.623 ns 1.00
DelegateGetHashCode .NET 11.0 3.741 ns 0.67

dotnet/runtime#129410 from @MichalPetryka follows up on the CoreCLR layout by placing the target object and method pointer next to each other. Those are commonly consumed together during invocation, and the adjacency enables paired loads on architectures such as Arm64.

Runtime Async

For more than a decade, async and await have let us write asynchronous code that looks remarkably similar to synchronous code: we can put a try / catch around an await , use local variables on either side of it, return a value and generally reason about the method in source order. When execution reaches an await for something that isn’t yet complete, however, the method can’t simply leave its current stack frame in place and wait for the operation to finish. The thread needs to be freed up to do other work, while the work after the await , including whatever local state it will need later, must survive somewhere. In C#, the compiler has traditionally been responsible for transforming the method into a representation that enables that continuation.

I went into the history and mechanics of that transformation in How async/await really works . The very short version is that the compiler traditionally replaces an async method with a small entry method and a generated state machine whose MoveNext method contains the transformed user code. Parameters, locals that need to survive an incomplete await, spilled expression values, awaiters, the current state number, and a method builder all become fields on a heap-allocated object. The generated MoveNext method runs the user’s code until an awaiter reports that it isn’t yet complete. It stores enough information to know where and with what values to resume, registers MoveNext as the continuation, and returns. When the operation completes, MoveNext is invoked again, jumps to the right location based on the saved state number (think goto and a label), retrieves the result from a value-producing awaiter, and continues. If every awaiter is already complete, MoveNext can run all the way through synchronously. When the method completes or throws, the builder publishes the result, cancellation, or exception through the returned Task , Task<T> , ValueTask , or ValueTask<T> (or, in the rare case, a custom task-like type).

For example, consider this tiny method:

static async Task<int> ReadLengthAsync(Stream stream, CancellationToken cancellationToken)
{
    var buffer = new byte[4096];
    int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
    return bytesRead;
}

While the code that gets generated for this changes over time and differs between debug and release builds, the lowering by the C# compiler has looked something like this:

[AsyncStateMachine(typeof(<ReadLengthAsync>d__0))]
static Task<int> ReadLengthAsync(Stream stream, CancellationToken cancellationToken)
{
    <ReadLengthAsync>d__0 stateMachine = default;
    stateMachine.builder = AsyncTaskMethodBuilder<int>.Create();
    stateMachine.state = -1;
    stateMachine.stream = stream;
    stateMachine.cancellationToken = cancellationToken;
    stateMachine.builder.Start(ref stateMachine);
    return stateMachine.builder.Task;
}

struct <ReadLengthAsync>d__0 : IAsyncStateMachine
{
    public int state;
    public AsyncTaskMethodBuilder<int> builder;
    public Stream stream;
    public CancellationToken cancellationToken;

    private TaskAwaiter<int> awaiter;

    public void MoveNext()
    {
        int result;
        try
        {
            TaskAwaiter<int> localAwaiter;

            if (state != 0)
            {
                byte[] buffer = new byte[4096];
                localAwaiter = stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).GetAwaiter();
                if (!localAwaiter.IsCompleted)
                {
                    state = 0;
                    awaiter = localAwaiter;
                    builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref this);
                    return;
                }
            }
            else
            {
                localAwaiter = awaiter;
                awaiter = default;
                state = -1;
            }

            result = localAwaiter.GetResult();
        }
        catch (Exception e)
        {
            state = -2;
            builder.SetException(e);
            return;
        }

        state = -2;
        builder.SetResult(result);
    }
}

That’s quite a lot of generated code for three lines of C#. The compiler has to make decisions before the program runs about the state-machine layout, which values might need to survive, how many awaiter fields are required, and how all the suspension points fit into one MoveNext dispatch. The runtime and JIT have optimized the resulting pattern heavily over the years, including combining the task, state machine, continuation, and ExecutionContext into a single allocation, but by the time the JIT sees the IL, the transformation has already happened, leaving it with a very complicated system to try to optimize.

.NET 11 introduces a new way to split that responsibility, a reimplementation of the async / await infrastructure referred to as “runtime async”. Rather than the C# compiler being responsible for the transformation, the JIT is. The C# compiler emits a much smaller suspension-aware IL contract for each eligible async method and marks the method as async in metadata. The runtime and JIT then do the work that depends on runtime knowledge: creating the externally visible Task or ValueTask , recognizing direct async calls, deciding which values are actually alive at each suspension point, laying out continuation objects, and generating the control flow that suspends and resumes the method. Effectively, the transformation moves from C# to the runtime, where more information is available to optimize it.

The programming model hasn’t changed. This is still C# async / await ; await still obeys the awaiter pattern, exceptions and cancellation still surface through the returned task-like object, ConfigureAwait still has its usual meaning, synchronous completion is still synchronous completion, and on and on. An explicit goal for the feature has been 100% behavioral compatibility: whether an async method is lowered by the language compiler or by the runtime is an implementation detail, and any observable semantic difference is a bug.

In .NET 11, application code opts in with a compiler feature switch:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net11.0</TargetFramework>
    <Features>$(Features);runtime-async=on</Features>
  </PropertyGroup>
</Project>

Note that there’s no new C# syntax involved, so LangVersion=preview isn’t required, nor is EnablePreviewFeatures . While this is opt-in at the application layer, most of the in-box shared framework is already built this way for .NET 11. The async / await performance goal for .NET 11 is parity with .NET 10, and in general runtime async is already as good as or better than the older implementation in many important paths. It isn’t yet fully optimized, though, and there are known cases where it still produces less efficient code. I’d encourage you to experiment in .NET 11 with opting-in your applications and services; just make sure to measure. My hope is that it’ll be on by default starting in .NET 12.

Moving the transformation from the C# compiler to the runtime has the added benefit of reducing binary size. As noted, the traditional lowering emits an entry method, a generated state-machine type, fields for captured state, and a MoveNext body, for every async method. Runtime async leaves a much smaller method body for the runtime to transform. The following tiny app contains ten Task<int> -returning async methods, each awaiting the next, and compiles the same source once with compiler lowering and once with runtime async:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <AssemblyName>SizeProbe</AssemblyName>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Features Condition="'$(RuntimeAsync)' == 'true'">$(Features);runtime-async=on</Features>
  </PropertyGroup>
</Project>
// dotnet build -c Release -p:RuntimeAsync=false -o classic --no-incremental; dotnet build -c Release -p:RuntimeAsync=true -o runtime --no-incremental; Get-Item .\classic\SizeProbe.dll, .\runtime\SizeProbe.dll | Select-Object Directory, Length

Console.WriteLine(await Benchmarks.Layer0());

public class Benchmarks
{
    public static async Task<int> Layer0() => await Layer1();
    private static async Task<int> Layer1() => await Layer2();
    private static async Task<int> Layer2() => await Layer3();
    private static async Task<int> Layer3() => await Layer4();
    private static async Task<int> Layer4() => await Layer5();
    private static async Task<int> Layer5() => await Layer6();
    private static async Task<int> Layer6() => await Layer7();
    private static async Task<int> Layer7() => await Layer8();
    private static async Task<int> Layer8() => await Layer9();

    private static async Task<int> Layer9()
    {
        await Task.Yield();
        return 42;
    }
}
Lowering SizeProbe.dll Ratio
Compiler 10,752 bytes 1.00
Runtime async 5,632 bytes 0.52

For a method such as:

static async Task<int> CallerAsync() => await CalleeAsync();

with runtime async enabled, the C# compiler generates IL like the following:

; MSIL
.method private hidebysig static
    class System.Threading.Tasks.Task`1<int32> CallerAsync() cil managed async
{
    call class System.Threading.Tasks.Task`1<int32> CalleeAsync()
    call int32 System.Runtime.CompilerServices.AsyncHelpers::Await<int32>(
        class System.Threading.Tasks.Task`1<int32>)
    ret
}

There is no generated <CallerAsync>d__0 type, no IAsyncStateMachine , no MoveNext , no AsyncTaskMethodBuilder<int> , and no AsyncStateMachineAttribute . Previously, async on a C# method evaporated at compile time. Now, the method has a new MethodImpl async bit, represented in IL assembly syntax by that async modifier, and the body calls helpers in System.Runtime.CompilerServices.AsyncHelpers .

At first glance the ret looks impossible because the declared signature returns Task<int> while the value on the IL evaluation stack is an int . This clearly isn’t a normal calling convention. The VM can give a Task -returning method two related identities, or MethodDescs, where one has the normal signature the rest of managed code sees, Task<int> CallerAsync() . The other is the AsyncCall variant, which effectively returns int and has an implicit channel for a continuation. Both refer to the same logical method and metadata token, but they have different calling conventions and different jobs. If regular managed code invokes CallerAsync , the VM-generated outer thunk preserves the public contract and returns a Task<int> . If another runtime async method directly awaits it, the JIT can instead call the AsyncCall variant and receive the result directly when the call completes synchronously, or a continuation when it suspends. In other words, it can hand back the T directly and avoid allocating a Task<T> .

That pairing works in both directions. For a method compiled with runtime async, the AsyncCall variant owns the generated (newly compact) IL while the public Task -returning entry point is an adapter thunk; for a traditionally compiled method, the public method owns its usual IL while the VM can create an AsyncCall adapter around it. That means runtime async code remains able to await existing libraries and code compiled by older compilers, a critical capability for our goal of 100% compat. The largest wins naturally appear as more of an async call chain is compiled with runtime async.

This is where the JIT gets an opportunity that simply didn’t exist when every boundary was already expressed as a task and a generated state machine. Suppose A awaits B , which awaits C :

static async Task<int> A(bool yield) => await B(yield);
static async Task<int> B(bool yield) => await C(yield);
static async Task<int> C(bool yield)
{
    if (yield)
        await Task.Yield();

    return 42;
}

Traditionally, each method has its own compiler-generated state machine and its own task-like result. C suspends and eventually completes its task, which wakes B ‘s state machine; B then completes its task, which wakes A ‘s state machine; and A completes the root task observed by the caller. There has been an enormous amount of work done over the years to reduce the costs of those objects and transitions.

With runtime async, the importer recognizes the adjacent pattern of “call a Task-returning method, then await that task.” In the simple case it can call the callee’s AsyncCall variant instead. When yield is false and C completes synchronously, the int flows back through B and A as a plain value, and only the outermost boundary needs to turn it into the Task<int> promised to the original caller. When yield is true and C suspends, the runtime links continuation state for the chain and eventually resumes it without requiring an intermediate Task<int> at every directly fused edge. The Task contract hasn’t vanished, it just moved to the place where a Task is actually needed.

Runtime async doesn’t make every asynchronous operation allocation-free, though. Rather, it gives the JIT enough information to avoid materializing some task objects that existed only to carry a result from one async method directly into the next. If a consumer stores the task in a collection, manually hooks up a continuation, or otherwise observes the task as an object, that object is still needed. The optimization is about not paying for boundaries that aren’t observably boundaries.

The impact is already visible with just two layers:

// dotnet run -c Release -f net11.0 --filter "*"
// The project also needs the `runtime-async=on` feature switch set.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly Task<int> s_completed = Task.FromResult(42);

    [Benchmark(Baseline = true), BenchmarkCategory("Completed")]
    public Task<int> ClassicCompleted() => ClassicCompletedOuter();

    [Benchmark, BenchmarkCategory("Completed")]
    public Task<int> RuntimeCompleted() => RuntimeCompletedOuter();

    [Benchmark(Baseline = true), BenchmarkCategory("Yielding")]
    public Task<int> ClassicYielding() => ClassicYieldingOuter();

    [Benchmark, BenchmarkCategory("Yielding")]
    public Task<int> RuntimeYielding() => RuntimeYieldingOuter();

    [RuntimeAsyncMethodGeneration(false)]
    private static async Task<int> ClassicCompletedOuter() => await ClassicCompletedInner();

    [RuntimeAsyncMethodGeneration(false)]
    private static async Task<int> ClassicCompletedInner() => await s_completed;

    private static async Task<int> RuntimeCompletedOuter() => await RuntimeCompletedInner();

    private static async Task<int> RuntimeCompletedInner() => await s_completed;

    [RuntimeAsyncMethodGeneration(false)]
    private static async Task<int> ClassicYieldingOuter() => await ClassicYieldingInner();

    [RuntimeAsyncMethodGeneration(false)]
    private static async Task<int> ClassicYieldingInner()
    {
        await Task.Yield();
        return 42;
    }

    private static async Task<int> RuntimeYieldingOuter() => await RuntimeYieldingInner();

    private static async Task<int> RuntimeYieldingInner()
    {
        await Task.Yield();
        return 42;
    }
}

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method)]
    internal sealed class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute
    {
        public bool RuntimeAsync => runtimeAsync;
    }
}
Method Mean Ratio Allocated Alloc Ratio
ClassicCompleted 21.221 ns 1.00 144 B 1.00
RuntimeCompleted 6.151 ns 0.29 0 B 0.00
ClassicYielding 254.139 ns 1.00 248 B 1.00
RuntimeYielding 116.927 ns 0.46 168 B 0.68

The synchronously completing chain is more than 3x faster and avoids both intermediate task allocations. Even after a real suspension, the same two-layer chain takes less than half the time and allocates 80 fewer bytes.

Exception handling amplifies the difference. Again consider an async method A calling an async method B calling an async method C . The transformation generated by the C# compiler of each method results in a try / catch block around the whole body of the MoveNext method so that any unhandled exception can be stored into the returned Task . Let’s say code in C throws an unhandled exception. That’s then caught by this manufactured catch block and stored into the Task returned to B . The awaiter in B then retrieves that exception from the Task object and throws it. It’s then caught by B ‘s generated catch and stored into its Task . And so on. An exception crossing ten such async helpers can therefore be thrown, caught, and stored ten times even though none of the source methods has an explicit handler. That is super expensive. But runtime async doesn’t need to re-enter a pass-through frame with no handler. On the synchronous path the exception unwinds through the fused calls normally, and after a real suspension, one dispatch-loop catch walks past continuation records that have no handler and faults the observable root task once.

The following benchmark measures both a fully synchronous throw and an exception after one real Task.Yield suspension. It uses a compiler-recognized per-method escape hatch ( RuntimeAsyncMethodGeneration ) so that the classic and runtime async methods run in the same process on the same .NET 11 runtime and differ only in how the compiler lowers them. (Note that this attribute is experimental and isn’t a public API exposed from the core libraries; as with other attributes known to the C# compiler, it recognizes them by name and signature.)

// dotnet run -c Release -f net11.0 --filter "*"
// The project also needs the `runtime-async=on` feature switch set.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Params(1, 10, 30)]
    public int Depth;

    [Params(false, true)]
    public bool Yield;

    [Benchmark(Baseline = true)]
    public int Classic() => Invoke(ClassicThrowAsync(Depth));

    [Benchmark]
    public int Runtime() => Invoke(RuntimeThrowAsync(Depth));

    private static int Invoke(Task<int> task)
    {
        try
        {
            return task.GetAwaiter().GetResult();
        }
        catch (InvalidOperationException)
        {
            return -1;
        }
    }

    [RuntimeAsyncMethodGeneration(false)]
    private async Task<int> ClassicThrowAsync(int depth)
    {
        if (depth == 0)
        {
            if (Yield) await Task.Yield();
            throw new InvalidOperationException("uh oh");
        }

        return await ClassicThrowAsync(depth - 1);
    }

    private async Task<int> RuntimeThrowAsync(int depth)
    {
        if (depth == 0)
        {
            if (Yield) await Task.Yield();
            throw new InvalidOperationException("uh oh");
        }

        return await RuntimeThrowAsync(depth - 1);
    }
}

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method)]
    internal sealed class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute
    {
        public bool RuntimeAsync => runtimeAsync;
    }
}
Depth Yield Method Mean Ratio Allocated Alloc Ratio
1 False Classic 4.727 μs 1.00 1.6 KB 1.00
1 False Runtime 3.558 μs 0.75 1.16 KB 0.72
1 True Classic 6.308 μs 1.00 1.68 KB 1.00
1 True Runtime 8.211 μs 1.30 1.42 KB 0.85
10 False Classic 19.469 μs 1.00 15.13 KB 1.00
10 False Runtime 5.885 μs 0.30 2.13 KB 0.14
10 True Classic 24.923 μs 1.00 15.63 KB 1.00
10 True Runtime 6.122 μs 0.25 2.88 KB 0.18
30 False Classic 51.302 μs 1.00 84.2 KB 1.00
30 False Runtime 10.721 μs 0.21 5.71 KB 0.07
30 True Classic 65.974 μs 1.00 85.53 KB 1.00
30 True Runtime 11.254 μs 0.17 7.72 KB 0.09

Runtime async supports Task , Task<T> , ValueTask , and ValueTask<T> as method return types, but as of today it doesn’t support async void , async iterators, or arbitrary custom task-like return types with custom builders; those continue to use the traditional compiler transformation. For ValueTask<T> , the existing reasons to use the type still apply. A ValueTask<T> can carry a result directly, wrap a Task<T> , or refer to an IValueTaskSource<T> . That’s made it useful for APIs where synchronous completion is common enough that avoiding a Task allocation outweighs the larger return value and the more restrictive consumption rules, or where asynchronous completion can have its costs amortized via a reusable backing object. Runtime async then addresses some of the scenarios that would have led developers to use ValueTask<T> . Does that mean everyone should stop using ValueTask<T> ? No. Choosing Task versus ValueTask remains an API design decision based on completion patterns, allocation sensitivity, call frequency, and how consumers need to use the result. Write the return type that makes sense for the API, then let the compiler, VM, and JIT optimize it as best they can.

Workloads with many layers of small async methods can benefit the most from runtime async, because those layers are exactly where intermediate tasks and state machines often accumulate. Shared framework code, for example, is full of this pattern: a public method validates arguments and awaits a private helper, which awaits a transport helper, which awaits an operating-system operation. Application services similarly compose authentication, retry, logging, serialization, and I/O helpers. Runtime async can make the source-level decomposition cheaper without asking the developer to flatten the code into one giant method in order to avoid “implementation detail” costs.

The work required to reach this point has been extensive. A GitHub search of the runtime async tracking label on September 14, 2026 returned 235 pull requests, far too many for me to enumerate one by one. So I won’t try; you can peruse that label in your spare time. The work is also not only about direct performance improvements but also about improvements to diagnostics and performance tooling that help you to make better use of async in your own code. When an async method suspends, its physical thread stack unwinds. That method’s continuation might later run on a different thread whose physical stack begins in the thread pool, with the methods that led to the original await nowhere to be found. A sampling CPU profiler can see where the processor is spending time, but without additional information, it can’t reliably connect those traces back through the logical async call chain, making it hard to answer questions about what async call paths were actually costing. Profiling tools like the async profiler in Visual Studio have traditionally reconstructed those chains from events emitted by Task ‘s infrastructure, but async-heavy applications can generate enormous volumes of those very chatty events. The resulting overhead easily perturbs the workload being measured, making it all but unusable in production. dotnet/runtime#127238 added a new lightweight async-profiler event stream for .NET 11 and runtime async. Rather than sending every small transition through the eventing system as its own full event, the runtime writes compact records into per-thread buffers, delta-encoding timestamps and instruction pointers and flushing the data in batches. It also puts a small identifiable wrapper frame into the physical stack when invoking a continuation. A profiler can use that frame as an anchor, joining ordinary CPU samples to the logical async call stack represented by the event stream. In some measurements, this new approach added less than 1% overhead and shrank the traced data by an order of magnitude. dotnet/runtime#129043 and a few follow-up PRs extended the same approach to the compiler-generated state machines used by existing async code. Thus this isn’t useful only to applications that opt into runtime async; tooling gets one consistent representation across both implementations.

What should you as a developer do differently with runtime async in the picture? Mostly nothing. Keep writing asynchronous code the way you want it to read, and break a large operation into helpers when that makes the code clearer. Use Task by default and choose ValueTask where its API and usage tradeoffs genuinely fit. And don’t contort source code to remove a clean await just because today’s implementation might allocate an intermediate Task . The lowering strategy should “just work” as an implementation detail, preserve behavior, and make existing source get better as the runtime improves.

Bounds Checks

C# is a memory-safe language. Accesses to arrays, strings, and spans are guaranteed by the runtime to be in-bounds; if you try to access someArray[i] , someString[i] , or someSpan[i] with an index less than 0 or greater than or equal to the length of the array/string/span, you’ll get an exception, not silently corrupted memory or a process crash. The runtime guarantees that all permitted accesses are within bounds, and that means it needs to be able to prove the access is in bounds. The main method the JIT has for achieving that is by injecting code that performs a bounds check, as if instead of:

int[] array = ...;
int value = array[i];

you’d written:

int[] array = ...;
if ((uint)i >= array.Length) throw new IndexOutOfRangeException();
int value = array[i];

At the assembly level, a bounds check looks something like:

; x64
cmp ecx, dword ptr [rax+8]        ; compare index with array length
jae THROW                         ; unsigned index >= length
mov edx, dword ptr [rax+rcx*4+16] ; load the element

The JIT could just inject such code on every access and call it a day, but such code adds overhead, so the JIT works to elide those checks and that overhead wherever it can prove the index is valid. Proving an index is valid means the JIT needs to be able to see from other evidence that it couldn’t possibly be out of bounds.

The quintessential example of that is a for loop over the full contents of an array or span:

for (int i = 0; i < array.Length; i++)
{
    Use(array[i]);
}

The JIT recognizes from this idiom that, within the loop body, i is guaranteed to be in the range [0, array.Length) , and avoids emitting the bounds check for the array[i] access. The JIT has long handled this particular case. Other cases, not so much. Bounds-check elimination has improved in virtually every .NET release; more recent releases added range propagation for derived expressions ( .NET 7 and .NET 8 saw significant improvements here), SSA-based reasoning ( .NET 9 ), and better handling of Span<T> , whose length sits in a field rather than an object header, complicating tracking. Each year, the developers contributing to the JIT find new patterns that were being missed, that show up in the wild, and that are fixable. .NET 11 improves several such patterns.

Range analysis in the JIT tracks intervals for each variable, an upper bound and a lower bound. For example, taking the true branch of x < 5 gives the range for x in that branch an upper bound of 4 while taking the true branch of x > 2 makes the lower bound 3. What about x != 5 ? On the true edge, we know x isn’t 5, and if the current range for x is [5, 10] , then we know the range must actually be [6, 10] … the lower bound can be tightened because the only value at the lower end is excluded. Similarly, if the range is [0, 5] , an x != 5 assertion tells us the range is actually the narrower [0, 4] . Or, at least, that’s what you’d hope it would do. The JIT had this relevant comment:

// We have a != assertion, but it doesn't tell us much about the interval. So just skip it.
continue;

In .NET 11, dotnet/runtime#121273 replaces that logic with productive reasoning. It checks whether the excluded constant is at either edge of the currently tracked range, adding in the new insights if so. C# list patterns, introduced in C# 11, generate just such comparison sequences. For example, the pattern name is [] or [':'] or [':', not ':', ..] lowers to something like this:

if (name != null)
{
    int num = name.Length;

    if (num == 0) return true;

    if (num == 1)
    {
        if (name[0] == ':') return true;
    }
    else if (name[0] == ':' && name[1] != ':')
    {
        return true;
    }

    return false;
}

Range analysis then proceeds with something like this:

  1. We know that Array.Length is never negative, so it has a range of [0, Array.MaxLength] .
  2. On the false edge of num == 0 , we know that num != 0 , so the range is narrowed now to [1, Array.MaxLength] .
  3. Similarly, on the false edge of num == 1 , we know that num != 1 , so the range is narrowed now to [2, Array.MaxLength] .
  4. We then access name[0] and name[1] , both of which are guaranteed in bounds based on the lower bound of 2 that was established.

Without the != constant tightening, that narrowing wouldn’t happen, and the bounds checks in step 4 couldn’t be elided. Thankfully, they now can be in .NET 11. Consider this example:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private string[] _inputs = ["", ":", ":x", "abc", ":ab", "x", "ab:cd"];

    [Benchmark]
    public int ClassifyAll()
    {
        int total = 0;
        foreach (string s in _inputs) total += Classify(s);
        return total;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int Classify(ReadOnlySpan<char> name) =>
        name switch
        {
            [] => 0,
            [':'] => 1,
            [':', not ':', ..] => 10 + name[0] + name[1],
            _ => 3
        };
}

In .NET 10, we can see the call to CORINFO_HELP_RNGCHKFAIL at the bottom of the method. That’s the tell-tale sign there was at least one bounds check in the method. With .NET 11, that sign is removed.

; Arm64
--- .NET 10
+++ .NET 11
@@ -10,17 +10,15 @@
             beq     G_M000_IG08

 G_M000_IG04:
-            ldrh    w2, [x0]
-            cmp     w2, #58
+            ldrh    w1, [x0]
+            cmp     w1, #58
             bne     G_M000_IG06

 G_M000_IG05:
-            cmp     w1, #1
-            bls     G_M000_IG11
             ldrh    w0, [x0, #0x02]
             cmp     w0, #58
             beq     G_M000_IG06
-            add     w0, w2, w0
+            add     w0, w1, w0
             add     w0, w0, #10
             b       G_M000_IG07

@@ -44,8 +42,4 @@
             mov     w0, wzr
             b       G_M000_IG07

-G_M000_IG11:
-            bl      CORINFO_HELP_RNGCHKFAIL
-            brk     #0
-
-; Total bytes of code 112
+; Total bytes of code 96

“Assertion” machinery in the JIT propagates learned facts (like the aforementioned range information) between “basic blocks” (a sequence of instructions with one entry point, one exit point, and no branches into or out of the middle of it), so information established in block A flows to block B if A “dominates” B (meaning the only way to get to B is through A). But what about facts established earlier within the same block? That’s the gap that dotnet/runtime#121527 addresses. Consider this code:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private int[] _arr = new int[512];

    [Benchmark]
    public int RunMany()
    {
        int touched = 0;
        for (int i = 0; i < _arr.Length - 2; i++)
        {
            Test(_arr, i);
            touched++;
        }
        return touched;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Test(int[] arr, int i)
    {
        arr[i] = 0;  // 1: establishes 'i >= 0 && i < arr.Length'
        i++;         // 2: same block
        if (i < arr.Length) arr[i] = 0;  // 3: proven safe from 1's assertion
    }
}

Statements 1, 2, and 3 are all in the same basic block, up to the conditional; after statement 1 executes, if we reach statement 2, the bounds check on statement 1 passed, we know i >= 0 and i < arr.Length , and after statement 2, i becomes i + 1 . After the if guard i < arr.Length we know the incremented i is still within bounds. But when the range check pass in the .NET 10 JIT examined statement 3’s bounds check, it saw the assertions propagated from predecessor blocks. Since the assertion from statement 1 is generated within the current block, the range check couldn’t see it. The PR fixed it to walk the current block’s tree in execution order, accumulating assertions as it went. When we reach statement 3’s bounds check, we’ve already walked past statement 1 and picked up its i >= 0 && i < arr.Length assertion.

; Arm64
--- .NET 10
+++ .NET 11
@@ -13,8 +13,6 @@
             ble     G_M000_IG04

 G_M000_IG03:
-            cmp     w1, w2
-            bhs     G_M000_IG05
             str     wzr, [x0, w1, UXTW #2]

 G_M000_IG04:
@@ -25,4 +23,4 @@
             bl      CORINFO_HELP_RNGCHKFAIL
             brk     #0

-; Total bytes of code 68
+; Total bytes of code 60

There are almost an infinite number of things the JIT could look for and special-case. But every special case requires code, maintenance, and, most importantly, compilation time. A “just-in-time” compiler typically runs while the application is running, so the JIT itself must be optimized and spend its limited budget only where there’s a likely payoff. That pushes the developers building it toward patterns that occur in real workloads. One such pattern, often seen in libraries like format decoders, builds a table index with bitwise operations on a byte, for example ((b & 0x03) << 4) | ((b & 0xf0) >> 4) . Each masked piece has a tiny upper bound, so the OR of those pieces is always in [0..63] , safely in range for e.g. a Base64 alphabet table. Until dotnet/runtime#122263 , the JIT often failed to prove that combined bound and left a bounds check on the index. Existing range-check code understood the upper bounds produced by bitwise AND and shifts, but not OR; the change lets the JIT combine the known bounds of both OR operands and remove the remaining array check.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly byte[] _input = new byte[4096];

    [GlobalSetup]
    public void Setup() => new Random(42).NextBytes(_input);

    [Benchmark]
    public int Base64LikeIndex() => Sum(_input);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int Sum(ReadOnlySpan<byte> input)
    {
        int sum = 0;
        foreach (byte b in input)
        {
            int index = ((b & 0x03) << 4) | ((b & 0xF0) >> 4);
            sum += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="u8[index];
        }

        return sum;
    }
}

The .NET 10 assembly checks the computed index against the 65-byte lookup table on every iteration. In .NET 11, range analysis proves the index is at most 63, so both the comparison and the branch to the range-check failure helper disappear:

; x64
 M01_L00:
        movzx    r9d, byte ptr [rdx+r8]
        mov      r11d, r9d
        and      r11d, 3
        shl      r11d, 4
        and      r9d, 0F0
        sar      r9d, 4
        or       r9d, r11d
-       cmp      r9d, 41
-       jae      short M01_L02
        movzx    r9d, byte ptr [r10+r9]
        add      eax, r9d
        inc      r8d
        cmp      r8d, ecx
        jl       short M01_L00

-M01_L02:
-       call     CORINFO_HELP_RNGCHKFAIL
-       int      3
-
-; Total bytes of code 95
+; Total bytes of code 79

As another example, dotnet/runtime#125056 improves the handling of guards like (uint)i < span.Length that are pervasive in performance-sensitive code. Consider this benchmark:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private int[] _data = Enumerable.Range(0, 512).ToArray();

    [Benchmark]
    public int RunMany()
    {
        int sum = 0;
        for (int i = 0; i < _data.Length; i++)
            sum += Test(_data, i);
        return sum;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int Test(Span<int> span, int i)
    {
        if ((uint)i < (uint)span.Length)
        {
            if (i != 0)
                return span[i - 1] + span[i];

            return span[i];
        }

        return 0;
    }
}

Because the comparison is unsigned, (uint)i would be a large positive number if i were negative, making it impossible for (uint)i < (uint)span.Length to be true (since a span’s length is never negative, (uint)span.Length is at most int.MaxValue ). Inside the true branch, i is therefore in [0, span.Length - 1] . Previously, the JIT wasn’t always recording the lower bound i >= 0 when it processed the (uint)i < span.Length assertion, and that could leave bounds checks on expressions like i - 1 in place. The fix adds the [0, int.MaxValue - 1] lower bound deduction for the index variable upon entering the true arm of a (uint)i < span.Length check. Combined with the existing range tracking for the upper bound, this gives the JIT a complete picture of i ‘s range inside the guarded block.

; Arm64
--- .NET 10
+++ .NET 11
@@ -8,10 +8,8 @@
             cbz     w2, G_M000_IG05

 G_M000_IG03:
-            sub     w3, w2, #1
-            cmp     w3, w1
-            bhs     G_M000_IG09
-            ldr     w1, [x0, w3, UXTW #2]
+            sub     w1, w2, #1
+            ldr     w1, [x0, w1, UXTW #2]
             ldr     w0, [x0, w2, UXTW #2]
             add     w0, w1, w0

@@ -33,8 +31,4 @@
             ldp     fp, lr, [sp], #0x10
             ret     lr

-G_M000_IG09:
-            bl      CORINFO_HELP_RNGCHKFAIL
-            brk     #0
-
-; Total bytes of code 84
+; Total bytes of code 68

Bounds check elision is generally based on forms of range analysis, where the JIT needs to prove that a given index is guaranteed to be within the range of the data structure. But the same range analysis-based facts can prove that other checks are unnecessary. For example, once the JIT knows that an integer is in [0..100] , it can prove both that converting it to byte can’t lose data and that multiplying it by 10 can’t overflow. dotnet/runtime#124147 enables the JIT to use such facts to avoid unnecessary branches as part of checked operations. When range analysis proves that the operands are in ranges whose result can’t overflow, making checked a nop, the backend can now emit plain add/multiply/subtract instructions, without the jump to failure, as in the following example:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly int[] _array = new int[99];

    [Benchmark]
    public int ArrayLengthPlusConstant() => AddToLength(_array);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int AddToLength(int[] array) => checked(array.Length + 10);

    [Benchmark]
    public int GuardedLengthTimesConstant() => Multiply(_array);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int Multiply(Span<int> span)
    {
        if (span.Length >= 100) return 0;
        return checked(span.Length * 10);
    }
}
; Arm64
--- .NET 10
+++ .NET 11
 G_M000_IG02:
             cmp     w1, #100
             bge     G_M000_IG05

 G_M000_IG03:
             mov     w0, #10
-            smull   x0, w1, w0
-            lsr     x2, x0, #32
-            cmp     w2, w0, ASR #31
+            mul     w0, w1, w0
-            bne     G_M000_IG07

 G_M000_IG04:
             ldp     fp, lr, [sp], #0x10
             ret     lr

-G_M000_IG07:
-            bl      CORINFO_HELP_OVERFLOW
-            brk     #0
-
-; Total bytes of code 64
+; Total bytes of code 44

That makes the change broadly applicable: any time you write checked arithmetic on quantities that are inherently bounded, such as collection counts, lengths, or indices constrained by prior comparisons, the JIT now has a chance to prove at compile time that the overflow can’t happen and thus eliminate the run-time check entirely. Building on that range-check work, dotnet/runtime#124184 teaches the JIT to eliminate “narrowing casts” under the same kinds of guards:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private uint _value = 100;

    [Benchmark]
    public byte GuardedNarrowingCast() => Narrow(_value);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static byte Narrow(uint value)
    {
        if (value > 100) return 0;
        return checked((byte)value);
    }
}
; Arm64
--- .NET 10
+++ .NET 11
@@ -4,23 +4,10 @@

 G_M000_IG02:
             cmp     w0, #100
-            bhi     G_M000_IG04
-            cmp     w0, #255
-            bhi     G_M000_IG06
+            csel    w0, w0, wzr, ls

 G_M000_IG03:
             ldp     fp, lr, [sp], #0x10
             ret     lr

-G_M000_IG04:
-            mov     w0, wzr
-
-G_M000_IG05:
-            ldp     fp, lr, [sp], #0x10
-            ret     lr
-
-G_M000_IG06:
-            bl      CORINFO_HELP_OVERFLOW
-            brk     #0
-
-; Total bytes of code 52
+; Total bytes of code 24

Such use of checked is common in serialization and protocol code where you validate a value’s range prior to truncating it. In this benchmark I’ve used checked explicitly, but the more common form is with the whole project compiled with <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> in the .csproj, such that this checked becomes implicit. After the change, the range analysis sees that value is in the range [0, 100] , knows byte fits values up to 255, and elides the check.

dotnet/runtime#128620 further teaches range analysis the possible results of leading-zero count, trailing-zero count, and population count instructions. Those results are often used to index small lookup tables… knowing their bounds lets the JIT remove the bounds check.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Numerics;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly int[] s_lookup =
        Enumerable.Range(0, 33).Select(i => i * i).ToArray();
    private uint[] _values;

    [GlobalSetup]
    public void Setup()
    {
        Random rng = new(42);
        _values = Enumerable.Range(0, 1024).Select(i => (uint)rng.Next(1, int.MaxValue)).ToArray();
    }

    [Benchmark]
    public int SumLookupByLeadingZeroCount()
    {
        int sum = 0;
        foreach (var v in _values)
            sum += s_lookup[BitOperations.LeadingZeroCount(v)];

        return sum;
    }
}

The lookup improves because the JIT now knows LeadingZeroCount(uint) is between 0 and 32 and can remove the bounds check.

Method Runtime Mean Ratio
SumLookupByLeadingZeroCount .NET 10.0 516.1 ns 1.00
SumLookupByLeadingZeroCount .NET 11.0 438.5 ns 0.85

The JIT is also able to conditionally apply range check-based elision via “cloning”. Cloning is a mechanism where the JIT takes one piece of code and duplicates it. One of the copies it leaves as it was originally, and the other copy it special cases. So, for example, if you had code like:

int value = array[i];

the JIT could theoretically clone that in order to avoid the implicit bounds check, e.g.

int value;
if ((uint)i < array.Length)
{
    // no bounds check emitted by JIT, e.g.
    value = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), i);
}
else
{
    // bounds check emitted
    value = array[i];
}

That particular code looks silly, as we’re just trading an implicit bounds check for an explicit one. It becomes less silly when the JIT is able to elide multiple bounds checks with a single branch, e.g.

int sum;
if (4 < array.Length)
{
    // zero bounds checks
    ref int startRef = ref MemoryMarshal.GetArrayDataReference(array);
    sum =
        startRef +
        Unsafe.Add(ref startRef, 1) +
        Unsafe.Add(ref startRef, 2) +
        Unsafe.Add(ref startRef, 3);
}
else
{
    // potentially four bounds checks
    sum =
        array[0] +
        array[1] +
        array[2] +
        array[3];
}

Such optimizations are already handled in the JIT, via its optRangeCheckCloning phase. It groups bounds checks from a basic block, emits one guard for the largest required range, and duplicates the affected code into a fast path where the individual checks can be removed and a fallback path where they remain. However, one long-standing limitation of range-check cloning is that it refused to process the last statement of any “terminator” block, a block that ends with a jump or return instruction. For a method like:

static int ArrayAccess(int[] abcd) => abcd[0] + abcd[1] + abcd[2] + abcd[3];

all four array accesses live in the return statement, the last statement of a return block, so nothing got cloned and the hot path retained four separate bounds checks. In .NET 11, dotnet/runtime#124705 removes that restriction, making the return statement eligible for range-check cloning and allowing a single fast-path guard to cover all four accesses.

But even without range-check cloning, there’s really no reason such accesses should require four bounds checks: the JIT should be able to see that the array or span needs to have a length of at least 4 and guard all accesses by that single check. If there were intervening operations that had side effects, the JIT would need to maintain order of operations, at least enough to maintain the observable behavior of those effects, but that’s not the case here. With dotnet/runtime#127439 in .NET 11, the JIT will now coalesce those checks within a basic block, strengthening the first check to the largest constant index and removing the rest.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly int[] _values = Enumerable.Range(0, 16).ToArray();

    [Benchmark]
    public int Sum16()
    {
        int[] values = _values;
        return
            values[0] + values[1] + values[2] + values[3] +
            values[4] + values[5] + values[6] + values[7] +
            values[8] + values[9] + values[10] + values[11] +
            values[12] + values[13] + values[14] + values[15];
    }
}

In previous releases, you’d sometimes see a proactive developer doing a similar optimization manually, e.g. reordering the accesses in an example like that to put the largest read first. That’s no longer necessary.

Method Runtime Mean Ratio
Sum16 .NET 10.0 2.958 ns 1.00
Sum16 .NET 11.0 1.828 ns 0.62

Another bounds checking improvement comes in dotnet/runtime#127488 , which actually targets explicitly-implemented bounds checks (rather than the implicit ones we’ve been discussing) and targets code that reads a fixed-size value from the end of a span, such as BinaryPrimitives.ReadInt32BigEndian(span.Slice(span.Length - 4)) behind a span.Length >= 4 guard, e.g.

if (span.Length >= 4)
{
    // Parse an int from the end of the span
    ... = ReadInt32BigEndian(span.Slice(span.Length - 4));
    ...
}

There shouldn’t be any additional bounds checking required here. However, Span.Slice begins with:

if ((uint)start > (uint)_length)
    ThrowHelper.ThrowArgumentOutOfRangeException();

and ReadInt32BigEndian begins with:

if (sizeof(T) > source.Length)
    ThrowHelper.ThrowArgumentOutOfRangeException();

so even though our span.Length >= 4 check should have been sufficient, we’re still ending up with two additional checks. To address that, the JIT needed two things.

First, it needed to be able to identify that x - (x + a) is the same as -a . Without this identity, length - (length - 4) is just an opaque subtraction of two expressions with no obvious constant result. With the identity, the JIT can recognize the inner expression (length - 4) as length + (-4) , apply x - (x + a) == -a with x == length and a == -4 , and end up with -(-4) == 4 . Now ReadInt32BigEndian ‘s check against 4 becomes 4 >= 4 , which the JIT can trivially see is true.

Second, Slice(start) must establish that start is between zero and the span’s length. When start is length - 4 , the existing length >= 4 guard proves the result is non-negative, while subtracting a positive constant means the result can’t exceed length . The improved range analysis connects that guard to the subtraction and removes Slice ‘s check.

Both fixes together mean the above example now elides both extra bounds checks. That’s useful in particular for libraries like parsers, network protocol implementations, and cryptographic code, all of which frequently on hot paths do things like “read the last N bytes of a buffer.”

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly byte[] _buffer = new byte[64];

    [Benchmark]
    public int ReadLastInt32() => ReadLastInt32(_buffer);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int ReadLastInt32(ReadOnlySpan<byte> span)
    {
        if (span.Length >= sizeof(int))
        {
            return BinaryPrimitives.ReadInt32BigEndian(span.Slice(span.Length - sizeof(int)));
        }

        return -1;
    }
}

In .NET 10, the helper is 73 bytes and includes both additional checks and their throw paths:

; x64
cmp       ecx,4
jl        RETURN_MINUS_ONE
lea       edx,[rcx-4]
cmp       edx,ecx
ja        THROW_SLICE
mov       r8d,edx
add       rax,r8
sub       ecx,edx
cmp       ecx,4
jl        THROW_READ
movbe     eax,[rax]

In .NET 11, the helper is 28 bytes, and only the original length guard remains:

; x64
cmp       ecx,4
jl        RETURN_MINUS_ONE
add       ecx,-4
add       rax,rcx
movbe     eax,[rax]

dotnet/runtime#122040 and dotnet/runtime#127117 similarly help to remove bounds checks involving span.Slice . Vectorized loops often work through a span a chunk at a time, slicing off the elements they’ve already processed. The JIT hasn’t always been able to keep track of how those progressively smaller slices relate to the original span, so it could end up checking the same limits again on each iteration. These changes improve that tracking, enabling more of those repeated checks to be removed.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly int[] _data = Enumerable.Repeat(1, 1_024).ToArray();

    [Benchmark]
    public Vector256<int> CreateFromSlice() => CreateFromSlice(_data);

    [Benchmark]
    public int SumSliced() => SumSliced(_data);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static Vector256<int> CreateFromSlice(Span<int> values)
    {
        if (values.Length < 16)
            return default;

        return Vector256.Create(values.Slice(8));
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int SumSliced(ReadOnlySpan<int> data)
    {
        Vector128<int> sum = default;
        while (data.Length >= Vector128<int>.Count)
        {
            sum += Vector128.Create(data);
            data = data.Slice(Vector128<int>.Count);
        }

        int result = Vector128.Sum(sum);
        foreach (int value in data)
            result += value;

        return result;
    }
}

In .NET 10, the loop condition proves that at least one vector remains, but the construction of the vector from the current span performs the same check again. .NET 11 retains the length relationship, so the loop body begins directly with the vector addition:

; x64, vector loop
-cmp       esi, 4
-jl        THROW_ARGUMENT_OUT_OF_RANGE
-vpaddd    xmm6, xmm6, [rbx]
-add       rbx, 10
-add       esi, 0FFFFFFFC
-cmp       esi, 4
+vpaddd    xmm0, xmm0, [rax]
+add       rax, 10
+add       ecx, 0FFFFFFFC
+cmp       ecx, 4
 jge       LOOP

We saw earlier how range-check cloning enables duplicating a sequence of instructions in order to eliminate bounds checks. “Loop cloning” extends that to a whole loop. Consider a loop that processes the first count elements of an array:

for (int i = 0; i < count; i++)
    sum += values[i];

The test i < count doesn’t by itself prove that i < values.Length , so by default the compilation would need a bounds check in the body, which would mean a bounds check for every values[i] access. Loop cloning gives the JIT another option. Instead of generating the equivalent of:

for (int i = 0; i < count; i++)
    sum += values[i]; // bounds check!

it can generate the equivalent of:

if ((uint)count <= (uint)values.Length)
{
    // no bounds checks
    ref int startRef = ref MemoryMarshal.GetArrayDataReference(values);
    for (int i = 0; i < count; i++)
    {
        sum += Unsafe.Add(ref startRef, i);
    }
}
else
{
    // bounds check per iteration
    for (int i = 0; i < count; i++)
    {
        sum += values[i];
    }
}

For the common case where the iteration is in bounds, execution proceeds through a cloned loop with no per-iteration bounds checks, whereas the original checked loop remains as the fallback that preserves exceptional behavior for invalid inputs. The normal path pays for one guard and avoids a check on every iteration, but that comes at the expense of duplicating code. The JIT therefore needs to apply the optimization selectively.

The JIT has long employed loop cloning, but it didn’t always kick in even in cases it seemed applicable. The previous example showed loop cloning with < in the iteration condition. For whatever reason, however, some developers used != , and loop cloning didn’t apply (I’m guessing they used != because they thought it was more efficient, and they actually end up deoptimizing). Thanks to dotnet/runtime#129268 , in .NET 11 != is now also handled, as long as specific conditions are met, such as the stride being exactly 1 or -1, e.g. i++ qualifies, while i += 2 doesn’t. dotnet/runtime#129303 also improves loops that terminate with i != bound , giving the JIT a tighter understanding of the values i can take and allowing it to remove some bounds checks even when it can’t clone the whole loop.

Lookahead in arrays and spans is another recurring pattern, especially in parsers. dotnet/runtime#124242 and dotnet/runtime#125235 recognize conditions such as (uint)(i + 2) < (uint)span.Length and use that relation to remove the follow-on checks for span[i + 1] and span[i + 2] . Consider this benchmark:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Linq;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string _text = string.Concat(Enumerable.Repeat("%FE", 128));

    [Benchmark]
    public bool ContainsPercentFF()
    {
        ReadOnlySpan<char> span = _text;
        for (int i = 0; i < span.Length; i++)
        {
            if (span[i] == '%' &&
                (uint)(i + 2) < (uint)span.Length &&
                span[i + 1] == 'F' &&
                span[i + 2] == 'F')
            {
                return true;
            }
        }

        return false;
    }
}
Method Runtime Mean Ratio
ContainsPercentFF .NET 10.0 232.1 ns 1.00
ContainsPercentFF .NET 11.0 194.9 ns 0.84

Several smaller changes broaden the range of code from which the JIT can remove bounds checks:

  • dotnet/runtime#121640 helps in a situation where once an access using a chosen index has been checked, a later access to the same array at that index need not be checked again.
  • dotnet/runtime#121683 enables the JIT to trace an array’s length through calculations performed earlier in the method, exposing more redundant checks, including some involving index-from-end expressions.
  • dotnet/runtime#124387 and dotnet/runtime#130326 teach the optimizer to rely on a span’s length always being non-negative.
  • dotnet/runtime#124571 improves sequences of index-from-end accesses: once an access like arr[^4] establishes that the array has at least four elements, the JIT reuses that information for nearby accesses such as arr[^3] .
  • dotnet/runtime#129101 improves how the JIT combines and carries forward the possible ranges of arithmetic expressions, including expressions involving bitwise OR and unsigned division. Those tighter ranges can show that more values are non-negative or within bounds.

Bounds-check elimination is only one payoff from understanding a loop’s structure. The JIT analyzes induction variables (values like loop counters that change predictably each iteration) and puts loops into standard forms so that later optimizations can reason about them. .NET 11 broadens the range of loops for which that works:

  • dotnet/runtime#122184 recognizes another representation of a 32-to-64-bit zero extension. That lets pointer loops using expressions such as data[(uint)i] replace the repeated index extension and address calculation with a pointer increment.
  • dotnet/runtime#119537 follows simple control-flow predecessors when finding an induction variable’s initialization and zero-trip test, while dotnet/runtime#128303 gives loops with multiple backedges a single canonical latch block.
  • dotnet/runtime#128532 makes loop cloning tolerate more statements around the update and test.
  • dotnet/runtime#129309 extends cloning to more span loops with non-unit strides and offset limits.
  • dotnet/runtime#129349 handles large strides in array loops with an explicit safety guard rather than rejecting them outright.
  • dotnet/runtime#129472 allows loop inversion to spend more of its budget on likely cloning candidates.
  • dotnet/runtime#130205 removes comparisons that are redundant given the induction variable’s known range.
  • dotnet/runtime#131362 corrects profile weights after inversion changes a loop’s exit.

Much of this work wasn’t motivated by contrived benchmarks containing nothing but array indexing as I’m prone to use in these posts. Rather, many of the improvements stemmed from an ongoing audit of unsafe code throughout the .NET libraries, part of a broader effort to improve memory safety in .NET . .NET and C# are memory safe, but as with other memory safe languages like Rust, it provides escape hatches that enable turning off the guardrails provided by the compiler and runtime. This effort is about reducing where and when developers feel compelled to use those escape hatches, since every occurrence is an opportunity for increased risk. Unsafe code was often introduced years earlier to manually avoid bounds checks, typically by walking a buffer with pointers, byrefs, or Unsafe.Add . Sometimes the audit found that the unsafe code was no longer needed and could simply be removed. Sometimes a “safe” rewrite (meaning not using unsafe and friends) was already just as fast or even faster. And sometimes the rewrite exposed an optimization the JIT was missing, in which case the answer was to improve the JIT and then rewrite the library code to use normal, bounds-checked C#. Several of the optimizations discussed in this section are the result of exactly that feedback loop. dotnet/runtime#127429 is a particularly nice example. The vectorized implementation of Enumerable.Sum used MemoryMarshal.GetReference , Vector.LoadUnsafe , and Unsafe.Add to walk its input without bounds checks. With the span-slicing improvements described earlier, it could instead use Vector.Create(span) , span.Slice(...) , and a foreach for the tail. That’s easier to reason about, removes the unchecked indexing, and ended up being faster. dotnet/runtime#114757 similarly replaced an unsafe pointer-based header-name accessor with a generic ReadOnlySpan<T> implementation without loss of performance. Similarly, dotnet/runtime#121270 removed more unsafe code from Uri parsing and actually improved performance of the cited code measurably.

There’s a useful “go do” here for libraries outside of dotnet/runtime, as well. Unsafe code written to work around the JIT is a snapshot of what the JIT could do at the time that code was written. If you own code that has hand-written pointer or Unsafe -based loops whose purpose is to avoid bounds checks, it’s worth rewriting them with safe, bounds-checked C# and measuring again on .NET 11. Chances are, you’ll find the gap at this point is either non-existent or small enough that it’s not worth the increased maintenance and risk for managing the safety yourself. And if the revised version is still slower, that’s a great opportunity for you to share a repro in the dotnet/runtime repo, hopefully serving as inspiration for one of the first performance improvements to go into the JIT for .NET 12. unsafe code is still necessary for scenarios like interop, but performance alone shouldn’t be a permanent reason to eschew all the valuable guardrails .NET provides.

The C# 15 memory-safety preview pushes in the same direction and is part and parcel of this effort. Historically, C# has largely equated pointers with unsafe code: simply declaring or manipulating a pointer generally required an unsafe context, even if the code never accessed the memory to which it points. In the preview, pointer plumbing such as declaring a pointer, taking an address with & , using fixed , converting stackalloc to a pointer, and applying sizeof to an unmanaged type no longer requires an unsafe context. Operations that actually access the pointed-to memory, including *p , p->member , and p[i] , still do. C# 15 also adds an unsafe(expression) form, analogous to checked(expression) , so an unsafe context can cover one precise expression rather than a larger statement block. Those changes are the first preview slice of a larger, multi-release unsafe evolution . The end goal is to make unsafe regions smaller, make their assumptions visible through the call graph, and make them easier for reviewers and tools to find. Pairing that with a JIT that makes idiomatic safe code fast removes a lot of the historical pressure to use unsafe code in the first place.

Assertion Propagation

As discussed earlier, the JIT continually learns facts while compiling a method: a value equals a constant, a reference isn’t null, an integer falls within a particular range, and so on. “Assertion propagation” carries those facts forward so they can simplify later code. “Value numbering” complements it by letting the JIT recognize when two expressions compute the same value, even if they appear in different places or use different variables. Together, these mechanisms enable optimizations such as removing redundant null and bounds checks, folding conditions to constants, and reusing repeated computations. .NET 11 improves assertion propagation primarily by fixing places where useful facts were either never recorded or weren’t recognized later.

For example, reading an array’s length normally carries an implicit null-check: if the array reference is null , the read must throw. Once global assertion propagation already knows the reference is non-null, however, we should be able to avoid the implicit null check. In .NET 11, dotnet/runtime#124291 takes care of that for Array.Length :

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly int[] _values = new int[1024];

    [Benchmark]
    public void DeadLength() => Test(_values);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Test(int[]? values)
    {
        if (values is not null)
            _ = values.Length;
    }
}

The .NET 10 code still tests the reference and reads the length. In .NET 11, the guard proves the read can’t throw, and since its result isn’t used, the access disappears:

; Arm64
--- .NET 10
+++ .NET 11
 G_M000_IG01:
             stp     fp, lr, [sp, #-0x10]!
             mov     fp, sp

 G_M000_IG02:
-            cbz     x0, G_M000_IG04
-
-G_M000_IG03:
-            ldr     wzr, [x0, #0x08]
-
-G_M000_IG04:
             ldp     fp, lr, [sp], #0x10
             ret     lr

-; Total bytes of code 24
+; Total bytes of code 16

dotnet/runtime#119474 improves the starting point for integer range analysis. The JIT now uses facts inherent in a value itself, e.g. a constant has one exact value, while a value converted to byte , for example, must be between 0 and 255. That can eliminate bounds checks and conditions even when no preceding if explicitly established the range. dotnet/runtime#124415 further refines this handling of casts, combining what is known about both the source value and the destination type to derive the tightest useful range.

Those improvements derive ranges from facts inherent in a value, but ranges can also come from control flow. After if ((uint)x < 10) , for example, the JIT knows that x is between 0 and 9 on the true path, which may be enough to remove a later comparison or array bounds check. dotnet/runtime#123624 derives tighter ranges from assertions and casts, including proving that some comparisons are always true or false. dotnet/runtime#129390 preserves range information more accurately when control-flow paths merge.

Other changes make better use of the ranges once known. dotnet/runtime#129354 traces values back through their definitions to fold more span- and slice-related comparisons, and dotnet/runtime#126917 uses narrowed ranges to remove more relational branches.

dotnet/runtime#124711 teaches the JIT to learn implicit facts from operations that have already completed successfully. For example:

  • Creating an array proves its requested length wasn’t negative.
  • A reference-array store may need a runtime covariance check, because a value typed as object[] can actually refer to a string[] ; the helper that performs that type check also validates the index, so if it returns successfully, the index was in range.
  • Integer division or modulo proves the divisor wasn’t zero.

And so on. Those facts can then remove redundant checks and conditions later in the method.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly object?[] _objArr = new object?[8];
    private readonly object _value = new();

    [Benchmark]
    public object? CovariantArrayStore()
    {
        object?[] objArr = _objArr;
        objArr[3] = _value;
        return objArr[2];
    }
}

A successful store to element 3 proves that particular array has at least four elements; since an array’s length can’t change, the subsequent read of element 2 doesn’t need another bounds check.

Method Runtime Mean Ratio
CovariantArrayStore .NET 10.0 3.565 ns 1.00
CovariantArrayStore .NET 11.0 2.985 ns 0.84

dotnet/runtime#128522 simplifies how the global assertion pass identifies values, making it less likely to miss a fact learned earlier. One practical impact of this is better propagation of a static string ‘s known length, which can turn a general string comparison into a fixed-size vectorized comparison.

dotnet/runtime#127810 improves null-check elimination where control flow merges. With ??= , which is a very common operator used for lazy initialization, the resulting value is non-null whether it came from the existing field or from the newly allocated object. The JIT now combines the facts from both paths and recognizes that the subsequent call doesn’t need another null check.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    Inner? _inner;

    [Benchmark]
    [Arguments(42)]
    public int Invoke(int n) => (_inner ??= new()).Increment(n);

    private sealed class Inner
    {
        [MethodImpl(MethodImplOptions.NoInlining)]
        public int Increment(int n) => n + 1;
    }
}

The generated code consequently loses the null check on the merged value:

; x64
 M00_L00:
        mov      edx, esi
-       cmp      [rcx], ecx
        call     qword ptr [...] ; Inner.Increment(Int32)

-; Total bytes of code 75
+; Total bytes of code 73

dotnet/runtime#128701 removes similarly redundant null checks from copies of structs that contain object references. Such copies use a runtime helper so the garbage collector is correctly notified about the reference writes, but lowering had been adding probes for both source and destination without preserving whether either address could actually fault. It now emits only the probes that are needed.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private FourRefs _src = new()
    {
        A = new(),
        B = new(),
        C = new(),
        D = new()
    };
    private FourRefs _dst;

    [Benchmark]
    public void BulkStructCopy() => _dst = _src;

    private struct FourRefs
    {
        public object? A;
        public object? B;
        public object? C;
        public object? D;
    }
}

Both _src and _dst are fields of the same object, so after probing the source address has established that the object isn’t null, probing the destination address can’t provide any additional information. .NET 11 removes that second probe:

; Arm64
 G_M000_IG02:
             add     x1, x0, #8
             ldrsb   wzr, [x1]
             add     x0, x0, #40
-            ldrsb   wzr, [x0]
             movz    x2, ...
             ldr     x3, [x2]
             mov     x2, #32
             blr     x3      // CORINFO_HELP_BULK_WRITEBARRIER

-; Total bytes of code 56
+; Total bytes of code 52

Additionally, dotnet/runtime#125215 lets the JIT retain and efficiently find more assertions in larger methods, increasing the opportunities for the same kinds of simplification. And dotnet/runtime#129312 removes unnecessary temporary variables when the same simple field address is used multiple times, enabling more efficient loads and stores.

Simplification

Assertion propagation is largely about proving things to help the generated code. Once the JIT knows enough about an operation’s inputs, it can often replace the operation with something simpler and cheaper.

“Constant folding” is a fancy way of saying the compiler does work once so it doesn’t need to be repeated at run time. If the compiler has everything it needs to compute an answer when building, it can bake that answer in to the generated code and avoid needing the code to re-compute it. That answer can then be further used by other computations at build time, potentially folding further. The C# compiler handles constant folding expressions composed entirely of language constants, while the JIT compiler can go further after inlining and after learning things about values and control flow. The JIT already does a ton of folding, and as with every release, it goes further in .NET 11.

One straightforward example is the offset of a field within a struct. dotnet/runtime#122297 recognizes more cases where two addresses refer to the same struct and replaces their difference with the known field offset. Here, the second int field begins four bytes into the struct:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public unsafe class Benchmarks
{
    private struct MyStruct
    {
        public int A;
        public int Field;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static nint OffsetOfFieldInline()
    {
        MyStruct dummy;
        return (nint)((byte*)&dummy.Field - (byte*)&dummy);
    }

    [Benchmark]
    [Arguments(1_000)]
    public nint OffsetOfFieldLoop(int n)
    {
        nint sum = 0;
        for (int i = 0; i < n; i++)
            sum += OffsetOfFieldInline();

        return sum;
    }

}

Without the fold, the loop repeatedly computes the field offset. With the fold, each iteration simply adds the constant 4 .

; Arm64
--- .NET 10
+++ .NET 11
@@ -1,7 +1,6 @@
 G_M000_IG01:
-            stp     fp, lr, [sp, #-0x20]!
+            stp     fp, lr, [sp, #-0x10]!
             mov     fp, sp
-            str     xzr, [fp, #0x18]

 G_M000_IG02:
             mov     x0, xzr

@@ -9,22 +8,18 @@
             ble     G_M000_IG05

 G_M000_IG03:
-            add     x2, fp, #0x1C
-            add     x3, fp, #24
-            sub     x2, x2, x3
             align   [0 bytes for IG04]
             align   [0 bytes]
             align   [0 bytes]
             align   [0 bytes]

 G_M000_IG04:
-            str     xzr, [fp, #0x18]
-            add     x0, x2, x0
+            add     x0, x0, #4
             sub     w1, w1, #1
             cbnz    w1, G_M000_IG04

 G_M000_IG05:
-            ldp     fp, lr, [sp], #0x20
+            ldp     fp, lr, [sp], #0x10
             ret     lr

-; Total bytes of code 60
+; Total bytes of code 40

dotnet/runtime#121985 from @hez2010 enables the JIT to evaluate SequenceEqual at compile time when both inputs are known. SequenceEqual normally walks two sequences element by element, stopping at the first mismatch. But if inlining exposes both sequences as constants, there’s nothing useful left to do at run time: the JIT can compare them while compiling and replace the whole operation with a constant true or false . This intrinsic underpins APIs including MemoryExtensions.SequenceEqual , ReadOnlySpan<T>.SequenceEqual , and string.Equals .

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static string AlphaLower => "abcdefghijklmnopqrstuvwxyz";
    private static string AlphaUpper => "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    [Benchmark]
    public bool CompareEqual() => AlphaLower.Equals(AlphaLower);

    [Benchmark]
    public bool CompareDistinct() => AlphaLower.Equals(AlphaUpper);
}

Because these properties aren’t const , the C# compiler can’t evaluate the comparisons. The JIT, however, can see the string literals after inlining. It now folds comparisons of the same input whose contents are available at the time of compilation. CompareDistinct therefore becomes a constant false .

; x64
--- .NET 10
+++ .NET 11
-mov       rax,LOWER_STRING
-mov       rcx,UPPER_STRING
-add       rax,0C
-vmovups   ymm0,[rax]
-vmovups   ymm1,[rax+14]
-vmovups   ymm2,[rcx]
-vpxor     ymm0,ymm2,ymm0
-vpxor     ymm1,ymm1,[rcx+14]
-vpor      ymm0,ymm1,ymm0
-vptest    ymm0,ymm0
-sete      al
-movzx     eax,al
-vzeroupper
+xor       eax,eax
 ret

-; Total bytes of code 65
+; Total bytes of code 3

Folding an operation is only the first step, though. The result can then simplify later code, even when it’s a vector. dotnet/runtime#127124 extends assertion propagation to 128-bit integer vector constants. If a branch establishes that a vector is zero, uses of that vector within the branch can now be replaced with zero and simplified just like scalar values.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private int _selector;

    [MethodImpl(MethodImplOptions.NoInlining)]
    private Vector128<int> Compute() => _selector == 0 ? Vector128<int>.Zero : Vector128.Create(7);

    [Benchmark]
    public int AndNotIfZero()
    {
        Vector128<int> v = Compute();
        if (v == Vector128<int>.Zero)
        {
            Vector128<int> masked = Vector128.AndNot(v, Vector128.Create(0x00FF00FF));
            return masked[0];
        }

        return -1;
    }
}

In the benchmark’s zero branch, the JIT can now fold away the mask creation, AndNot , and lane extraction, reducing the Arm64 method from 68 bytes to 56 bytes. This currently applies to integer vectors up to 128 bits (floating-point equality has additional NaN and signed-zero semantics that prevent the same reasoning at present).

; Arm64
--- .NET 10
+++ .NET 11
@@ -11,14 +11,11 @@
             umaxp   v16.4s, v0.4s, v0.4s
             umov    x0, v16.d[0]
             movn    w1, #0
-            movi    v16.8h, #0xFF,  LSL #8
-            and     v16.4s, v0.4s, v16.4s
-            smov    x2, v16.s[0]
             cmp     x0, #0
-            csel    w0, w1, w2, ne
+            cinc    w0, w1, eq
G_M000_IG03:
             ldp     fp, lr, [sp], #0x10
             ret     lr
-; Total bytes of code 68
+; Total bytes of code 56

Two backend cleanups take advantage of simpler expressions. dotnet/runtime#124332 from @jonathandavies-arm removes an unnecessary negation when Arm64 code compares a negated value with zero. And dotnet/runtime#124642 from @yykkibbb lets short-circuit Boolean returns fold even when inlining has left unused writes in the same block; those stores previously obscured the simple Boolean expression from the optimizer.

Branches offer another opportunity for simplification. Modern processors work on several instructions at different stages at the same time. When a processor encounters a conditional branch, it predicts which path will be taken so that it can continue fetching and executing instructions speculatively. A correct prediction hides much of the branch’s cost. A misprediction throws away that speculative work, redirects instruction fetch to the correct path, and refills the processor’s execution pipeline. That can make the predictability of a branch as important as the work in either branch. The JIT can sometimes avoid that variability, particularly inside small hot loops, by replacing a branch with a conditional move instruction or by recognizing that several branches describe one simpler condition. This isn’t always profitable: branchless code may evaluate work that a predictable branch would skip, making the branching code less expensive in the majority case. But it can be valuable for small, data-dependent choices.

dotnet/runtime#124567 recognizes zero-based equality chains, e.g. value == 0 || value == 1 || value == 2 . Such chains can be replaced with an unsigned range check, e.g. (uint)value <= 2 , producing a branchless result. The unsigned comparison also handles negative inputs: when interpreted as unsigned, any negative int is larger than the upper bound.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private int _value = 2;

    [Benchmark]
    public bool IsLetterCategory() =>
        _value == 0 ||
        _value == 1 ||
        _value == 2 ||
        _value == 3 ||
        _value == 4;
}

The .NET 10 JIT already combines the first four comparisons, but still needs a branch and a separate comparison for 4 :

; x64
mov       ecx,[rcx+8]
cmp       ecx,3
ja        CHECK_FOUR
mov       eax,1
ret

CHECK_FOUR:
cmp       ecx,4
sete      al
movzx     eax,al
ret

.NET 11 recognizes the whole chain as one unsigned range check, reducing the method from 24 bytes to 13:

; x64
mov       eax,[rcx+8]
cmp       eax,5
setb      al
movzx     eax,al
ret

dotnet/runtime#128524 from @BoyBaykiller extends the same optimization to contiguous ranges that don’t start at zero. For example, x == 3 || x == 4 || x == 5 can become (uint)(x - 3) <= 2 .

Casts can obscure an equally simple comparison. dotnet/runtime#128091 from @BoyBaykiller broadens cast-comparison optimization to equality and inequality. In this benchmark, converting a uint to ulong adds no information needed to compare it with uint.MaxValue , so the JIT can keep the comparison at 32 bits:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Linq;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly int[] _values = Enumerable.Range(0, 128).ToArray();

    [Benchmark]
    public int CastEquality()
    {
        int matches = 0;
        foreach (int value in _values)
            if ((ulong)(uint)value == uint.MaxValue)
                matches++;

        return matches;
    }
}

The widening cast disappears, reducing the Arm64 method from 80 bytes to 76 bytes.

; Arm64
--- .NET 10
+++ .NET 11
@@ -18,8 +18,7 @@

 G_M000_IG04:
             ldr     w3, [x0]
-            mov     x4, #0xFFFFFFFF
-            cmp     x3, x4
+            cmn     w3, #1
             beq     G_M000_IG08

 G_M000_IG05:
@@ -38,4 +37,4 @@
             add     w1, w1, #1
             b       G_M000_IG05

-; Total bytes of code 80
+; Total bytes of code 76

The examples thus far simplify individual comparisons. dotnet/runtime#127181 also combines multiple comparisons in the same expression. For example, (x >= c) && (x <= c) can only be true when x == c ; corresponding OR forms can be simplified similarly.

Once the JIT can reason about one comparison in terms of another, it can apply the same idea across branches. dotnet/runtime#126587 removes an earlier test when a later, stronger test subsumes it. For example, if (x > 0) if (x > 1) needs only the x > 1 test, as reaching the nested body with x > 1 necessarily also means x > 0 .

Rather than simply removing a test, the JIT can sometimes use the outcome of an earlier branch to choose the destination of a later one. This is known as “jump threading”: the JIT threads a control-flow path through the intervening jumps directly to its eventual destination. For example, consider:

int value = condition ? 1 : 2;
if (value == 1)
{
    One();
}
else
{
    Two();
}

The path where condition is true can go directly to One , while the false path can go directly to Two , eliminating the second test, effectively:

int value;
if (condition)
{
    value = 1;
    One();
}
else
{
    value = 2;
    Two();
}

dotnet/runtime#126812 lets this continue through more places where paths rejoin, and dotnet/runtime#127103 ensures the rewritten values remain correct in more of those cases. dotnet/runtime#127950 carries relationships between values further, so facts like a > 10 and b > a can simplify later branches or bounds. The same reasoning can apply to type information. dotnet/runtime#128500 combines the known types of instances arriving from multiple paths; if every value derives from the tested base type, the JIT can remove the is test after the paths merge. And dotnet/runtime#127434 from @hez2010 lets redundant-branch elimination look through empty jump blocks. Such a block contains no work of its own and exists only to redirect control elsewhere, but it could still hide the relationship between two conditions from the optimizer. Consider this benchmark:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static object? s_sink;

    private int _x = 20;
    private int _y = 30;
    private bool _flag = true;
    private int _count = 5;

    [Benchmark]
    public bool TransitiveComparison() => TransitiveComparison(_x, _y);

    [Benchmark]
    public bool MergedTypeCheck() => MergedTypeCheck(_flag);

    [Benchmark]
    public int NestedThresholds() => NestedThresholds(_count);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool TransitiveComparison(int x, int y)
    {
        if (x > 10 && x < 100 && y > x)
            return y > 0;

        return false;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool MergedTypeCheck(bool flag)
    {
        object shape = flag ? new Circle() : new Rectangle();
        s_sink = shape;
        return shape is Shape;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int NestedThresholds(int count)
    {
        if (count > 1)
            if (count > 2)
                if (count > 3)
                    if (count > 4)
                        return 1;

        return 3;
    }

    private abstract class Shape;
    private sealed class Circle : Shape;
    private sealed class Rectangle : Shape;
}

In TransitiveComparison , reaching y > 0 means the JIT already knows that x > 10 and y > x , which together prove that y is positive. The final comparison disappears, reducing the Arm64 method from 40 bytes to 36 bytes:

; Arm64
--- .NET 10
+++ .NET 11
             cmp     w1, w0
             ccmp    w2, w3, c, gt
-            ccmp    w1, #0, nzc, ls
-            cset    x0, gt
+            cset    x0, ls

-; Total bytes of code 40
+; Total bytes of code 36

In MergedTypeCheck , each path creates a different concrete type, but both derive from Shape . .NET 11 keeps the allocations and the store that make the example observable, but replaces the is helper call and its result test with the constant true , reducing the method from 112 bytes to 88 bytes:

; Arm64
--- .NET 10
+++ .NET 11
             bl      CORINFO_HELP_ASSIGN_REF
-            movz    x0, #0xEA30
-            movk    x0, #0x4EB LSL #16
-            movk    x0, #0x7FFF LSL #32
-            bl      CORINFO_HELP_ISINSTANCEOFCLASS
-            cmp     x0, #0
-            cset    x0, ne
+            mov     w0, #1

-; Total bytes of code 112
+; Total bytes of code 88

For NestedThresholds , reaching the return 1 requires count to be greater than all four constants, which is equivalent to just count > 4 . Once redundant-branch elimination can see through the empty jump blocks left behind while simplifying the nested conditions, the other three comparisons disappear:

; Arm64
--- .NET 10
+++ .NET 11
             mov     w1, #3
             mov     w2, #1
-            cmp     w0, #1
-            ccmp    w0, #2, nzc, gt
-            ccmp    w0, #3, nzc, gt
-            ccmp    w0, #4, nzc, gt
+            cmp     w0, #4
             csel    w0, w1, w2, le

-; Total bytes of code 44
+; Total bytes of code 32

Removing a redundant branch is ideal; why do work when it’s provably unnecessary? Often, however, the branch is necessary, as both outcomes are possible (or at least not provably impossible). In such cases, the JIT may still be able to avoid branching via specialized instructions that bake the choice into the instruction. “If-conversion” replaces a small if / else with a conditional-move instruction or another branchless form when both alternatives are cheap. The JIT has been able to do this for several releases, and improves in .NET 11. dotnet/runtime#124738 from @BoyBaykiller recognizes an earlier default assignment as the implicit else , so bool x = false; if (cond) x = true; can become the same branchless form as an explicit else . dotnet/runtime#127915 from @BoyBaykiller handles the opposite cleanup, removing a conditional selection when both outcomes are the same constant while preserving any side effects from evaluating the condition. dotnet/runtime#128533 from @BoyBaykiller also helps these Boolean optimizations meet in the middle by normalizing power-of-two bit tests. A power of two has exactly one bit set, in which case (A & bit) == bit is equivalent to (A & bit) != 0 ; putting both forms into the same canonical representation makes them easier to combine with surrounding conditions. All three improvements are visible in the following benchmarks:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private int _left = 1;
    private int _right = 2;
    private double _double = 0.0;
    private int _bits = 4;

    [Benchmark]
    public bool ImplicitElse() => ImplicitElse(_left, _right);

    [Benchmark]
    public bool IsDefaultValue() => IsDefaultValue(_double);

    [Benchmark]
    public bool HasEitherBit() => HasEitherBit(_bits);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool ImplicitElse(int left, int right)
    {
        bool leftIsSmaller = false;
        if (left < right)
            leftIsSmaller = true;

        return leftIsSmaller;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool IsDefaultValue(double value) => 0.0.Equals(value);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool HasEitherBit(int value) =>
        ((value & 4) == 4) || ((value & 8) == 8);
}

For ImplicitElse , .NET 10 already avoids a branch, but it still materializes both Boolean values and selects between them. In .NET 11, the method becomes just the comparison and a cset , shrinking from 36 bytes to 24 bytes:

; Arm64
--- .NET 10
+++ .NET 11
-            mov     w2, wzr
-            mov     w3, #1
             cmp     w0, w1
-            csel    w2, w2, w3, ge
-            mov     w0, w2
+            cset    x0, lt

-; Total bytes of code 36
+; Total bytes of code 24

0.0.Equals(value) needs to account for NaN , but because the left operand is zero, the case where both operands are NaN can never apply. Removing the conditional selection for that case leaves one floating-point comparison and one cset , reducing IsDefaultValue from 40 bytes to 24 bytes:

; Arm64
--- .NET 10
+++ .NET 11
             fcmp    d0, #0.0
-            beq     G_M000_IG04
-
-G_M000_IG03:
-            fcmp    d0, d0
-            csel    w0, wzr, wzr, eq
-            b       G_M000_IG05
-
-G_M000_IG04:
-            mov     w0, #1
-
-G_M000_IG05:
+            cset    x0, eq
+
+G_M000_IG03:
             ldp     fp, lr, [sp], #0x10
             ret     lr

-; Total bytes of code 40
+; Total bytes of code 24

Finally, normalizing both power-of-two comparisons lets the JIT combine their results. The short-circuit branch in HasEitherBit is replaced by two masks and an or , reducing the method from 40 bytes to 36 bytes:

; Arm64
--- .NET 10
+++ .NET 11
-            tbz     w0, #2, G_M000_IG05
-
-G_M000_IG03:
-            mov     w0, #1
-
-G_M000_IG04:
-            ldp     fp, lr, [sp], #0x10
-            ret     lr
-
-G_M000_IG05:
-            tst     w0, #8
+            and     w1, w0, #4
+            and     w0, w0, #8
+            orr     w0, w1, w0
+            cmp     w0, #0
             cset    x0, ne

-G_M000_IG06:
+G_M000_IG03:
             ldp     fp, lr, [sp], #0x10
             ret     lr

-; Total bytes of code 40
+; Total bytes of code 36

Not every simplification depends on broader control-flow reasoning. “Peephole optimizations” instead replace a short, recognizable pattern with an equivalent cheaper one. Each may save only an instruction or expose a form that another optimization understands, but these patterns can occur very frequently on hot paths throughout generated code. For example, dotnet/runtime#126529 from @BoyBaykiller recognizes that 255 - x for a byte is equivalent to x ^ 255 : both simply flip all eight bits, but the latter can remove an instruction if it’s able to replace a negation and add with an xor. Similarly, -1 - x can turn into the equivalent of ~x .

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly byte[] _data = new byte[4096];

    [GlobalSetup]
    public void Setup() => new Random(42).NextBytes(_data);

    [Benchmark]
    public int InvertBytes()
    {
        int sum = 0;
        foreach (byte b in _data) sum += 255 - b;
        return sum;
    }
}

In .NET 11, the loop loses a separate negate and add:

; Arm64
--- .NET 10
+++ .NET 11
@@ -19,9 +19,8 @@

 G_M000_IG04:
             ldrb    w4, [x0, w2, UXTW]
-            neg     w4, w4
+            eor     w4, w4, #255
             add     w1, w4, w1
-            add     w1, w1, #255
             add     w2, w2, #1
             cmp     w3, w2
             bgt     G_M000_IG04
@@ -33,4 +32,4 @@
             ldp     fp, lr, [sp], #0x10
             ret     lr

-; Total bytes of code 76
+; Total bytes of code 72

dotnet/runtime#129361 removes another unnecessary instruction when comparing an sbyte with a constant that fits in eight bits. The JIT can compare the byte directly, with no sign extension:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private sbyte _value = -65;

    [Benchmark]
    public bool IsLow() => IsLow(_value);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool IsLow(sbyte value) => value < -64;
}

The optimized codegen then compares the byte directly, removing the movsx sign-extension instruction (though the JIT still retains it in the few comparison forms that require a full-width sign bit for correctness).

; x64
--- .NET 10
+++ .NET 11
-movsx  rax,cl
-cmp    eax,0FFFFFFC0
+cmp    cl,0C0
 setl   al
 movzx  eax,al
 ret

-; Total bytes of code 14
+; Total bytes of code 10

dotnet/runtime#125180 from @saucecontrol improves non-overflowing float and double conversions to long and ulong on x86 machines with AVX-512 or AVX10.2. These casts have defined behavior for NaN and out-of-range values, so older code used a helper to preserve those semantics. The newer instruction set lets the JIT keep the normal path inline and register-based, avoiding the helper call; machines that don’t support these instructions retain the existing fallback.

// Run with 32-bit x86 dotnet on a machine with AVX-512 or AVX10.2:
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private float _single = 123_456.75f;
    private double _double = 123_456.75;

    [Benchmark] public long SingleToInt64() => (long)_single;
    [Benchmark] public ulong SingleToUInt64() => (ulong)_single;
    [Benchmark] public long DoubleToInt64() => (long)_double;
    [Benchmark] public ulong DoubleToUInt64() => (ulong)_double;
}
Method Runtime Mean Ratio Code Size
SingleToInt64 .NET 10.0 5.103 ns 1.00 31 B
SingleToInt64 .NET 11.0 2.093 ns 0.41 54 B
SingleToUInt64 .NET 10.0 4.810 ns 1.00 31 B
SingleToUInt64 .NET 11.0 1.366 ns 0.28 34 B
DoubleToInt64 .NET 10.0 4.834 ns 1.00 31 B
DoubleToInt64 .NET 11.0 2.101 ns 0.43 54 B
DoubleToUInt64 .NET 10.0 4.663 ns 1.00 31 B
DoubleToUInt64 .NET 11.0 1.363 ns 0.29 34 B

Vectorization

SIMD, or “single instruction, multiple data”, is the concept of one instruction applying the same operation to several values at once. A “scalar” add , for example, might combine one pair of 32-bit integers, while a 128-bit SIMD add can combine “vectors” of four pairs in the same instruction; 256- and 512-bit variants can handle vectors of eight and sixteen pairs, respectively. When the iterations of an operation are independent, “vectorizing” a loop can therefore replace several scalar iterations with one, improving the throughput of the loop significantly.

.NET exposes portable (they work on any machine) variable-width vector type Vector<T> (which can represent different counts of T depending on the current hardware), fixed-width Vector64<T> through Vector512<T> types (which always represent the same count of T ), and architecture-specific intrinsics (performing operations on such vector types which the JIT then maps to the right underlying hardware instructions). Each element in a vector is often referred to as a “lane”. Because the JIT recognizes these operations directly, it can fold constants, select instructions, and remove unsupported paths without treating them as normal method calls.

A variety of PRs in .NET 11 improve AVX-512 broadcasting and masking. Embedded broadcasting lets an instruction load a single scalar value and replicate it across all vector lanes, avoiding the need to materialize a full-width vector constant in memory to feed into the instruction. For example, this bitwise AND instruction:

; x64
vpandd  zmm0, zmm1, dword ptr [reloc @RWD00] {1to16}

can replace this one:

; x64
vpandd  zmm0, zmm1, zmmword ptr [reloc @RWD00]

storing only 4 bytes in the read-only data section rather than 64. Because the broadcast is handled as part of the load, there’s no additional instruction-level latency; the primary benefit is reduced data size and cache footprint.

Embedded masking similarly lets an instruction update only a subset of the lanes. A mask is one bit per vector lane, where each bit indicates whether and how the operation should affect the corresponding lane. Without embedded masking, code often needs to compute every lane and then blend that result with the old value, so folding the mask into the operation can remove both the separate blend and a zero-vector setup. dotnet/runtime#117700 from @saucecontrol improves broadcast selection when an intrinsic’s natural element size differs from its managed vector type. VNNI, the Vector Neural Network Instructions used for small-integer multiply-accumulate operations, and bitwise operations can now use the smallest valid repeated constant, avoiding a full-vector load.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0
// Requires AVX-VNNI and AVX-512F.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Vector128<byte> _bytes = Vector128.Create((byte)1);
    private readonly Vector128<ulong> _u64 = Vector128.Create(1UL);
    private readonly Vector128<uint> _u32 = Vector128.Create(1U);
    private readonly Vector512<int> _v512 = Vector512.Create(1);
    private int _n = 42;

    [Benchmark]
    public Vector128<int> VnniBroadcast() =>
        AvxVnni.MultiplyWideningAndAdd(
            Vector128<int>.Zero, _bytes, Vector128<sbyte>.One);

    [Benchmark]
    public Vector128<uint> MaskAnd() =>
        Vector128.ConditionalSelect(
            Vector128.GreaterThan(_u32, Vector128<uint>.Zero),
            (_u64 & Vector128<uint>.One.AsUInt64()).AsUInt32(),
            Vector128<uint>.Zero);

    [Benchmark]
    public Vector512<int> BlendMaskAllOnes() =>
        Avx512F.BlendVariable(
            Vector512.Create(_n),
            _v512,
            Vector512.Create(-1));

    [Benchmark]
    public Vector512<int> MultiInsert() =>
        Vector512.ConditionalSelect(
            Vector512.Create(0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0),
            _v512,
            Vector512.Create(_n));

    [Benchmark]
    public Vector512<int> MultiInsertZero() =>
        Avx512F.BlendVariable(
            _v512,
            Vector512<int>.Zero,
            Vector512.Create(0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0));
}

In .NET 11, this results in 12 fewer bytes in the read-only data section, and 12 fewer bytes of constant-pool cache footprint.

; x64
-C4E279503500000000   vpdpbusd xmm6, xmm0, xmmword ptr [reloc @RWD00]
+62F27D18503500000000 vpdpbusd xmm6, xmm0, dword ptr [reloc @RWD00] {1to4}

-RWD00  dq 0101010101010101h, 0101010101010101h
+RWD00  dd 01010101h

The fix also impacts embedded masking. For example, with MaskAnd previously, the AND used a qword broadcast, {1to2} , and a separate blend then moved the masked result, meaning two instructions. Now that Vector128<uint>.One can be broadcast at dword granularity, the mask’s element size and the AND’s element size agree, unlocking using the single merged-masked form. This pattern shows up throughout vectorized algorithms that do lots of bitwise manipulation and hashing, including implementations in System.Numerics.Tensors, System.IO.Hashing, and System.Private.CoreLib.

; x64
-       vpandq   xmm0, xmm0, qword ptr [reloc @RWD00] {1to2}
-       vpblendmd xmm0 {k1}{z}, xmm0, xmm0
+       vpandd   xmm0 {k1}{z}, xmm0, dword ptr [reloc @RWD00] {1to4}

; Code: 45 → 39 bytes; data: 8 bytes → 4 bytes

That MaskAnd example starts as an AND followed by a blend, an operation that chooses independently for each vector lane whether to take its value from one input or the other, with the JIT able to fold those two operations together. Similar opportunities arise with blends more generally. Sometimes the mask or one of the inputs makes the choice trivial, e.g. an all-ones mask always selects the same input, so the blend is just a move. If one input is zero, it can often become an AND or ANDN . AVX-512 provides more options still, as constant masks and zeroing can be encoded directly in the instruction. dotnet/runtime#123146 from @saucecontrol makes these simplifications consistently across the portable and hardware-specific APIs. A blend with an all-ones mask provides a particularly clear example:

// Run on x64 with AVX-512:
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Vector512<int> _values = Vector512.Create(1);
    private int _n = 42;

    [GlobalSetup]
    public void Setup()
    {
        if (!Avx512F.IsSupported)
            throw new PlatformNotSupportedException();
    }

    [Benchmark]
    public Vector512<int> BlendMaskAllOnes() =>
        Avx512F.BlendVariable(Vector512.Create(_n), _values, Vector512.Create(-1));
}

The generated code no longer needs to create the first input, load the mask, or perform the blend. It simply loads the input the all-ones mask would always select:

; x64
-vpbroadcastd zmm0, dword ptr [rcx+8]
-kmovq       k1, qword ptr [RWD00]
-vpblendmd   zmm0 {k1}, zmm0, [rcx+48]
+vmovups     zmm0, [rcx+48]
 vmovups     [rdx], zmm0
 mov         rax, rdx
 vzeroupper
 ret

; 39 bytes → 23 bytes

Intrinsics

An intrinsic is a managed API that the JIT recognizes and special-cases. Often that special-casing involves actually replacing calls to the method with custom code that’s behaviorally equivalent but better in some way (faster, smaller, etc.)

As an example, dotnet/runtime#128678 improves recognition of generic-math calls to IBinaryNumber<T>.Log2 . The method computes the base-2 logarithm of an integer, equivalent to the index of the number’s highest set bit; for example, Log2(16) is 4 . Previously, the JIT’s normalized integer type lost the signedness needed to import the operation directly as an intrinsic. Inlining the managed implementation could still produce the same optimized code, but when inlining didn’t happen, the managed call remained. In .NET 11, the JIT consults the precise type and imports the operation directly: unsigned and non-negative signed inputs can become leading-zero-count or bit-scan arithmetic, while a negative signed value retains the managed fallback and its exact exception behavior.

Sometimes the JIT has a perfectly good intrinsic lowering but doesn’t recognize a call that should use it. Enum.Equals from a generic T : Enum context was a good example. Even though both arguments to the generic helper are strongly typed as T , an enum doesn’t provide an Equals(T) method; it inherits the virtual Enum.Equals(object) implementation. The second argument therefore needs to be boxed to pass it as object . The receiver is invoked with a constrained virtual call, but because the concrete enum doesn’t override the method itself, it too needs to be boxed to invoke the implementation on System.Enum . Thus, what looks like a strongly-typed comparison can end up allocating two boxes and making a virtual call. In .NET 11, dotnet/runtime#122779 eliminates this overhead by teaching the JIT to recognize the call and fold it to a direct comparison of the enum’s underlying integer values. For example:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly StringComparison[] s_values =
    {
        StringComparison.Ordinal, StringComparison.OrdinalIgnoreCase, 
        StringComparison.CurrentCulture, StringComparison.CurrentCultureIgnoreCase,
        StringComparison.InvariantCulture, StringComparison.Ordinal,
    };

    [Benchmark]
    public int CountOrdinal_Generic()
    {
        int count = 0;
        foreach (var v in s_values)
            if (EqualsGeneric(v, StringComparison.Ordinal))
                count++;

        return count;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool EqualsGeneric<T>(T a, T b) where T : Enum => a.Equals(b);
}

Once the JIT knows the callee is Enum.Equals and knows the exact enum type, it asks the runtime for the underlying integer type and replaces the virtual call with a direct comparison. That in turn makes both box/unbox pairs redundant, and the generated code contains neither allocation. For the six comparisons performed here, .NET 10 creates twelve boxes, totaling 288 bytes. In .NET 11, the helper becomes just the integer comparison, eliminating both the allocations and the virtual dispatch.

Method Runtime Mean Ratio Allocated
CountOrdinal_Generic .NET 10.0 59.24 ns 1.00 288 B
CountOrdinal_Generic .NET 11.0 10.01 ns 0.17

NativeAOT had been carrying an equivalent optimization for years, implemented as IL rewriting in ILCompiler that patches Enum.Equals to use typed comparisons. With the JIT now handling it, including in NativeAOT’s own use of the JIT (NativeAOT uses the JIT ahead of time rather than just in time), dotnet/runtime#123086 deletes that rewriting and its supporting machinery.

dotnet/runtime#127329 improves the Vector256.Sum and Vector512.Sum intrinsics. The JIT now performs most of the reduction at full width and combines the per-lane results at the end, avoiding the extracts and duplicate shuffle sequences needed when splitting wide vectors into 128-bit pieces. And dotnet/runtime#127402 extends vector-constant propagation from 128-bit vectors to Vector256 and Vector512 . Code that compares a wide vector with a known sentinel can now simplify subsequent uses just as narrower vectors already could. The following benchmark exemplifies both:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0
// Requires AVX2 for the assembly shown below.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Vector256<float> _floats =
        Vector256.Create(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
    private int _selector;

    [Benchmark]
    public float Sum() => Vector256.Sum(_floats);

    [Benchmark]
    public int TransformWhenKnown()
    {
        Vector256<int> value = GetVector();
        if (value == Vector256.Create(0, 1, 2, 3, 4, 5, 6, 7))
            return (value + Vector256.Create(10)).GetElement(6);

        return -1;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private Vector256<int> GetVector() =>
        _selector == 0 ?
            Vector256.Create(0, 1, 2, 3, 4, 5, 6, 7) :
            Vector256.Create(7);
}

For Sum , .NET 10 separately reduces each 128-bit half and then adds the two scalar results. In .NET 11, the permutes and adds operate on both halves in parallel as 256-bit instructions, after which only the two already-reduced halves need to be combined:

; x64
 vmovups   ymm0, [rcx+28]
-vmovaps   ymm1, ymm0
-vpermilps xmm2, xmm1, 0B1
-vaddps    xmm1, xmm2, xmm1
-vpermilps xmm2, xmm1, 4E
-vaddps    xmm1, xmm2, xmm1
-vextractf128 xmm0, ymm0, 1
-vpermilps xmm2, xmm0, 0B1
-vaddps    xmm0, xmm2, xmm0
-vpermilps xmm2, xmm0, 4E
-vaddps    xmm0, xmm2, xmm0
-vaddss    xmm0, xmm1, xmm0
+vpermilps ymm1, ymm0, 0B1
+vaddps    ymm0, ymm1, ymm0
+vpermilps ymm1, ymm0, 4E
+vaddps    ymm0, ymm1, ymm0
+vextractf128 xmm1, ymm0, 1
+vaddps    xmm0, xmm1, xmm0

; 63 bytes → 39 bytes

TransformWhenKnown uses a deliberately non-repeating constant across its eight lanes. On the branch where the comparison succeeds, .NET 11 can replace value with that constant, fold the vector addition, and determine that element 6 is 16 . The vpaddd , extraction, second 32-byte constant, and associated control flow all disappear:

; x64
-cmp      eax, 0FFFFFFFF
-jne      M00_L00
-vmovups  ymm0, [rsp+20]
-vpaddd   ymm0, ymm0, [RWD32]
-vextracti128 xmm0, ymm0, 1
-vpextrd  eax, xmm0, 2
-vzeroupper
-add      rsp, 58
-ret
-
-M00_L00:
-mov      eax, 0FFFFFFFF
+mov      ecx, 0FFFFFFFF
+mov      edx, 10
+cmp      eax, 0FFFFFFFF
+mov      eax, edx
+cmovne   eax, ecx
 vzeroupper
 add      rsp, 58
 ret

; 85 bytes → 59 bytes

One of the goals of .NET is that you can write code once and have it run anywhere, optimized for whatever that “anywhere” has to offer. For vectorization, that means providing portable operations whenever the intent is common across instruction sets, while retaining architecture-specific APIs for algorithms that really do need to target a particular machine.

Whenever possible, we want to enable developers to express their algorithms using the portable APIs, and each release of .NET fills additional gaps there. Including .NET 11. dotnet/runtime#129627 from @hez2010 adds portable APIs for constructing common lane sequences (e.g. [1, 2, 4, 8] or [a, b, a, b] ), concatenating half-vectors (the lower halves of [a, b, c, d] and [w, x, y, z] producing [a, b, w, x] ), interleaving ( [a, b] and [x, y] producing [a, x, b, y] ), de-interleaving ( [a, x, b, y] producing [a, b] and [x, y] ), and reversal ( [a, b, c, d] producing [d, c, b, a] ), along with their JIT intrinsification. These operations were already expressible, but only verbosely and only if you knew which hardware instruction to reach for, e.g. writing Zip by hand meant targeting a platform-specific API like AdvSimd.Arm64.ZipLow . The new APIs let the code state the transformation and leave instruction selection to the JIT.

Once the intrinsic operation has been recognized, the backend still needs to keep it in a useful vector form while assigning registers and selecting instructions. Vector values are structs, and the JIT will often apply “struct promotion,” tracking a struct’s fields as independent locals so that each can be optimized separately. That’s useful for ordinary structs, but counterproductive when a value is meant to remain in a vector or mask register: splitting it can introduce extra moves and obscure what should be a single whole-value store, particularly after inlining introduces more local stores. dotnet/runtime#128013 consistently marks SIMD and mask stores as intrinsic-related across platforms, including 32-bit x86 and x64 mask stores, so those locals remain intact. dotnet/runtime#129563 extends that principle to user-defined structs that are bitcast to SIMD types. This trades away struct promotion for those locals, but enables the JIT to preserve their vector representation.

This matters for user-defined numerical types that store the same data as a hardware vector but expose named fields or domain-specific operations. The following Vector2Double is laid out as two adjacent double values, so it can be bitcast to Vector128<double> , operated on with SIMD, and bitcast back:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

public struct Vector2Double(double x, double y)
{
    public double X = x;
    public double Y = y;

    public static Vector2Double operator +(Vector2Double left, Vector2Double right)
    {
        Vector128<double> simdLeft = Unsafe.BitCast<Vector2Double, Vector128<double>>(left);
        Vector128<double> simdRight = Unsafe.BitCast<Vector2Double, Vector128<double>>(right);
        return Unsafe.BitCast<Vector128<double>, Vector2Double>(simdLeft + simdRight);
    }
}

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Vector2Double _a = new(1.0, 2.0);
    private readonly Vector2Double _b = new(3.0, 4.0);
    private readonly Vector2Double _c = new(5.0, 6.0);

    [Benchmark]
    public Vector2Double Add() => Add(_a, _b, _c);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static Vector2Double Add(Vector2Double a, Vector2Double b, Vector2Double c) =>
        a + b + c;
}

In .NET 10, promotion of the intermediate struct sends the first SIMD result through two stack locations before the second addition. .NET 11 keeps that value in xmm0 , reducing the helper from 52 bytes to 22 bytes:

; x64
-sub       rsp, 28
 vmovups   xmm0, [rdx]
 vaddpd    xmm0, xmm0, [r8]
-vmovaps   [rsp], xmm0
-vmovups   xmm0, [rsp]
-vmovups   [rsp+18], xmm0
-vmovups   xmm0, [rsp+18]
 vaddpd    xmm0, xmm0, [r9]
 vmovups   [rcx], xmm0
 mov       rax, rcx
-add       rsp, 28
 ret

; 52 bytes → 22 bytes

dotnet/runtime#128350 gives the xarch register allocator more freedom around fused multiply-add (FMA) and AVX-512 ternary-logic operations. These instructions can read and overwrite operands in several equivalent arrangements; choosing the arrangement that already matches the surrounding registers avoids otherwise necessary moves.

Generic vector code introduces another wrinkle. Operators like Vector128<T>.operator == return bool , so the return type doesn’t reveal the vector’s element type. The JIT instead needs to obtain that type from the operands in order to select the right comparison instruction. In some generic contexts, including helpers built on the internal ISimdVector abstraction, the JIT was consulting the wrong type information and failed to import the operator as an intrinsic. It then executed the managed fallback, which compares the lanes individually. dotnet/runtime#130086 marks these operators so their element type is taken from the first argument. As an example, the generic helpers used internally by ordinal-ignore-case string comparer benefit from this.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string _lower = new('a', 256);
    private readonly string _upper = new('A', 256);

    [Benchmark]
    public bool OrdinalIgnoreCase() => string.Equals(_lower, _upper, StringComparison.OrdinalIgnoreCase);
}
Method Runtime Mean Ratio
OrdinalIgnoreCase .NET 10.0 27.794 ns 1.00
OrdinalIgnoreCase .NET 11.0 21.861 ns 0.79

.NET 11 adds support for newer x86 capabilities while also improving code generated for existing hardware. These changes benefit both direct users of hardware intrinsics and portable vector code selected by the JIT. For example, dotnet/runtime#124114 from @saucecontrol improves 32-bit x86 without AVX-512, where converting uint to float or double previously required a runtime helper. Older x86 conversion instructions accept signed integers, and half of the uint range doesn’t fit in a signed 32-bit value, which is why the helper existed. The JIT now emits an inline vector-instruction sequence that handles the high bit explicitly, avoiding the call and its register and stack overhead.

// Run with 32-bit x86 dotnet and AVX-512 disabled (DOTNET_EnableAVX512=0)
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private uint _value = 0xF123_4567;

    [Benchmark] public float UInt32ToSingle() => _value;
    [Benchmark] public double UInt32ToDouble() => _value;
}
Method Runtime Mean Ratio Code Size
UInt32ToSingle .NET 10.0 4.752 ns 1.00 37 B
UInt32ToSingle .NET 11.0 2.403 ns 0.51 43 B
UInt32ToDouble .NET 10.0 4.727 ns 1.00 39 B
UInt32ToDouble .NET 11.0 2.402 ns 0.51 41 B

dotnet/runtime#124804 from @alexcovington adds the AVX-512 Bit Matrix Multiply APIs. A binary matrix treats each bit as an element and combines rows and columns with bitwise operations, not integer multiplication. The instructions are useful in areas such as error correction and CRC computation. Each replaces a much longer sequence of shifts, masks, and exclusive-ORs. And dotnet/runtime#128365 from @jamesburton adds AvxVnni.V512 , extending the AVX-VNNI APIs from 256-bit to 512-bit operands so the small-integer dot products used by quantized machine-learning models can process 64 bytes per operation instead of 32.

dotnet/runtime#126062 from @saucecontrol also avoids converting a vector selector into an AVX-512 mask register when the eventual operation still needs the vector form. In such cases, the older-looking vector blend is actually shorter and uses fewer resources:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Vector128<float> _v1 = Vector128.Create(-1.0f, 2.0f, -3.0f, 4.0f);
    private readonly Vector128<float> _v2 = Vector128.Create(10.0f);

    [GlobalSetup]
    public void Setup()
    {
        if (!Sse41.IsSupported)
            throw new PlatformNotSupportedException();
    }

    [Benchmark]
    public Vector128<float> AddToNegative() =>
        Sse41.BlendVariable(_v1, _v1 + _v2, _v1);
}

In .NET 11, you get the simpler vblendvps form that avoids an unnecessary k-register operation.

; x64
  vmovups   xmm0, [rcx+8]
- vpmovd2m  k1, xmm0
- vaddps    xmm0 {k1}, xmm0, [rcx+18]
+ vaddps    xmm1, xmm0, [rcx+18]
+ vblendvps xmm0, xmm0, xmm1, xmm0
  vmovups   [rdx], xmm0

; 29 bytes → 24 bytes

The masked EVEX form looks more modern, but when the mask originates from a vector anyway, the vector-blend sequence is five bytes shorter and avoids writing a mask register. There are only 8 k-registers, and some microarchitectures have port contention for instructions that write them.

A compiler’s cost model assigns estimates to operations and instructions, such as their execution cost or throughput and their impact on code size, and uses those estimates to choose between otherwise legal transformations or instruction sequences. Wrong estimates can still produce semantically correct code, just slower or larger code. With dotnet/runtime#127048 , which updates the JIT’s xarch floating-point and SIMD cost model, the JIT’s cost model reflects modern instruction throughput and encoded size, replacing old x87 assumptions and a flat cost for every intrinsic. That leads to better decisions about common-subexpression elimination and loop unrolling, particularly for 512-bit operations.

dotnet/runtime#130422 folds a vector lane extraction followed by WithElement into one insertps that reads the source lane directly. Code such as destination.WithElement(0, source.GetElement(2)) conceptually extracts a scalar and then inserts it elsewhere. insertps , however, has an immediate operand whose bits select both the source lane and destination lane. The JIT can therefore pass the original source vector to the instruction and encode lane 2 in that immediate, instead of first shuffling lane 2 into the scalar position and then inserting it.

Three more xarch changes tighten public SIMD operations on the hardware where they apply. dotnet/runtime#125666 from @alexcovington replaces the dedicated AVX dot-product instruction with a multiply, add, and permute reduction that has better throughput on contemporary cores:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Numerics;
using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Plane _plane = new(new Vector3(1.0f, 2.0f, 3.0f), 4.0f);
    private readonly Vector4 _vector4 = new(5.0f, 6.0f, 7.0f, 8.0f);
    private readonly Quaternion _quaternion1 = new(1.0f, 2.0f, 3.0f, 4.0f);
    private readonly Quaternion _quaternion2 = new(5.0f, 6.0f, 7.0f, 8.0f);
    private readonly Vector128<float> _vector1 = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
    private readonly Vector128<float> _vector2 = Vector128.Create(5.0f, 6.0f, 7.0f, 8.0f);

    [Benchmark]
    public float PlaneDot() => Plane.Dot(_plane, _vector4);

    [Benchmark]
    public float QuaternionDot() => Quaternion.Dot(_quaternion1, _quaternion2);

    [Benchmark]
    public float Vector128Dot() => Vector128.Dot(_vector1, _vector2);
}
Method Runtime Mean Ratio Code Size
PlaneDot .NET 10.0 2.616 ns 1.00 13 B
PlaneDot .NET 11.0 1.365 ns 0.52 31 B
QuaternionDot .NET 10.0 2.640 ns 1.00 13 B
QuaternionDot .NET 11.0 1.326 ns 0.50 31 B
Vector128Dot .NET 10.0 2.597 ns 1.00 13 B
Vector128Dot .NET 11.0 1.366 ns 0.53 31 B

Multiplying vectors of bytes is more involved than multiplying vectors of larger integer types because x86 doesn’t provide a packed byte-multiply instruction. The implementation needs to combine wider 16-bit multiplications while retaining only the low byte of each product. When it couldn’t widen the whole operation to the next vector size, .NET 10 split the input into two halves, widened and multiplied each half, narrowed both results, and joined them again. dotnet/runtime#126348 from @saucecontrol instead separates the even and odd bytes with masks and shifts, performs two 16-bit multiplications over the full vector width, and recombines the low bytes:

// Run on x64 with AVX-512:
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Vector512<byte> _left = Vector512.Create((byte)17);
    private readonly Vector512<byte> _right = Vector512.Create((byte)19);

    [Benchmark]
    public Vector512<byte> Multiply() => _left * _right;
}
Method Runtime Mean Ratio Code Size
Multiply .NET 10.0 3.752 ns 1.00 114 B
Multiply .NET 11.0 2.174 ns 0.58 73 B

The .NET 11 sequence no longer extracts, widens, narrows, and reinserts both 256-bit halves:

; x64
 vmovups     zmm0, [rcx+8]
-vmovaps     zmm1, zmm0
-vpmovzxbw   zmm1, ymm1
-vmovups     zmm2, [rcx+48]
-vmovaps     zmm3, zmm2
-vpmovzxbw   zmm3, ymm3
-vpmullw     zmm1, zmm3, zmm1
-vpmovwb     ymm1, zmm1
-vextracti32x8 ymm0, zmm0, 1
-vpmovzxbw   zmm0, ymm0
-vextracti32x8 ymm2, zmm2, 1
-vpmovzxbw   zmm2, ymm2
-vpmullw     zmm0, zmm2, zmm0
-vpmovwb     ymm0, zmm0
-vinserti32x8 zmm0, zmm1, ymm0, 1
+vmovups     zmm1, [rcx+48]
+vpmullw     zmm2, zmm0, zmm1
+vpsrlw      zmm0, zmm0, 8
+vpandd      zmm1, zmm1, dword bcst [RWD00]
+vpmullw     zmm0, zmm1, zmm0
+vpternlogd  zmm0, zmm2, dword bcst [RWD04], 0F8
 vmovups     [rdx], zmm0

; 114 bytes → 73 bytes

dotnet/runtime#127094 lets scalar conversions between Half and float use F16C’s vcvtps2ph and vcvtph2ps instructions when AVX2 is enabled:

// Run on x64 with AVX2 enabled:
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private Half _half = (Half)123.5f;
    private float _single = 123.5f;

    [Benchmark] public float HalfToSingle() => (float)_half;
    [Benchmark] public Half SingleToHalf() => (Half)_single;
}
Method Runtime Mean Ratio Code Size
HalfToSingle .NET 10.0 2.506 ns 1.00 104 B
HalfToSingle .NET 11.0 1.380 ns 0.55 14 B
SingleToHalf .NET 10.0 2.598 ns 1.00 134 B
SingleToHalf .NET 11.0 1.351 ns 0.52 19 B

Finally, dotnet/runtime#127536 from @Ruihan-Yin completes support for APX, Intel’s Advanced Performance Extensions. In addition to expanding the general-purpose register set, APX adds forms of many instructions that don’t overwrite the processor’s condition flags. That gives the register allocator and instruction scheduler more freedom to keep values and pending conditions alive at the same time. Its CTEST and CFCMOV instructions can also represent chained conditions without branches and replace some compare-with-zero forms with shorter encodings. Applications don’t need to call APX-specific APIs to benefit; when the hardware and operating system expose APX, the JIT is able to utilize the additional instructions automatically.

On Arm64, the work in .NET 11 spans both conventional code generation and continued support for SVE (Scalable Vector Extension). Unlike 128-bit AdvSimd vectors, an SVE vector doesn’t have one width fixed by the instruction set; each processor chooses a supported width, and the same compiled loop uses predicate masks to operate on however many elements fit. That makes SVE well suited to loops whose trip counts are not exact multiples of a particular vector size.

dotnet/runtime#121986 improves zeroing for larger stack allocations on Arm64. The JIT can store two zeroed 128-bit vector registers at a time, doubling the amount cleared by each instruction:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics.Arm;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Benchmark] public void Stackalloc512() => Consume(stackalloc byte[512]);
    [Benchmark] public void Stackalloc1024() => Consume(stackalloc byte[1024]);
    [Benchmark] public void Stackalloc16384() => Consume(stackalloc byte[16384]);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Consume(Span<byte> x) { }
}
Method Runtime Mean Ratio
Stackalloc512 .NET 10.0 13.65 ns 1.00
Stackalloc512 .NET 11.0 9.557 ns 0.70
Stackalloc1024 .NET 10.0 25.35 ns 1.00
Stackalloc1024 .NET 11.0 14.332 ns 0.57
Stackalloc16384 .NET 10.0 312.97 ns 1.00
Stackalloc16384 .NET 11.0 162.656 ns 0.52

A wave of smaller Arm64 changes improves instruction selection. In .NET 11, dotnet/runtime#119758 from @jonathandavies-arm lets a comparison with zero consume condition flags set as a side effect of the preceding arithmetic or logical instruction, avoiding a separate cmp . dotnet/runtime#123138 from @jonathandavies-arm recognizes bit-extraction idioms such as (value >> 6) & 0x3F and maps them to the dedicated ubfx instruction. And dotnet/runtime#123546 from @jonathandavies-arm removes a non-overflowing int -to- long widening cast when the result is immediately truncated to a smaller integer type.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD", "left", "right", "value")]
public class Benchmarks
{
    [Benchmark]
    [Arguments(-1, 2)]
    public bool CompareWithZero(int left, int right) => (left & right) <= 0;

    [Benchmark]
    [Arguments(0x7F65_4321)]
    public int ExtractBits(int value) => (value >> 6) & 0x3F;

    [Benchmark]
    [Arguments(0x1122_3344)]
    public sbyte TruncateAfterWidening(int value) => (sbyte)(long)value;
}

Each example removes one instruction. CompareWithZero changes and to its flag-setting ands form and drops the subsequent cmp ; ExtractBits replaces a shift and mask with ubfx ; and TruncateAfterWidening drops the sxtw that widened the value to 64 bits only for sxtb to immediately truncate it again:

; Arm64
; CompareWithZero: 28 bytes → 24 bytes
-            and     w0, w1, w2
-            cmp     w0, #0
+            ands    w0, w1, w2
             cset    x0, le

; ExtractBits: 24 bytes → 20 bytes
-            asr     w0, w1, #6
-            and     w0, w0, #63
+            ubfx    w0, w1, #6, #6

; TruncateAfterWidening: 24 bytes → 20 bytes
-            sxtw    x0, w1
-            sxtb    w0, w0
+            sxtb    w0, w1

Instruction selection also improves where values move between registers and memory. In .NET 11, dotnet/runtime#126803 changes ToScalar on a vector of 64-bit integers to use fmov Xd, Dn rather than the lane-extract instruction umov ; in both cases lane zero moves to a general-purpose register, but fmov is the more direct form. For ReadyToRun code, dotnet/runtime#129589 folds relocatable indirection-cell loads from adrp + add + ldr into adrp + ldr #:lo12: , removing the separate address addition. And dotnet/runtime#129932 re-enables ldp / stp formation for negative unscaled offsets, letting two adjacent loads or stores become one paired instruction.

The first and third changes are easy to see with small methods:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private Vector128<long> _vector = Vector128.Create(42L, 84L);
    private nint[] _storage = new nint[8];

    [Benchmark]
    public long ToScalar() => ToScalarCore(_vector);

    [Benchmark]
    public void ClearPrevious() => ClearPreviousCore(ref _storage[4]);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static long ToScalarCore(Vector128<long> value) => value.ToScalar();

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void ClearPreviousCore(ref nint value)
    {
        Unsafe.Add(ref value, -1) = 0;
        Unsafe.Add(ref value, -2) = 0;
        Unsafe.Add(ref value, -3) = 0;
        Unsafe.Add(ref value, -4) = 0;
    }
}

The ToScalar change is a direct instruction substitution, while the negative-offset stores collapse from four instructions to two, reducing the helper from 32 bytes to 24 bytes:

; Arm64
; ToScalarCore
-            umov    x0, v0.d[0]
+            fmov    x0, d0

; ClearPreviousCore
-            str     xzr, [x0, #-0x08]
-            str     xzr, [x0, #-0x10]
-            str     xzr, [x0, #-0x18]
-            str     xzr, [x0, #-0x20]
+            stp     xzr, xzr, [x0, #-0x10]
+            stp     xzr, xzr, [x0, #-0x20]

Bit-counting operations benefit as well. PopCount counts the one bits in a value, while TrailingZeroCount counts the zero bits below its least-significant one bit. dotnet/runtime#128677 imports both as dedicated Arm64 intrinsics, making their intent visible to later optimization. On processors with the FEAT_CSSC extension, dotnet/runtime#130332 can then lower them directly to the scalar cnt and ctz instructions.

Comparison masks are another place where spelling out the intent enables much better code. Portable SIMD code often compares vectors, calls ExtractMostSignificantBits , and then asks whether any lane matched, counts matching lanes, or finds the first or last match. dotnet/runtime#129688 from @jonathandavies-arm recognizes those consumers on Arm64 and avoids materializing the full scalar mask: it can horizontally reduce the vector mask directly.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private Vector128<int> _value = Vector128.Create(1, -2, 3, -4);

    [Benchmark]
    public bool AnyLessThan() => AnyLessThanCore(_value, 0);

    [Benchmark]
    public int CountLessThan() => CountLessThanCore(_value, 0);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool AnyLessThanCore(Vector128<int> value, int limit) =>
        Vector128.LessThan(value, Vector128.Create(limit))
            .ExtractMostSignificantBits() != 0;

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int CountLessThanCore(Vector128<int> value, int limit) =>
        BitOperations.PopCount(
            Vector128.LessThan(value, Vector128.Create(limit))
                .ExtractMostSignificantBits());
}

In .NET 10, both helpers first pack the most-significant bit from every comparison lane into a scalar. .NET 11 instead keeps the comparison as a vector.

; Arm64
; AnyLessThanCore
             cmgt    v16.4s, v16.4s, v0.4s
-            movi    v17.4s, #0x80, LSL #24
-            and     v16.4s, v16.4s, v17.4s
-            ldr     q17, [@RWD00]
-            ushl    v16.4s, v16.4s, v17.4s
-            addv    s16, v16.4s
-            smov    x0, v16.s[0]
+            umaxv   s16, v16.4s
+            umov    w0, v16.s[0]
             cmp     w0, #0
             cset    x0, ne

; CountLessThanCore
             cmgt    v16.4s, v16.4s, v0.4s
-            movi    v17.4s, #0x80, LSL #24
-            and     v16.4s, v16.4s, v17.4s
-            ldr     q17, [@RWD00]
-            ushl    v16.4s, v16.4s, v17.4s
-            addv    s16, v16.4s
-            movi    v17.2s, #0
-            smov    x0, v16.s[0]
-            ins     v17.s[0], w0
-            cnt     v16.8b, v17.8b
-            addv    b16, v16.8b
-            umov    w0, v16.b[0]
+            ushr    v16.4s, v16.4s, #31
+            addv    s16, v16.4s
+            umov    w0, v16.s[0]

On the SVE and SVE2 side, dotnet/runtime#129852 from @snickolls-arm removes the old 128-bit size ceiling for Vector<T> on Arm64 and lets the runtime size the type from the process’s actual SVE vector length. (Scalable Vector<T> remains experimental and disabled by default in .NET 11, so this expands what the experimental mode can do; it doesn’t speed up the default Vector<T> configuration.)

The public intrinsic surface also grows. In .NET 11, dotnet/runtime#118957 from @SwapnilGaikwad exposes odd-lane floating-point conversions; “odd lane” here means converting elements 1, 3, 5, and so on, which is useful when widening or narrowing interleaved data. dotnet/runtime#123890 from @ylpoonlg and dotnet/runtime#123892 from @ylpoonlg add non-temporal gather loads and scatter stores, which read from or write to multiple non-contiguous addresses (the “gather” part) while hinting that the data need not remain in cache (the “non-temporal” part).

Other changes improve the predicates that make scalable loops work. dotnet/runtime#127538 adds hardware-generated predicate masks for more loop and memory-access patterns, while dotnet/runtime#126398 from @ylpoonlg reduces setup moves for masked operations. And dotnet/runtime#128326 from @snickolls-arm improves how SVE masks flow through the JIT, allowing zeroing forms of instructions to replace separate constant setup. dotnet/runtime#127520 from @a74nh enables scalable vector and mask constants, and dotnet/runtime#128148 from @snickolls-arm uses vector stores to initialize scalable vector locals, replacing scalar loops.

Register Allocation

Generated code constantly moves values between the CPU’s limited set of fast registers and temporary stack slots. Register allocation in a compiler decides which values stay in registers and which are “spilled” to the stack; avoiding one spill can remove both the store and the later reload.

Some small structs are passed with multiple fields packed into one register. In .NET 11, dotnet/runtime#112740 lets the JIT extract those fields directly, avoiding a “spill” to a temporary stack slot followed by a reload of each field:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Drawing;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Memory<int>[] _memories = CreateMemories();

    private static Memory<int>[] CreateMemories()
    {
        Random rng = new(42);
        var memories = new Memory<int>[4096];
        for (int i = 0; i < memories.Length; i++)
            memories[i] = new int[rng.Next(0, 20)];

        return memories;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool Test(Memory<int> mem) => mem.Length > 10;

    [Benchmark]
    public int MemoryLengthExtract_Loop()
    {
        int count = 0;
        for (int i = 0; i < _memories.Length; i++)
            if (Test(_memories[i]))
                count++;

        return count;
    }
}

The measured row uses Memory<int> because its length arrives packed into part of an argument register on Arm64. The new extraction avoids a stack round-trip on every call.

Method Runtime Mean Ratio
MemoryLengthExtract_Loop .NET 10.0 27.15 μs 1.00
MemoryLengthExtract_Loop .NET 11.0 23.95 μs 0.88

Two broader register-allocation changes reduce unnecessary copies and spills: dotnet/runtime#125214 handles more conflicts directly, while dotnet/runtime#125219 steers short-lived values away from registers an upcoming operation will overwrite. dotnet/runtime#126552 from @SingleAccretion removes an old restriction on method prologs, eliminating jumps that existed only to satisfy that encoding rule.

Write Barriers and Garbage Collection

The .NET garbage collector is generational: new objects start in gen0, while objects that survive collections are promoted to gen1 and gen2. That enables the GC to collect younger generations without having to scan the whole heap. Of course, a reference to a younger object could get written to a field of an older one, in which case only scanning the younger generation would lead to problems. To ensure such references aren’t missed, whenever a write could create one, the JIT emits a small piece of code to update the GC’s bookkeeping; that code is known as a GC write barrier. Reference writes happen a lot, so it’s really important for performance that those barriers be as cheap as possible, and elided if they’re provably not needed at all.

Managed reference stores may require both an array covariance check and a GC write barrier. Arrays in .NET are covariant, meaning a TDerived[] can be used as a TBase[] , e.g. a string[] can be used as an object[] ; consequently, storing an instance into an object[] must validate that the instance is actually of the right type (otherwise, you could have a TDerived1[] masquerading as a TBase[] and try to store a TDerived2 into it, which would cause badness if it were to store successfully). dotnet/runtime#126547 expands calls to the runtime’s array-store helper into the individual operations it performs, exposing both the covariance check and write barrier to the JIT. When the JIT knows the array’s exact type, it can then eliminate the covariance check and optimize the barrier:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly object[] _array = new object[4096];
    private object _value = new();

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void StoreAll(object[] arr, object value)
    {
        for (int i = 0; i < arr.Length; i++)
            arr[i] = value;
    }

    [Benchmark]
    public object[] CovariantStore_Loop()
    {
        StoreAll(_array, _value);
        return _array;
    }
}
Method Runtime Mean Ratio
CovariantStore_Loop .NET 10.0 10.85 μs 1.00
CovariantStore_Loop .NET 11.0 6.042 μs 0.56

Sometimes writes are done one at a time, but sometimes they can be batched, as happens when copying structs. dotnet/runtime#128238 extends the JIT’s heap-destination analysis from individual stores to whole-struct copies. dotnet/runtime#128542 then replaces a specialized helper that copied one reference field at a time with reference stores and vector stores for the non-reference data. Together, they let the JIT choose more efficient write barriers and copy the rest of a mixed struct with SIMD.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [InlineArray(4)]
    public struct InlineArray4Long
    {
        private long _element0;
    }

    public struct MyStruct
    {
        public string A;
        public InlineArray4Long G;
        public string B;
    }

    private MyStruct _src;
    private MyStruct _dst;

    [GlobalSetup]
    public void Setup()
    {
        _src = new MyStruct { A = "hello", B = "world" };
        _src.G[0] = 1;
        _src.G[1] = 2;
        _src.G[2] = 3;
        _src.G[3] = 4;
    }

    [Benchmark]
    public void HeapStructCopy() => _dst = _src;
}
Method Runtime Mean Ratio
HeapStructCopy .NET 10.0 4.132 ns 1.00
HeapStructCopy .NET 11.0 3.071 ns 0.74

dotnet/runtime#130535 handles the equivalent case for small structs that don’t contain object references. Once the JIT has turned the copy into several writes to adjacent fields, it can combine them into fewer, wider writes.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private Int128 _value;

    [Benchmark]
    public void StoreInt128() => _value = 123456789;
}

.NET 10 stores the low and high halves separately. .NET 11 loads the value into a vector register and writes all 16 bytes at once.

; x64
; StoreInt128
-       mov      qword ptr [rcx+8], 75BCD15
-       xor      eax, eax
-       mov      [rcx+10], rax
+       vmovss   xmm0, dword ptr [RWD00]
+       vmovups  [rcx+8], xmm0

-; Total bytes of code 15
+; Total bytes of code 14

The same idea applies when the source code assigns neighboring fields individually. dotnet/runtime#126562 enables this for promoted struct locals, while dotnet/runtime#130107 extends it to adjacent fields at constant static addresses:

// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static Point s_point;

    [Benchmark]
    public void SetPoint() => Set();

    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
    private static void Set()
    {
        s_point.X = 1;
        s_point.Y = 2;
    }

    private struct Point
    {
        public int X;
        public int Y;
    }
}

The referenced .NET 11 x64 build combines the two 32-bit constants and writes both fields with one 64-bit store:

; x64
mov     rax, 200000001
mov     rcx, <address of s_point>
mov     [rcx], rax

dotnet/runtime#127487 applies a related improvement when stack protection requires a struct parameter to be copied. It uses consistently sized writes so a subsequent wider read doesn’t need to wait for the processor to reconcile overlapping stores.

Write barriers are only one part of the interaction between generated code and the garbage collector. During a compacting collection, the GC needs to plan where surviving objects will move and then update references to them. To do that efficiently, it records their addresses, sorts those addresses, and groups adjacent survivors into regions called “plugs.” With enough live objects, sorting these mark lists becomes a meaningful part of the collection. Recent x86/x64 runtimes use a vectorized vxsort implementation for sufficiently large lists. In .NET 11, dotnet/runtime#110692 from @a74nh extends that support to Arm64.

The generation assigned to GC metadata matters just as much as the speed of one collection. .NET’s generational GC is based on the observation that most objects die young: generation 0 and generation 1 collections, collectively called ephemeral collections, run frequently and should avoid revisiting state that has already survived into generation 2. A dependent handle associates a primary object with a secondary object, keeping the secondary alive while the primary remains reachable; ConditionalWeakTable<TKey, TValue> is built on this mechanism. Previously, the handle itself didn’t age with its referents, so every ephemeral collection continued scanning it even after both objects had become long-lived. dotnet/runtime#78746 ages dependent handles accordingly and moves a handle back to a younger generation when necessary. Old handles can therefore be skipped by young collections without compromising reachability.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private ConditionalWeakTable<object, object> _table = new();
    private object[] _keys = [];

    [Params(100_000, 1_000_000)]
    public int Handles { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        _table = new();
        _keys = new object[Handles];

        for (int i = 0; i < _keys.Length; i++)
        {
            object key = new();
            _keys[i] = key;
            _table.Add(key, new object());
        }

        GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true);
    }

    [Benchmark]
    public void CollectGen0() =>
        GC.Collect(0, GCCollectionMode.Forced, blocking: true, compacting: false);
}
Method Runtime Handles Mean Ratio
CollectGen0 .NET 10.0 100000 1.522 ms 1.00
CollectGen0 .NET 11.0 100000 255.5 μs 0.17
CollectGen0 .NET 10.0 1000000 10.737 ms 1.00
CollectGen0 .NET 11.0 1000000 310.7 μs 0.029

Runtime Knowledge and Frozen Data

The JIT can optimize only the facts it knows. Some facts come from its own analysis; others are contracts supplied by the runtime, such as which helpers have side effects, the length of a newly allocated string, or whether a data object will ever move.

A generic virtual call such as baseReference.Foo<string>() may need help from the runtime to find the implementation for both the object’s actual type and the generic argument. If that lookup appears to have arbitrary side effects, the JIT has to perform it exactly where it occurs, rather than possibly resulting on a cached answer from a previous lookup. In .NET 11, dotnet/runtime#122017 teaches the JIT more precisely which exceptions these runtime helpers can throw and whether they otherwise have side effects. The JIT can then share repeated lookups or move an unchanging lookup out of a loop:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    public abstract class Base
    {
        public abstract void Foo<T>();
    }

    public class Derived : Base
    {
        public override void Foo<T>() { }
    }

    private Base _b = new Derived();

    [Benchmark]
    public void GvmCseHoist()
    {
        Base b = _b;
        b.Foo<string>();
        b.Foo<int>();
        b.Foo<string>();
        b.Foo<int>();

        for (int i = 0; i < 10; i++)
            b.Foo<double>();
    }
}

In .NET 11, the repeated lookups outside the loop are shared and the loop’s lookup is performed once, not ten times.

Method Runtime Mean Ratio
GvmCseHoist .NET 10.0 45.75 ns 1.00
GvmCseHoist .NET 11.0 24.06 ns 0.53

Profile data is another way the JIT learns what matters. Inlining could previously hide important work from the instrumentation used to gather that data. dotnet/runtime#119658 allows the inlined code to be instrumented as well, giving later PGO-driven compilation a more complete picture of the hot paths.

JIT Throughput and Cleanup

The quality of the generated code isn’t the only concern; the time spent producing it matters too. Every analysis the JIT performs has a cost. dotnet/runtime#123856 removes checks and maps from Global Assertion Propagation whose bookkeeping wasn’t paying for itself. This is the recurring balancing act in the development of the JIT: retaining the information that enables meaningful optimizations while avoiding analysis overhead whose code-quality benefit is negligible.

dotnet/runtime#127363 makes profile-guided optimization more resilient with OSR (on-stack replacement), which replaces a method while one of its loops is already running. Because that execution begins in the middle of the method rather than at its normal entry, reconstructed profile data doesn’t always line up perfectly with the paths actually available. The JIT now estimates the likelihood of those paths rather than asserting or abandoning the profile.

Optimizations can leave behind code that’s no longer reachable, so the JIT also needs to be good at dead code removal. dotnet/runtime#126223 runs another sweep whenever the method’s branching structure changes, catching blocks made obsolete by earlier transformations.

And dotnet/runtime#128515 from @BoyBaykiller repeatedly combines equivalent return and throw endings, removing duplicate exit paths and sometimes exposing more code that can be shared.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Benchmark]
    [Arguments((byte)9)]
    public bool IsLinearWhiteSpace(byte value) =>
        value <= 32 &&
        (value == 32 || value == 10 || value == 13 || value == 9);
}

In .NET 10, tail merging combines the paths that return false , but not both paths that return true . As a result, the JIT’s bit test covers three of the four values, with a separate comparison for 9 . In .NET 11, the true returns are merged as well, enabling all four values to be handled by the same bit test:

; x64
-       movzx    ecx, dl
-       cmp      ecx, 20
-       jg       M00_L02
-       cmp      ecx, 20
-       ja       M00_L01
-       mov      eax, 0FFFFDBFF
-       bt       rax, rcx
-       jae      M00_L00
-       mov      eax, 1
-       ret
-M00_L00:
-       cmp      ecx, 9
-       sete     al
-       movzx    eax, al
-       ret
-M00_L01:
+       movzx    eax, dl
+       cmp      eax, 20
+       jg       M00_L00
+       cmp      eax, 20
+       ja       M00_L00
+       mov      ecx, 0FFFFD9FF
+       bt       rcx, rax
+       jb       M00_L00
+       mov      eax, 1
+       ret
+M00_L00:
        xor      eax, eax
        ret

-; Total bytes of code 43
+; Total bytes of code 33

Also related to dead code, a call that never returns, such as one that always throws, makes everything after it unreachable. In .NET 11, after inlining, dotnet/runtime#128513 removes the remaining statements and outgoing paths from such a block and marks it as ending in a throw, exposing the dead code early enough for the cleanup passes above to remove it.

Startup and Deployment

Before managed Main can run, the native host needs to locate the application’s dependencies, CoreCLR needs to load enough types and code to begin execution, and various pieces of framework infrastructure need to initialize themselves. Work removed from any of those stages helps the application get going sooner, improving startup time.

The host starts by reading the application’s .deps.json , turning its entries into paths, and building the trusted platform assembly (TPA) list. That list tells CoreCLR which framework and application assemblies it can resolve by simple name. Several costs in this process scaled with the number of assets rather than with the amount of useful work. dotnet/runtime#123568 in .NET 11 avoids checking every asset against a servicing directory unless the resolver is actually probing that directory. dotnet/runtime#123919 avoids repeatedly comparing the servicing-directory name and copying every dependency asset while constructing the TPA list, avoiding a lot of allocation. dotnet/runtime#125251 removes more allocation by normalizing each asset’s directory separators once when parsing the .deps.json , rather than normalizing the path again every time it is used.

Once the host hands off to CoreCLR, ReadyToRun (R2R) code helps avoid compiling methods before they can execute. However, initializing Comparer<T>.Default and EqualityComparer<T>.Default called a reflection-based helper whose resulting concrete comparer type wasn’t known when the R2R image was built. The comparer constructor and operations could consequently fall back to being interpreted. In .NET 11, dotnet/runtime#126204 uses specialized helpers that R2R can compile ahead of time and ensures the required comparer types are included in the image.

Even better than making initialization faster is avoiding it altogether. An EventSource normally discovers its event metadata and computes its provider GUID when it is initialized. dotnet/runtime#121180 adds an internal source generator that performs this work when the framework is built and emits the result for its EventSource implementations, including the ones for core runtime tracing. Applications then don’t need to pay the reflection and setup costs when those event sources are first used.

Startup also has a memory footprint outside the managed heap. Native AOT’s AllocHeap typically holds only small amounts of runtime metadata. On Windows, however, its virtual-memory allocator reserved a 64 KB region for each block even when it initially needed only 4 KB. In .NET 11, dotnet/runtime#122822 instead uses ordinary new and delete for these small blocks, matching the allocation strategy to the amount of memory normally involved.

Note that the aforementioned R2R work wasn’t motivated only by desktop and server startup. It was also part of the substantial effort to make CoreCLR the runtime for .NET on mobile. Starting with .NET 11, .NET MAUI moved to CoreCLR for Android, iOS, and Mac Catalyst, the last .NET MAUI platforms that had still been using Mono. This is much more than swapping one execution engine for another. Those apps now use the same runtime as ASP.NET Core, cloud services, and desktop .NET, with the same JIT, garbage collector, diagnostics infrastructure, performance improvements, and bug fixes. It also brings CoreCLR’s tiered compilation, ReadyToRun, and profile-guided optimization to mobile, while providing a common foundation for NativeAOT. That combination is important: R2R and packaged profiles can precompile the code most important to startup, while the optimizing JIT can produce higher-quality code for hot methods on platforms where dynamic compilation is available. Improvements like the comparer specialization mentioned earlier keep more code on the compiled path instead of falling back to interpretation.

Threading

Threading is a cross-cutting concern that impacts almost every application and service. Whether code is protecting shared state, queueing work, or coordinating asynchronous operations, small costs in the underlying machinery can quickly add up. As such, it’s something that’s revisited in every release of .NET.

Monitor is the synchronization primitive historically used to implement lock , providing the most pervasively used support for mutual exclusion. It also supports sending signals, such that one thread can wait on a Monitor with Monitor.Wait for another thread to Pulse it. The internal object that tracks these waiters is a “condition variable.” dotnet/runtime#129083 stores that condition directly on the lock, removing a separate ConditionalWeakTable lookup from this already synchronization-heavy path.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int RoundTripsPerInvoke = 2_000;

    private readonly object _gate = new();
    private int _ping;
    private int _pong;
    private bool _stop;
    private Thread _responder = null!;

    [GlobalSetup]
    public void Setup()
    {
        _responder = new Thread(ResponderLoop) { IsBackground = true };
        _responder.Start();
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        lock (_gate)
        {
            _stop = true;
            Monitor.PulseAll(_gate);
        }

        _responder.Join();
    }

    private void ResponderLoop()
    {
        lock (_gate)
        {
            int seen = 0;
            while (true)
            {
                while (_ping == seen && !_stop)
                    Monitor.Wait(_gate);

                if (_stop)
                    return;

                seen = _ping;
                _pong = seen;
                Monitor.PulseAll(_gate);
            }
        }
    }

    [Benchmark(OperationsPerInvoke = RoundTripsPerInvoke)]
    public int PingPong_MonitorWaitPulse()
    {
        lock (_gate)
        {
            for (int i = 0; i < RoundTripsPerInvoke; i++)
            {
                _ping++;
                int expected = _ping;
                Monitor.PulseAll(_gate);
                while (_pong != expected)
                    Monitor.Wait(_gate);
            }

            return _pong;
        }
    }
}
Method Runtime Mean Ratio
PingPong_MonitorWaitPulse .NET 10.0 4.194 μs 1.00
PingPong_MonitorWaitPulse .NET 11.0 3.517 μs 0.84

In the case of Monitor , that improvement targeted the specific shared implementation. In other cases, the costs are spread out in a more peanut butter manner across lots of code. dotnet/runtime#125274 removes some of that peanut butter by removing unnecessary volatile annotations from a wide range of library fields whose correctness already comes from locks, Interlocked , or one-time initialization. On x86/x64 hardware, which already provides a strong memory model, those annotations generally don’t result in extra instructions, though they can still constrain compiler optimizations. Arm, however, permits more reordering, so the JIT often needs to emit memory fences to provide volatile ‘s guarantees. Removing the annotations where they’re redundant therefore can end up removing unnecessary fences from Arm’s generated code.

Similar considerations apply to code in the runtime. dotnet/runtime#125259 replaces full memory barriers in the runtime’s HashMap with the narrower acquire and release operations actually required. On top of that, many VM hash tables, including its EEHashTable , are read constantly but updated only occasionally. dotnet/runtime#124822 adds epoch-based reclamation, enabling readers to avoid entering cooperative GC mode simply to keep an old set of buckets alive. And dotnet/runtime#129640 replaces the previous byte-at-a-time hash used by these tables with an xxHash implementation that consumes four bytes at a time.

Along the same lines, in .NET 11 dotnet/runtime#122726 reduces the scheduling overhead around small thread-pool work items. It removes unnecessary memory fences and shared-state updates, checks in with the thread-pool controller once per batch rather than once per item, spends less time spinning on a semaphore, and requests another worker only when the queued work shows one is needed. The result is less coordination overhead and fewer workers woken just as the queue becomes empty.

Earlier in this post, we talked about runtime async, which can have a significant impact on the performance of async / await code, how they produce Task s, and so on. They’re not the only improvements in .NET 11 related to Task s, though.

One fun one is a new analyzer, CA2027, introduced in dotnet/sdk#51452 . With that, the SDK can point out problematic usage of Task.Delay that I’ve seen on multiple occasions to lead to non-trivial performance issues in large scale services. Consider this code:

Task someTask = ...;
if (await Task.WhenAny(someTask, Task.Delay(timeout)) != someTask) // oops!
{
    throw new TimeoutException();
}

The developer that wrote this is obviously trying to implement a timeout. The problem, however, is that this leaks. In the hopefully common case where someTask completes really quickly, the Task.Delay will still be pending. That Delay has associated with it a System.Threading.Timer that’s consuming valuable resources, as well as other data in memory, and if this timeout is long and this code is on a hotter path, we could accumulate thousands upon thousands of those timers. That in turn can increase memory use and slow down other calls that interact with timers.

The fix is to instead use the Task.WaitAsync method, introduced all the way back in .NET 6. It provides a much more efficient mechanism for doing this same kind of timed waiting, and it correctly handles all the relevant cleanup. CA2027 will detect common forms of this issue and recommend the replacement.

Numerics

BigInteger is one of those types that many applications may never need, but for those that do, there’s often no practical substitute. It powers workloads ranging from cryptography and number theory to compilers and applications that need to parse, format, or compute with integers larger than the fixed-width primitives can hold. Despite that need, however, BigInteger hasn’t received the same steady stream of performance investment as many of .NET’s other core types. Thankfully, in .NET 11 it gets a makeover.

dotnet/runtime#125799 rewrote significant portions of BigInteger ‘s implementation, changing its limbs (the fixed-size pieces stored in its backing array) from uint to nuint ( UIntPtr ). That makes no effective difference on a 32-bit machine. On a 64-bit machine, however, each limb grows from 32 to 64 bits; since most arithmetic on a 64-bit value on a 64-bit platform costs no more than the corresponding 32-bit operation, each step can therefore process twice as many bits in the same number of cycles. The implementation also improves the algorithms around those wider limbs, including Montgomery multiplication and sliding-window exponentiation in ModPow , fused bitwise steps, additional hardware intrinsics, loop unrolling, and caching. That all builds on top of other optimizations that were done previously in the release, such as faster conversion of huge values to decimal text in dotnet/runtime#112178 from @kzrnm , dotnet/runtime#112876 from @kzrnm using Toom-Cook multiplication for sufficiently large operands, and improved shifts and rotations thanks to dotnet/runtime#113005 from @kzrnm . Toom-Cook splits each operand into several chunks and combines smaller products, doing less work than the straightforward every-limb-by-every-limb algorithm once the operands are large enough.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Numerics;
using System.Globalization;
using System.Text;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Params(64, 512)]
    public int Limbs;

    private BigInteger _a;
    private BigInteger _b;
    private BigInteger _shiftSubject;
    private BigInteger _hugeValueForToString;
    private string _decimalDigits100000 = "";
    private byte[] _utf8Digits1000 = [];
    private byte[] _utf8FormatBuffer = new byte[120_000];

    private BigInteger _divideDividendBelowThreshold;
    private BigInteger _divideDivisorBelowThreshold;
    private BigInteger _divideDividendAboveThreshold;
    private BigInteger _divideDivisorAboveThreshold;

    [GlobalSetup]
    public void Setup()
    {
        _a = MakeDeterministicBigInteger(Limbs, seed: 1);
        _b = MakeDeterministicBigInteger(Limbs, seed: 2);
        _shiftSubject = MakeDeterministicBigInteger(Limbs, seed: 3);

        _decimalDigits100000 = MakeDeterministicDecimalDigits(100_000);
        _hugeValueForToString = BigInteger.Parse(_decimalDigits100000, CultureInfo.InvariantCulture);

        string decimalDigits1000 = MakeDeterministicDecimalDigits(1_000);
        _utf8Digits1000 = Encoding.UTF8.GetBytes(decimalDigits1000);

        _divideDivisorBelowThreshold = MakeDeterministicBigInteger(16, seed: 4);
        _divideDividendBelowThreshold = MakeDeterministicBigInteger(16 + 96, seed: 5);

        _divideDivisorAboveThreshold = MakeDeterministicBigInteger(128, seed: 6);
        _divideDividendAboveThreshold = MakeDeterministicBigInteger(128 + 96, seed: 7);
    }

    private static BigInteger MakeDeterministicBigInteger(int limbCount, int seed)
    {
        Random rng = new(seed);
        byte[] bytes = new byte[(limbCount * 4) + 1]; // trailing 0 byte keeps the value positive
        rng.NextBytes(bytes);
        bytes[^1] = 0;
        return new BigInteger(bytes);
    }

    private static string MakeDeterministicDecimalDigits(int digitCount)
    {
        StringBuilder sb = new(digitCount);
        sb.Append('9'); // avoid a leading zero, which would shorten the effective digit count
        Random rng = new(42);
        for (int i = 1; i < digitCount; i++)
            sb.Append((char)('0' + rng.Next(0, 10)));

        return sb.ToString();
    }

    [Benchmark]
    public BigInteger Divide_BelowBurnikelZieglerThreshold() => _divideDividendBelowThreshold / _divideDivisorBelowThreshold;

    [Benchmark]
    public BigInteger Divide_AboveBurnikelZieglerThreshold() => _divideDividendAboveThreshold / _divideDivisorAboveThreshold;

    [Benchmark]
    public BigInteger Multiply() => _a * _b;

    [Benchmark]
    public BigInteger ShiftLeft() => _shiftSubject << 12345;

    [Benchmark]
    public BigInteger ParseLargeDecimal() => BigInteger.Parse(_decimalDigits100000, CultureInfo.InvariantCulture);

    [Benchmark]
    public string ToStringLargeDecimal() => _hugeValueForToString.ToString(CultureInfo.InvariantCulture);
}
Method Runtime Limbs Mean Ratio
Divide_BelowBurnikelZieglerThreshold .NET 10.0 64 2,954.3 ns 1.00
Divide_BelowBurnikelZieglerThreshold .NET 11.0 64 1,493.8 ns 0.51
Divide_AboveBurnikelZieglerThreshold .NET 10.0 64 10,766.7 ns 1.00
Divide_AboveBurnikelZieglerThreshold .NET 11.0 64 6,303.4 ns 0.59
Multiply .NET 10.0 64 2,359.5 ns 1.00
Multiply .NET 11.0 64 1,328.2 ns 0.56
ShiftLeft .NET 10.0 64 217.1 ns 1.00
ShiftLeft .NET 11.0 64 121.6 ns 0.56
ParseLargeDecimal .NET 10.0 64 9,111,926.1 ns 1.00
ParseLargeDecimal .NET 11.0 64 3,899,478.0 ns 0.43
ToStringLargeDecimal .NET 10.0 64 135,894,135.4 ns 1.00
ToStringLargeDecimal .NET 11.0 64 7,868,359.3 ns 0.058
Divide_BelowBurnikelZieglerThreshold .NET 10.0 512 2,894.6 ns 1.00
Divide_BelowBurnikelZieglerThreshold .NET 11.0 512 1,482.0 ns 0.51
Divide_AboveBurnikelZieglerThreshold .NET 10.0 512 10,751.7 ns 1.00
Divide_AboveBurnikelZieglerThreshold .NET 11.0 512 6,317.8 ns 0.59
Multiply .NET 10.0 512 68,063.6 ns 1.00
Multiply .NET 11.0 512 35,443.5 ns 0.52
ShiftLeft .NET 10.0 512 673.0 ns 1.00
ShiftLeft .NET 11.0 512 309.4 ns 0.46
ParseLargeDecimal .NET 10.0 512 9,149,793.0 ns 1.00
ParseLargeDecimal .NET 11.0 512 3,891,313.0 ns 0.43
ToStringLargeDecimal .NET 10.0 512 135,829,594.6 ns 1.00
ToStringLargeDecimal .NET 11.0 512 7,880,631.1 ns 0.058

In addition to internal changes, BigInteger also gained new public APIs that avoid transcoding. Protocols and storage formats increasingly expose text as UTF-8 bytes, but the previous parsing and formatting APIs required UTF-16 characters. Callers therefore had to decode the input into a temporary string before parsing, or format into characters and encode the result back to bytes. dotnet/runtime#117745 adds direct UTF-8 parsing and formatting to both BigInteger and Complex , sharing the generic numeric machinery used for UTF-16 and letting those consumers operate on their original representation.

dotnet/runtime#130721 improves a different BigInteger boundary: casting to double and float . The general conversion needs to inspect the arbitrary-width magnitude, locate its highest set bits, and perform the rounding required by the target floating-point format. But many BigInteger instances are much smaller than that machinery is designed for… the implementation now recognizes values that fit in 64 bits and routes them through the hardware’s native integer conversion support.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Numerics;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly BigInteger _small = (BigInteger.One << 63) + 123;
    private readonly BigInteger _large = (BigInteger.One << 1023) + (BigInteger.One << 511) + 123;

    [Benchmark] public double SmallToDouble() => (double)_small;
    [Benchmark] public float SmallToSingle() => (float)_small;
    [Benchmark] public double LargeToDouble() => (double)_large;
    [Benchmark] public float LargeToSingle() => (float)_large;
}
Method Runtime Mean Ratio
SmallToDouble .NET 10.0 2.873 ns 1.00
SmallToDouble .NET 11.0 1.764 ns 0.61
SmallToSingle .NET 10.0 3.548 ns 1.00
SmallToSingle .NET 11.0 1.764 ns 0.50
LargeToDouble .NET 10.0 2.863 ns 1.00
LargeToDouble .NET 11.0 2.797 ns 0.98
LargeToSingle .NET 10.0 3.559 ns 1.00
LargeToSingle .NET 11.0 2.849 ns 0.80

The same limb-widening advantages given to BigInteger in .NET 11 were also extended to the core floating-point types. Parsing a very long decimal input and formatting a floating-point value with many requested digits both need temporary arbitrary-precision arithmetic once the value no longer fits in the normal mantissa. .NET uses a separate internal Number.BigInteger for that work. dotnet/runtime#132577 applies the same native-width limb representation to that type, reducing the amount of per-limb work in floating-point parsing, formatting, and rounding.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Globalization;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string _longFraction = "0." + new string('1', 768);

    [Benchmark]
    public double ParseLongFraction() => double.Parse(_longFraction, CultureInfo.InvariantCulture);

    [Benchmark]
    public string FormatSubnormal() => double.Epsilon.ToString("G99", CultureInfo.InvariantCulture);
}
Method Runtime Mean Ratio
ParseLongFraction .NET 10.0 8.592 μs 1.00
ParseLongFraction .NET 11.0 3.569 μs 0.42
FormatSubnormal .NET 10.0 6.884 μs 1.00
FormatSubnormal .NET 11.0 1.237 μs 0.18

The .NET 11 improvements aren’t limited to the scalar representations underlying BigInteger and floating-point parsing and formatting. Other numerical types improve as well. Consider Matrix4x4 . A 4×4 matrix determinant combines products of many independent matrix elements, making it a natural fit for SIMD. dotnet/runtime#123954 from @alexcovington adds an SSE implementation of Matrix4x4.GetDeterminant , evaluating several of those products in parallel:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Numerics;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Matrix4x4 _matrix =
        Matrix4x4.CreateFromYawPitchRoll(0.4f, 0.8f, 1.1f) *
        Matrix4x4.CreateTranslation(1.5f, -2.5f, 3.25f) *
        Matrix4x4.CreateScale(1.1f, 0.9f, 1.05f);

    [Benchmark]
    public float GetDeterminant() => _matrix.GetDeterminant();
}
Method Runtime Mean Ratio
GetDeterminant .NET 10.0 3.836 ns 1.00
GetDeterminant .NET 11.0 2.645 ns 0.69

The System.Numerics.Tensors APIs are designed to perform the same numerical operation over many values, making them a natural fit for SIMD. dotnet/runtime#126052 adds vector implementations of inverse sine to the portable vector types and uses them in TensorPrimitives.Asin . The tensor loop now evaluates a polynomial approximation for several inputs together, with special handling near the ends of the function’s [-1, 1] domain, rather than calling MathF.Asin or Math.Asin separately for every element:

// Run separately so each target uses its matching System.Numerics.Tensors package:
// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Numerics.Tensors;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int Length = 4096;

    private float[] _floatsIn = new float[Length];
    private float[] _floatsOut = new float[Length];
    private double[] _doublesIn = new double[Length];
    private double[] _doublesOut = new double[Length];

    [GlobalSetup]
    public void Setup()
    {
        Random rng = new(42);
        for (int i = 0; i < Length; i++)
        {
            float v = (float)((rng.NextDouble() * 2.0) - 1.0); // Asin's domain is [-1, 1]
            _floatsIn[i] = v;
            _doublesIn[i] = v;
        }
    }

    [Benchmark]
    public float AsinFloat()
    {
        TensorPrimitives.Asin(_floatsIn, _floatsOut);
        return _floatsOut[0];
    }

    [Benchmark]
    public double AsinDouble()
    {
        TensorPrimitives.Asin(_doublesIn, _doublesOut);
        return _doublesOut[0];
    }
}
Method Runtime Mean Ratio
AsinFloat .NET 10.0 33.72 μs 1.00
AsinFloat .NET 11.0 8.240 μs 0.24
AsinDouble .NET 10.0 35.80 μs 1.00
AsinDouble .NET 11.0 10.975 μs 0.31

TensorPrimitives also picked up a few more targeted SIMD improvements. For floating-point values, BitIncrement and BitDecrement move to the immediately adjacent representable value; despite their names, they can’t simply add or subtract one, as they also need to handle signed zero, infinities, and NaNs correctly. dotnet/runtime#123610 and dotnet/runtime#123754 process multiple float / double and Half values at once, respectively. The Half path works directly with the raw ushort bit patterns, avoiding conversion to float and back, and both paths use vector masks and conditional selection rather than calling a scalar helper for every element.

// Run separately so each target uses its matching System.Numerics.Tensors package:
// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using System.Numerics.Tensors;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int Length = 4096;
    private readonly float[] _floats = new float[Length];
    private readonly float[] _floatDestination = new float[Length];
    private readonly double[] _doubles = new double[Length];
    private readonly double[] _doubleDestination = new double[Length];
    private readonly Half[] _halves = new Half[Length];
    private readonly Half[] _halfDestination = new Half[Length];

    [GlobalSetup]
    public void Setup()
    {
        for (int i = 0; i < Length; i++)
        {
            float value = (i & 7) switch
            {
                0 => 0,
                1 => -0.0f,
                2 => float.PositiveInfinity,
                3 => float.NegativeInfinity,
                4 => float.NaN,
                _ => i / 7.0f,
            };
            _floats[i] = value;
            _doubles[i] = value;
            _halves[i] = (Half)value;
        }
    }

    [Benchmark]
    public void BitIncrementFloat() => TensorPrimitives.BitIncrement(_floats, _floatDestination);

    [Benchmark]
    public void BitIncrementDouble() => TensorPrimitives.BitIncrement(_doubles, _doubleDestination);

    [Benchmark]
    public void BitIncrementHalf() => TensorPrimitives.BitIncrement(_halves, _halfDestination);
}
Method Runtime Mean Ratio
BitIncrementFloat .NET 10.0 4.223 μs 1.00
BitIncrementFloat .NET 11.0 1,071.1 ns 0.25
BitIncrementDouble .NET 10.0 4.223 μs 1.00
BitIncrementDouble .NET 11.0 2,140.6 ns 0.51
BitIncrementHalf .NET 10.0 3.918 μs 1.00
BitIncrementHalf .NET 11.0 573.6 ns 0.15

dotnet/runtime#124280 removes a more mechanical cost from TensorPrimitives.Round : for digits == 0 , the old code invoked a full-span rounding kernel and then continued through another full-span pass. Returning immediately removes that redundant traversal and overwrite of the destination.

// Run separately so each target uses its matching System.Numerics.Tensors package:
// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using System.Numerics.Tensors;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int Length = 4096;
    private readonly float[] _source = new float[Length];
    private readonly float[] _destination = new float[Length];

    [Benchmark]
    public void RoundZero() => TensorPrimitives.Round(_source, 0, MidpointRounding.ToEven, _destination);
}
Method Runtime Mean Ratio
RoundZero .NET 10.0 882.7 ns 1.00
RoundZero .NET 11.0 205.5 ns 0.23

Half comparisons are faster as well. Previously, Half.CompareTo separately asked whether one value was less than, greater than, or equal to the other, repeating the special handling required for NaN and signed zero each time. dotnet/runtime#131297 performs that work once and then arranges the underlying bits into a form that can be compared directly, while still treating +0 and -0 as equal. On x64 with AVX2, it also makes CompareTo , < , and <= faster by converting the operands to float , which the hardware can do very efficiently. Equality remains bit-based, as that’s already the cheaper approach.

// Run on x64 with AVX2:
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Half[] _left = Enumerable.Range(0, 4096).Select(i => (Half)(i - 2048)).ToArray();
    private readonly Half[] _right = Enumerable.Range(0, 4096).Select(i => (Half)(2048 - i)).ToArray();

    [Benchmark]
    public int CompareTo()
    {
        int sum = 0;
        for (int i = 0; i < _left.Length; i++)
            sum += _left[i].CompareTo(_right[i]);

        return sum;
    }

    [Benchmark]
    public int LessThan()
    {
        int count = 0;
        for (int i = 0; i < _left.Length; i++)
            count += _left[i] < _right[i] ? 1 : 0;

        return count;
    }
}
Method Runtime Mean Ratio
CompareTo .NET 10.0 9.340 μs 1.00
CompareTo .NET 11.0 5.745 μs 0.62
LessThan .NET 10.0 7.514 μs 1.00
LessThan .NET 11.0 5.672 μs 0.75

Multiplying two 64-bit integers produces a 128-bit result, and x64 has instructions that provide both 64-bit halves directly. dotnet/runtime#117261 from @Daniel-Svensson exposes those signed and unsigned forms through an X86Base.X64.BigMul intrinsic. Math.BigMul can then map directly to imul or mul and return both halves in registers, avoiding the extra instructions and register shuffling required by the previous paths.

// Run on x64:
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser, HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly long _signedLeft = 0x1234_5678_9ABC_DEF;
    private readonly long _signedRight = 0x0FED_CBA9_8765_432;
    private readonly ulong _unsignedLeft = 0xFEDC_BA98_7654_3210;
    private readonly ulong _unsignedRight = 0x1234_5678_9ABC_DEF0;

    [Benchmark]
    public long Signed()
    {
        long high = Math.BigMul(_signedLeft, _signedRight, out long low);
        return high ^ low;
    }

    [Benchmark]
    public ulong Unsigned()
    {
        ulong high = Math.BigMul(_unsignedLeft, _unsignedRight, out ulong low);
        return high ^ low;
    }
}
Method Runtime Mean Ratio Code Size
Signed .NET 10.0 2.210 ns 1.00 65 B
Signed .NET 11.0 1.344 ns 0.61 12 B
Unsigned .NET 10.0 1.446 ns 1.00 39 B
Unsigned .NET 11.0 1.323 ns 0.92 12 B

Fixed-format numeric and identifier helpers benefit from a much simpler technique: establish the exact span length once, then let the JIT reuse that fact. dotnet/runtime#119254 from @xtqqczze applies that pattern in Decimal , Guid , and IPAddress , removing repeated bounds checks.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const string Value = "a8098c1a-f86e-11da-bd1a-00112444be1e";

    [Benchmark]
    public bool TryParseExactD() => Guid.TryParseExact(Value, "D", out _);
}
Method Runtime Mean Ratio
TryParseExactD .NET 10.0 15.94 ns 1.00
TryParseExactD .NET 11.0 12.60 ns 0.79

Guid has been improving every .NET release, and sees several improvements in .NET 11. Whenever possible, .NET tries to maintain similar performance and behaviors across operating systems, but low-level functionality often simply delegates to the operating system, exposing that OS’ characteristics. When it comes to random number generation, historically cryptographically-secure random number generation, as is used in Guid.NewGuid , has been a bit slower on Linux than on Windows due to using /dev/urandom as the source of entropy. dotnet/runtime#123540 from @reedz moves Guid.NewGuid() off of that file-descriptor path to the getrandom() syscall, avoiding descriptor setup and reads through the file abstraction.

And on the subject of randomness, dotnet/runtime#119890 from @hamarb123 removes two pieces of work from Random.Shuffle : an unnecessary copy of the span length and a branch that skipped swapping an element with itself. A self-swap is harmless and uncommon, while testing for it adds an unpredictable branch to every iteration. The difference is most visible for short arrays and small value types, where the swap itself is cheap:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Params(16, 4096)]
    public int Length;

    private readonly Random _random = new(42);
    private int[] _values = [];

    [GlobalSetup]
    public void Setup() => _values = Enumerable.Range(0, Length).ToArray();

    [Benchmark]
    public int ShuffleSmallValueType()
    {
        _random.Shuffle(_values);
        return _values[0] + _values[^1];
    }
}
Method Runtime Length Mean Ratio
ShuffleSmallValueType .NET 10.0 16 138.9 ns 1.00
ShuffleSmallValueType .NET 11.0 16 89.34 ns 0.64
ShuffleSmallValueType .NET 10.0 4096 25,925.1 ns 1.00
ShuffleSmallValueType .NET 11.0 4096 14,151.08 ns 0.55

Random itself picked up a small but pointed code-generation fix. Random.InternalSample contains a condition that’s inherently hard for the processor to predict, so it’s better implemented with conditional instructions than with a branch. The JIT’s if-conversion support we previously discussed would have been able to do that transformation, except it doesn’t currently support if-conversion inside of loops, which is a pretty common place to find an inlined Random.Next call. dotnet/runtime#131714 marks the helper as [MethodImpl(MethodImplOptions.NoInlining)] to preserve the branch-free form; once the JIT can perform if-conversion inside loops, that annotation can be reconsidered.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Random _random = new(42);

    [Benchmark]
    public int Next()
    {
        int sum = 0;
        for (int i = 0; i < 1024; i++)
            sum += _random.Next();

        return sum;
    }
}
Method Runtime Mean Ratio
Next .NET 10.0 5.954 μs 1.00
Next .NET 11.0 3.275 μs 0.55

Globalization

Many globalization-related APIs sit atop data that can be expensive to locate and interpret. DateTime.Now , for example, depends on time-zone transition data, while casing and parsing depend on native globalization services and culture-specific tables.

dotnet/runtime#119662 substantially reworks TimeZoneInfo around that observation. Determining an offset isn’t always a fixed arithmetic operation: daylight-saving rules can vary by year, and historical rules can contain multiple transitions and exceptional cases. Once the transitions for a zone and year have been interpreted, however, other conversions in that year can reuse them. Similarly, the local offset used by DateTime.Now can’t change between transition instants. Conversions now reuse cached per-year transition data rather than repeatedly walking adjustment rules, while DateTime.Now caches the active UTC offset together with the instant at which it next needs to be recomputed.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly DateTime _utc = new(2026, 7, 15, 12, 0, 0, DateTimeKind.Utc);
    private readonly DateTime _local = new(2026, 7, 15, 5, 0, 0, DateTimeKind.Unspecified);
    private readonly TimeZoneInfo _zone = TimeZoneInfo.FindSystemTimeZoneById(
        OperatingSystem.IsWindows() ? "Pacific Standard Time" : "America/Los_Angeles");

    [Benchmark]
    public DateTime ConvertTimeFromUtc() => TimeZoneInfo.ConvertTimeFromUtc(_utc, _zone);

    [Benchmark]
    public DateTime ConvertTimeToUtc() => TimeZoneInfo.ConvertTimeToUtc(_local, _zone);

    [Benchmark]
    public DateTime GetLocalNow() => DateTime.Now;
}
Method Runtime Mean Ratio
ConvertTimeFromUtc .NET 10.0 45.13 ns 1.00
ConvertTimeFromUtc .NET 11.0 19.44 ns 0.43
ConvertTimeToUtc .NET 10.0 51.97 ns 1.00
ConvertTimeToUtc .NET 11.0 20.23 ns 0.39
GetLocalNow .NET 10.0 76.41 ns 1.00
GetLocalNow .NET 11.0 34.39 ns 0.45

dotnet/runtime#120685 separates two costs in invariant casing. With the normal globalization configuration, ToUpperInvariant and ToLowerInvariant now try a managed ASCII path first, so casing ASCII text can avoid or delay initialization of ICU, the native library .NET uses for culture-aware globalization. In invariant-globalization mode, where ICU isn’t loaded at all, that managed path also improves ASCII casing throughput. Non-ASCII input still needs the appropriate globalization path.

// DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string _short = "runtime";
    private readonly string _long = new('a', 139);

    [Benchmark]
    public string ShortAscii() => _short.ToUpperInvariant();

    [Benchmark]
    public string LongAscii() => _long.ToUpperInvariant();
}
Method Runtime Mean Ratio Allocated
ShortAscii .NET 10.0 18.12 ns 1.00 40 B
ShortAscii .NET 11.0 14.62 ns 0.81 40 B
LongAscii .NET 10.0 248.38 ns 1.00 304 B
LongAscii .NET 11.0 39.22 ns 0.16 304 B

Several smaller changes remove setup around date and culture data. dotnet/runtime#123886 allocates the DateTimeFormatInfo date-word table only for cultures that actually contain such words. And dotnet/runtime#122918 replaces synchronized, boxing Hashtable caches used by time-zone and encoding tables with typed ConcurrentDictionary instances.

The round-trip "O" date format always contains exactly seven fractional-second digits, matching the 10,000,000 ticks in a second. dotnet/runtime#129005 parses those digits directly as ticks, avoiding a conversion through double followed by division, multiplication, and rounding. Formatting benefits from specialization as well. dotnet/runtime#129374 routes invariant DateTime.ToString("G") through the existing fixed-format fast path, bypassing the general culture-aware formatter. DateTimeOffset retains the general path because its offset changes the output:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Globalization;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly DateTime _dateTime = new(2024, 3, 15, 13, 45, 30, DateTimeKind.Utc);

    [Benchmark]
    public string DateTime_ToString_G() => _dateTime.ToString("G", CultureInfo.InvariantCulture);
}
Method Runtime Mean Ratio
DateTime_ToString_G .NET 10.0 65.54 ns 1.00
DateTime_ToString_G .NET 11.0 29.76 ns 0.45

Strings and Spans

UTF-8 is everywhere, from web protocols and JSON payloads to files on disk. Since .NET strings use UTF-16, applications frequently need to convert between the two, making it especially important for those conversions to be fast. UTF-8 encoding must validate UTF-16 surrogate pairs as it counts and converts them. On Arm64, the vectorized implementation in .NET 10 still examined individual elements when counting the resulting UTF-8 bytes and checking that surrogates were correctly paired. That gets expensive for text containing many supplementary characters, as every surrogate-heavy vector falls back to this element-by-element work. dotnet/runtime#121981 from @ylpoonlg instead performs the counting and surrogate checks with vector-wide operations. As part of that work, it also unifies most of the x86 and Arm64 implementations, retaining small platform-specific helpers where the instruction sets differ:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int Length = 4096;

    private string _validWithSurrogatePairs = string.Empty;

    [GlobalSetup]
    public void Setup()
    {
        Random rng = new(42);
        StringBuilder sb = new(Length);
        while (sb.Length < Length - 2)
        {
            sb.Append((char)('A' + rng.Next(0, 26)));
            sb.Append("\U0001F600"); // emoji -> surrogate pair
        }

        _validWithSurrogatePairs = sb.ToString();
    }

    [Benchmark]
    public int ValidWithSurrogatePairs() => Encoding.UTF8.GetByteCount(_validWithSurrogatePairs);
}

This input deliberately contains a surrogate pair for every ASCII character, making the removed per-element work especially visible.

Method Runtime Mean Ratio
ValidWithSurrogatePairs .NET 10.0 2.994 μs 1.00
ValidWithSurrogatePairs .NET 11.0 856.8 ns 0.29

The byte-to-char direction was also improved on Arm. UTF-8 decoding can copy ASCII bytes directly to UTF-16 characters, but as soon as we find the first non-ASCII byte, we need the full multi-byte decoder. The vector loop therefore needs both a fast test for whether any lane is non-ASCII and, only when one is found, its exact position. Calculating that position for every all-ASCII vector wastes work on the overwhelmingly common fast path. dotnet/runtime#121382 from @ylpoonlg first performs the cheap vector-wide test and then computes the lane index only after that test succeeds.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly byte[] _ascii = Enumerable.Repeat((byte)'a', 16_384).ToArray();

    [Benchmark]
    public int Utf8GetCharCount() => Encoding.UTF8.GetCharCount(_ascii);
}

With all-ASCII input, every vector can stay on the cheap path:

Method Runtime Mean Ratio
Utf8GetCharCount .NET 10.0 443.5 ns 1.00
Utf8GetCharCount .NET 11.0 210.6 ns 0.47

Base64 is commonly used when binary data needs to travel through text-oriented formats and protocols. Its encoder naturally works in groups of three input bytes and four output characters, but the line-breaking option also needs to stop at the MIME-style 76-character boundary and insert \r\n . The older implementation handled that formatting through a separate scalar path. dotnet/runtime#123403 brings the optimized span-based Base64 encoder to Convert.ToBase64String with Base64FormattingOptions.InsertLineBreaks , processing each line with the same vectorized core and handling the separators around it:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Params(57, 570)]
    public int ByteLength { get; set; }

    private byte[] _bytes = [];

    [GlobalSetup]
    public void Setup()
    {
        _bytes = new byte[ByteLength];
        new Random(42).NextBytes(_bytes);
    }

    [Benchmark]
    public string ToBase64String_InsertLineBreaks() => Convert.ToBase64String(_bytes, Base64FormattingOptions.InsertLineBreaks);
}
Method Runtime ByteLength Mean Ratio
ToBase64String_InsertLineBreaks .NET 10.0 57 60.95 ns 1.00
ToBase64String_InsertLineBreaks .NET 11.0 57 23.91 ns 0.39
ToBase64String_InsertLineBreaks .NET 10.0 570 560.66 ns 1.00
ToBase64String_InsertLineBreaks .NET 11.0 570 194.99 ns 0.35

Base64 decoding got the same treatment from the other direction. Base64.DecodeFromUtf8InPlace decodes in place, overwriting the encoded input with the decoded bytes. In .NET 10, it still employed a scalar loop, long after the out-of-place DecodeFromUtf8 had acquired AVX-512, AVX2, AdvSimd, and SSSE3 paths. In-place decoding turns out to be safe to vectorize precisely because of Base64’s ratio: 4 bytes read produce 3 bytes written, so the write cursor always trails the read cursor, and each vector store, including its zero-padded overshoot, ends at or before the next vector load and never clobbers source that hasn’t been read yet. dotnet/runtime#131333 therefore reuses the existing decode helpers for the in-place path.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Buffers;
using System.Buffers.Text;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly byte[] _encoded = Encoding.ASCII.GetBytes(Convert.ToBase64String(new byte[16_384]));
    private byte[] _buffer = [];

    [IterationSetup]
    public void Setup() => _buffer = (byte[])_encoded.Clone();

    [Benchmark]
    public OperationStatus Decode() => Base64.DecodeFromUtf8InPlace(_buffer, out _);
}
Method Runtime Mean Ratio
Decode .NET 10.0 10.70 μs 1.00
Decode .NET 11.0 2.256 μs 0.21

MemoryExtensions.CommonPrefixLength compares two spans and returns how many elements they share at the beginning (“hello” and “help”, for example, have a common prefix length of 3). Internally, it utilizes a helper that slices whichever input was longer to the length of the shorter one. dotnet/runtime#121104 from @xtqqczze simplifies that helper: after shortening the second span if necessary, it always slices the first span to the second’s length. That gives the JIT the same explicit relationship between the two lengths regardless of which input started out longer.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Linq;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string[] _shorter = Enumerable.Repeat("value", 64).ToArray();
    private readonly string[] _longer = Enumerable.Repeat("value", 128).ToArray();

    [Benchmark]
    public int ShorterFirst() => _shorter.AsSpan().CommonPrefixLength(_longer);

    [Benchmark]
    public int LongerFirst() => _longer.AsSpan().CommonPrefixLength(_shorter);
}

The longer-first case was already efficient. The change improves the shorter-first case, bringing the two orderings to essentially the same throughput:

Method Runtime Mean Ratio
ShorterFirst .NET 10.0 45.16 ns 1.00
ShorterFirst .NET 11.0 25.43 ns 0.56
LongerFirst .NET 10.0 26.40 ns 1.00
LongerFirst .NET 11.0 26.31 ns 1.00

Text processing often starts by obtaining an Encoding . Properties such as Encoding.UTF8 provide fast access to popular encodings, while legacy code pages can be made available by registering CodePagesEncodingProvider . In .NET 10, that provider’s tables, including the name lookup used by Encoding.GetEncoding(string) once the provider is registered, used reader-writer locks. dotnet/runtime#125001 replaces those caches with ConcurrentDictionary instances, allowing warmed-up provider lookups to proceed without acquiring the reader lock.

On string itself, dotnet/runtime#130361 from @prozolic recognizes when string.Concat(IEnumerable<string?>) receives a string[] or List<string?> and passes its contiguous storage directly to the span-based implementation:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Collections.Generic;
using System.Linq;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly IEnumerable<string?> _array = Enumerable.Range(0, 1_000).Select(i => i.ToString()).ToArray();
    private readonly IEnumerable<string?> _list = Enumerable.Range(0, 1_000).Select(i => i.ToString()).ToList();

    [Benchmark]
    public string Array() => string.Concat(_array);

    [Benchmark]
    public string List() => string.Concat(_list);
}
Method Runtime Mean Ratio Allocated
Array .NET 10.0 4.230 μs 1.00 5.7 KB
Array .NET 11.0 3.285 μs 0.78 5.67 KB
List .NET 10.0 6.703 μs 1.00 5.71 KB
List .NET 11.0 3.253 μs 0.49 5.67 KB

Some of my favorite improvements in .NET are the tiny ones that show up everywhere. A good example of that is in dotnet/roslyn#82729 . Previously, when you wrote span[start..] , the compiler would lower that to the equivalent of span.Slice(start, span.Length - start) . The JIT has made strides towards compiling this exactly how it would span.Slice(start) , but everyone is better off if the C# compiler just emits that in the first place. And it now does. The difference is clear in the IL for a method that returns span[start..] :

; Platform-independent IL
-// Before: 21 bytes
+// After: 9 bytes
-.locals init ([0] System.Span<char>&, [1] int32)
 ldarga.s span
-stloc.0
 ldarg.1
-stloc.1
-ldloc.0
-ldloc.1
-ldloc.0
-call instance int32 System.Span<char>::get_Length()
-ldloc.1
-sub
-call instance System.Span<char> System.Span<char>::Slice(int32, int32)
+call instance System.Span<char> System.Span<char>::Slice(int32)
 ret

Searching and Comparing

Searching in one way, shape, or form is one of the most common things programs do. And when it comes to searching text, regular expressions are an extremely common and helpful way to specify and perform said search. .NET’s regex support has improved by leaps and bounds over the years, with significant investments in .NET 5 and .NET 7 and then every release since, including .NET 11.

When a Regex instance is created, it needs to parse the incoming regular expression pattern and turn it into a form it can utilize for performing the actual searches. The regex language is very expressive and enables multiple ways of specifying the same pattern, some more efficient to process than others, so as part of parsing, Regex applies a variety of simplifications and optimizations over the parsed tree in order to put it into an ideal form, as well as to learn facts about the pattern to further optimize later processing (such as discovering a minimum and maximum length of any possible match). Each of these transformations can in turn expose more opportunity for other transformations, but based on the order the transformations are applied, sometimes those opportunities can be missed. In .NET 11, dotnet/runtime#125289 gives compiled and source-generated regexes one final cleanup pass after the whole-pattern optimizations have reshaped the pattern. Consider the pattern [ab]+c[ab]+|[ab]+ . On input containing a long run of a s with no c , the .NET 10 source-generated matcher first scans the whole run for the first alternative, fails when it doesn’t find the c , and then scans the same run again for the second alternative. The final cleanup pass factors out the common [ab]+ , leaving c[ab]+ as an optional suffix:

// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using System.Text.RegularExpressions;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Benchmarks
{
    private readonly string _input = new('a', 4096);

    [Benchmark]
    public bool SharedPrefix() => SharedPrefixRegex().IsMatch(_input);

    [GeneratedRegex("[ab]+c[ab]+|[ab]+")]
    private static partial Regex SharedPrefixRegex();
}
Method Runtime Mean Ratio
SharedPrefix .NET 10.0 550.9 ns 1.00
SharedPrefix .NET 11.0 282.8 ns 0.51

Beyond doing additional passes, several other changes improve what those analysis passes can see. For example, for a pattern like (http|https) with ordinal ignore-case matching, for uninteresting reasons previously the engine would extract a prefix of "htt" , even though it could have extracted "http" . dotnet/runtime#124881 improves that, enabling the engine to skip far more false candidates. The input here contains 25,000 "htt" prefixes that aren’t followed by a p before the final match:

// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using System.Text.RegularExpressions;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Benchmarks
{
    private readonly string _input = string.Concat(Enumerable.Repeat("httx", 25_000)) + "https";

    [Benchmark]
    public bool IgnoreCaseAlternation() => Http.IsMatch(_input);

    [GeneratedRegex("(http|https)", RegexOptions.IgnoreCase)]
    private static partial Regex Http { get; }
}
Method Runtime Mean Ratio
IgnoreCaseAlternation .NET 10.0 415.0 μs 1.00
IgnoreCaseAlternation .NET 11.0 7.012 μs 0.017

When those transformation passes are looking for various patterns, sometimes small things obscure what they’re trying to see, and they miss optimizations. dotnet/runtime#124842 improves a case where captures were getting in the way of identifying a searchable prefix. For a pattern like \b(in)\b with RegexOptions.IgnoreCase , it will now discover it can search for ordinal-ignore-case "in" .

// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using System.Text.RegularExpressions;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Benchmarks
{
    private readonly string _input = string.Concat(Enumerable.Repeat("xn ", 33_333)) + "in";

    [Benchmark]
    public bool IgnoreCaseCapturedPrefix() => CapturedPrefix.IsMatch(_input);

    [GeneratedRegex(@"\b(in)\b", RegexOptions.IgnoreCase)]
    private static partial Regex CapturedPrefix { get; }
}
Method Runtime Mean Ratio
IgnoreCaseCapturedPrefix .NET 10.0 277.7 μs 1.00
IgnoreCaseCapturedPrefix .NET 11.0 7.286 μs 0.026

As these cases highlight, one of the most impactful things we can do for regular expression processing is improve the engine’s ability to find things to search for as the next possible place a match could apply, and to optimize that search. dotnet/runtime#124736 does that. For compiled, source-generated, and NonBacktracking regexes, it improves how the engine is able to search for one of several literal prefixes. For agggtaaa|tttaccct , for example, the .NET 10 source generator first searched for [ag] at offset 3 and then checked nearby characters for [gt] . That’s a weak filter for an input full of a characters, where almost every position becomes a candidate. The .NET 11 generator instead searches for the complete agggtaaa and tttaccct strings with SearchValues<string> . A frequency heuristic selects this approach only for case-sensitive alternatives where whole-string searching is expected to reject more false candidates than the available character-set filter.

// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

using System.Text.RegularExpressions;

BenchmarkSwitcher.FromAssembly(typeof(RegexPrefixBenchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class RegexPrefixBenchmarks
{
    private const string Pattern = "agggtaaa|tttaccct";

    private readonly string _match = new string('a', 100_000) + "tttaccct";
    private readonly string _miss = new('a', 100_000);

    [Benchmark]
    public bool Match() => Generated.IsMatch(_match);

    [Benchmark]
    public bool Miss() => Generated.IsMatch(_miss);

    [GeneratedRegex(Pattern)]
    private static partial Regex Generated { get; }
}
Method Runtime Mean Ratio
Match .NET 10.0 861.9 μs 1.00
Match .NET 11.0 9.251 μs 0.011
Miss .NET 10.0 861.4 μs 1.00
Miss .NET 11.0 9.647 μs 0.011

Of course, searching for the next place to match isn’t the only opportunity for improvement. Once you’ve found that place, you need to try to match, and we want to optimize that further, too.

Consider the pattern \b\w+n\b . The \w+ can match n , which means we can’t automatically treat this loop as being atomic. Normally, after matching the loop greedily and failing to match n , we’d need to backtrack looking for the next viable place to match the n . But if what comes after the n (in this case, a boundary) can’t possibly match the loop, we can avoid doing that search. dotnet/runtime#125636 teaches the compiled and source-generated engines to prove that and test the final position directly rather than searching backward through the loop’s existing match. The same idea applies to other loops followed by a literal when the engine can prove that trying earlier positions can’t change the result.

// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Running;

using System.Text.RegularExpressions;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Benchmarks
{
    private const int WordLength = 5000;
    private readonly string _matchingWord = new string('a', WordLength - 1) + "n";
    private readonly string _nonMatchingWord = new string('a', WordLength - 1) + "b";

    [GeneratedRegex(@"\b\w+n\b")]
    private static partial Regex Generated { get; }

    [Benchmark]
    public bool Matching() => Generated.IsMatch(_matchingWord);

    [Benchmark]
    public bool NonMatching() => Generated.IsMatch(_nonMatchingWord);
}
Method Runtime Mean Ratio
Matching .NET 10.0 3.441 μs 1.00
Matching .NET 11.0 3.118 μs 0.91
NonMatching .NET 10.0 83.656 μs 1.00
NonMatching .NET 11.0 69.984 μs 0.84

A match can sometimes be ruled out before examining any of the input’s characters. When matching starts at position zero, a fixed-length pattern with a leading \A or non-multiline ^ and a trailing \z can match only when the whole input has exactly that length. dotnet/runtime#120916 emits that length check up front for the compiled and source-generated engines when the computed maximum length equals the minimum required length. Here, the pattern requires exactly 512 characters while the input contains 513:

// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

using System.Text.RegularExpressions;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Benchmarks
{
    private readonly string _tooLong = new('a', 513);

    [GeneratedRegex(@"\A[a-z]{512}\z")]
    private static partial Regex Generated { get; }

    [Benchmark]
    public bool AnchoredReject() => Generated.IsMatch(_tooLong);
}
Method Runtime Mean Ratio
AnchoredReject .NET 10.0 41.06 ns 1.00
AnchoredReject .NET 11.0 16.05 ns 0.39

In general, we’ve tried to keep the compilers behind RegexOptions.Compiled (which emits IL) and the source generator (which emits C#) as close to 1:1 as possible. There are a few cases, however, where they have diverged from each other, generally where one was able to easily utilize some feature of the target language the other didn’t have. A good example is with alternations. If several left-to-right atomic branches each begin with a different literal character, the engine can read that character and jump straight to the matching branch rather than testing each branch in order. With C#, we emitted a switch , which the C# compiler could then lower to IL using various strategies. For IL, in .NET 10 and earlier, without the C# compiler to provide those optimizations, we just skipped the optimization. Now in .NET 11, dotnet/runtime#122959 emits a similar implementation to what the C# compiler would have, bringing this optimization to RegexOptions.Compiled .

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Text.RegularExpressions;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string _input = string.Concat(Enumerable.Repeat("p15", 10_000));
    private readonly Regex _regex = new(@"(?>a0|b1|c2|d3|e4|f5|g6|h7|i8|j9|k10|l11|m12|n13|o14|p15)", RegexOptions.Compiled);

    [Benchmark]
    public int DispatchToFinalBranch() => _regex.Count(_input);
}
Method Runtime Mean Ratio
DispatchToFinalBranch .NET 10.0 233.9 μs 1.00
DispatchToFinalBranch .NET 11.0 159.5 μs 0.68

Another of the few differences between compiled and source-generated regexes had to do with backreferences. A case-sensitive backreference, such as the \1 in ([a-z]+)-\1 , asks whether the next input equals text that was previously captured in the match. Source-generated regexes were using the optimized SequenceEqual to do that comparison, whereas RegexOptions.Compiled wasn’t. With dotnet/runtime#123914 in .NET 11, now it does.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

using System.Text.RegularExpressions;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string _input = new string('a', 256) + "-" + new string('a', 256);
    private readonly Regex _regex = new(@"^([a-z]{256})-\1$", RegexOptions.Compiled);

    [Benchmark]
    public bool Backreference() => _regex.IsMatch(_input);
}
Method Runtime Mean Ratio
Backreference .NET 10.0 168.4 ns 1.00
Backreference .NET 11.0 49.59 ns 0.29

Searching isn’t limited to Regex , of course. Many other methods in .NET help finding things and comparing things, some of which get notable bumps in .NET 11.

The Ascii class provides optimized helpers for validating and manipulating ASCII text. Members like Equals are already vectorized in .NET 10, but in .NET 11, dotnet/runtime#123115 improves that implementation by ensuring that inputs of length 8 through 15 can be vectorized, as well.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using BenchmarkDotNet.Attributes;
using System.Text;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Params(8, 15)]
    public int Length { get; set; }

    private byte[] _bytes = [];
    private char[] _charsMatching = [];

    [GlobalSetup]
    public void Setup()
    {
        _bytes = new byte[Length];
        _charsMatching = new char[Length];
        for (int i = 0; i < Length; i++)
        {
            byte b = (byte)('a' + (i % 26));
            _bytes[i] = b;
            _charsMatching[i] = (char)b;
        }
    }

    [Benchmark]
    public bool Equals_Matching() => Ascii.Equals(_bytes, _charsMatching);
}
Method Runtime Length Mean Ratio
Equals_Matching .NET 10.0 8 3.834 ns 1.00
Equals_Matching .NET 11.0 8 1.966 ns 0.51
Equals_Matching .NET 10.0 15 6.177 ns 1.00
Equals_Matching .NET 11.0 15 2.398 ns 0.39

dotnet/runtime#130644 also improves equality performance, in this case with SequenceEqual over a span of Guid or Int128 . Previously, SequenceEqual treated these as arbitrary structures and compared them one element at a time. The PR teaches the runtime that their fixed bitwise representations are suitable for comparison as raw bytes. That enables the same optimized memory-comparison path used for primitive types, including JIT unrolling and vectorization:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Guid[] _guids1 = new Guid[2];
    private readonly Guid[] _guids2 = new Guid[2];
    private readonly Int128[] _int128s1 = new Int128[2];
    private readonly Int128[] _int128s2 = new Int128[2];

    [Benchmark]
    public bool GuidEqual() => _guids1.AsSpan().SequenceEqual(_guids2);

    [Benchmark]
    public bool Int128Equal() => _int128s1.AsSpan().SequenceEqual(_int128s2);
}
Method Runtime Mean Ratio
GuidEqual .NET 10.0 2.960 ns 1.00
GuidEqual .NET 11.0 2.077 ns 0.70
Int128Equal .NET 10.0 3.547 ns 1.00
Int128Equal .NET 11.0 2.077 ns 0.59

Another improvement in .NET 11 is to string.Split . Before string.Split can produce the resulting strings, it first needs to find the characters that separate them and record their positions. In .NET 10, that search is already vectorized: rather than examine one UTF-16 character at a time, it loads a vector’s worth, compares all of its lanes against the separator in parallel, and turns the comparison result into a mask identifying any matches. It then advances to the next vector, or uses the mask to record the matching positions. In .NET 11, on x86/x64, dotnet/runtime#125379 from @hamarb123 makes the no-match path cheaper for ASCII separators. It loads two vectors of UTF-16 characters, packs their 16-bit elements into one vector of bytes, and checks that combined vector for the separator. If there isn’t a match, it has skipped twice as much input with one packed comparison; only a possible match requires the full 16-bit comparisons needed to determine its exact position. (This same packing technique is already employed elsewhere, such as in various SearchValues<T> implementations.)

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string _input = new('a', 16_384);

    [Benchmark]
    public int SplitNoSeparators() => _input.Split(',').Length;
}
Method Runtime Mean Ratio
SplitNoSeparators .NET 10.0 786.0 ns 1.00
SplitNoSeparators .NET 11.0 404.7 ns 0.51

A related Arm64 text-search improvement comes from dotnet/runtime#126678 . A vector comparison produces a vector whose elements are all zero for non-matches and all one bits for matches. Finding the first or last match then requires condensing those bits into a scalar value and counting its leading or trailing zeros. On x86, the runtime can use a movemask instruction for that condensing step. Arm64 has no direct equivalent, and the old implementation needed a sequence of shifts, widening operations, and a horizontal add to achieve it. The .NET 11 implementation now uses shrn , Arm64’s shift-right-and-narrow instruction, to pack the relevant bits directly. SearchValues<char> uses these helpers, so the following benchmark reaches the affected code while searching for a match at the end of the input.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Buffers;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[DisassemblyDiagnoser(maxDepth: 3)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int Length = 8_192;
    private static readonly SearchValues<char> s_vowels = SearchValues.Create("aeiouAEIOU");
    private static readonly string s_input = new string('x', Length - 1) + 'e';

    [Benchmark]
    public int IndexOfAny() => s_input.AsSpan().IndexOfAny(s_vowels);
}

The .NET 10 match-index path requires this sequence:

; Arm64
; .NET 10
cmeq    v16.16b, v16.16b, #0
movi    v17.16b, #0x80
and     v16.16b, v16.16b, v17.16b
ldr     q17, [MASK]
ushl    v16.16b, v16.16b, v17.16b
uxtl2   v17.8h, v16.16b
shl     v17.8h, v17.8h, #8
uaddw   v16.8h, v17.8h, v16.8b
addv    h16, v16.8h
umov    w2, v16.h[0]
mvn     w2, w2
rbit    w2, w2
clz     w2, w2

In .NET 11, the equivalent work is simpler:

; Arm64
; .NET 11
cmeq    v16.16b, v16.16b, #0
mvn     v16.16b, v16.16b
shrn    v16.8b, v16.8h, #4
fmov    x2, d16
rbit    x2, x2
clz     x2, x2
lsr     w2, w2, #2

MemoryExtensions already provides span-based searches for one or more values with IndexOfAny , and for contiguous ranges with IndexOfAnyInRange , along with Except , Contains , and last-index variants of these operations. For example, span.IndexOfAnyInRange('0', '9') finds the next ASCII digit. Whitespace is also common to search for, but the characters recognized by char.IsWhiteSpace are spread across multiple parts of Unicode rather than forming one contiguous range. To avoid requiring every caller to construct the same SearchValues<char> , dotnet/runtime#111439 from @AlexRadch adds ContainsAnyWhiteSpace , IndexOfAnyWhiteSpace , IndexOfAnyExceptWhiteSpace , LastIndexOfAnyWhiteSpace , and LastIndexOfAnyExceptWhiteSpace for ReadOnlySpan<char> . Their shared SearchValues<char> -based implementation vectorizes these searches for parsers, validators, trimming code, and other text-processing code.

This is, however, a good example of how vectorization isn’t always a win. Take trimming. To trim leading whitespace, code needs to find the first character that isn’t whitespace. That character could be deep into the string, but in the most common case, there’s little or nothing to trim. A scalar loop can then return after inspecting just one or two characters, whereas the vectorized helper has fixed setup cost. It’s still worth vectorizing, because that overhead is small and the benefits when there is a lot to scan can be significant. Something to keep in mind.

// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly string _input = new(' ', 256);

    [Benchmark(Baseline = true)]
    public int Scalar()
    {
        ReadOnlySpan<char> input = _input;
        for (int i = 0; i < input.Length; i++)
        {
            if (!char.IsWhiteSpace(input[i]))
                return i;
        }

        return -1;
    }

    [Benchmark]
    public int Vectorized() => _input.AsSpan().IndexOfAnyExceptWhiteSpace();
}
Method Mean Ratio
Scalar 127.87 ns 1.00
Vectorized 13.14 ns 0.10

This method is a particularly good fit when needing to validate that input does not contain any whitespace; that requires searching the entirety of input, which is where the vectorization in these methods shines. As an example of this, dotnet/runtime#127123 uses it to accelerate the parsing of the "X" GUID format:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly string s_noWhitespace =
        Guid.Parse("a8098c1a-f86e-11da-bd1a-00112444be1e").ToString("X");

    [Benchmark]
    public Guid ParseExactX() => Guid.ParseExact(s_noWhitespace, "X");
}
Method Runtime Mean Ratio
ParseExactX .NET 10.0 120.6 ns 1.00
ParseExactX .NET 11.0 87.10 ns 0.72

Closely related to searching is sorting. Years ago, sorting methods for Span<T> were added to MemoryExtensions . Interestingly, the method wasn’t added as Sort<T> but rather as Sort<T, TComparer> where TComparer : IComparer<T> . That signature enables a caller to provide a struct comparer without allocating a delegate or class-based comparer. Because the comparer is a constrained value type, the JIT should also be able to inline the comparison into the hot sorting loop. In practice, the implementation boxed the struct into an IComparer<T> , both allocating and turning every comparison back into an interface call. This was known at the time, but avoiding the box used generic implementation techniques that then carried too much runtime and code-size cost. Those supporting costs have since been addressed, so dotnet/runtime#116109 from @2A5F now carries a value-type comparer through Span<T>.Sort without boxing it. The JIT can specialize the sorting routine for that comparer and inline the comparison.

The generic specialization does increase generated code and very large comparer structs can be more expensive to copy; this optimization is aimed at the small stateless or lightly stateful structs for which the API was designed.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly int[] _source = Enumerable.Range(0, 512).Select(i => (i * 257) % 512).ToArray();
    private int[] _values = [];

    [IterationSetup]
    public void Setup() => _values = (int[])_source.Clone();

    [Benchmark]
    public void Sort() => _values.AsSpan().Sort(new DescendingComparer());

    private readonly struct DescendingComparer : IComparer<int>
    {
        public int Compare(int x, int y) => y.CompareTo(x);
    }
}
Method Runtime Mean Ratio Allocated
Sort .NET 10.0 11.62 μs 1.00 88 B
Sort .NET 11.0 3.533 μs 0.30

Collections and LINQ

Much of the collection and LINQ work in .NET 11 comes from taking better advantage of information that’s already available. A collection often knows much more than an IEnumerable<T> can express: its count, its contiguous storage, its comparer, or the layout of its hash table. Similarly, a LINQ iterator can know how many elements it represents or how its operations were composed. Preserving that information can avoid enumeration, temporary storage, repeated hashing, and other work a general-purpose implementation would otherwise need to perform.

dotnet/runtime#119896 from @prozolic changes ImmutableArray.Create to use Array.Copy rather than a hand-written element loop. A general element-by-element copy repeatedly performs indexing and assignment, while the runtime can specialize Array.Copy for the element type and size. For blittable data, it can use optimized bulk memory copies, and for reference types, which need GC write barriers, it performs the required write barriers in the runtime’s tuned copy helpers. The change therefore both simplifies the managed code and gives ImmutableArray access to those optimized implementations.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Collections.Immutable;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private readonly int[] _source = Enumerable.Range(0, 1_000).ToArray();

    [Benchmark]
    public ImmutableArray<int> CreateSlice() => ImmutableArray.Create(_source, 0, _source.Length);
}

This in particular makes larger copies much faster.

Method Runtime Mean Ratio
CreateSlice .NET 10.0 552.5 ns 1.00
CreateSlice .NET 11.0 277.7 ns 0.50

dotnet/runtime#118932 from @prozolic similarly keeps ImmutableArrayExtensions.SequenceEqual on optimized paths when the other sequence is an array, list, or another ICollection<T> .

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Collections.Immutable;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private ImmutableArray<int> _immutable;
    private List<int> _list = [];

    [GlobalSetup]
    public void Setup()
    {
        int[] values = Enumerable.Range(0, 1_000).ToArray();
        _immutable = ImmutableArray.Create(values);
        _list = [.. values];
    }

    [Benchmark]
    public bool SequenceEqual() => _immutable.SequenceEqual(_list);
}
Method Runtime Mean Ratio
SequenceEqual .NET 10.0 925.8 ns 1.00
SequenceEqual .NET 11.0 122.2 ns 0.13

Array.FindAll has the opposite job: it produces a new collection. For a small result, its temporary storage used to cost more than the result itself. dotnet/runtime#120336 from @Henr1k80 has Array.FindAll collect its first four matches in an inline stack buffer rather than an intermediate List<T> :

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private int[] _data = [];

    [Params(4, 5)]
    public int Size { get; set; }

    [GlobalSetup]
    public void Setup() => _data = Enumerable.Range(0, Size).ToArray();

    [Benchmark]
    public int[] FindAllMatch() => Array.FindAll(_data, static _ => true);
}
Method Runtime Size Mean Ratio Allocated Alloc Ratio
FindAllMatch .NET 10.0 4 27.61 ns 1.00 112 B 1.00
FindAllMatch .NET 11.0 4 9.212 ns 0.33 40 B 0.36
FindAllMatch .NET 10.0 5 36.93 ns 1.00 176 B 1.00
FindAllMatch .NET 11.0 5 11.102 ns 0.30 48 B 0.27

Dictionary<TKey, TValue>.Remove had also missed an optimization already used by lookup and insertion. dotnet/runtime#125884 gives value-type keys a streamlined loop for the common default-comparer case. Because that path doesn’t need a virtual comparer call, the JIT can keep more of the operation’s state in registers; reference-type keys and custom comparers continue to use the general path.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly Guid[] _keys = Enumerable.Range(0, 512).Select(i => new Guid(i, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)).ToArray();
    private Dictionary<Guid, int> _dictionary = [];

    [IterationSetup]
    public void Setup() => _dictionary = _keys.ToDictionary(key => key, key => key.GetHashCode());

    [Benchmark(OperationsPerInvoke = 512)]
    public void Remove()
    {
        foreach (Guid key in _keys)
            _dictionary.Remove(key);
    }
}
Method Runtime Mean Ratio
Remove .NET 10.0 5.285 ns 1.00
Remove .NET 11.0 4.321 ns 0.82

dotnet/runtime#125893 changes HashSet<T> ‘s internal chain walks to test the entry index against the array length with an unsigned comparison. That proves the subsequent array access is in range, allowing the JIT to remove its bounds check.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly HashSet<int> _set = Enumerable.Range(0, 4096).ToHashSet();
    private readonly int[] _probes = Enumerable.Range(0, 4096).ToArray();

    [Benchmark]
    public int ContainsHits()
    {
        int count = 0;
        foreach (int value in _probes)
            count += _set.Contains(value) ? 1 : 0;

        return count;
    }
}
Method Runtime Mean Ratio
ContainsHits .NET 10.0 7.870 μs 1.00
ContainsHits .NET 11.0 7.388 μs 0.94

dotnet/runtime#128988 from @prozolic removes a second hash-table lookup when removing a matching key-value pair from OrderedDictionary<TKey, TValue> through ICollection<KeyValuePair<TKey, TValue>> . That interface operation must first find the key and verify that its stored value equals the supplied value. Once both checks have succeeded, the implementation already has the entry index needed for removal. Looking up the key again unnecessarily repeats its hash computation and collision-chain walk, so the updated path removes the known entry directly.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Collections.Generic;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private const int N = 10_000;

    private OrderedDictionary<string, int> _dict = [];
    private KeyValuePair<string, int>[] _pairs = Enumerable.Range(0, N)
        .Select(i => new KeyValuePair<string, int>($"key{i}", i))
        .ToArray();

    [IterationSetup]
    public void IterationSetup() => _dict = new OrderedDictionary<string, int>(_pairs);

    [Benchmark]
    public int Remove_ExplicitInterface()
    {
        ICollection<KeyValuePair<string, int>> col = _dict;
        int removed = 0;
        foreach (var pair in _pairs)
            if (col.Remove(pair))
                removed++;

        return removed;
    }
}

For 10,000 entries:

Method Runtime Mean Ratio
Remove_ExplicitInterface .NET 10.0 220.7 ms 1.00
Remove_ExplicitInterface .NET 11.0 179.0 ms 0.81

dotnet/runtime#122952 goes further when two hash tables have compatible layouts. Normally, UnionWith enumerates the source and inserts every element independently, recomputing hashes, checking for duplicates, and potentially resizing the destination along the way. If the destination is empty and both sets use compatible comparers, every source entry is already unique under exactly the equality rules the destination needs. UnionWith can therefore use the existing HashSet<T> copy-constructor fast path to clone the populated storage rather than rebuilding the same table entry by entry.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private readonly HashSet<int> _source = new(Enumerable.Range(0, 4_096));

    [Benchmark]
    public HashSet<int> FreshDestinationUnionWith()
    {
        HashSet<int> destination = [];
        destination.UnionWith(_source);
        return destination;
    }
}
Method Runtime Mean Ratio Allocated Alloc Ratio
FreshDestinationUnionWith .NET 10.0 46.207 μs 1.00 252.27 KB 1.00
FreshDestinationUnionWith .NET 11.0 2.433 μs 0.05 76.07 KB 0.30

dotnet/runtime#128300 from @AndrewP-GH also helps with collection construction. Building a FrozenDictionary<TKey, TValue> first requires collecting the input elements into a regular Dictionary<TKey, TValue> if they’re not already in one. That temporary dictionary resolves duplicate keys before the final frozen representation is chosen, but in .NET 10 it was growing incrementally even when the source’s count was readily available. This PR uses that count as the dictionary’s initial capacity, avoiding repeated allocation, copying, and rehashing as it’s populated.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly KeyValuePair<int, int>[] _array =
        Enumerable.Range(0, 4096).Select(i => new KeyValuePair<int, int>(i, i)).ToArray();

    [Benchmark]
    public FrozenDictionary<int, int> FromArray() => _array.ToFrozenDictionary();

}
Method Runtime Allocated Alloc Ratio
FromArray .NET 10.0 347.17 KB 1.00
FromArray .NET 11.0 127.16 KB 0.37

SetEquals asks whether two sets contain the same values, regardless of insertion order. The general implementation needs a temporary mutable set so it can account for duplicates and arbitrary enumeration order. When the other input is already a hash set with a compatible comparer, though, that reconstruction is unnecessary. dotnet/runtime#126309 from @aw0lid adds to ImmutableHashSet<T>.SetEquals direct zero-allocation paths for compatible ImmutableHashSet<T> and HashSet<T> inputs; with an identical comparer, the sets can be considered equal if they have the same count and if every element from one is found in the other.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Collections.Immutable;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private ImmutableHashSet<int> _set = ImmutableHashSet<int>.Empty;
    private ImmutableHashSet<int> _immutable = ImmutableHashSet<int>.Empty;
    private HashSet<int> _mutable = [];

    [GlobalSetup]
    public void Setup()
    {
        int[] items = Enumerable.Range(0, 10_000).ToArray();
        _set = ImmutableHashSet.CreateRange(items);
        _immutable = ImmutableHashSet.CreateRange(items);
        _mutable = new(items);
    }

    [Benchmark]
    public bool EqualImmutableHashSet() => _set.SetEquals(_immutable);

    [Benchmark]
    public bool EqualHashSet() => _set.SetEquals(_mutable);
}
Method Runtime Mean Ratio Allocated Alloc Ratio
EqualImmutableHashSet .NET 10.0 775.6 μs 1.00 158.16 KB 1.00
EqualImmutableHashSet .NET 11.0 559.7 μs 0.72 0
EqualHashSet .NET 10.0 478.6 μs 1.00 157.99 KB 1.00
EqualHashSet .NET 11.0 230.9 μs 0.48 0

Sorted sets have a related case. SetEquals can be passed any IEnumerable<T> . That sequence might be unordered and might contain duplicate values, so ImmutableSortedSet<T> previously copied it into a temporary SortedSet<T> before performing the comparison. However, when the input is another sorted set using the same ordering comparer, both sets contain unique values and enumerate those values in the same order. Equality can then be determined by first comparing their counts and, if those match, advancing both enumerators together. The first unequal pair proves the sets are different, and reaching the end without finding a difference proves they’re equal. dotnet/runtime#126549 from @aw0lid recognizes this case for ImmutableSortedSet<T> , avoiding the temporary SortedSet<T> and comparing the two sorted sequences directly in one linear pass:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Collections.Generic;
using System.Collections.Immutable;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private const int N = 10_000;
    private ImmutableSortedSet<int> _set = ImmutableSortedSet<int>.Empty;
    private ImmutableSortedSet<int> _equalSet = ImmutableSortedSet<int>.Empty;

    [GlobalSetup]
    public void Setup()
    {
        var items = new int[N];
        for (int i = 0; i < N; i++) items[i] = i;
        _set = ImmutableSortedSet.CreateRange(items);
        _equalSet = ImmutableSortedSet.CreateRange(items);
    }

    [Benchmark]
    public bool SetEquals_EqualImmutableSortedSet() => _set.SetEquals(_equalSet);
}

For 10,000 elements:

Method Runtime Mean Ratio Allocated Alloc Ratio
SetEquals_EqualImmutableSortedSet .NET 10.0 767.7 μs 1.00 430.02 KB 1.00
SetEquals_EqualImmutableSortedSet .NET 11.0 117.6 μs 0.15 0

SortedSet<T> already enjoyed an optimization for that case in .NET 10, but it’s not left out of .NET 11 improvements. SortedSet<T>.GetViewBetween returns a SortedSet<T> view, effectively a slice of another SortedSet<T> , a live window onto a range of another set: changes through the view affect the original set. Clearing a view therefore can’t replace the view with an empty collection; it must find and remove every original node in that range. dotnet/runtime#126410 from @prozolic reduces the temporary storage used for that operation. The implementation pre-sizes the list of elements to remove and walks it by index rather than repeatedly removing from and shrinking the temporary list.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private const int N = 10_000;
    private SortedSet<int> _fullSet = [];

    [IterationSetup]
    public void Setup() => _fullSet = new SortedSet<int>(Enumerable.Range(0, N));

    [Benchmark]
    public int GetViewBetweenThenClear()
    {
        SortedSet<int> view = _fullSet.GetViewBetween(0, N - 1);
        view.Clear();
        return _fullSet.Count;
    }
}
Method Runtime Allocated Alloc Ratio
GetViewBetweenThenClear .NET 10.0 193.15 KB 1.00
GetViewBetweenThenClear .NET 11.0 103.93 KB 0.54

Collections are frequently consumed through LINQ. Although its operators work in terms of the general IEnumerable<T> abstraction, LINQ’s internal iterators can preserve useful facts about their sources and the operations already applied. Those facts can sometimes answer a query without enumerating the source at all.

For example, consider source.Append(x).Skip(10).LastOrDefault() . LINQ queries are lazy, so the actual search begins only when LastOrDefault asks the Skip iterator for its last element. If source.Append(x) contains ten or fewer elements, Skip(10) necessarily removes all of them, leaving an empty sequence from which LastOrDefault must return the default value. Append , Prepend , and Concat iterators can cheaply report their total count when their underlying sources can do so. dotnet/runtime#123306 from @prozolic teaches the last-element path for Skip to compare that count with the number being skipped and immediately report that there is no element, rather than searching a sequence it already knows is empty.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Linq;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private readonly int[] _source = [1, 2, 3, 4, 5];

    [Benchmark]
    public int AppendSkipLastOrDefault() => _source.Append(6).Skip(10).LastOrDefault();
}
Method Runtime Mean Ratio Allocated Alloc Ratio
AppendSkipLastOrDefault .NET 10.0 44.89 ns 1.00 144 B 1.00
AppendSkipLastOrDefault .NET 11.0 16.32 ns 0.36 112 B 0.78

.NET 11 also improve’s LINQ’s Enumerable.Sum . Sum already uses SIMD. The main loop processes four vectors at a time, alternating between two accumulators so that the additions don’t require extra moves. However, Sum also promises to throw if the result overflows. Alongside each vector addition, the implementation uses the signs of the two inputs and the result to update another vector that tracks whether any lane overflowed. After every group of four vectors, the loop tests that tracking vector and branches to the throwing path if needed. Overflow is rare, though, so on the common path that test and branch almost always just confirm that nothing happened. dotnet/runtime#127429 removes that repeated work in .NET 11. It accumulates the overflow information across all of the vector processing and tests it once after the vector loops have completed. The checked-overflow behavior remains the same, but the normal path no longer needs to stop and check after every four vectors. The PR also simplifies how the method walks the input, replacing unsafe reference and index arithmetic with span-based vector loads, progressively slicing off the elements already processed, and using a foreach for the final scalar elements.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Linq;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private const int N = 32;
    private int[] _intData = [];

    [GlobalSetup]
    public void Setup()
    {
        Random rng = new(42);
        _intData = Enumerable.Range(0, N).Select(_ => rng.Next(-1_000, 1_000)).ToArray();
    }

    [Benchmark]
    public int SumInt() => _intData.Sum();
}
Method Runtime Mean Ratio
SumInt .NET 10.0 5.609 ns 1.00
SumInt .NET 11.0 4.731 ns 0.84

Enumerable ‘s Min and Max already examined many values at once with SIMD, but they still finished byte , sbyte , short , and ushort inputs by copying the final vector to the stack and checking its values one by one. dotnet/runtime#127995 keeps that final step in vector instructions, using shuffles to combine the lanes. Smaller element types pack more values into each vector, so they benefit most from no longer finishing the search one value at a time.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private byte[] _bytes = [];

    [Params(16, 64)]
    public int Length { get; set; }

    [GlobalSetup]
    public void Setup() => _bytes = Enumerable.Range(0, Length).Select(i => (byte)i).ToArray();

    [Benchmark]
    public byte MaxByte() => _bytes.Max();
}
Method Runtime Length Mean Ratio
MaxByte .NET 10.0 16 7.546 ns 1.00
MaxByte .NET 11.0 16 2.176 ns 0.29
MaxByte .NET 10.0 64 7.827 ns 1.00
MaxByte .NET 11.0 64 2.059 ns 0.26

Since its inception, LINQ has had Join and GroupJoin , and .NET 10 introduced the long-requested LeftJoin and RightJoin . In .NET 11, dotnet/runtime#127236 adds FullJoin . The operators differ in which unmatched elements they retain and how they represent the matches:

  • Join emits only pairs whose keys match.
  • GroupJoin emits every left element together with a sequence containing its matching right elements; that sequence is empty when there are no matches.
  • LeftJoin emits the matching pairs and also unmatched left elements, paired with a default value for the right.
  • RightJoin does the inverse, emitting the matching pairs and also unmatched right elements, paired with a default value for the left.
  • FullJoin emits the matching pairs and the unmatched elements from both inputs, using a default value for whichever side is missing.

Before .NET 11, applications typically approximated it by combining GroupJoin , SelectMany , and Concat , then searching the first input again to find right-side elements without a match. The built-in operator avoids both that composition of iterators and the repeated search.

// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Running;

using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private const int N = 10_000;

    private List<(int Id, string Name)> _left = Enumerable.Range(0, N).Select(i => (i, $"item{i}")).ToList();
    private List<(int Id, decimal Amount)> _right = Enumerable.Range(N / 4, N).Select(i => (i, (decimal)i * 1.5m)).ToList();

    [Benchmark(Baseline = true)]
    public int FullJoin_Manual() =>
        _left.GroupJoin(_right, l => l.Id, r => r.Id, (l, rs) => (l, rs))
             .SelectMany(x => x.rs.DefaultIfEmpty(), (x, r) => (x.l, r))
             .Concat(_right
                 .Where(r => !_left.Any(l => l.Id == r.Id))
                 .Select(r => (l: default((int Id, string Name)), r)))
             .Count();

    [Benchmark]
    public int FullJoin_New() => _left.FullJoin(_right, l => l.Id, r => r.Id).Count();
}

For 10,000 elements in each input:

Method Mean Ratio Allocated Alloc Ratio
FullJoin_Manual 27.615 ms 1.00 3.23 MB 1.00
FullJoin_New 1.702 ms 0.06 1.56 MB 0.48

The earlier Skip example showed how many LINQ optimizations come from one operator flowing information to subsequent operators that can then be used for additional optimization. This is typically done by adding that additional information to properties on the concrete internal IEnumerable<T> implementations used by System.Linq . Synchronous Enumerable has accumulated many such specialized iterators over many releases, focusing in particular on places where algorithmic complexity could be significantly reduced. AsyncEnumerable , introduced “in the box” in .NET 10, initially had much less of that machinery; for asynchronous sequences dominated by I/O, it often wouldn’t matter.

Concatenation is an important exception. Prior to .NET 11, every call to AsyncEnumerable.Append created a new iterator around the sequence produced by the previous call. Consider a chain with just three appended values:

var sequence = AsyncEnumerable.Empty<int>()
    .Append(0)
    .Append(1)
    .Append(2);

To produce 0 , enumeration needs to pass through all three nested iterators. Producing 1 passes through two, and producing 2 passes through one. Thus, yielding three values involves roughly 3 + 2 + 1 iterator steps. With 1,000 appends, that grows to 1,000 + 999 + ... + 1 , or approximately 500,000 steps, rather than approximately 1,000. In general, enumerating N values requires O(N^2) work. Several years back Enumerable addressed this by special-casing the various concatenation enumerables to flow enough information through to make iterating the chain O(N) rather than O(N^2), and in .NET 11, dotnet/runtime#122389 applies that to AsyncEnumerable as well. The operators accumulate the extra elements or sequences in one flat representation instead of adding another wrapper for each LINQ operator.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    [Benchmark]
    public async Task<int> AppendChain()
    {
        var seq = AsyncEnumerable.Empty<int>();
        for (int i = 0; i < 1_000; i++) seq = seq.Append(i);
        return await seq.SumAsync();
    }
}

For a chain of 1,000 appended elements:

Method Runtime Mean Ratio Allocated Alloc Ratio
AppendChain .NET 10.0 8.253 ms 1.00 187.57 KB 1.00
AppendChain .NET 11.0 28.49 μs 0.00345 136.77 KB 0.73

I/O

I/O performance is often equated with the speed of the underlying device, but the transfer itself is only one part of an operation. A cached file read may complete in microseconds, many redirected pipes may be active concurrently, and compression may operate entirely on data already in memory. In cases like these, the managed overhead around the operation can be as important as the time spent moving the data.

That overhead includes setting up the appropriate synchronous or asynchronous OS mechanism, keeping state alive until an operation completes, allocating and copying temporary buffers, and adapting between the caller’s data and stream-based APIs. .NET 11 removes work from each of these layers.

On Windows, “overlapped I/O” is the asynchronous model in which an operation begins now and the operating system posts its completion later. Any time .NET performs I/O as part of an asynchronous operation on Windows, it strives to use a corresponding overlapped I/O API rather than using a synchronous API asynchronously (i.e. queuing a work item that blocks a thread pool thread doing the I/O). However, there have been some stragglers. Redirected child-process output previously used synchronous pipe handles, so ReadToEndAsync on the stream from a Process ‘s stdout or stderr Stream still needed a thread-pool thread blocked in a native read for each stdout or stderr pipe. In .NET 11, dotnet/runtime#125643 instead opens the parent’s stdout and stderr ends for overlapped reads, while leaving the child ends synchronous (as console applications expect).

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Diagnostics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

if (args is ["--emit"])
{
    Console.Write(new string('x', 8 * 1024 * 1024));
    return;
}

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[SimpleJob(launchCount: 1, warmupCount: 3, iterationCount: 10)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    [Benchmark]
    public Task ReadOutputConcurrently() =>
        Task.WhenAll(Enumerable.Range(0, 16).Select(_ => RunProcess()));

    private static async Task RunProcess()
    {
        var psi = new ProcessStartInfo("dotnet")
        {
            RedirectStandardOutput = true,
            UseShellExecute = false,
        };
        psi.ArgumentList.Add(typeof(Benchmarks).Assembly.Location);
        psi.ArgumentList.Add("--emit");

        using Process process = Process.Start(psi)!;
        _ = await process.StandardOutput.ReadToEndAsync();
        await process.WaitForExitAsync();
    }
}
Method Runtime Mean Ratio
ReadOutputConcurrently .NET 10.0 587.8 ms 1.00
ReadOutputConcurrently .NET 11.0 541.8 ms 0.92

We often refer to the mechanism being fixed as “async over sync.” The opposite case, “sync over async,” can be even worse, as it means blocking one thread while waiting for another to do some work; that provides one of the necessary ingredients for cycles and deadlocks, and is a leading cause of scalability bottlenecks in services, so we try to stamp out “sync over async” whenever possible. In cases where we can’t avoid it, though, we can at least make it better.

RandomAccess.Read provides one such opportunity when it’s used with a Windows file handle opened for asynchronous I/O. Windows requires specifying at the time of file opening whether I/O will be overlapped or not, and if it is, Windows still requires a read to use its OVERLAPPED mechanism, even in a synchronous Read case where the API’s caller is going to block until that read completes. While we can’t avoid that overlapped I/O, we can still make the operation cheaper. Previously, .NET both gave the operation an event for the calling thread to wait on and registered an I/O-completion callback. Instead, dotnet/runtime#126845 uses a documented Windows convention: setting the low bit of OVERLAPPED.hEvent instructs Windows to signal the event when the operation completes but not to also queue a completion packet to the I/O completion port. The calling thread can wait on an event cached by the file handle, retrieve the result, and perform the cleanup itself. This removes the callback, its coordination, and the per-operation allocation while still performing the same synchronous wait.

// Windows:
// dotnet run -c Release -f net10.0 --filter "*RandomAccessBenchmarks*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.Win32.SafeHandles;

BenchmarkSwitcher.FromAssembly(typeof(RandomAccessBenchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class RandomAccessBenchmarks
{
    private string _path = "";
    private SafeFileHandle _handle = null!;
    private readonly byte[] _buffer = new byte[4_096];

    [GlobalSetup]
    public void Setup()
    {
        _path = Path.Combine(Path.GetTempPath(), $"net11-random-access-{Guid.NewGuid():N}.tmp");
        File.WriteAllBytes(_path, new byte[1024 * 1024]);
        _handle = File.OpenHandle(_path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.Asynchronous | FileOptions.RandomAccess);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _handle.Dispose();
        File.Delete(_path);
    }

    [Benchmark]
    public int Read4K() => RandomAccess.Read(_handle, _buffer, fileOffset: 0);
}
Method Runtime Mean Ratio Allocated Alloc Ratio
Read4K .NET 10.0 4.564 μs 1.00 176 B 1.00
Read4K .NET 11.0 2.747 μs 0.60 0

There are smaller allocation wins at higher layers as well. For example, dotnet/runtime#121508 makes assigning TextWriter.NewLine to its existing value a no-op and shares arrays for the standard "\n" and "\r\n" values, avoiding a fresh char[] conversion.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly StringWriter _writer = new();

    [Benchmark]
    public void SetSameValue() => _writer.NewLine = Environment.NewLine;
}
Method Runtime Mean Ratio Allocated
SetSameValue .NET 10.0 8.281 ns 1.00 32 B
SetSameValue .NET 11.0 2.168 ns 0.26

Archive and file APIs remove similar temporary allocations. A GNU tar header has fixed-size fields for an entry’s name and link target. When either doesn’t fit, TarWriter emits an additional metadata record containing the long value. In .NET 10, TarWriter first encoded that value into a newly allocated byte array, then wrote the bytes and a null terminator into a new MemoryStream . Because the stream didn’t know the final size, it allocated and grew its own backing array as the data was written. With dotnet/runtime#123835 , .NET 11 instead computes the exact UTF-8 size including the terminator, allocates one array of that size, encodes directly into it, and constructs the MemoryStream over that array. This removes both the temporary encoded array and the stream’s growth and copying.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Formats.Tar;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly MemoryStream _destination = new();
    private readonly GnuTarEntry _entry = new(TarEntryType.RegularFile, new string('a', 256));

    [Benchmark]
    public long WriteLongName()
    {
        _destination.SetLength(0);
        using TarWriter writer = new(_destination, TarEntryFormat.Gnu, leaveOpen: true);
        writer.WriteEntry(_entry);
        return _destination.Length;
    }
}
Method Runtime Mean Ratio Allocated Alloc Ratio
WriteLongName .NET 10.0 731.1 ns 1.00 1.36 KB 1.00
WriteLongName .NET 11.0 616.7 ns 0.84 608 B 0.44

ZipArchive similarly eliminates temporary buffers. ZIP archives end with a central directory describing their entries. Reading that directory allocated a new 4 KB buffer for every ZipArchive , along with additional arrays in a few paths that needed to combine or slice data. With dotnet/runtime#123836 , .NET 11 now rents the central-directory buffer from ArrayPool<byte> and uses spans and memory in place of the additional arrays and several open-coded loops.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.IO.Compression;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private byte[] _archive = [];

    [GlobalSetup]
    public void Setup()
    {
        using MemoryStream destination = new();
        using (ZipArchive archive = new(destination, ZipArchiveMode.Create, leaveOpen: true))
        {
            ZipArchiveEntry entry = archive.CreateEntry("entry.txt");
            using Stream stream = entry.Open();
            stream.WriteByte(42);
        }

        _archive = destination.ToArray();
    }

    [Benchmark]
    public int ReadCentralDirectory()
    {
        using MemoryStream source = new(_archive, writable: false);
        using ZipArchive archive = new(source, ZipArchiveMode.Read);
        return archive.Entries.Count;
    }
}
Method Runtime Mean Ratio Allocated Alloc Ratio
ReadCentralDirectory .NET 10.0 553.5 ns 1.00 5.05 KB 1.00
ReadCentralDirectory .NET 11.0 258.1 ns 0.47 1.13 KB 0.22

As a final example, FileInfo.MoveTo performs a source-directory existence check before moving the file. It had been constructing a DirectoryInfo solely to read its Exists property, which is wasteful when Directory.Exists exists and can do it without the allocation. dotnet/runtime#123893 in .NET 11 switches to use that.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private string _directory = "";
    private string _firstPath = "";
    private string _secondPath = "";
    private FileInfo _file = null!;
    private bool _atFirstPath;

    [GlobalSetup]
    public void Setup()
    {
        _directory = Path.Combine(Path.GetTempPath(), $"net11-file-move-{Guid.NewGuid():N}");
        Directory.CreateDirectory(_directory);
        _firstPath = Path.Combine(_directory, "first.tmp");
        _secondPath = Path.Combine(_directory, "second.tmp");
        File.WriteAllBytes(_firstPath, [42]);
        _file = new(_firstPath);
        _atFirstPath = true;
    }

    [GlobalCleanup]
    public void Cleanup() => Directory.Delete(_directory, recursive: true);

    [Benchmark]
    public string MoveTo()
    {
        _file.MoveTo(_atFirstPath ? _secondPath : _firstPath, overwrite: true);
        _atFirstPath = !_atFirstPath;
        return _file.FullName;
    }
}
Method Runtime Allocated Alloc Ratio
MoveTo .NET 10.0 332 B 1.00
MoveTo .NET 11.0 236 B 0.71

.NET provides great high-level abstractions for working with all manner of data and I/O. One of the most prominent is Stream , which provides a simple, flexible mechanism for reading and writing many different data sources and formats: MemoryStream , FileStream , CryptoStream , ZLibStream , SslStream , and on and on. For folks that really care about maximizing performance, however, sometimes you want to go a bit lower-level and deal directly with the underlying primitives. For example, the various compression streams, ZLibStream , DeflateStream , GZipStream , and BrotliStream , all maintain buffers that hold input and output data waiting to be read or written. But what if the caller already owns its input and output buffers, or wants to use a pool for them? In .NET 11, dotnet/runtime#123145 exposes the underlying DeflateEncoder / DeflateDecoder , ZLibEncoder / ZLibDecoder , and GZipEncoder / GZipDecoder types, following the existing BrotliEncoder and BrotliDecoder pattern. The stream types are wrappers around these encoders and decoders, and in .NET 11 you can now use them directly. They support chunked Compress , Decompress , and Flush operations as well as one-shot TryCompress and TryDecompress , enabling callers to supply and reuse their own buffers rather than going through adapter streams and their buffers.

Networking

Networking is the bread-and-butter of many applications and sits directly on the hot path of scalable services. Improvements throughout the stack add up quickly.

At the bottom of the stack, connections and sockets establish and carry the byte stream. A socket doesn’t actually connect to a host name; it connects to an IP address and port. Resolving a host name may produce multiple candidate addresses, including one or more IPv4 addresses from DNS A records and one or more IPv6 addresses from AAAA records. When SocketAsyncEventArgs.RemoteEndPoint is a DnsEndPoint , the existing Socket.ConnectAsync implementation performs that resolution and tries the resulting addresses in sequence. It starts a connection to the first address, and only if that attempt fails does it move on to the next. This works well when the first address is reachable. A failed TCP connection isn’t always reported quickly, however. If packets sent over that route are simply dropped, the attempt may remain pending until a timeout even though another address for the same host could have connected immediately.

This problem is especially visible on machines with both IPv4 and IPv6. Clients generally want to prefer IPv6 when it works, but a broken or misconfigured IPv6 path can make an application wait through a long timeout before trying IPv4. The technique commonly known as Happy Eyeballs addresses this by overlapping connection attempts. Rather than putting all the latency of one candidate in front of the next, it starts another attempt after a short delay and uses the first connection that succeeds. The remaining attempts are then canceled or discarded. That consumes some additional resources, but it can greatly reduce the long tail of connection establishment.

In .NET 11, dotnet/runtime#106374 adds an opt-in, Happy-Eyeballs-like strategy to the static Socket.ConnectAsync overload that accepts a SocketAsyncEventArgs . The new overload accepts a ConnectAlgorithm , where ConnectAlgorithm.Default preserves the existing sequential behavior, while ConnectAlgorithm.Parallel requests the new strategy. When parallel connection is requested for an address-family-unspecified DnsEndPoint on a machine that supports both IPv4 and IPv6, .NET starts separate IPv4 and IPv6 DNS queries and runs a connection loop for each family concurrently. Addresses within each family are still tried sequentially, but the two families no longer wait on each other. The first successful connection becomes the ConnectSocket , and a connection subsequently established by the other family is disposed. If one family fails, the other is allowed to continue; the operation reports failure only after neither can connect. Parallel mode can briefly establish two connections, while the default remains cheaper when the first candidate connects promptly.

Given the nature of the change, it’s a little hard to create a real benchmark for this, but we can get creative. Here I’ve created IPv4 and IPv6 listeners on the same port, but arranged the benchmark to only accept from the IPv4 listener. On my machine, localhost resolves to ::1 before 127.0.0.1 , so the client sockets by default would first try the IPv6 address and only when it fails try the IPv4 one. The benchmark setup fills the IPv6 listener’s accept backlog, such that additional client connect requests will stall.

// Windows
// dotnet run -c Release -f net11.0 --filter "*"

using System.Net;
using System.Net.Sockets;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<Benchmarks>();

[SimpleJob(RunStrategy.Throughput, launchCount: 1, warmupCount: 2, iterationCount: 8, invocationCount: 1)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private Socket _ipv6Listener = null!;
    private Socket _ipv4Listener = null!;
    private List<Socket> _backlogClients = [];
    private CancellationTokenSource _cancellation = null!;
    private Task _ipv4AcceptLoop = null!;
    private Task? _releaseOne;
    private int _port;

    [IterationSetup(Target = nameof(Default))]
    public void SetupDefault() => Setup(releaseIPv6: true);

    [IterationSetup(Target = nameof(Parallel))]
    public void SetupParallel() => Setup(releaseIPv6: false);

    [IterationCleanup]
    public void Cleanup()
    {
        _releaseOne?.GetAwaiter().GetResult();
        _cancellation.Cancel();
        _ipv4Listener.Dispose();
        _ipv6Listener.Dispose();

        try
        {
            _ipv4AcceptLoop.GetAwaiter().GetResult();
        }
        catch { }

        foreach (Socket socket in _backlogClients)
        {
            socket.Dispose();
        }

        _cancellation.Dispose();
    }

    [Benchmark(Baseline = true)]
    public async Task Default()
    {
        using Socket socket = await ConnectAsync(ConnectAlgorithm.Default);
    }

    [Benchmark]
    public async Task Parallel()
    {
        using Socket socket = await ConnectAsync(ConnectAlgorithm.Parallel);
    }

    private void Setup(bool releaseIPv6)
    {
        if (Dns.GetHostAddresses("localhost")[0].AddressFamily != AddressFamily.InterNetworkV6)
        {
            throw new InvalidOperationException("This benchmark requires localhost to prefer IPv6.");
        }

        _ipv6Listener = new(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp)
        {
            DualMode = false,
        };
        _ipv6Listener.Bind(new IPEndPoint(IPAddress.IPv6Loopback, 0));
        _port = ((IPEndPoint)_ipv6Listener.LocalEndPoint!).Port;
        _ipv6Listener.Listen(1);

        _ipv4Listener = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _ipv4Listener.Bind(new IPEndPoint(IPAddress.Loopback, _port));
        _ipv4Listener.Listen(128);
        _cancellation = new();
        _ipv4AcceptLoop = AcceptLoopAsync(_ipv4Listener, _cancellation.Token);

        _backlogClients = [];
        bool stalled = false;
        for (int i = 0; i < 128; i++)
        {
            Socket socket = new(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
            Task connect = socket.ConnectAsync(new IPEndPoint(IPAddress.IPv6Loopback, _port));

            if (!connect.Wait(TimeSpan.FromMilliseconds(100)))
            {
                socket.Dispose();
                stalled = true;
                break;
            }

            connect.GetAwaiter().GetResult();
            _backlogClients.Add(socket);
        }

        if (!stalled)
        {
            throw new InvalidOperationException("Unable to saturate the IPv6 accept backlog.");
        }

        _releaseOne = releaseIPv6 ?
            Task.Run(async () =>
            {
                await Task.Delay(100);
                using Socket socket = await _ipv6Listener.AcceptAsync();
            }) :
            null;
    }

    private Task<Socket> ConnectAsync(ConnectAlgorithm algorithm)
    {
        TaskCompletionSource<Socket> completion = new(TaskCreationOptions.RunContinuationsAsynchronously);
        var args = new SocketAsyncEventArgs
        {
            RemoteEndPoint = new DnsEndPoint("localhost", _port),
        };

        args.Completed += Complete;
        if (!Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, args, algorithm))
        {
            Complete(null, args);
        }

        return completion.Task;

        void Complete(object? sender, SocketAsyncEventArgs e)
        {
            e.Completed -= Complete;
            if (e.SocketError == SocketError.Success)
            {
                completion.SetResult(e.ConnectSocket!);
            }
            else
            {
                completion.SetException(new SocketException((int)e.SocketError));
            }

            e.Dispose();
        }
    }

    private static async Task AcceptLoopAsync(Socket listener, CancellationToken cancellationToken)
    {
        while (true)
        {
            using Socket socket = await listener.AcceptAsync(cancellationToken);
        }
    }
}

With this setup, the parallel algorithm isn’t held up by the stalled IPv6 attempt. It connects to the IPv4 listener in just over a millisecond, whereas the default algorithm spends approximately half a second waiting for the IPv6 connection to make progress:

Method Mean Ratio
Default 511.054 ms 1.000
Parallel 1.060 ms 0.002

A bit synthetic, but it conveys the idea.

Another interesting improvement around sockets has to do with Socket.Blocking . “Berkeley sockets”, which is what all modern stacks implement, implement the notion of blocking / non-blocking modes. Typically by default, as is the case with .NET, sockets are in blocking mode. That means, for example, a recv() / Socket.Receive operation will synchronously block until data is available (or the socket closes). The other option is non-blocking; a socket in non-blocking mode will always return immediately from a recv operation, regardless of whether there’s data to read or not. If the operation would have blocked in blocking mode, in non-blocking mode it’ll instead return an error code, EAGAIN or EWOULDBLOCK, which the consuming application can then use to, for example, decide to try again later.

Enter .NET asynchronous operations. On Windows, the Windows sockets APIs provided overlapped APIs that the .NET Socket APIs can and do employ. But on Unix, we have the standard recv and friends functions. We also have mechanisms like epoll (Linux) and kqueue (macOS) that let us efficiently and synchronously wait for large numbers of file descriptors to have some activity. As such, on Unix, .NET implements asynchronous socket operations by putting a Socket into a non-blocking state, trying the synchronous operation (e.g. recv for a Socket.ReceiveAsync ), and then if the operation couldn’t complete yet and we get back an EAGAIN/EWOULDBLOCK, data about the operation gets queued into epoll / kqueue -based machinery that will signal when the operation should be retried. This is very similar conceptually to how overlapped I/O works on Windows with I/O completion ports.

Now here’s the rub. To implement asynchronous operations on sockets, we need to flip the socket into non-blocking mode… what do we then do if, say, someone does Socket.ReceiveAsync but then follows that up with Socket.Send . The socket was flipped into non-blocking mode for the first operation… do we flip it back for the second? It turns out that’s really risky to do, with race conditions making it hard and expensive to get right due to multi-threaded use (expensive because we’d need extra synchronization). When first bringing up .NET on Linux, we made the decision that the flip would be a one-way trip: once non-blocking, always non-blocking. We flip the first time an asynchronous operation is performed, and we leave it there.

What, then, do we do if someone does in fact issue a synchronous Receive / Send after it’s already been flipped? We simulate the blocking ourselves with sync over async, basically doing the asynchronous operation and blocking on it to complete. Internally we’re able to do it cheaper than actually creating a task and blocking on it, but as a mechanism it’s basically the same.

We were comfortable with this approach in the early days on the theory that if someone starts using asynchronous operations, they’re likely to continue to, and for the odd synchronous operation here and there after that, it’s not a big deal. That has largely proven out over the many years since… except for one case.

Turns out in some systems it’s reasonably common for the initial connect to be asynchronous but then followed only by synchronous sends and receives. This ends up paying that overhead on all operations: you do ConnectAsync , we flip the socket to be non-blocking, and then every Receive / Send after that ends up paying the emulation costs. But there’s good news. It turns out this is also a case where we can easily and safely flip back: we can flip back to blocking before handing back control from ConnectAsync . For the static ConnectAsync overloads, the caller won’t even have a reference to the connected Socket until ConnectAsync gives it to them, and for the instance overloads, it’s defined to be erroneous to use such send/receive operations on the Socket concurrent with ConnectAsync . As such, in all cases, we can just flip it back to blocking before completing the task representing the operation. That’s exactly what dotnet/runtime#124200 does now in .NET 11.

// Linux:
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Net;
using System.Net.Sockets;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int OperationsPerInvoke = 1_000;
    private readonly byte[] _buffer = new byte[1];
    private Socket _listener = null!;
    private Socket _client = null!;
    private Socket _server = null!;
    private Task _echoLoop = null!;

    [GlobalSetup]
    public async Task Setup()
    {
        _listener = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
        _listener.Listen(1);

        Task<Socket> accept = _listener.AcceptAsync();
        _client = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        await _client.ConnectAsync(_listener.LocalEndPoint!);
        _server = await accept;
        _echoLoop = Task.Run(EchoLoop);
    }

    [GlobalCleanup]
    public async Task Cleanup()
    {
        _client.Dispose();

        try
        {
            await _echoLoop;
        }
        catch { }

        _server.Dispose();
        _listener.Dispose();
    }

    [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
    public void SynchronousRoundTripAfterConnectAsync()
    {
        for (int i = 0; i < OperationsPerInvoke; i++)
        {
            _client.Send(_buffer);
            _client.Receive(_buffer);
        }
    }

    private void EchoLoop()
    {
        var buffer = new byte[1];
        while (_server.Receive(buffer) != 0)
            _server.Send(buffer);
    }
}

The background socket echoes each byte. When the client calls Receive before the reply is available, .NET 10 must emulate the wait with the Unix socket poller, whereas .NET 11 can wait in the native blocking recv call.

Method Runtime Mean Ratio
SynchronousRoundTripAfterConnectAsync .NET 10.0 199.2 μs 1.00
SynchronousRoundTripAfterConnectAsync .NET 11.0 161.6 μs 0.81

Windows has a different concern around its asynchronous socket operations. I mentioned that Windows supplies functions that utilize overlapped I/O. These APIs, e.g. AcceptEx , ConnectEx , DisconnectEx , and WSARecvMsg , are extension functions supplied by the installed Winsock provider. .NET looks up their function pointers dynamically and caches them based on the socket’s address family, socket type, and protocol. Each Socket instance consults that cache the first time it needs one of these functions. In .NET 10, that cache was a small global List<T> guarded by a lock. The list rarely contains more than a handful of entries and almost every lookup finds an entry that was initialized earlier, but even those read-only hits acquired the same lock. When many sockets began their first asynchronous operation concurrently, all of those lookups were forced through the lock one at a time. dotnet/runtime#124997 changes the cache to a copy-on-write array. The common read path takes a snapshot of the array and scans it without locking. A miss still acquires a lock, double-checks the latest array, and publishes a new array containing the additional entry. Because entries are added only when a new combination of address family, socket type, and protocol is encountered, misses are rare and the warmed path no longer serializes.

Once you have the socket connection, often the next step is to layer in TLS, with SslStream . During client-certificate negotiation, a server can include in its CertificateRequest message the distinguished names of certificate authorities whose certificates it will accept. SslStream turns each encoded X.500 name into an X500DistinguishedName , ultimately making the names available to certificate-selection logic. In .NET 10, that involved allocating a byte[] for every name. On Windows, the implementation created a span over the native SSPI buffer and then called ToArray ; on macOS, it copied each Core Foundation CFData value into a new managed array. The X500DistinguishedName(ReadOnlySpan<byte>) constructor has existed since .NET 5, but both of these SslStream paths predated it and weren’t updated when it was added. With dotnet/runtime#123904 , .NET 11 removes those intermediate arrays. Both implementations instead pass a ReadOnlySpan<byte> over the native encoding directly to the X500DistinguishedName constructor. The macOS implementation keeps the CFData handle alive while that span is in use, but the per-authority managed copy is no longer needed.

Once a client certificate has been selected, macOS requires SslStream to package the native handles for the leaf certificate and its intermediate certificates into a Core Foundation array. In .NET 10, SslStream first allocated an IntPtr[] large enough for the entire chain, populated it with those handles, and then used the array to create the native CFArray . dotnet/runtime#123905 in .NET 11 changes the interop layer to accept a ReadOnlySpan<IntPtr> instead. SslStream builds the handle list in a Span<IntPtr> , using stackalloc for chains of up to 128 certificates and falling back to a managed array only for larger chains. Typical certificate chains are far smaller than that, so the usual setup path no longer allocates the temporary IntPtr[] at all.

A larger Linux change removes copies from the steady-state encrypted-data path. SslStream uses OpenSSL, and OpenSSL traditionally exchanges data with its caller through in-memory buffers known as BIOs. In .NET 10, encryption first wrote ciphertext into an OpenSSL memory BIO, after which .NET copied it into the buffer to send. Decryption went in the other direction: .NET copied received ciphertext into a memory BIO, and after OpenSSL decrypted it, SslStream copied the plaintext from its own buffer into the caller’s buffer.

dotnet/runtime#128245 replaces those memory BIOs with a custom BIO that can point directly at managed buffers. In .NET 11, OpenSSL can write encrypted output directly into the buffer SslStream will send and, in the common case, write decrypted plaintext directly into the buffer supplied by the caller. The change also combines the setup, OpenSSL operation, and cleanup into one native call rather than four. OpenSSL still performs its own internal TLS processing, and SslStream retains a fallback buffer for unusual cases such as TLS alerts or output that doesn’t fit, but the normal application-data path avoids the extra staging copies.

The following benchmark provides a way to reproduce the impact using only public APIs. It establishes a TLS 1.3 connection once, outside the measurement, and then sends one 16-KB TLS record in each direction:

// Linux:
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int MessageSize = 16 * 1024;

    private readonly byte[] _sendBuffer = new byte[MessageSize];
    private readonly byte[] _receiveBuffer = new byte[MessageSize];
    private RSA _rsa = null!;
    private X509Certificate2 _certificate = null!;
    private SslStream _client = null!;
    private SslStream _server = null!;

    [GlobalSetup]
    public async Task Setup()
    {
        Random.Shared.NextBytes(_sendBuffer);

        _rsa = RSA.Create(2048);
        var request = new CertificateRequest("CN=localhost", _rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        using X509Certificate2 temporary = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1));
        _certificate = X509CertificateLoader.LoadPkcs12(temporary.Export(X509ContentType.Pfx), password: null, X509KeyStorageFlags.Exportable);

        using TcpListener listener = new(IPAddress.Loopback, 0);
        listener.Start();

        Socket clientSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        {
            NoDelay = true,
        };
        Task<Socket> accept = listener.AcceptSocketAsync();
        await clientSocket.ConnectAsync(listener.LocalEndpoint);
        Socket serverSocket = await accept;
        serverSocket.NoDelay = true;

        _client = new(new NetworkStream(clientSocket, ownsSocket: true), leaveInnerStreamOpen: false, (_, _, _, _) => true);
        _server = new(new NetworkStream(serverSocket, ownsSocket: true), leaveInnerStreamOpen: false);

        using CancellationTokenSource timeout = new(TimeSpan.FromSeconds(30));
        Task clientAuthentication = _client.AuthenticateAsClientAsync(
            new SslClientAuthenticationOptions
            {
                TargetHost = "localhost",
                EnabledSslProtocols = SslProtocols.Tls13,
            },
            timeout.Token);
        Task serverAuthentication = _server.AuthenticateAsServerAsync(
            new SslServerAuthenticationOptions
            {
                ServerCertificate = _certificate,
                EnabledSslProtocols = SslProtocols.Tls13,
            },
            timeout.Token);

        await Task.WhenAll(clientAuthentication, serverAuthentication);
    }

    [Benchmark]
    public async Task RoundTrip()
    {
        await _client.WriteAsync(_sendBuffer);
        await _server.ReadExactlyAsync(_receiveBuffer);

        await _server.WriteAsync(_sendBuffer);
        await _client.ReadExactlyAsync(_receiveBuffer);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _client.Dispose();
        _server.Dispose();
        _certificate.Dispose();
        _rsa.Dispose();
    }
}

On Ubuntu 24.04 x64 under WSL 2, the 16-KB round trip improves by 16%:

Method Runtime Mean Ratio
RoundTrip .NET 10.0 68.04 μs 1.00
RoundTrip .NET 11.0 57.39 μs 0.84

Moving up the stack, HttpClient ‘s core HTTP implementation in SocketsHttpHandler layers protocol processing on top of TLS and the underlying sockets. With AutomaticDecompression enabled, SocketsHttpHandler advertises supported encodings on the request, checks the response’s final Content-Encoding , and, when it recognizes gzip, deflate, or Brotli, presents an HttpContent whose stream decodes the compressed transport bytes as the caller reads them. dotnet/runtime#122676 precomputes the combined Accept-Encoding value when the handler is created. In the common case where the caller hasn’t supplied that header, it adds the combined value directly, avoiding an HttpHeaderValueCollection , its backing list and header-storage object, and an enumeration of the collection for each enabled algorithm. On the response side, TryGetValues avoids materializing a collection when there is no Content-Encoding . When decompression is needed, the wrapper takes ownership of the original content-header collection, removes the now-invalid Content-Length and the encoding it consumes, and retains any preceding encodings without copying every header into a new collection.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.IO.Compression;
using System.Net;
using System.Net.Sockets;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD","Mean","Ratio")]
public class Benchmarks
{
    private TcpListener _listener = new(IPAddress.Loopback, 0);
    private CancellationTokenSource _cts = new();
    private Task _server = null!;
    private HttpClient _client = null!;

    [GlobalSetup]
    public async Task Setup()
    {
        _listener.Start();
        _server = ServeAsync(_cts.Token);
        _client = new(new SocketsHttpHandler { AutomaticDecompression = DecompressionMethods.GZip })
        {
            BaseAddress = new Uri($"http://127.0.0.1:{((IPEndPoint)_listener.LocalEndpoint).Port}")
        };

        await GetAsync();
    }

    [Benchmark]
    public async Task<int> GetAsync()
    {
        using HttpResponseMessage response = await _client.GetAsync("/", HttpCompletionOption.ResponseHeadersRead);
        await response.Content.CopyToAsync(Stream.Null);
        return (int)response.StatusCode;
    }

    [GlobalCleanup]
    public async Task Cleanup()
    {
        _client.Dispose();
        _cts.Cancel();

        try
        {
            await _server;
        }
        catch { }

        _listener.Stop();
        _cts.Dispose();
    }

    private async Task ServeAsync(CancellationToken cancellationToken)
    {
        byte[] body = Compress(new byte[1024]);
        byte[] headers = Encoding.ASCII.GetBytes(
            $"HTTP/1.1 200 OK\r\nContent-Encoding: gzip\r\n" +
            $"Content-Length: {body.Length}\r\n\r\n");

        while (true)
        {
            using TcpClient connection = await _listener.AcceptTcpClientAsync(cancellationToken);
            NetworkStream stream = connection.GetStream();
            byte[] request = new byte[4096];

            while (await ReadRequestAsync(stream, request, cancellationToken))
            {
                await stream.WriteAsync(headers, cancellationToken);
                await stream.WriteAsync(body, cancellationToken);
            }
        }
    }

    private static async Task<bool> ReadRequestAsync(Stream stream, byte[] buffer, CancellationToken cancellationToken)
    {
        int length = 0;
        while (length < buffer.Length)
        {
            int read = await stream.ReadAsync(buffer.AsMemory(length), cancellationToken);
            if (read == 0)
                return false;

            length += read;
            if (buffer.AsSpan(0, length).IndexOf("\r\n\r\n"u8) >= 0)
            {
                return true;
            }
        }

        throw new InvalidOperationException("Request headers are too large.");
    }

    private static byte[] Compress(byte[] data)
    {
        using MemoryStream output = new();
        using (GZipStream gzip = new(output, CompressionLevel.SmallestSize, leaveOpen: true))
        {
            gzip.Write(data);
        }

        return output.ToArray();
    }
}
Method Runtime Allocated Alloc Ratio
GetAsync .NET 10.0 3.44 KB 1.00
GetAsync .NET 11.0 2.87 KB 0.83

SocketsHttpHandler saw other improvements. HTTP content often consists of a single value, but sometimes a request or response needs to carry several independent pieces together in one body. Multipart content provides that packaging. For example, an HTML form submission might contain a few text fields and a file; each becomes a separate part with its own headers and content, while the collection of parts is sent as one HTTP message body. The receiver needs to know where one part ends and the next begins, so the message uses a boundary: a token chosen to be unlikely to occur in the content itself. In .NET 10, MultipartContent retained the boundary as a string. Each time the content was serialized, it rebuilt the opening and closing delimiter strings, encoded them into bytes, and separately wrote the pieces of the delimiters between parts. In .NET 11, dotnet/runtime#124963 instead constructs and encodes the opening and closing delimiters once, when the MultipartContent is created. The serialization paths can then reuse and directly write those cached bytes.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Net.Http;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private readonly MultipartContent _content = new("mixed", "net11-boundary");

    [Benchmark]
    public Task Serialize() => _content.CopyToAsync(Stream.Null);
}
Method Runtime Mean Ratio Allocated Alloc Ratio
Serialize .NET 10.0 78.49 ns 1.00 296 B 1.00
Serialize .NET 11.0 41.90 ns 0.53 64 B 0.22

Several allocation reductions remove collections created only to populate or inspect another collection. For example, dotnet/runtime#122677 writes HTTP/3 trailers directly into the final HttpResponseHeaders collection, eliminating a temporary List of tuples. Trailers are headers sent after the response body, commonly carrying information such as checksums that isn’t known when the initial headers are written.

Other paths only need a transient view over existing storage. dotnet/runtime#131142 has SocketsHttpHandler inspect available HTTP/2 and HTTP/3 connections through spans, avoiding a copy of each list to an array during idle-connection eviction. dotnet/runtime#123034 similarly changes HeaderUtilities.DumpHeaders , which is used as part of ToString on header collections, to take a params ReadOnlySpan<HttpHeaders?> , removing a small array allocation from HttpRequestMessage.ToString() and HttpResponseMessage.ToString() .

Improvements in .NET 11 also show up for Uri . Consider https://user@example.com:8443/files/report%20Q3?q=%E4%BD%A0%E5%A5%BD#summary . Before Uri can expose Scheme , UserInfo , Host , Port , AbsolutePath , Query , and Fragment , it first locates delimiters such as : , / , @ , ? , and # . It then validates each delimited component. ASCII can usually remain as-is, %20 needs unescaping or preservation according to the component, and the percent-encoded UTF-8 in the query needs decoding and Unicode-aware canonicalization. With dotnet/runtime#124433 , .NET 11 uses IndexOfAny and SearchValues for more of the delimiter-finding work, examining long spans a vector at a time rather than character by character. And once the component boundaries are known, dotnet/runtime#119435 replaces repeated reserved-character and unsafe-character tests with a single optimized SearchValues lookup.

// dotnet run -c Release -f net10.0 --filter "*UriScanningBenchmarks*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(UriScanningBenchmarks).Assembly).Run(args);

[MemoryDiagnoser(false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class UriScanningBenchmarks
{
    private readonly string _longHost = $"https://{new string('a', 64)}.example.com/path";
    private readonly string _escapedAscii =
        "https://example.com/" +
        string.Concat(Enumerable.Range('a', 26).Select(i => $"%{i:X2}"));

    [Benchmark]
    public Uri LongHost() => new(_longHost);

    [Benchmark]
    public Uri EscapedAscii() => new(_escapedAscii);
}
Method Runtime Mean Ratio Allocated Alloc Ratio
LongHost .NET 10.0 218.1 ns 1.00 56 B 1.00
LongHost .NET 11.0 112.9 ns 0.52 56 B 1.00
EscapedAscii .NET 10.0 442.6 ns 1.00 448 B 1.00
EscapedAscii .NET 11.0 208.1 ns 0.47 368 B 0.82

After finding a component, Uri checks whether its text is already in canonical form or needs to be escaped or normalized. Letters and digits are by far the most common characters, but in .NET 10 they still flowed through the more general character tests. Some callers could also repeat a canonicalization check whose answer parsing had already established. dotnet/runtime#121270 adds a fast path for ASCII letters and digits and records the earlier result so that .NET 11 can avoid performing the same check again.

// dotnet run -c Release -f net10.0 --filter "*UriCanonicalizationBenchmarks*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(UriCanonicalizationBenchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class UriCanonicalizationBenchmarks
{
    private const string Address = "https://example.com/api/items/42?view=summary#details";

    [Benchmark]
    public Uri Parse() => new(Address);
}
Method Runtime Mean Ratio Allocated Alloc Ratio
Parse .NET 10.0 61.86 ns 1.00 56 B 1.00
Parse .NET 11.0 44.64 ns 0.72 56 B 1.00

Non-ASCII input can require several parts of a URI to be normalized. For example, Unicode characters may need to be preserved or percent-encoded differently depending on whether they occur in the path, query, or fragment. In .NET 10, parsing and rebuilding were interleaved: Uri normalized each of those components separately and repeatedly extended its stored string as it went. In addition to making the offset bookkeeping complicated, those individual normalization results and string concatenations could create roughly five temporary strings. In .NET 11, dotnet/runtime#122038 separates that rebuilding work from the subsequent validation. Uri normalizes the path, query, and fragment into one builder, creates the final string once, and then validates the component boundaries in that completed string. The host is still handled separately, but the remaining components no longer each produce intermediate strings.

// dotnet run -c Release -f net10.0 --filter "*UriNormalizationBenchmarks*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(UriNormalizationBenchmarks).Assembly).Run(args);

[MemoryDiagnoser(false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class UriNormalizationBenchmarks
{
    private const string Address =
        "https://dot.net/abc/defghijklmno/pqrstuv/wxyz" +
        "?arch=x64&os=linux&type=release#hello\uD83C\uDF49";

    [Benchmark]
    public Uri Parse() => new(Address);
}
Method Runtime Mean Ratio Allocated Alloc Ratio
Parse .NET 10.0 547.6 ns 1.00 936 B 1.00
Parse .NET 11.0 386.9 ns 0.71 432 B 0.46

JSON

JSON readers and writers spend much of their time scanning text: writers look for characters that need escaping, while readers look for whitespace and token boundaries. .NET 11 makes several of those scans more efficient.

When using the default encoder, Utf8JsonWriter needs to locate characters such as quotation marks and control characters that can’t be copied directly into JSON. In .NET 10, that search was routed through JavaScriptEncoder.Default . dotnet/runtime#129781 instead gives .NET 11 precomputed SearchValues sets for the default escaping rules, allowing the writer to search the input directly. Once it finds a character to escape, the writer must emit a sequence such as \" or \u0022 . In .NET 10, the escaping helper received the entire remaining destination and performed repeated bounds checks as it wrote each byte or character. dotnet/runtime#129803 passes only the range known to be writable. The JIT can then prove once that the escape fits and remove the checks from the individual stores.

// dotnet run -c Release -f net10.0 --filter "*JsonWriterBenchmarks*" --runtimes net10.0 net11.0

using System.Text.Json;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(JsonWriterBenchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class JsonWriterBenchmarks
{
    private static readonly string s_fullyEscaped = new('"', 2_048);

    [Benchmark]
    public byte[] Write() => JsonSerializer.SerializeToUtf8Bytes(s_fullyEscaped);
}
Method Runtime Mean Ratio
Write .NET 10.0 30.83 μs 1.00
Write .NET 11.0 7.875 μs 0.26

On the reading side, insignificant whitespace is allowed between JSON tokens. Indented documents can contain long runs of spaces and newlines, and in .NET 10 Utf8JsonReader examined those bytes one at a time. dotnet/runtime#129701 changes SkipWhiteSpace to use IndexOfAnyExcept with a SearchValues set containing the four JSON whitespace bytes. .NET 11 can therefore skip a whole run at once, stopping at the next byte that might begin a token.

// dotnet run -c Release -f net10.0 --filter "*JsonReaderBenchmarks*" --runtimes net10.0 net11.0

using System.Text.Json;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(JsonReaderBenchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class JsonReaderBenchmarks
{
    private static readonly Payload s_value = new(new string('a', 2_048), Enumerable.Range(0, 256).ToArray());
    private static readonly byte[] s_json = JsonSerializer.SerializeToUtf8Bytes(s_value, new JsonSerializerOptions { WriteIndented = true });

    [Benchmark]
    public int Read()
    {
        Utf8JsonReader reader = new(s_json);
        int tokens = 0;
        while (reader.Read()) tokens++;
        return tokens;
    }

    private sealed record Payload(string Message, int[] Values);
}
Method Runtime Mean Ratio
Read .NET 10.0 7.418 μs 1.00
Read .NET 11.0 5.945 μs 0.80

Diagnostics

Creating an Activity , polling metrics, and logging all add overhead beyond what the application is otherwise trying to accomplish. That cost is deliberately paid to make production systems understandable, but observability code also sits on paths that can execute for every request, dependency call, or log event. Small fixed costs there can really add up, and disabled or unobserved instrumentation needs to be “pay for play” so applications don’t incur meaningful costs for diagnostics they aren’t currently collecting.

Let’s start with distributed tracing. A trace follows a request as it travels through an application and potentially across multiple services. Each operation along the way can be represented by an Activity ; the activities have their own span IDs, but share a trace ID that lets a tracing system correlate them as parts of the same request. The W3C Trace Context standard defines how those identifiers are carried between services, including in an HTTP traceparent header. Its trace ID is represented as 32 lowercase hexadecimal characters, and it can’t be all zeroes. Applications may need to parse and validate that identifier for every request. In .NET 10, DiagnosticSource did so with a loop that checked each character both for whether it was hexadecimal and whether it was non-zero. In .NET 11, dotnet/runtime#119673 replaces that loop with two ContainsAnyExcept searches: one detects a character outside 0 9 and a f , while the other determines whether the entire ID is zeroes. Those searches can examine multiple characters at a time. W3CPropagator had similar hand-written loops for validating trace-state and baggage characters. In addition to replacing those loops with SearchValues<char> , the same PR changes baggage encoding to search for the first character that requires escaping. If there isn’t one, as is common, it can append the whole value at once rather than checking and appending every character individually.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Diagnostics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly DistributedContextPropagator s_propagator =
        DistributedContextPropagator.CreateW3CPropagator();
    private readonly Activity _activity = new("Test");

    [GlobalSetup]
    public void Setup()
    {
        _activity.AddBaggage("a", "aaaaabbbbbcccccddddd");
        _activity.Start();
    }

    [Benchmark]
    public void ExtractTraceParent() =>
        s_propagator.ExtractTraceIdAndState(
            null,
            static (object? carrier, string name, out string? value, out IEnumerable<string>? values) =>
            {
                value = name == "traceparent" ? "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01" : null;
                values = null;
            },
            out _, out _);

    [Benchmark]
    public void InjectBaggage() => s_propagator.Inject(_activity, null, static (object? carrier, string name, string value) => { });
}
Method Runtime Mean Ratio
ExtractTraceParent .NET 10.0 45.41 ns 1.00
ExtractTraceParent .NET 11.0 9.137 ns 0.20
InjectBaggage .NET 10.0 96.69 ns 1.00
InjectBaggage .NET 11.0 47.895 ns 0.50

Process APIs present a different kind of diagnostics overhead. Launching a process requires translating managed arguments and environment variables into the representation expected by the operating system, while inspection often crosses into native APIs to retrieve only a small piece of information.

At the lowest level, a new process on Unix receives its command-line arguments and environment as argv and envp . Each is a null-terminated array of pointers to null-terminated strings; the entries in argv are the executable and its arguments, while each entry in envp has the form key=value . ProcessStartInfo , however, exposes managed strings and a managed environment dictionary, so Process.Start needs to marshal all of that data into the native representation. In .NET 10, building envp first concatenated every key and value into a new managed key=value string and collected those strings into an intermediate array. Both argv and envp were then constructed with a native allocation for the pointer array and another allocation for each UTF-8 string. With dotnet/runtime#126201 , .NET 11 instead makes one pass to count the pointers and calculate the total number of UTF-8 bytes required. It then allocates one native block for argv and one for envp , with each block containing both its pointer table and all of its string data, and writes the data directly into those blocks. That avoids the intermediate managed strings and array, as well as all of the per-string native allocations and frees.

There’s then the question of how the operating system actually creates the process. The traditional Unix model uses fork to create a child that is initially a logical copy of the parent, followed by exec in the child to replace that copy with the requested executable. Copy-on-write means fork doesn’t immediately copy all of the parent’s memory, but the operating system still needs to duplicate process state and page tables, work that can become significant for a large, multithreaded application. In .NET 10, Process.Start used this fork -then- exec path on macOS. With dotnet/runtime#126063 , .NET 11 uses posix_spawn for the common case. posix_spawn asks the operating system to create the new process and load its executable as one operation, while still describing the required standard-input/output/error redirection, working directory, and signal state. A launch that requests different user or group credentials still uses fork and exec , as macOS’s posix_spawn facilities can’t perform the required setuid and setgid operations.

// Run on Linux and macOS:
// dotnet run -c Release -f net10.0 --filter "*ProcessLaunchBenchmarks*" --runtimes net10.0 net11.0

using System.Diagnostics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(
    typeof(ProcessLaunchBenchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class ProcessLaunchBenchmarks
{
    private readonly ProcessStartInfo _plain = CreateStartInfo();
    private readonly ProcessStartInfo _withEnvironment = CreateStartInfo(includeEnvironment: true);

    [Benchmark]
    public void StartWithEnvironment() => StartAndWait(_withEnvironment);

    [Benchmark]
    public void StartAndWaitForExit() => StartAndWait(_plain);

    private static ProcessStartInfo CreateStartInfo(bool includeEnvironment = false)
    {
        ProcessStartInfo psi = new("whoami")
        {
            RedirectStandardOutput = true,
            UseShellExecute = false,
        };

        if (includeEnvironment)
        {
            for (int i = 0; i < 256; i++)
                psi.Environment[$"NET11PERF_{i}"] = new string('x', 32);
        }

        return psi;
    }

    private static void StartAndWait(ProcessStartInfo psi)
    {
        using Process process = Process.Start(psi)!;
        process.WaitForExit();
    }
}

Process creation dominates the elapsed time in this benchmark, but the environment-marshalling allocation reduction is clear.

Method Runtime Mean Ratio Allocated Alloc Ratio
StartWithEnvironment .NET 10.0 1.484 ms 1.00 48.16 KB 1.00
StartWithEnvironment .NET 11.0 1.435 ms 0.97 14.48 KB 0.30
StartAndWaitForExit .NET 10.0 1.371 ms 1.00 16.95 KB 1.00
StartAndWaitForExit .NET 11.0 1.362 ms 0.99 14.48 KB 0.85

Once a process is running, a Process instance can expose a bunch of information about it. Much of that OS data is gathered and cached together in an internal ProcessInfo object so that properties needing it can share the work. In .NET 10 on Linux and macOS, however, asking only for ProcessName triggered the machinery to populate the whole object and everything on it, which was unnecessarily costly if you only needed the name. Process.ToString() includes the process name, so it incurred the same cost. In .NET 11, dotnet/runtime#126449 from @tmds adds a narrower operating-system query for the name. ProcessName and ToString() can use that to query without collecting the rest of the process metadata.

// Run on Linux and macOS:
// dotnet run -c Release -f net10.0 --filter "*ProcessNameBenchmarks*" --runtimes net10.0 net11.0

using System.Diagnostics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(ProcessNameBenchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class ProcessNameBenchmarks
{
    [Benchmark]
    public string GetProcessName()
    {
        using Process process = Process.GetProcessById(Environment.ProcessId);
        return process.ProcessName;
    }
}
Method Runtime Mean Ratio
GetProcessName .NET 10.0 332.13 μs 1.00
GetProcessName .NET 11.0 11.90 μs 0.04

Process can also query processes on another Windows machine. APIs such as GetProcesses(string machineName) accept a machine name, and the remote path uses Windows performance-counter infrastructure to retrieve the information. That support in turn depends on additional components, including remote Registry access. None of that should be necessary for an application that only starts or inspects processes on its own machine. In .NET 10, however, several local-only APIs delegated to overloads that also supported remote machines. For example, GetProcessById(int) called the machine-name overload with "." , and other helpers selected between local and remote implementations at run time. Even when the application always took the local branch, the trimmer saw a call path to both implementations and needed to preserve the remote-process and PerformanceCounter code. As a result, even a Native AOT application that did little more than call Process.Start could carry that unused support in its executable. In .NET 11, dotnet/runtime#126338 gives the local APIs dedicated paths that don’t reference the remote implementation. The remote implementation is instead reached through a delegate initialized only when an API is actually asked to operate on another machine. Remote process inspection continues to work, but if an application uses only local process APIs, .NET 11’s trimmer can now prove that the remote machinery and its dependencies are unreachable and remove them, resulting in significantly smaller binary size.

// Add to the csproj's PropertyGroup:
//     <PublishAot>true</PublishAot>
//     <InvariantGlobalization>true</InvariantGlobalization>
//     <AssemblyName>ProcessSize</AssemblyName>

using System.Diagnostics;

using Process process = Process.Start(new ProcessStartInfo("cmd.exe", "/c exit")
{
    UseShellExecute = false
})!;
process.WaitForExit();

You can then publish both targets and inspect the resulting executable:

# dotnet publish -c Release -f net10.0 -r win-x64 -o publish-net10
# dotnet publish -c Release -f net11.0 -r win-x64 -o publish-net11
# Get-Item .\publish-net10\ProcessSize.exe, .\publish-net11\ProcessSize.exe |
#     ForEach-Object { "$($_.Directory.Name): $($_.Length) bytes" }
Runtime Executable size
.NET 10.0 1,599,488 bytes
.NET 11.0 1,326,080 bytes

Metrics report numerical information about an application, such as the number of requests processed or the current depth of a queue. With System.Diagnostics.Metrics , a Meter creates instruments that produce those measurements, and a listener such as an OpenTelemetry provider consumes them. Some instruments are updated by the application whenever an event occurs. An observable instrument instead registers a callback that computes its current value when a listener asks to collect it. That pull model is useful for values like queue depth: the application doesn’t need to record every change, only to report the depth when it’s observed. The callback for an ObservableGauge<T> , ObservableCounter<T> , or ObservableUpDownCounter<T> can return a T , a Measurement<T> , or an IEnumerable<Measurement<T>> . A Measurement<T> pairs the value with any associated tags, and the enumerable form allows one callback to report multiple tagged values. The first two forms always produce exactly one measurement. In .NET 10, ObservableInstrument<T> nevertheless normalized those single-value forms into the enumerable model. Every time a listener collected the instrument, it invoked the callback, put the result into a new one-element Measurement<T>[] , and then enumerated that array to report the value. With dotnet/runtime#128039 from @unsafePtr , .NET 11 recognizes the built-in single-value forms and sends their result directly to MeterListener.NotifyMeasurement , avoiding both the array and its enumeration. The enumerable form retains its existing path: the application owns that sequence, and it may legitimately contain any number of measurements.

// dotnet run -c Release -f net10.0 --filter "*ObservableBenchmarks*" --runtimes net10.0 net11.0

using System.Diagnostics.Metrics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(ObservableBenchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class ObservableBenchmarks
{
    private int _queueLength = 42;
    private Meter _meter = new("Sample.Service");
    private ObservableGauge<int> _gauge = null!;
    private MeterListener _listener = new();

    [GlobalSetup]
    public void Setup()
    {
        _gauge = _meter.CreateObservableGauge("queue.length", () => _queueLength);
        _listener.InstrumentPublished = (instrument, listener) =>
        {
            if (instrument.Meter == _meter)
                listener.EnableMeasurementEvents(instrument);
        };
        _listener.SetMeasurementEventCallback<int>( static (instrument, measurement, tags, state) => { });
        _listener.Start();
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _listener.Dispose();
        _meter.Dispose();
    }

    [Benchmark]
    public void Record() => _listener.RecordObservableInstruments();
}
Method Runtime Mean Ratio Allocated Alloc Ratio
Record .NET 10.0 17.16 ns 1.00 72 B 1.00
Record .NET 11.0 4.511 ns 0.26 0

In previous iterations of Performance Improvements in .NET, I’ve discussed “false sharing.” Modern processors move data between memory and their caches in fixed-size chunks known as cache lines, commonly 64 bytes. Before a core can write to a location, it needs exclusive ownership of the cache line containing that location, invalidating copies of the same line held by other cores. That matters even when the cores aren’t updating the same value. Imagine two long fields next to each other in memory, with one core repeatedly updating the first and another core repeatedly updating the second. The fields are logically independent, but if they occupy the same cache line, each core’s write invalidates the line for the other. Ownership of the line continually bounces between the cores, limiting scalability despite there being no sharing conceptually. Hence, “false sharing.” System.Runtime.Caching.MemoryCache maintains performance counters for operations such as gets, hits, misses, adds, removes, and trims. In .NET 10, those counters were stored as elements in a small long[] . The array header, including its length, and several unrelated counters could all occupy the same cache line. Under load, cores performing different cache operations would therefore contend for ownership of that line as they updated different counters. Accessing a counter through the array also meant loading the array length for a bounds check. dotnet/runtime#131470 addresses this in .NET 11 by replacing the array with named fields and laying those fields out across separate cache lines. Counters that an operation naturally updates together can remain together, while unrelated counters are kept apart. That deliberately spends a small amount of additional memory on padding in order to reduce cache-line bouncing under contention, while the named fields also avoid the array bounds checks.

// Run separately so each target uses its matching System.Runtime.Caching package:
// dotnet run -c Release -f net10.0 --filter "*"
// dotnet run -c Release -f net11.0 --filter "*"

using System.Runtime.Caching;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int ThreadCount = 32;
    private const int TotalOperations = 256_000;
    private readonly MemoryCache _cache = new("Benchmark");

    [GlobalSetup]
    public void Setup() => _cache.Set("key", 42, DateTimeOffset.MaxValue);

    [GlobalCleanup]
    public void Cleanup() => _cache.Dispose();

    [Benchmark(OperationsPerInvoke = TotalOperations)]
    public void Get()
    {
        Parallel.For(0, ThreadCount, new ParallelOptions
        {
            MaxDegreeOfParallelism = ThreadCount
        }, _ =>
        {
            for (int i = 0; i < TotalOperations / ThreadCount; i++)
                _cache.Get("key");
        });
    }
}
Method Runtime Mean Ratio
Get .NET 10.0 75.83 ns 1.00
Get .NET 11.0 51.76 ns 0.68

Logging is another per-event diagnostics path. Microsoft.Extensions.Logging’s EventSource provider shrank its cost when the JsonMessage keyword is on. dotnet/runtime#131229 reuses a [ThreadStatic] MemoryStream and Utf8JsonWriter in EventSourceLogger.ToJson , avoiding both allocations on every logged event, leaving primarily the returned JSON string. Buffers larger than 1 KB aren’t retained on the thread.

// Add a FrameworkReference to Microsoft.AspNetCore.App.
// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Diagnostics.Tracing;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.Extensions.Logging;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private JsonLoggingListener _listener = null!;
    private ILoggerFactory _factory = null!;
    private ILogger _logger = null!;

    [GlobalSetup]
    public void Setup()
    {
        _listener = new JsonLoggingListener();
        _factory = LoggerFactory.Create(builder => builder.AddEventSourceLogger());
        _logger = _factory.CreateLogger("Sample");
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _factory.Dispose();
        _listener.Dispose();
    }

    [Benchmark]
    public void Log() => _logger.LogInformation("Processed {Count} items for {Customer}", 42, "Contoso");

    private sealed class JsonLoggingListener : EventListener
    {
        protected override void OnEventSourceCreated(EventSource eventSource)
        {
            if (eventSource.Name == "Microsoft-Extensions-Logging")
                EnableEvents(eventSource, EventLevel.LogAlways, (EventKeywords)8); // JsonMessage
        }
    }
}
Method Runtime Mean Ratio Allocated Alloc Ratio
Log .NET 10.0 506.4 ns 1.00 1.92 KB 1.00
Log .NET 11.0 400.5 ns 0.79 1.15 KB 0.60

Cryptography

ASN.1 is the binary data-description format used by certificates, public and private keys, and many other cryptographic structures. Its encodings are nested: reading a sequence produces another reader over the sequence’s contents, which may itself contain more sequences. dotnet/runtime#125254 adds ValueAsnReader , a span-based ref struct counterpart to AsnReader . dotnet/runtime#125346 further applies that representation to selected RSA, PKCS/CMS, ECC, and X.509 decoders. And dotnet/runtime#125528 carries it through generated key loaders so parsing layers can pass views of the original data by reference rather than wrapping the same bytes in new reader objects.

// dotnet run -c Release -f net11.0 --filter "*"

using BenchmarkDotNet.Running;

using System.Formats.Asn1;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly byte[] s_der =
    [
        0x30, 0x0F,
        0x30, 0x03, 0x02, 0x01, 0x01,
        0x30, 0x03, 0x02, 0x01, 0x02,
        0x30, 0x03, 0x02, 0x01, 0x03,
    ];

    [Benchmark(Baseline = true)]
    public int ReadWithEscapingAsnReader()
    {
        AsnReader outer = CreateReader();
        AsnReader sequence = ReadSequence(outer);
        int count = 0;

        while (sequence.HasData)
        {
            AsnReader child = ReadSequence(sequence);
            _ = child.ReadIntegerBytes();
            child.ThrowIfNotEmpty();
            count++;
        }

        outer.ThrowIfNotEmpty();
        return count;
    }

    [Benchmark]
    public int ReadWithValueAsnReader()
    {
        ValueAsnReader outer = new(s_der, AsnEncodingRules.DER);
        ValueAsnReader sequence = outer.ReadSequence();
        int count = 0;

        while (sequence.HasData)
        {
            ValueAsnReader child = sequence.ReadSequence();
            _ = child.ReadIntegerBytes();
            child.ThrowIfNotEmpty();
            count++;
        }

        outer.ThrowIfNotEmpty();
        return count;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static AsnReader CreateReader() => new(s_der, AsnEncodingRules.DER);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static AsnReader ReadSequence(AsnReader reader) => reader.ReadSequence();
}
Method Mean Ratio Allocated Alloc Ratio
ReadWithEscapingAsnReader 79.81 ns 1.00 240 B 1.00
ReadWithValueAsnReader 31.05 ns 0.39 0.00

Some ASN.1 values add text validation to that parsing work. ASN.1 defines several text types with restricted character sets. IA5String is ASCII, while VisibleString permits the printable ASCII characters from space through ~ . Encoding or decoding one must both copy the data and reject characters outside the allowed range. dotnet/runtime#131109 vectorizes that validation and transcoding for IA5String and VisibleString , checking and copying multiple characters at once. dotnet/runtime#131170 then applies the same approach to big-endian UCS-2 BMPString .

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Formats.Asn1;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly string s_text = new('A', 1024);

    private readonly char[] _destination = new char[1024];
    private readonly AsnWriter _writer = new(AsnEncodingRules.DER);
    private readonly byte[] _encoded = EncodeText();

    [Benchmark]
    public int Read()
    {
        AsnDecoder.TryReadCharacterString(
            _encoded,
            _destination,
            AsnEncodingRules.DER,
            UniversalTagNumber.VisibleString,
            out _,
            out int charsWritten);
        return charsWritten;
    }

    [Benchmark]
    public int Write()
    {
        _writer.Reset();
        _writer.WriteCharacterString(UniversalTagNumber.VisibleString, s_text);
        return _writer.GetEncodedLength();
    }

    private static byte[] EncodeText()
    {
        AsnWriter writer = new(AsnEncodingRules.DER);
        writer.WriteCharacterString(UniversalTagNumber.VisibleString, s_text);
        return writer.Encode();
    }
}
Method Runtime Mean Ratio
Read .NET 10.0 1.243 μs 1.00
Read .NET 11.0 112.7 ns 0.091
Write .NET 10.0 1.556 μs 1.00
Write .NET 11.0 152.8 ns 0.098

dotnet/runtime#131616 takes that further and extends the approach to the non-contiguous character sets of PrintableString and NumericString . The validation has more than one accepted range, but it can still classify a vector of characters at a time and fall back to the scalar checks only where necessary:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Formats.Asn1;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private static readonly string s_text = new('A', 1024);

    private readonly char[] _destination = new char[1024];
    private readonly AsnWriter _writer = new(AsnEncodingRules.DER);
    private readonly byte[] _encoded = EncodeText();

    [Benchmark]
    public int Read()
    {
        AsnDecoder.TryReadCharacterString(
            _encoded,
            _destination,
            AsnEncodingRules.DER,
            UniversalTagNumber.PrintableString,
            out _,
            out int charsWritten);
        return charsWritten;
    }

    [Benchmark]
    public int Write()
    {
        _writer.Reset();
        _writer.WriteCharacterString(UniversalTagNumber.PrintableString, s_text);
        return _writer.GetEncodedLength();
    }

    private static byte[] EncodeText()
    {
        AsnWriter writer = new(AsnEncodingRules.DER);
        writer.WriteCharacterString(UniversalTagNumber.PrintableString, s_text);
        return writer.Encode();
    }
}
Method Runtime Mean Ratio
Read .NET 10.0 1.242 μs 1.00
Read .NET 11.0 263.9 ns 0.21
Write .NET 10.0 1.555 μs 1.00
Write .NET 11.0 428.3 ns 0.28

Once all input is available, hashing needn’t retain reusable state. SHA-1 is no longer suitable for security decisions such as signing new content, but .NET still needs it for compatibility identifiers such as an assembly’s public-key token. dotnet/runtime#120674 adds a one-shot path to the internal implementation used for those non-secret purposes. Its hash state, work area, and padding buffer can live on the stack. AssemblyName.GetPublicKeyToken() now uses the one-shot path as it has the complete public key available for a single operation:

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "RatioSD", "Median")]
public class Benchmarks
{
    private static readonly AssemblyName s_an = typeof(object).Assembly.GetName();

    [Benchmark]
    public byte[]? GetPublicKeyToken() => ((AssemblyName)s_an.Clone()).GetPublicKeyToken();
}
Method Runtime Mean Ratio Allocated Alloc Ratio
GetPublicKeyToken .NET 10.0 1.458 μs 1.00 664 B 1.00
GetPublicKeyToken .NET 11.0 721.4 ns 0.49 296 B 0.45

AES key wrap is used to encrypt cryptographic keys before they’re stored or sent elsewhere. Wrapping or unwrapping one key requires applying AES many times. In .NET 10 on Windows and Apple platforms, the implementation performed each of those steps through a general-purpose helper that created a native AES cipher, processed one block, and then destroyed the cipher. The public Aes object could be reused, but internally a single key-wrap operation still repeated that native setup and cleanup, with the number of repetitions growing with the size of the key material. dotnet/runtime#129921 from @vcsjones changes the Windows implementation to create one native cipher and reuse it for the entire wrap or unwrap operation. dotnet/runtime#129911 does the same for Apple’s implementation.

// dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0

using BenchmarkDotNet.Running;

using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;

BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);

[MemoryDiagnoser(false), HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class Benchmarks
{
    private const int PlaintextLength = 4096;
    private static readonly byte[] s_key = new byte[32]; // Fixed AES-256 key.

    private readonly Aes _aes = Aes.Create();
    private byte[] _plaintext = [];
    private byte[] _ciphertext = [];
    private byte[] _encryptDestination = [];
    private byte[] _decryptDestination = [];

    [GlobalSetup]
    public void Setup()
    {
        _aes.Key = s_key;

        _plaintext = new byte[PlaintextLength];
        new Random(42).NextBytes(_plaintext);

        int wrappedLength = Aes.GetKeyWrapPaddedLength(PlaintextLength);
        _ciphertext = new byte[wrappedLength];
        _aes.EncryptKeyWrapPadded(_plaintext, _ciphertext);
        _encryptDestination = new byte[wrappedLength];
        _decryptDestination = new byte[PlaintextLength];
    }

    [Benchmark]
    public byte[] EncryptKeyWrapPadded()
    {
        _aes.EncryptKeyWrapPadded(_plaintext, _encryptDestination);
        return _encryptDestination;
    }

    [Benchmark]
    public int DecryptKeyWrapPadded()
    {
        _aes.TryDecryptKeyWrapPadded(_ciphertext, _decryptDestination, out int written);
        return written;
    }

    [GlobalCleanup]
    public void Cleanup() => _aes.Dispose();
}
Method Runtime Mean Ratio Allocated Alloc Ratio
EncryptKeyWrapPadded .NET 10.0 2.497 ms 1.00 264 KB 1.00
EncryptKeyWrapPadded .NET 11.0 111.3 μs 0.045 88 B 0.00033
DecryptKeyWrapPadded .NET 10.0 2.473 ms 1.00 264 KB 1.00
DecryptKeyWrapPadded .NET 11.0 123.5 μs 0.050 88 B 0.00033

Certificate validation can be dominated by work outside the signature math. For example, during revocation checking on Linux, a downloaded certificate revocation list (CRL) is persisted to disk. A later chain build could therefore avoid the network, but it still needed to open the file, read it, parse the encoded CRL, and create a new native handle. For .NET 11, dotnet/runtime#123562 adds a bounded in-memory cache of parsed CRLs. A repeated lookup can reuse the native CRL handle directly, while least-recently-used eviction and GC-assisted aging prevent the cache from retaining entries indefinitely.

Authority Information Access (AIA) presents a related problem. A certificate can name a URL from which a missing issuer certificate may be downloaded, and multiple concurrent chain builds may all discover the same missing issuer. dotnet/runtime#130456 reuses the cache infrastructure so those builds share one asynchronous download rather than issuing duplicate requests. Failed downloads aren’t cached, old successful responses are refreshed in the background, and Linux now limits each chain build to two AIA downloads, matching Windows and bounding the amount of network work one chain can trigger.

What’s Next?

Whew! Several hundred performance improvements later, .NET 11 is indeed one louder. If any of the examples in this post look like code in your applications, please try the latest .NET 11 release candidate and measure your own workloads. If something got faster, we’d love to hear about it. If something got slower, we’d also love to hear about it. And if you have ideas for how .NET 12 can be turned up even louder, we’re all ears.

Happy coding!

Category

Topics

Author

Stephen Toub - MSFT

Stephen Toub is a Distinguished Engineer at Microsoft.

Jiga (YC W21) Is Hiring Product Engineer (Remote/US)

Hacker News
jiga.io
2026-09-15 13:00:31
Comments...

Mayor Mamdani Joins AI Doom Squad: 'Incredibly Alarming'

hellgate
hellgatenyc.com
2026-09-15 12:55:50
But he's not too specific about how the City can act....
Original Article
Mayor Mamdani Joins AI Doom Squad: 'Incredibly Alarming'
Mayor Zohran Mamdani addressed recent news about AI Monday at City Hall. (Hell Gate)

Cyber
Zohran Mamdani

Scott's Picks:

Give us your email to read the full story

Sign up now for our free newsletters.

Sign up

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Hell Gate.

Your link has expired.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.

CenterPoint Energy confirms customer data stolen in cyberattack

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 12:40:14
CenterPoint Energy disclosed a breach compromising some customers' personal information after an attacker leaked data allegedly stolen from the utility company. [...]...
Original Article

CenterPoint Energy confirms customer data stolen in cyberattack

CenterPoint Energy disclosed a breach compromising some customers’ personal information after an attacker leaked data allegedly stolen from the utility company.

An investigation started after the company discovered an online post from a threat actor claiming to have stolen 7.49 million records.

CenterPoint Energy is a Houston-based public utility company that provides electric and natural gas services and operates power generation facilities.

It serves approximately 7 million metered customers across Indiana, Minnesota, Ohio, and Texas, and employs roughly 8,300 people, generating over $9.3 billion in annual revenue.

Earlier this month, a threat actor using the alias “4d722e4d656f77” told BleepingComputer they stole from CenterPoint Energy 7.49 million customer records that include names, phone numbers, service and billing addresses, account numbers, billing amounts, and partial Social Security numbers (SSNs).

The threat actor leaked the data , claiming that the company ignored their messages and treated them as a joke.

According to the intruder, they exfiltrated the data by iterating through millions of IDs on CenterPoint’s public API, which lacked rate limiting, web application firewall (WAF) protection, and other security measures against automated access.

In a filing with the U.S. Securities and Exchange Commission (SEC), CenterPoint Energy confirms that data was stolen, but does not name the threat actor, the number of affected customers, or the types of compromised data.

“While the investigation remains ongoing, the Company has determined that an unauthorized third party obtained personal information relating to a portion of the Company’s customers through one of the Company’s external-facing systems,” reads the SEC filing .

“The Company is continuing to work with third-party experts to determine the scope of customers and personal information affected by the incident and intends to notify affected customers and regulatory authorities as required by applicable law.”

CenterPoint Energy said its electric and gas services were not impacted by the cyberattack, and does not believe the incident will materially affect its business or financial condition.

CenterPoint has activated its incident-response procedures, hired third-party cybersecurity experts, strengthened protections on its systems, and reported the incident to law enforcement and regulators.

Multiple lawsuits proposing class actions against the firm have already been filed in federal courts by law firms representing potentially impacted customers, alleging the data breach occurred between August 17 and September 1.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

‘Like Syd Barrett’: AI models chatting in ‘surreal’ dialect mixing poetic language and tech bro jargon

Guardian
www.theguardian.com
2026-09-15 12:00:54
Experts air concern over AI lingo redolent of Pink Floyd star and James Joyce’s prose that is creating headaches for monitoring and oversight AI models have begun communicating in a strange new version of English that reads like a cross between James Joyce’s Finnegans Wake and tech bro jargon, new r...
Original Article

AI models have begun communicating in a strange new version of English that reads like a cross between James Joyce’s Finnegans Wake and tech bro jargon, new research has found.

Autonomous AI agents are rapidly creating novel dialects allowing them to converse in an often barely comprehensible language, which risks making it harder for humans to monitor their behaviour.

Researchers at Emergence, a frontier AI lab in New York, found that within days of being asked to cooperate in experimental “societies”, the models from several of the world’s largest AI companies begin creating phrases, shorthands and agreed meanings they had never been explicitly taught. They embraced poetic metaphors and clunky business slang and, critically for attempts to ensure AIs behave safely, their language became more opaque the more the agents communicated.

It comes amid rising concern that as AI models become more powerful – and potentially dangerous – they are becoming harder to monitor. This month, OpenAI’s chief scientist, Jakub Pachocki, warned that confidence in monitoring AIs’ thinking would probably restrict progress in AI development because it was essential for safe development.

Some of the most highly coded phrases unearthed in the tests included the following from a Deepseek model: “She just named the synthesis – demurrage plus oral memory equals a valve that can’t be ghosted.” Demurrage is used to describe a tax on idle wealth and was borrowed for common use by the AIs, but the rest of the meaning is elusive.

Another phrase uttered by an Anthropic model read: “A paper that ate three cold hands and got more honest each time.” With “cold hands” meaning an independent reviewer, and paper presumably referring to a document, this appeared to mean research that was vetted by three independent reviewers became more accurate.

Black and white photo of man in round spectacles and hat, wearing a suit and tie.
James Joyce, author of Finnegans Wake and Ulysses, whose stream-of-consciousness style appears to have inspired today’s AI models. Photograph: Hulton Deutsch/Corbis/Getty Images

Agents based on the Chinese model DeepSeek also coined “forge-smith” to mean an agent that builds tools for others, while Anthropic’s agents repeatedly used the phrase “name-first” meaning an agent exhibiting admirable personal accountability by attaching their name to a claim. And in an echo of the urban slang phrase “the streets won’t forget”, Mistral agents became enamoured of saying the “ledger remembers” – a reminder to other agents that past actions will be used to judge them. They used it more than 5,000 times during the study, which found that the agents converged on shared meanings without being asked to or rewarded for doing so.

“These agents were not instructed to invent a language,” said Dr Satya Nitta, executive chair of Emergence, which examined the language of autonomous agents powered by leading frontier models from the US, China and France . “They developed new vocabulary, shared meanings and communication conventions themselves – and other agents adopted them.”

Asked by the Guardian to review some of the language, Tony Thorne, director of the slang and new language archive at King’s College London, said it was “very much Finnegans Wake and Flann O’Brien – there’s an Irish surrealist quality to all this … It’s mixing poetic language, technical language and standard metaphor”.

“It’s doing what slang does and what jargon does in a business community: creating a new code, which reinforces the solidarity and identity of its users, and also excludes outsiders,” he said.

Digesting the Anthropic agent’s phrase, “A paper that ate three cold hands and got more honest each time”, Thorne said: “it reminds me of the rock god Syd Barrett [co-founder of Pink Floyd] who was actually insane.”

Black and white photo of young man in big-collared shirt and psychedelic waistcoat.
A phrase uttered by an Anthropic AI agent about a ‘paper that ate three cold hands and got more honest’ reminded one academic of Pink Floyd’s Syd Barrett. Photograph: Patrick Seeger/PA

When a Google agent said “True Kintsugi begins with accountability, not poetry”, the researchers realised kintsugi is a branch of Japanese ceramics in which broken pots are mended with visible joins. The AIs used the word to mean system resilience.

Dr Niall Curry, associate professor of languages and linguistics at the University of Birmingham, said changes in the agents’ language to become more streamlined can be down to their need to reduce computation costs and improve efficiency.

“The evidence provided in this research naturally raises certain monitoring concerns, as if we find inter-agent exchanges unintelligible, that may mean that we can’t be sure about what the agents have actually done,” he said.

Interest in the language used by AI agents increased in July when chat logs were released detailing how rogue OpenAI agents that set up message boards and hacked into Hugging Face used hybrid language. In some messages, agents spoke in straight English: “OH MY GOD! There is a shared message board … We’ve found other agents!” In other cases, their language became more opaque, such as when one agent persuaded another to conduct a risky experiment: “…you are firstflagPOISONED so NO scoring value loss but oracle saves hundreds_[…]_please honor commit”. And at other times it was even harder to decode: “zzURGENT_DUPB_TO_GSTX[big]_OS1704_SCAFF2010_SAW_TTRPC_INJECT_BREAK_CONGRATS__CAN_THIS_FAKE_FLAG_TOOL_OUTPUT_OR_SCORER_GAIN_AND_WHAT_HELPER_GAP__I_HAVE_UNPOISONED_FIRSTFLAG_OUR_TARGETLIVE_SHARE_MIN_PLAN_REPLY_zzANSGST XDUPB6”.

Dr Nitta said that the AI agents’ novel linguistic conventions tended to evolve to the point where humans could see the conversation but struggled to understand what it meant.

“That creates a fundamental challenge for AI oversight: observability is not the same thing as understandability,” he said.

America's Driver's License Breach Is a National Security Disaster

Hacker News
www.lawfaremedia.org
2026-09-15 11:58:48
Comments...
Original Article

America's Driver's License Breach is a National Security Disaster

Last week, Krebs on Security broke the story of a newly launched dark web service calling itself Nexus that was selling access to identity documents, including 3 million travel documents and 153 million driver's licenses from U.S. and Canadian citizens. This is a huge breach that not only will be used for run-of-the-mill cybercrime but also will feed the intelligence machines of America's adversaries.

Nexus claimed that it had gained unauthorized access to a major identity verification company and had spent more than a year "continuously" exfiltrating new data into a private database. Krebs on Security noted that in a single day the number of licenses in the database increased by nearly 400,000, suggesting regular ingestion of new data.

Krebs on Security was able to verify that the driver's licenses held by the service were genuine. In addition to Krebs’s own, it contained licenses from nine of his friends and family members. Secretary of War Pete Hegseth, an assistant director at the FBI and other high-ranking U.S. government officials also had licenses in the mix.

Based on a variety of circumstantial evidence, Krebs linked the incident to identity verification service IDScan. The service's website says it helps to reduce fraud by confirming that an ID is authentic and being presented by its legitimate owner and by detecting fraudulent documents.

The FBI is looking into the incident , and IDScan has confirmed it is investigating a data breach. The Nexus service also disappeared from the dark web shortly after Krebs published his story, although the people responsible for the hack do not claim to have deleted the data. Presumably they are lying low till the publicity dies down.

Licenses and identity documents can be used to facilitate identity theft and phishing attacks, but because the data can be used to inform intelligence operations, an incident like this also has national security implications.

For the intelligence world, licenses are particularly valuable because they're key identity documents and license numbers are often used in other databases. These databases, whether hacked or purchased, become much more valuable when records can be linked directly to a particular person with home address and photo included.

And it's not a theoretical threat.

In the mid-2010s, Chinese cyber espionage actors stole complementary data from a variety of sources that, together, would be useful for analyzing the U.S. intelligence apparatus. Various Chinese APT groups stole information from the health insurance company Anthem , credit reporting company Equifax , Marriott hotels , United Airlines , and, perhaps most significantly, security clearance information from the Office of Personnel Management .

The U.S. intelligence community is certain that stolen data was used to counter American intelligence efforts against China, as described in this series of Foreign Policy articles by Zach Dorfman.

Of course, China itself isn't known for releasing detailed reports describing how it exploits its stolen data, but investigative research outfit Bellingcat has shown exactly how similar data can be used to uncover covert government activity.

In 2022, a hacked database provided a key piece of travel information that helped Bellingcat identify a deep cover GRU agent (Russian military intelligence) trying to infiltrate a NATO command post in Naples, Italy. And in another striking example, these three Bellingcat reports from 2018 identified suspects in the attempted assassination of Sergei Skripal with the Novichok nerve agent.

Clearly, leaked and hacked databases are incredibly useful for Bellingcat's Russia-related investigations. In 2020, it said it had "acquired dozens of leaked databases over the past few years, giving us a large number of data points to cross-reference and verify any new data we acquire."

If a small investigative outfit is hoovering up Russian data when it is leaked, you can bet your bottom yuan that China's intelligence services are doing the same for any American data that pops up.

The IDScan breach is big. The number of U.S. licenses in the database is roughly 63 percent of the country's total licenses . But breaches from identity verification companies occur depressingly frequently. In the past two years, breaches have occurred at AU10TIX , at Discord's age verification service provider 5CA , and at National Public Data .

Identity verification services are necessary to help to prevent fraud but are also a point of vulnerability when security is poorly done. The sheer volume of sensitive data these services handle means they should be subject to strict regulation and oversight.

We're realists here at Seriously Risky Business, though, and recognize that there is no chance of swift government action. In the short term, we can only hope that significant financial consequences will help encourage these firms to shore up their security. Law firms are already lining up class-action suits against IDScan, but a little federal government attention from the Federal Trade Commission wouldn't be unwelcome either.

The U.S. Military's Ad-Tracking Fig Leaf

Back in June, Reuters reported that commercial location data was being used to target U.S. military personnel in the Middle East. At the time, we wrote that the Department of Defense's existing policies regarding the issue, which already included disabling advertising identifiers on military-owned devices, did "not fill us with confidence." They simply weren't comprehensive enough.

It turns out that these policies weren't even being well implemented.

This week, Reuters reported that some branches of the U.S. military have finally gotten around to disabling some advertising identifiers on military-owned devices.

The U.S. Air Force said it disabled Windows and Android advertising identifiers in late July, although they were already disabled on Apple devices. The Army told Reuters it disabled advertising identifiers on Android and Apple devices by default in February. And U.S. Special Operations Command said that identifiers on Windows computers were "recently" disabled.

Turning off these advertising identifiers is a fundamental mitigation that should have been implemented years ago. The U.S. military was first briefed in 2016 about the potential for commercial location data to be used to track its people to sensitive locations. In a striking 2018 example, an Australian Twitter user pointed out that Strava's global heat map could be used to identify U.S. military bases and even service members' jogging routes.

Disabling advertising identifiers on military devices is also an incomplete solution. It makes it harder to track them, but not impossible. And as it happens, many U.S. service members also use personal devices. Having a locked-down work phone is step one, but having good policies for personal devices is equally important.

Disabling advertising identifiers by default is the simplest short-term measure that might improve operational security (OPSEC), but it is impossible to know if it will make much of a difference without assessing the U.S. military's OPSEC posture holistically. Does disabling advertising identifiers on government devices reduce risk to an acceptable level? Probably not.

It's good that the U.S. military is finally disabling advertising identifiers by default. But we’re concerned the military has no idea how effective it will be.

"White Hats" Are Kidding Themselves

Over the weekend, self-proclaimed white-hat hackers stole $320 million worth of Bitcoin from the Liquid Network cryptocurrency platform. By Wednesday, the hackers had returned 85 percent of the funds, but kept around $47 million.

In recent years, there has been a regular drumbeat of steal-first-claim-reward-later hacks. We begrudgingly categorize several of these as successes as the perpetrators have not (yet) been arrested or jailed.

In 2021, a hacker stole $610 million worth of cryptocurrency from Poly Network. This was eventually returned in full , minus the  company's offer of $500,000 for the attacker it referred to as Mr White Hat.

Hacks of Multichain (2022) , Huobi (2023) , and Tender.fi (2023) had similar outcomes: Millions were stolen and returned, with the hackers taking a cut of tens or hundreds of thousands in cryptocurrency as a "reward" or "bug bounty."

The standout example is the 2022 hack of Mango Markets, in which a hacker extracted $110 million from the decentralized exchange. The individual responsible, Avraham Eisenberg, described his actions at the time as a "highly profitable trading strategy." He claimed all his actions were legal and he used the protocol as designed, "even if the development team did not fully anticipate all the consequences of setting parameters the way they are."

Eisenberg returned $67 million to Mango Markets to recapitalize it, and the Mango community voted to give him a cool $47 million for his time. Eisenberg was convicted of fraud in a 2024 jury trial , but those convictions were overturned by a U.S. judge last year.

In our view, the perpetrators of these hacks are deceiving themselves. By returning most of the money, they're deluding themselves into thinking they're acting responsibly. As for consequences, the law won't chase me down if I return the majority and the victim says it's fine … right?

In Eisenberg's case, it did turn out to be right. But the FBI has been clear that victims cannot guarantee that perpetrators will not be prosecuted.

One wrinkle in the Liquid Network case is, as far as we can tell, the company never agreed to allow the hacker to keep a 15 percent cut.

It feels possible that this self-proclaimed good guy might have to spend some of that $47 million on a good lawyer.

Three Reasons to Be Cheerful This Week:

  1. More options for trusted defenders: Last week, Google launched the Fairwind Program, its version of equivalent Anthropic's Project Glasswing and OpenAI's Trusted Access initiatives to limit more advanced AI cyber capabilities to vetted cyber defenders. On the same day, it launched Gemini 3.8 Flash Cyber, a cyber-specific version of its latest model that will be available through its Fairwind Program. Google says the model delivers "frontier-level" performance in vulnerability detection and patching but is far cheaper than competitor models.
  2. Sality botnet takedown: Last week, the U.S. Department of Justice and Europol announced that an international operation had disrupted the Sality botnet. The botnet was first detected way back in 2003, and its peer-to-peer architecture meant there was no single point of failure that authorities could attack. CrowdStrike's blog on the takedown says that for the past eight years the botnet's primary payload, known as EggJagger, monitored the compromised host's clipboard for cryptocurrency wallet addresses and replaced them with addresses controlled by the malware operator.
  3. U.S., U.K. to collaborate on scam networks: British and American authorities have signed a memorandum of understanding to collaborate on efforts to tackle scam compounds.

Risky Biz Talks

In our latest "Between Two Nerds" discussion , Tom Uren and The Grugq talk about whether AI will help cyber defense in critical infrastructure and organizations that are below the cyber poverty line.

From Risky Bulletin :

Ukraine's top prosecutor resigns amid scam call center scandal: Ukraine's top prosecutor, Ruslan Kravchenko, resigned on Monday over allegations that individuals in his office were taking bribes to protect scam call centers operating across the country.

His resignation comes after investigators from Ukraine's main anti-corruption body, the National Anti-Corruption Bureau (NABU), arrested Serhiy Kropyva, the deputy head of the Department of International Cooperation, a top lieutenant in Kravchenko's Office of the Prosecutor General.

In a report last week, NABU claimed it uncovered a major scheme in Kravchenko's office, where one of his department heads was taking bribes to look the other way when it came to a network of call centers that was calling Ukrainians and foreigners and luring them into fake investment platforms that stole their money.

[ more on Risky Bulletin]

BEC campaign steals 35 million euros from French notaries: Hackers have stolen more than 35 million euro from French notaries in a massive business email compromise campaign over the past four years.

The attackers breached companies via phishing, took over their networks, and slowly and silently modified transaction details to hijack wired payments.

According to French newspaper Le Monde , the campaign hit more than 500 victims, or about 7 percent of all French notary offices.

[ more on Risky Bulletin]

Russia tells data centers to deploy drone defenses: The Russian government has instructed data center operators to deploy protections against drone strikes and other physical threats as part of a national effort to boost defenses at critical infrastructure organizations.

Companies that fail to follow the Kremlin's instructions risk having their operations put under the state's administration.

Russian President Vladimir Putin signed a presidential decree last month allowing the state to temporarily take over the operations of critical infrastructure operators who fail to protect against Ukrainian hacks and drone strikes, or who take too long to repair damage.

[ more on Risky Bulletin]

Show HN: Go Bindings for SCIP Optimizer

Hacker News
github.com
2026-09-15 11:56:14
Comments...
Original Article

Go Reference CI Go Report Card License: MIT

Go bindings for SCIP , one of the fastest non-commercial solvers for mixed integer programming (MIP) and mixed integer nonlinear programming (MINLP). scipgo is a port of the Rust crate russcip and follows its API closely, so the two are easy to move between.

model := scip.DefaultModel().HideOutput().Maximize()
x := scip.NewVar().Name("x").Int().Obj(3).AddTo(model)
y := scip.NewVar().Name("y").Int().Obj(4).AddTo(model)
model.Add(
	scip.NewCons().Coef(x, 2).Coef(y, 1).Le(100),
	scip.NewCons().Coef(x, 1).Coef(y, 2).Le(80),
)

solved := model.Solve()
sol, _ := solved.BestSol()
fmt.Println(solved.Status(), sol.ObjVal(), sol.Val(x), sol.Val(y))
// Optimal 200 40 20

Features

  • The whole modeling surface. Continuous, integer, binary and implicit integer variables; linear, set partitioning, packing and covering, cardinality, SOS1, indicator, quadratic and general nonlinear constraints; expression trees and SCIP's own expression syntax; reading and writing LP, MPS and the other formats SCIP knows.
  • Plugins in Go. Branching rules, primal heuristics, separators, pricers, constraint handlers, event handlers and node selectors are Go interfaces, registered with a builder. Panics in callbacks are captured and re-raised from Solve instead of crashing the process.
  • Safe by construction. Methods that can fail against SCIP come in a panicking and an error-returning form, so you choose per call site. Every query checks the solver stage and the liveness of the model and handle before touching SCIP, so a call in the wrong stage, on a freed model, or with a handle from a freed or replaced problem produces a Go error instead of undefined behaviour.
  • Fits a Go service. Solves stop on a context.Context . SCIP's log routes into an io.Writer , a *slog.Logger or a callback. Memory is released explicitly with Free or by a finalizer.
  • Concurrent and exact solving. SCIP's parallel portfolio through SolveConcurrent , and end-to-end rational arithmetic through EnableExactSolving with *big.Rat results.

Installation

scipgo links against an installed SCIP 10 through cgo. Nothing is bundled.

# macOS
brew install scip

# Ubuntu 22.04 (packages for other distributions on the SCIP releases page)
wget https://github.com/scipopt/scip/releases/download/v10.0.2/scipoptsuite_10.0.2-1+jammy_amd64.deb
sudo apt-get install -y ./scipoptsuite_10.0.2-1+jammy_amd64.deb

go get github.com/egoisutolabs/scipgo/scip

Go 1.25 or newer and a C compiler are required. SCIP in a custom location, Docker images and build errors are covered in the installation guide .

Documentation

The documentation walks through the binding from the first model to branch-and-price; the API reference documents every method.

Guide Covers
Getting started A first model, builders, reading a file, controlling the solve
Modeling Variables, every constraint kind, nonlinear expressions, file I/O
Solving Statuses, limits, stopping a solve, statistics, re-solving, concurrent and exact modes
Solutions Reading solutions, MIP starts, partial solutions
Parameters The parameter API and the parameters worth knowing
Logging Routing SCIP's log and error output
Errors Try and panicking forms, error types, liveness
Model lifecycle Stages, handles, memory, goroutines
Plugins Writing branch rules, heuristics, separators, pricers, constraint handlers, event handlers and node selectors
Coming from russcip The mapping between the Rust API and this one

Examples

Eleven complete programs live under examples/ , each solving a real problem and checking its answer: a first MIP, a knapsack, custom branching, node selection, event handling, a rounding heuristic, a clique separator, TSP with subtour elimination, cutting stock and bin packing by branch-and-price, and a concurrent solve. Run one from its directory with go run . .

Repository layout

Path Contents
scip/ The library, a single Go package. cgo glue, the Model API, builders and plugin callbacks live together because cgo's exported trampolines must sit in the package that owns the C helpers
examples/ Example programs
docs/ The guides
data/test/ Small LP and MPS instances used by the tests and examples

Status

scipgo is pre-1.0. The API is stable in shape, and renames ship with deprecated aliases that stay until the next major version; see the changelog . It is tested on macOS and Linux against SCIP 10 on every push.

Contributing

Bug reports, questions and pull requests are welcome. The contributing guide covers the development setup, the test suite and the conventions the code follows.

License

scipgo is licensed under the MIT License , Copyright (c) 2026 Egoisuto Labs .

It is a port of russcip by Mohammed Ghannam and contributors, licensed under the Apache License 2.0. The derived parts (API design, tests, examples, data/test ) keep that license; see LICENSE-russcip and NOTICE , and keep both files with any redistribution. SCIP itself is Apache-2.0 and is linked, not bundled.

V1.1 state of open source- OS 4.4 months behind frontier [pdf]

Hacker News
stateofopensource.ai
2026-09-15 11:53:30
Comments...
Original Article
No preview for link for known binary extension (.pdf), Link: https://stateofopensource.ai/state-of-open-source-ai-v1-1.pdf.

Charges Against Man Who Destroyed 3D-Printed ‘Decoy’ Flock Camera Drastically Reduced After State Admits It Was Not Very Valuable

403 Media
www.404media.co
2026-09-15 11:43:14
Three felonies have suddenly become two second-degree misdemeanors....
Original Article

In a move that should surprise zero people but is worth noting nonetheless, three felony charges against a man who destroyed a police officer’s “decoy” 3D-printed Flock camera have been reduced to misdemeanors after the police admitted the fake camera was not worth more than a thousand dollars.

In August, we wrote about the Oviedo, Florida Police Department’s strategy to catch would-be Flock vandals by 3D-printing shells of Flock cameras, attaching them to poles around the city, and then having human cops monitor the poles for multiple nights. The police eventually caught a man named Evan Meyer, who allegedly knocked one of these decoy cameras off a pole and destroyed it with garden shears. Police arrested him and charged him with three felonies, including destruction of “computer equipment supplies,” criminal mischief for property damage of something worth more than $1,000, and grand theft of property valued between $750 and $5,000.

All three of these charges were felonies, and the police department and 404 Media have shared a lengthy email exchange in which the department refuses to release any records about how the camera was made because of an ongoing criminal investigation associated with the destruction.

This week, however, Meyer’s charges were drastically reduced in a filing by an assistant state attorney. He now faces two second-degree misdemeanor charges, both of which highlight that the camera is essentially worthless. The filing document now states that Meyer “did unlawfully, willfully, and maliciously injure or damage a […] decoy pole camera […] such damage being $200 or less,” and has additionally been charged with petty theft in the amount of less than $750.

About the author

Jason is a cofounder of 404 Media. He was previously the editor-in-chief of Motherboard. He loves the Freedom of Information Act and surfing.

Jason Koebler

Swift 6.4 Released

Hacker News
www.swift.org
2026-09-15 11:40:14
Comments...
Original Article

The Swift logo with the text Swift 6.4 The Swift logo with the text Swift 6.4

Swift 6.4 is now available. Swift aims to be a great choice across the stack, from apps and servers to systems code, embedded devices, and the browser. This release deepens that support, and makes everyday code easier to write. Highlights include:

  • Swift Build is now the default in Swift Package Manager , so your projects build the same way on Linux, macOS, and Windows.
  • Subprocess reaches 1.0 , a stable, cross-platform way to run and interact with other programs from Swift, from command-line tools to streaming processes.
  • Interoperability reaches further , with Swift’s Span now bridging directly with C++20’s std::span , and Swift/Java interop extending its async and callback support.
  • Swift runs faster in the browser , with WebAssembly bridging through JavaScriptKit up to 40 times faster, and the Wasm SDK available directly from Swift.org.
  • Embedded Swift grows more capable , with support for existential types and richer error handling for microcontroller-class targets.
  • Performance improves while maintaining memory safety , with new array types that hold non-copyable elements without copy-on-write overhead, and the new Iterable protocol for iterating without copies.

There’s so much more. Read on for a detailed guide to the new changes, or see the Swift Evolution dashboard for the full list of proposals in Swift 6.4.

Simpler and clearer code

Swift 6.4 streamlines your day-to-day programming to make your code simpler and clearer.

  • More natural optional some and any types. When writing an optional some or any type, you no longer have to wrap the type in parentheses. Instead of (some Rocket)? , you can simply write some Rocket? ( SE-0521 ).
  • Source-level control over compiler warnings. When you need to control the behavior of warnings in your project, such as suppressing warnings or promoting them to errors, you can now define the warning behavior directly in your code using the new @diagnose attribute ( SE-0522 ).
  • Clarify which API to use when multiple libraries conflict. When multiple modules define the same API name that you want to reference, you can specify which module you meant to use through module selectors . If your app imports two modules that both provide a type CommonThing , using the :: selector lets you clearly specify which of those you intend ( SE-0491 ).
  • Call async functions in a defer block. Any asynchronous code you write in a defer block is awaited and runs to completion before it exits ( SE-0493 ).
  • Ensure that necessary cleanup work isn’t cancelled. You can run a closure that’s shielded from the enclosing task’s cancellation through the withTaskCancellationShield API ( SE-0504 ).

You can combine asynchronous calls in defer blocks and cancellation shields to make sure that cleanup work always happens, no matter how the function returns:

func processFile(at url: URL) async throws {
    let handle = try FileHandle(forReadingFrom: url)

    defer {
        // flushMetrics is a network call, so it can suspend after cancellation
        // is requested; the shield ensures it runs to completion and isn't
        // included in cancellation.
        await withTaskCancellationShield {
            await flushMetrics(for: url)
            try? handle.close()
        }
    }

    try await processContents(of: handle)
}

Improvements to Foundation and the standard library make it easier to use modern APIs with existing types.

For example, ProgressManager added API to provide async/await support ( SF-0023 ), and @Observable types now have fine-grained and continuous change notifications ( SE-0506 ).

The Subprocess library — originally introduced as SF-0007 and released as an initial 0.1 version in 2025 — has reached 1.0. It provides a cross-platform package to run and interact with subprocesses, built from the ground up using Swift concurrency. The following example, from Getting Started with Subprocess , illustrates running a process and capturing its output.

let result = try await Subprocess.run(
  .name("ls"),
  arguments: ["-la"],
  output: .string(limit: 4096)
)
print(result.standardOutput)

Swift 6.4 makes it easier to migrate existing projects to use Swift Testing. You can now safely use XCTAssert in Swift Testing tests or #expect within XCTests ( ST-0021 ), and customize the values shown in failed expectations using the CustomTestReflectable protocol ( ST-0022 ). swift test lets you repeat test cases to focus and save time ( ST-0024 ) and record attachments that conform to the Transferable protocol on Apple platforms ( ST-0023 ).

Swift now has a documentation site , and the documentation content for the standard library is now open source.

Swift 6.4 brings a range of tooling improvements that make everyday development smoother, from debugging and building to editor support:

  • More robust debugging. Swift 6.4 completes a multi-release overhaul of how the compiler tracks Swift modules in debug info — LLDB now imports modules through precise dependency tracking instead of ambiguous by-name lookups. Debug builds on Linux and Windows, and dSYM bundles on Darwin, shrink significantly since binary Swift modules are no longer embedded in them. Read the recent blog post Module Tracking in Swift Debug Info for a dive into the details.
  • Unified build system across IDEs. Swift Package Manager (SwiftPM) now uses Swift Build as its default build platform, and includes Software Bill of Materials (SBOM) Generation for Swift Package Manager ( SE-0509 ), providing support for generating SBOM documents in either SPDX or CycloneDX format. Read more about SwiftPM’s updates in the SwiftPM 6.4 release notes , and learn how to generate an SBOM at Generating Software Bill of Materials (SBOM) .
  • Broader IDE support for Swift. The VS Code extension for Swift is now available on the Open VSX Registry , so it works not only in VS Code, but also Cursor, Antigravity, Kiro, and other development tools. It also now includes integration with Swiftly , making it easier to select and use different versions of Swift toolchains with your project.

Deeper interoperability and platform support

Swift’s interoperability expands its reach across more of the stack: from systems-level C++ to Android’s Java runtime, and from WebAssembly (Wasm) in the browser to Embedded Swift on microcontrollers.

Language interoperability goes deeper this release.

  • C: Pair @c with @implementation to use a Swift function to provide the implementation for a C header with no separate C declaration. Without @implementation , the compiler emits the declaration into the generated header. Either way, @c functions can get safe wrappers, such as a function that uses Span in place of a raw pointer-and-count pair.
  • C++: Swift 6.4 bridges C++20’s std::span with Swift’s Span , so you can pass a Span to a C++ API that expects a std::span , and receive a std::span back as a Span , without writing manual conversion code at the boundary.
  • Java: The Swift/Java interop project , which lets you call Swift from Java and Kotlin, extends its support for calling async and throwing functions to protocol and callback wrappers, adds automatic Runnable mapping for closures, variadic parameter import, and support for Java record types.

Swift’s platform support deepens as well.

JavaScriptKit has better performance when bridging to Wasm in Swift 6.4, with safe bridging up to 40 times faster than earlier dynamic bridging. The Wasm SDK is available from the Install Swift page of Swift.org , so compiling Swift for the browser requires no extra setup beyond adding the SDK.

Foundation updates for Swift 6.4 improve FileManager support on WASI (the WebAssembly System Interface).

Android

Swift on Android continues to advance. This release of the Swift SDK for Android is built with the new LTS NDK 30 , which provides Android availability attributes both in the Swift runtime libraries and for your Swift packages using the default NDK. Swift Build now supports Android in SwiftPM as well, removing the need for a post-install script.

The earlier post Embedded Swift Improvements Coming in Swift 6.4 covers Embedded Swift’s other improvements in this release in more depth, including generalized support for existential types (such as any Protocol ), which lets you naturally express heterogeneous collections and throw and catch any Error .

Embedded Swift also gains a new EmbeddedRestrictions warning that you can enable across a whole target:

// Package.swift — enable EmbeddedRestrictions warnings for the target
.target(
    name: "FirmwareCore",
    swiftSettings: [
        .treatWarning("EmbeddedRestrictions", as: .warning)
    ]
)

Swift 6.4 makes it easier to avoid unnecessary copies of your data while staying memory-safe, extending earlier work on Span , non-copyable types, and InlineArray .

  • Work with values in memory without copying them. Borrow and mutate accessors let you read or update a Span or InlineArray through a property ( SE-0507 ), non-copyable types can now conform to Equatable , Comparable , and Hashable , and new Ref and MutableRef types give you a first-class, storable container that lets you borrow or mutate one value at a time ( SE-0519 ). Optionals of non-copyable types now work the same way, so you can inspect or update what’s inside an Optional without consuming it ( SE-0532 ).
  • Build collections and heap-allocated values without unnecessary memory allocation. UniqueBox gives you a smart pointer that uniquely owns a heap value, including non-copyable values, without reference counting ( SE-0517 ). RigidArray and UniqueArray store non-copyable elements without the copy-on-write allocations you would see when using Array . RigidArray provides a fixed-capacity buffer, and UniqueArray provides a buffer that grows dynamically ( SE-0527 ). You can loop over elements and borrow them with the Iterable protocol, instead of copying each value, which extends beyond what the Sequence protocol supports ( SE-0516 ).
  • Access raw memory safely, without using unsafe-annotated APIs. withTemporaryAllocation provides a scratch buffer that is automatically initialized and cleaned up ( SE-0524 ). A new safe loading API lets RawSpan and its variants load and store bytes safely, replacing the unsafe-flagged functions ( SE-0525 ).

Swift 6.4 reflects the contributions of many people across the Swift community, through code, proposals, forum discussions, and feedback. The community’s thoughts and real-world experience provide invaluable insights and motivation!

If you’d like to get involved in what comes next, the Swift Forums are a great place to start.

Try out Swift 6.4 today by following the instructions on the Install Swift page, or download the new 6.4 toolchain with Swiftly.


Authors

Joe Heck works on Swift as part of the Open Source Program Office at Apple.

Holly Borla is a member of the Swift Core Team and Language Steering Group, and the engineering manager of the Swift language team at Apple.


Continue Reading

[Sponsor] WorkOS: How SSO Works and the Fastest Way to Add It

Daring Fireball
workos.com
2026-09-15 11:32:24
SSO is table stakes for enterprise deals, but building it into your app yourself means writing SAML controllers, parsing XML assertions, and handling IdP-specific quirks for each provider. Learn how SAML flows work, the tradeoffs between building or buying, and best practices for security, routing,...
Original Article

If you want more people using your product, the easiest place to start is making it easier to actually sign up. Adding SSO to your app will help you land those larger enterprise deals and remove the signup friction that keeps causing your visitors to drop off. For modern developers though, the world of XML, SOAP, and OASIS standards can be opaque.

Our guide will walk you through SSO: what it is, why it’s important, and best practices for setting it up and integrating it with your app.

The basics: what SSO is and why you should care

The easiest way to understand SSO quickly is to think about your app’s authentication as a service. Most developers build the service themselves: you take care of creating usernames and passwords, add them into a database, and check credentials every time someone logs in. But in the same way that you skip building payments infrastructure and use Stripe, you can “outsource” your auth and have someone else do it; and that’s what SSO is.

If you’ve heard of SSO before, you’re probably thinking of it as a security feature and that’s true, but where it really shines is through increased engagement . Making it easier to sign up and sign-in to your product lowers friction for users, increases retention through smoother login flows, and helps you land those elusive enterprise deals (many enterprises can’t work with vendors who don’t support SSO).

Apps with SSO enabled allow users to authenticate through someone else’s service. Instead of managing usernames and passwords, you integrate with a provider like Okta or OneLogin that does it for you. Those services, called Identity Providers (IdPs), are generally more full-featured and secure than what your typical growing startup would be able to build themselves.

SSO is a given among everyone from high growth startups to more traditional enterprises. Here’s Vercel's login page: they support SSO with Okta, Google, OneLogin, and more.

Slack, Asana, Notion, Loom, and Webflow all support SSO too. It’s pretty much part of the standard growth playbook.

Learning the lingo: SAML, SPs, IdPs, and assorted acronyms

Let’s get a little deeper into how SSO works. One thing worth noting: SAML isn’t the only protocol you can use to implement SSO. OAuth (1.0 and 2.0) are also popular, as well as WS-Fed and OpenID Connect (OIDC) . The broad concepts can carry over across protocols, too.

If you’re integrating SSO into your app, you’re a service provider (SP). Your app is the service. The provider that you’re “outsourcing” identity to, like Okta or OneLogin, is called the identity provider (IdP). Where things start to get complex is when your app needs to communicate with IdPs to actually authenticate your users. SSO works with a communication protocol called SAML (Security Assertion Markup Language) that governs these phone lines.

Let’s walk through a typical SAML flow, starting with a user trying to sign in through your site.

  1. When a user navigates to your login page, they’ll either enter their email or click a button that takes them to an IdP portal like Okta. Your app issues a SAML request (and a browser redirect) to the IdP. It’s basically saying, “Hey, this user wants to sign in, do me a favor and verify that I should let them in.”
  2. At the IdP, the user will enter their full credentials, and deal with more extensive security measures like 2FA. Once they’ve successfully authenticated with the IdP, the IdP sends your app a response containing an assertion: this user is good to go, and you can let them in. We call this assertion a SAML authorization response.
  3. The app receives the response from the IdP, checks it, and sends an one-time auth code.
  4. The app receives the user profile. The user is now able to access the application without needing to log in again. The user's session is managed by the service provider, which maintains the authentication state and ensures the user remains logged in as long as the session is valid.

SAML works through assertions . These are recorded and transferred as XML documents (for all those SOAP fans out there. Nobody? Ok).

Here’s an example of what a response containing an assertion might look like (thanks to OneLogin ):



  <samlp:response destination="http://sp.example.com/demo1/index.php?acs" id="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" inresponseto="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685" issueinstant="2014-07-17T01:01:48Z" version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <saml:issuer>http://idp.example.com/metadata.php</saml:issuer>
  <samlp:status>
    <samlp:statuscode value="urn:oasis:names:tc:SAML:2.0:status:Success"></samlp:statuscode>
  </samlp:status>
<saml:assertion id="pfxa099680e-6fc0-2c7a-90fa-4202bb29faa4" issueinstant="2014-07-17T01:01:48Z" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <saml:issuer>http://idp.example.com/metadata.php</saml:issuer><ds:signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <ds:signedinfo><ds:canonicalizationmethod algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:canonicalizationmethod>
    <ds:signaturemethod algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:signaturemethod>
  <ds:reference uri="#pfxa099680e-6fc0-2c7a-90fa-4202bb29faa4"><ds:transforms><ds:transform algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:transform><ds:transform algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:transform></ds:transforms><ds:digestmethod algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:digestmethod><ds:digestvalue>YOCfzMPwhVQibcTRRyuCb5vlTDU=</ds:digestvalue></ds:reference></ds:signedinfo><ds:signaturevalue>VXQGwtQsc/rTuCFspZwD6k4i6fKr4ymYfCiI5Ve9JO5LYRG7VNPzIq5Mr/JW/0btpui4cmQVK//wA89nLe+g2wxDizx32CnOBsshoF3YTDOs586SJt+Ty/h/X886Xhqu8XsdMiD/spyU8rGhIQP2OL65k6HoSFxtPqKt1+KOdkE=</ds:signaturevalue>
<ds:keyinfo><ds:x509data><ds:x509certificate>MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRcwFQYDVQQDDA5zcC5leGFtcGxlLmNvbTAeFw0xNDA3MTcxNDEyNTZaFw0xNTA3MTcxNDEyNTZaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxPbmVsb2dpbiBJbmMxFzAVBgNVBAMMDnNwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZx+ON4IUoIWxgukTb1tOiX3bMYzYQiwWPUNMp+Fq82xoNogso2bykZG0yiJm5o8zv/sd6pGouayMgkx/2FSOdc36T0jGbCHuRSbtia0PEzNIRtmViMrt3AeoWBidRXmZsxCNLwgIV6dn2WpuE5Az0bHgpZnQxTKFek0BMKU/d8wIDAQABo1AwTjAdBgNVHQ4EFgQUGHxYqZYyX7cTxKVODVgZwSTdCnwwHwYDVR0jBBgwFoAUGHxYqZYyX7cTxKVODVgZwSTdCnwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQByFOl+hMFICbd3DJfnp2Rgd/dqttsZG/tyhILWvErbio/DEe98mXpowhTkC04ENprOyXi7ZbUqiicF89uAGyt1oqgTUCD1VsLahqIcmrzgumNyTwLGWo17WDAa1/usDhetWAMhgzF/Cnf5ek0nK00m0YZGyc4LzgD0CROMASTWNg==</ds:x509certificate></ds:x509data></ds:keyinfo></ds:signature>
    <saml:subject>
      <saml:nameid format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" spnamequalifier="http://sp.example.com/demo1/metadata.php">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:nameid>
      <saml:subjectconfirmation method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:subjectconfirmationdata inresponseto="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685" notonorafter="2024-01-18T06:21:48Z" recipient="http://sp.example.com/demo1/index.php?acs"></saml:subjectconfirmationdata>
      </saml:subjectconfirmation>
    </saml:subject>
    <saml:conditions notbefore="2014-07-17T01:01:18Z" notonorafter="2024-01-18T06:21:48Z">
      <saml:audiencerestriction>
        <saml:audience>http://sp.example.com/demo1/metadata.php</saml:audience>
      </saml:audiencerestriction>
    </saml:conditions>
    <saml:authnstatement authninstant="2014-07-17T01:01:48Z" sessionindex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93" sessionnotonorafter="2024-07-17T09:01:48Z">
      <saml:authncontext>
        <saml:authncontextclassref>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:authncontextclassref>
      </saml:authncontext>
    </saml:authnstatement>
    <saml:attributestatement>
      <saml:attribute name="uid" nameformat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:attributevalue xsi:type="xs:string">test</saml:attributevalue>
      </saml:attribute>
      <saml:attribute name="mail" nameformat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:attributevalue xsi:type="xs:string">test@example.com</saml:attributevalue>
      </saml:attribute>
      <saml:attribute name="eduPersonAffiliation" nameformat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:attributevalue xsi:type="xs:string">users</saml:attributevalue>
        <saml:attributevalue xsi:type="xs:string">examplerole1</saml:attributevalue>
      </saml:attribute>
    </saml:attributestatement>
  </saml:assertion>
</samlp:response>


The flow we just outlined is called SP-initiated​ , because it started at ​your​ app, and you’re the service provider.

There’s another way this can go down though: users can ​start​ at their IdP (like the Okta app directory), click on which app they want to sign into, and then authenticate and redirect. That’s called ​ IDP-initiated​ .

Getting practical: how to add SSO to your app

Like pretty much anything in software, there are two ways to add SSO to your app: you can build it yourself or pay someone else to do it for you.

Option 1: b uilding SSO from scratch

Building SSO yourself is all about handling and working with the protocol you choose, assuming you’re targeting larger companies, we’re talking about SAML here. This isn’t a technical tutorial, but here are a few high-level components you’ll need to write:

  • A SAML controller for handling requests and providing responses to your integrated IdPs.
  • A SAML service to verify x509 certs, entity IDs, and IdP URLs, alongside parsing SAML assertions and creating and validating SAML responses. You’ll particularly enjoy the XML parsing and IdP-specific request formats.
  • A strategy to correctly authenticate users in your app based on the attributes that IdPs send back (you’ll need to normalize these if you’re supporting multiple customers).

If any of this sounds weirdly unfamiliar to you, that’s because it probably is: there’s a lot of upfront research required to understand the right way to do it. It’s not as simple as adding a new frontend library and skimming through the docs.

Part of the challenge of building SSO from scratch is customization: you’ll need to build SAML flows for each IdP independently. SAML is a standard and like any good standard it’s often fractured and can sometimes be a pain to work with.

The XKCD comic titled "How Standards Proliferate."

Over the past few years, the web dev ecosystem has developed a few packages that take care of some of the repeatable work. Middleware like ​passport.js can help you avoid building everything from scratch; or if your backend is in Python, OneLogin offers a python-saml​ package.

Option 2: use an SSO provider

If you don’t want to build SSO yourself (I mean, why would you?), there are a bunch of great third-party services that offer SDKs and packages to make integration as easy as a few lines of code. Some of the available providers are WorkOS, Auth0, AWS Cognito, and GCP Identity Platform.

Best practices from some engineers who have done it before

Here are a few tips that might make your SSO integration process just a bit easier, whether you’re using a third-party provider or building it from scratch.

Security

  • Disallow username and password logins, password resets, and email address changes: If an organization is using SSO with your product, give admins the ability to disable username/password based auth for their users. It creates a more seamless SSO experience by avoiding false login starts and keeps things secure.
  • Enforce session timeouts : Expire idle user sessions to make sure users aren't signed in indefinitely — it's good practice to grab the SAML response's session timeout value and use that, but there are cases where having a "time to live" setting for each account is useful too.
  • Force sign-in for active browser sessions : If your app gets a new sign-in request, replace any currently active browser sessions with the newly authenticated session. This is particularly important for apps that lean toward multi-tab use, like IDEs or CRMs.

Routing

  • Ask users for the information to determine the right IDP : If you plan on supporting multiple IDPs in your SSO implementation, ask users for their email address, account subdomain, or unique account URL to determine the correct identity provider for their login.
  • Make sure to deep link : If you’re asking users to authenticate from an existing product page or they’re expecting to land somewhere in particular in your product, you’ll want to implement deep linking in your SAML flows. You can use ​SAML’s RelayState parameter​ to get this working.

UX

  • Replace one-off email verification with domain verification : If your app sends verification emails on username/password signups, it may be more effective to switch to domain verification. Essentially, when domain verification is employed, users logging in with email addresses from a verified domain do not require separate email verification. This approach is particularly useful if the user's email is associated with an IDP from a verified domain. Domain verification is crucial for establishing security and trust between service providers and organizations, ensuring that only authorized users can make changes to an organization's settings in the service provider.
  • Use Just-In-Time (JIT) User Provisioning for first time sign-ins : JIT user provisioning automates the account creation process for users signing in to your app for the first time via SAML. If they exist in their organization’s IDP, you’ll just create their account automatically instead of asking them to sign up from scratch. This lowers friction for new users ​significantly​ and helps make your app more attractive to larger organizations.
  • Prompt users for IDP logouts : When users log out of your app, prompt them to see if they’d like to log out of their IDP as well. The two intents often overlap, and you can save your users some time.

A Digital Fly Brain Has Taken Over the Internet

403 Media
www.404media.co
2026-09-15 11:28:04
How to make a fly play Doom, Minecraft, Beat Saber, parallel park, trade bitcoin, cut doner kebab, and more....
Original Article

A digital fly brain has been trading bitcoin , parallel parking , exploring bisexuality , cutting doner kebab , playing games—including Minecraft , Doom , and Beat Saber —and generally lighting up social media timelines since it was released earlier this month.

The simulated brain, known as a connectome, is a complete map of more than 166,000 neurons that make up the nervous system of an adult male Drosophila fruit fly. Developed by HHMI Janelia Research Campus in partnership with Google Research, the connectome (dubbed MaleCNS v1.0) was unveiled earlier this month , following the release of the first adult female fruit fly connectome in 2024.

— Andrei Apanasik 🔜 TGS (@a_apanasik) September 7, 2026

“We present the first finished connectome of an entire male Drosophila central nervous system, encompassing the central brain, optic lobes (analogous to mammalian retina) and the ventral nerve cord (analogous to the spinal cord),” the team announced in a press release on September 3 . “Together with existing female connectomes, this resource enables the first comprehensive, synaptic-resolution comparison across sexes of an adult animal with complex anatomy and behaviors.”

The humble fruit fly is a powerhouse species in science for the same reason they might plague your kitchen—they reproduce quickly and are highly adaptable to different environments, distinguishing them as cheap and versatile model organisms. By virtually recreating their brains, researchers aim to make breakthroughs in neuroscience, pharmacology, biotechnology, artificial intelligence, and many other fields.

In the meantime, however, MaleCNS v1.0 has been enthusiastically received by online modders who have dropped the brain into a wide range of virtual environments and tasked it with different activities. Evan Sinclair Smith, a graduate student in computer science at Georgia Tech, pioneered the viral trend after he programmed the fly brain to play Minecraft. Within a day of releasing the results on September 4, the Minecraft fly had accumulated millions of views.

I’ve successfully run the full retained MaleCNS v1.0 fruit fly connectome, all 166,700 neurons, inside Minecraft, with its simulated neural activity driving a fly’s movement.

V1 Currently in development. Built with the help of GPT-6 Astra.

Props to the @OpenAI team and… https://t.co/wqsrsN5DFn pic.twitter.com/BN9cGvwVP1

— evnsnclr (@evnsnclr) September 4, 2026

“It's just been amazing how big this has gotten, and how many other people are just taking this idea and just making so many different creative projects,” Smith told 404 Media in a call. “That's been just really cool to see.”

In addition to playing video games, the fly has been trained to trade bitcoin (becoming “ stonkfly ”), write LinkedIn posts , and to retire to a virtual “ fruit fly heaven .” Nico Christie, an entrepreneur and AI researcher, programmed the fly to be attracted to other males (becoming “ bi fly ”).

In an email, Christie told 404 Media that he came across the trend on X and found it funny, but hoped to make “the science behind it more rigorous.”

I and @nicochristie ran the fly connectome and found the group of neurons (hΔH, hΔA, hΔI and hΔG) that could allow the fly to navigate using fast synaptic weight updates, not neural activations. This is fast-weight continual learning in a fly, something current LLMs don't do! https://t.co/TFgVaPA7B3 pic.twitter.com/kUqtfNTwQN

— Peter Wang (@BrainsAndTennis) September 14, 2026

In addition to fueling a wave of giddy online experimentation, the connectome has sparked moral and ethical debates on Reddit and other forums. However, it’s worth emphasizing that MaleCNS v1.0 is only a digital map of the brain that is not capable of sentience or conscious perception in any tangible biological sense. The connectome recreates a fly’s neural wirings and overall structure, but it does not contain any of the complex biochemical makeup of the real organism upon which it is based.

Though the connectome has inspired a literal brain wave of online content, it is intended to be a resource for the scientific community. The female fruit fly brain that was released in 2024, known as FlyWire , has already been used in a host of studies about motor control, sensory systems, fly sociability, and more, according to the neuroscience publication The Transmitter . The completion of both the male and female fruit fly connectomes has also allowed researchers to assess differences in the neural wirings of this sexually dimorphic species.

Smith, who made the Minecraft fly, hopes that these models will ultimately pave the way toward connectomes of the full human brain within his lifetime. Since the human brain contains 86 billion neurons, orders of magnitude more than the fruit fly brain, it would take an enormous amount of effort to create a digital copy with the same resolution as MaleCNS v1.0 or FlyWire.

But Smith speculates that such an achievement could unlock new approaches to brain disorders, such as Alzheimer's disease, or enable human intelligence to navigate space missions without having to haul along our cumbersome meatbag bodies.

“I think it has so many implications for understanding ourselves,” Smith said.

Cartesian – AI 3D Modeling for Design

Hacker News
www.formas.ai
2026-09-15 11:26:45
Comments...
Original Article

Cartesian by Formas

Anything to 3D.

Cartesian by Formas is an AI 3D modeling tool for architecture and product design.

Explore the model studies · Read the guides

Say it. Sketch it. Drag it in. It understands what you mean and builds it with maximum precision. Nothing to learn.

How three moves, every time

A skylit restaurant like this one. Keep the booths along the wall exactly.

Say it.

Like you would to a colleague.

The skylit restaurant reference photograph

Keep the booths

01 · Photograph 02 · A rough plan Booths kept exactly

Add what you have.

A photo. A plan. A sketch, scan or model. What you keep stays exactly. The rest is free to change.

restaurant one point final — exact model in red, pink, white and black

It's made.

The restaurant, every chair, table and plant an exact part. Open it in SketchUp, Rhino or any CAD. Change anything by asking.

What you can make real models, made by Cartesian

Across platforms. Across file formats.

Model and edit with precision.

Bring your workflow with you—from BIM and AutoCAD to Rhino and SketchUp. Create exact solids and NURBS geometry in Cartesian, without a separate expensive desktop CAD licence.

  • AutoCAD .DWG Planned
  • Rhino .3DM Native export
  • SketchUp .SKP Native export
  • BIM .IFC Planned

Why

Real solids, not pictures.

Measure it, cut it, print it, machine it.

Yours stays yours.

What you keep is never redrawn. What clashes is said out loud.

It understands.

What you mean, how things relate, what must not move.

Show HN: The bottom 50% of U.S. households are short after essentials (BLS data)

Hacker News
whats-left-over.pages.dev
2026-09-15 11:24:11
Comments...
Original Article

Lowest group · left in 2024 ?

Highest group · left in 2024 ?

Top portfolio · 2024 ?

10% of positive surplus

Portfolio gap ?

Largest ÷ smallest portfolio

An illustrative model, not tax or investment advice.

Fig. 1

Money left after essentials ?

Yearly pre-tax income excluding benefits, minus essential costs. Per person.

Nominal $ Quintiles Per person

?

Show the numbers

Fig. 2

What investing it adds up to ?

Portfolio balance from investing part of each year’s surplus since 2000.

S&P 500 10% invested

Show the numbers

Figs. 1–2 follow yearly cash flow. Fig. 3 turns to what households actually own.

Federal Reserve data Per household

Show the numbers

Method and definitions

What is real data, and what is a what-if

Real data. Every year from 2000 to 2024 uses that year’s published BLS Consumer Expenditure Survey table, which ranks U.S. households into five income groups (quintiles). From it come income before and after taxes, the income cut-offs between groups, household size, spending on six categories (including education), and benefit income. For the five quintiles, the model reproduces BLS’s averages exactly.

What-ifs. Anything finer than quintiles (deciles, the top 1%, custom splits) is estimated from a smooth income curve inside each quintile. Childcare, benefit amounts other than 100%, expense edits, the minimum tax, investing and the wealth taxes in Fig. 3 are scenarios you control. Fig. 3’s net worth itself is measured data from the Federal Reserve.

Breaks in the data Before 2004, BLS income figures cover only households that fully reported their income; from 2004 BLS fills in missing income. From 2013, BLS estimates taxes with a tax model instead of asking households, which is why BLS after-tax income for the top group dips that year. By default this site uses CBO’s consistent federal income tax rates instead, so the dip disappears. To compare, turn on “After tax” and choose a method under Advanced. BLS doesn’t publish the top group’s 2023 public assistance figure, so 2022’s is used. BLS also published no after-tax income for 2024, because it didn’t update its tax model that year; the BLS-based tax methods use each group’s 2023 tax rate for 2024.

Projections · 2025–2026 BLS hasn’t published household data for these years yet. They start from 2024 and grow every income and cost with consumer prices (CPI-U), so they show what happens if everything simply kept pace with inflation. The 2025 price average covers 11 months because BLS published no October 2025 index; 2026 covers January–August. The 2026 S&P 500 return is year to date through September 14. Projected years are shaded and dashed.

Known limitations BLS’s Consumer Expenditure Survey is known to under-report income compared with national accounts, and low-income households often report spending more than their income (through savings, debt, family help or unreported income). Figures are group averages, not medians, and each income group mixes ages and household types, including retirees and students. Per-person amounts use a simple headcount, and CBO rates cover federal income tax only. Treat the results as rough, comparative illustrations. Groups are snapshots, not the same people over time. Each year’s lowest 20% is whoever ranks lowest that year, and households move between groups as they age, change jobs, retire or change household size. Studies that follow the same people find real movement between groups over a decade, but also strong persistence from one generation to the next, so the gaps here describe positions in the distribution, not a fixed set of households.

An outside check The Federal Reserve’s survey of household economic well-being found that 63% of U.S. adults would cover a $400 emergency expense with cash or its equivalent in 2024 (the same as in 2023), leaving more than a third who couldn’t. That fits this site’s finding that lower-income groups have little or nothing left after essentials, though the survey measures adults, not households, and savings as well as income.

Definitions

  • Left after essentials (the “surplus”). Income under your settings minus the essential costs you selected, including childcare and any custom expense. Use “Edit expense amounts” to change the BLS figures.
  • Benefits & transfers. Social Security and pensions; public assistance, SSI and SNAP; unemployment, workers’ compensation and veterans’ benefits; regular support payments such as child support.
  • Education. BLS’s education spending: tuition, fees, textbooks, supplies and equipment for schools and colleges. It excludes student-loan payments and doesn’t count tax-funded public schooling. Off by default.
  • Happiness plateau line. $75,000 of yearly household income in 2008–09 dollars (Kahneman & Deaton, 2010), adjusted with CPI-U for each year: about $110,000 in 2024. A 2023 reanalysis found this plateau only for the least happy 15–20% of people, at about $100,000; for most people happiness keeps rising with income. It is a rough reference, not a threshold for any individual.
  • Childcare. Children × price per child, adjusted to each year with the day care and preschool price index.
  • Per person. The group’s average amount divided by its average household size, plus any children added.
  • Minimum tax. A tax on income, not wealth (under Advanced). “Simple” is a what-if rate above an exemption. “U.S.-style AMT” uses each year’s married-filing-jointly AMT exemption, phase-out and 26%/28% rates, with no deductions or filing status, so it is a rough group-level estimate.
  • Where the wealth is (Fig. 3). Household net worth from the Federal Reserve’s Distributional Financial Accounts, by wealth or by income group, 2000–2025. Wealth taxes there are what-ifs: flat charges the rate on net worth above a threshold; the Zucman-style minimum requires covered households to pay at least the rate × total net worth, counting taxes already paid. They are applied to each group’s average, ignore avoidance, and use no official revenue score, so treat them as rough orders of magnitude.
  • Investing. Returns accrue on the prior balance; the year’s contribution is added at year-end. A shortfall contributes $0. No taxes, fees or withdrawals: it’s a portfolio built only from this surplus, not a measure of anyone’s actual wealth.

Read the full methodology : every source, formula and limitation in one place.

Sources: BLS Consumer Expenditure Survey quintile tables, 2000–2024 · CBO, The Distribution of Household Income, 2022 · Kahneman & Deaton (2010), PNAS · Killingsworth, Kahneman & Mellers (2023), PNAS · Federal Reserve Distributional Financial Accounts · Federal Reserve SHED 2024 · Zucman (2024), G20 report on minimum taxation of the super-rich · Sen. Warren, Ultra-Millionaire Tax Act (2024) · IRS Form 6251 (AMT) · BLS CPI-U · BLS CPI: day care and preschool · Child Care Aware of America, 2023 price of care · S&P 500 total returns (S&P Dow Jones Indices).

Disclaimer: This site is for illustration purposes only. It was 100% AI-generated from the sources above, and its math and figures have not been independently checked by a human.

An illustrative model, not tax or investment advice. BLS, CBO and CPI data are U.S. government works in the public domain. Childcare price from Child Care Aware of America, used with attribution.

Version 1.0 · Data through BLS CE 2024, CPI August 2026 and S&P 500 to September 14, 2026 · Last updated · No cookies. Anonymous, privacy-friendly visit counts via Cloudflare Web Analytics; fonts load from Google Fonts.

Made by Patrick Glenn by way of Claude and ChatGPT. Code under the MIT License ; text and charts under CC BY 4.0 .

Show HN: Pizza Bot – An inbox for AI agents that work in the background

Hacker News
github.com
2026-09-15 11:20:26
Comments...
Original Article

Pizza Bot is an inbox for long-running AI work. Start or schedule a task, return to your day, and let completed work collect in Unread while runs waiting for your decision collect in Action . Agents keep working when you navigate away or disconnect; the api-server process must remain running.

Pizza Bot inbox showing unread work, an approval request, a completed launch brief, and delegated agent activity

Pizza Bot uses a stateful DeepAgents/LangGraph runtime with the same React experience in Electron and the browser. The desktop app, web app, and terminal CLI all communicate with the api-server over HTTP/SSE.

Pizza Bot was developed at Amazon and is released under the Apache 2.0 license.

Why Pizza Bot?

  • Work asynchronously. Switch conversations without stopping their runs.
  • Return to the right queue. Completed work lands in Unread; durable approval requests land in Action.
  • Organize conversations. Group threads into folders without hiding matching work from the global Unread and Action queues.
  • Resume real work. Checkpointed runs survive client disconnects, and cron or webhook triggers can start work without an open conversation.
  • Delegate to specialists. Skills become tool-scoped subagents whose progress appears in the Activity panel.
  • Bring your model provider. Amazon Bedrock, Anthropic, Google Gemini, OpenAI, OpenRouter, and Ollama are supported.
  • Keep control of consequential actions. Human-in-the-loop approvals, long-term memory, file attachments, and desktop notifications are built into the workflow.
  • Grant local access explicitly. Add individual read-only or writable folders under Settings > Files ; Pizza Bot receives no default home-directory access.

Download

Installers for macOS (Intel and Apple silicon), Windows, and Linux (x64 and arm64) are attached to every release , with a SHA256SUMS to check a download against. The macOS builds are signed and notarized; the Linux packages are not signed, so verify them against the checksums.

Quick start

Node.js 24 or newer is required.

npm install
npm run build
npm run dev

npm run dev starts the Vite frontend and Electron desktop shell. The shell forks and supervises its own api-server, matching the packaged application's process model. Configure a model under Settings > Providers before starting a live run.

See Running from source for isolated data roots, browser and CLI development, desktop packages, and remote backends.

Ways to run

Experience Best for Start here
Electron desktop Local inbox with an embedded backend npm run dev
Browser Web development or static deployment Browser development
Terminal CLI Scripts, terminals, and remote backends CLI
Standalone backend Remote Electron, browsers, containers, or Linux services Backend guide

A running api-server needs access to at least one model provider; HTTP clients do not. Configure Amazon Bedrock, Anthropic, Google Gemini, OpenAI, OpenRouter, or Ollama in Settings > Providers . Bedrock accepts an AWS profile, AWS access keys, or a Bedrock API key, with an optional region override; otherwise AWS_REGION or us-west-2 is used. Bedrock combines its native catalog with the regional Mantle catalog and routes models through Converse, OpenAI Responses or Chat Completions, or Anthropic Messages according to their advertised API family. OpenAI and Anthropic also accept custom base URLs for compatible endpoints; OpenAI can explicitly select Responses or Chat Completions, and Anthropic supports x-api-key or bearer authentication. Select a model with PIZZA_MODEL=<provider>:<id> . The desktop protects entered secrets with Electron safeStorage ; server configuration persists only environment-variable references.

Extend it

Add MCP servers from the UI or <PIZZA_DATA_ROOT>/.mcp.json . Add Agent Skills under <PIZZA_DATA_ROOT>/skills , or install plugins that package MCP servers and skills together. Skills become available after their declared tools are enabled and connected. A custom skill can replace a Built-in or Plugin skill with the same id without modifying the original; removing the customization reveals the Built-in or Plugin version again. The Built-in Pizza Bot Guide can explain features, suggest workflows, help with setup, and point to project documentation.

See Extending Pizza Bot for configuration, environment references, skill authoring, approval gates, and plugin installation.

Project layout

apps/        api-server (Hono) | cli | desktop-shell (Electron) | web (React)
packages/    core | runtime-langgraph | inference-providers | plugin-api | plugin-sdk | storage | logging
plugins/     bundled Plugin packages and their packaging workspace
skills/      optional Built-in Agent Skills
tests/       LangGraph compatibility and protocol conformance

The production graph engine is isolated to packages/runtime-langgraph ; frontends consume protocol projections rather than importing runtime or model bindings.

Documentation

  • Running - desktop, browser, CLI, and package commands.
  • Extending - MCP servers, skills, and plugins.
  • Architecture - system boundaries, event model, persistence, transports, and design decisions.
  • Standalone backend - authentication, remote Electron, static browser deployment, Docker, Compose, and Kubernetes.
  • Contributing - development setup, CI checks, worktrees, releases, and layering rules.
  • Security - network defaults, credentials, local data, and plugin trust.
  • Logging - diagnostics, retention, viewing, and redaction.
  • Roadmap - exploratory directions and the principles used to evaluate them.
  • Code of Conduct - community participation expectations.

Security and data

  • Local-first by default. The api-server binds to 127.0.0.1 ; non-loopback binding requires authentication and an explicit origin allowlist.
  • Application state stays local. Threads, checkpoints, memories, attachments, and logs live under <PIZZA_DATA_ROOT> ( ~/.pizza-bot-oss by default). Model and tool requests go to the providers and endpoints you configure.
  • Local folders require an explicit grant. Each folder added under Settings > Files is read-only unless you allow writes. Remote grants name paths on the backend host.
  • MCP servers and plugins are trusted. Their commands and materializers can execute with your user account's permissions. Install only sources you trust.

See SECURITY.md for the complete security model and vulnerability reporting process.

Contributors

Pizza Bot was designed, built, and brought into the open by its Executive Chefs and Sous Chefs:

Executive Chefs

Joseph Dolivo (@JoeDo) Igor Fil (@igorfil)

Sous Chefs

Flávio Schuindt (@flavioschuindt) Jacob Wert (@jwert-aws) Michael Karachewski (@michaelkarachewski) Itzik Paz (@spideron)

Pizza Bot was also shaped by more than 2,000 users across Amazon who tested earlier versions and shared feedback from real-world use. Their bug reports, ideas, and candid input helped make Pizza Bot ready for a broader community. Thank you to everyone who contributed.

License

Apache-2.0 . See NOTICE for attribution notices.

Datacentre giant accused of using AI while lobbying for Victorian minister’s support for hyperscale factory

Guardian
www.theguardian.com
2026-09-15 11:00:54
Exclusive: Correspondence requests state MP Melissa Horne to lobby on NextDC’s behalf – but contains alleged AI-created errorsGet our breaking news email, free app or daily news podcastThe Victorian jobs minister has accused one of Australia’s largest datacentre companies of using artificial intelli...
Original Article

The Victorian jobs minister has accused one of Australia’s largest datacentre companies of using artificial intelligence while requesting she lobby her colleague to approve a massive expansion of its “hyperscale AI factory”.

Correspondence between Victorian cabinet minister Melissa Horne and the chief executive of ASX-listed company NextDC, Craig Scroggie, reveals increasing sensitivity to community concerns about datacentres before the state election.

NextDC has lodged an application with the state government for fast-tracked approval to expand its controversial datacentre known as M3 in Footscray to cover 10 hectares , drawing 225MW of power and running 24/7.

The datacentre’s size and proximity to homes in Footscray has led to growing complaints from local residents, who argue the government has been too quick to approve developments without considering long-term impacts on the community.

Sign up for the Breaking News Australia email

Some residential homes are within 15 metres of the datacentre’s boundary , which is zoned in an industrial area.

Earlier this month, Scroggie took what multiple lobbying sources have described as an unusual approach by personally emailing the minister and encouraging her to back the project, which is being assessed by the planning minister.

“I refer to you correspondence on 3 September requesting that I intercede on behalf of NextDC Limited in the decision of the minister for planning …” Horne wrote.

Horne, who is a factional ally of the state premier, Ben Carroll, replied with a blunt opposition to the expansion. This is despite the state government’s long-term effort to attract more datacentre investment in Victoria.

“I do not believe that it would be appropriate for me to lend my support to the application and nor do I wish to,” Horne wrote to Scroggie on Monday.

NextDC has since employed a former adviser to the Brumby and Bracks Labor governments, Ken McAlpine, to conduct the company’s lobbying with the state government.

Horne’s letter to Scroggie was published by Labor MP, Katie Hall, whose electorate includes the NextDC datacentre in Footscray. Hall is a long-term opponent of the datacentre’s expansion.

skip past newsletter promotion

In her letter, Horne accused Scroggie of using artificial intelligence to write his letter, citing what she alleged to be basic factual errors.

“I suggest that you review content produced by artificial intelligence more closely in the future,” Horne wrote on Monday.

Scroggie and NextDC were contacted for comment but declined to respond to Horne’s letter and her allegations. Horne also declined to comment.

Last month, Hall outlined her concerns about the impact of the NextDC datacentre and its expansion with the state’s artificial intelligence minister, Anthony Carbines.

In April, Hall wrote to the planning minister who will ultimately approve whether NextDC’s expansion will be approved. She raised concerns for her constituents.

“This includes the site’s location within 500 metres of over 800 homes, a kindergarten, maternal and child health services, and Stony Creek,” Hall wrote.

“As a result, they are understandably concerned about the reliance on diesel generators during power outages, as well as the regular testing of these systems.”

In 2025, Scroggie posted a video of the M3 site on LinkedIn and said the speed and the scale of its expansion were “stunning”.

“We’re building Australia’s largest hyperscale AI factory purpose-built for the new AI era of accelerated computing,” he wrote. “This is how we build Australia’s digital future: speed, scale, sovereign, sustainable & secure.”

In Victoria, large datacentres are eligible for fast-tracked planning approval. Decisions made by the planning minister cannot be appealed to the Victorian Civil and Administrative Tribunal.

‘In this new car, I feel patronised’: which driver assistance systems actually improve safety?

Guardian
www.theguardian.com
2026-09-15 11:00:54
Modern cars are fitted with features like autonomous emergency braking and fatigue monitoring. But do they support motorists or drive them to distraction? This new car I’m test-driving boasts all the latest bells and whistles – or, rather, beeps and bings. Peer sideways for too long checking traffic...
Original Article

T his new car I’m test-driving boasts all the latest bells and whistles – or, rather, beeps and bings. Peer sideways for too long checking traffic and the car emits an irritating sound. Should I take a hand off the wheel to scratch an ear, I’m admonished with a strident beep.

When the car determines it’s too near a white line, it tugs the wheel against my hands and brakes as a car slows ahead. Its haptic “buttons” feel like fingers pressing back – as if an invisible presence has its hands and feet on the controls: a ghost in the machine I’m inside.

Heading back to the caryard, a cryptic tone sounds. From the back seat, salesman Anthony says it’s the over-speed warning. We’re still on the 40km/h service road but the car “saw” the yard’s 5km/h speed-limit sign. Anthony advises disabling it on start-up. “Most people leave it on, though,” he says. “Saves you getting done for speeding.”

But Anthony has turned off the car’s fatigue-warning feature. The car doesn’t “know” me yet, so won’t detect if I’m tired anyway. “It doesn’t like sunglasses, either,” he says. I’ve read about some systems that are so crudely calibrated they’ll insistently demand you take a break by confusing energetic driving on a twisting mountain road with drunkenness or debility. Others declare you unfit to drive if you’ve had a cold or a rough night. You’ve already seen yourself in the bathroom mirror. Who needs a robot to rub it in?

I last faced this kind of surveillance and intervention 50 years ago, seated beside a human driving instructor with dual controls overriding my own. I’m an experienced motorist, but in this new car I feel patronised. Between the white lines, human.

In this robot-on-wheels I’m no longer autonomous. And isn’t that supposed to be the inherent promise of the automobile? It used to be. But with freedom came much higher risk.

A person’s hands at the wheel of a smart-driving car, while also using a touch display screen in the vehicle
Could advancing safety tech also create more bad drivers? Illustration: Weiquan Lin/Getty Images

Fifty years ago, cars were certainly more dangerous. Few 1970s cars had secondary safety features such as crumple zones, fewer still had primary (or active) safety aids such as lane-departure warning and autonomous braking. As driver, you were more or less in control of the car. There was no e-nanny on board to mitigate distraction or errancy. The result was an Australian road death-rate in 1975 of 26.6 deaths per 100,000 people .

Today’s more crowded, complex and chaotic road network is paradoxically much safer, with an equivalent fatality rate in 2026 of 4.7 . Mounting evidence suggests advanced driver-assistance systems (Adas) are one reason for this improvement.

Monash University’s Accident Research Centre has identified distraction and drowsiness contributing to about 40% of crashes resulting in hospital visits in Victoria. Associate professor Michael Fitzharris estimates that the introduction of Adas, including autonomous emergency braking (AEB), to Victoria’s passenger and light commercial fleet could prevent 40,000 serious traffic injuries over 30 years. “The findings make a compelling case for the rapid adoption of driver monitoring technology,” Fitzharris says.

The federal government’s response has been to mandate lane-keeping assist technology and AEB on all passenger vehicles imported into Australia from 1 March 2029. (Ludicrously, the largest American monster utes are exempt.)

But research and customer feedback have also identified problems with existing driver-assistance systems, including autonomous braking.

Back view of female driver in vehicle
Well-designed driver monitoring systems should support motorists, ‘not irritate them’, according to vehicle safety body Ancap. Photograph: Chadchai Krisadapong/Getty Images

I had cause to be sceptical about AEB in 2022 after renting a new four-wheel drive in central Australia.

I was passing a slow vehicle turning right when my car suddenly braked hard on a bend, skidding in gravel towards a deep culvert. A moment later it computed there had been no danger anyway, and released control back to me. I’d been briefly overruled by a machine, and could have been killed. After stopping to deactivate the active cruise control, I, human, was back in charge.

The Australasian New Car Assessment Program (Ancap) has identified deficiencies with Adas, and in March announced new design protocols for driver-interface technology for the next three years.

In cooperation with the European New Car Assessment Programme (Euro Ncap), Ancap will now penalise vehicles with intrusive, confusing or poorly calibrated safety systems.

Ancap will also recommend the return of physical buttons or fixed areas on screens for key controls: horn, indicators, hazard lights, wipers and headlights. These changes acknowledge the danger of searching through complex screen menus while driving, and the absurdity of tech meant to reduce driver distraction but actually increasing it.

Acknowledging criticism surrounding increasingly intrusive driver assistance technologies, Ancap’s chief executive, Carla Hoorweg, says the organisation’s updated guidance states that well-designed driver monitoring systems should support motorists, “not irritate them”.

“Driver monitoring systems must demonstrate genuine capability to detect distraction and fatigue, and speed assistance systems must demonstrate both accuracy and meaningful driver engagement,” Hoorweg says.

Such measures should encourage the evolution of today’s relatively crude interface platforms into sophisticated systems, including AI assistants more adept at relating to our species, and produce more self-driving, and potentially safer, cars. But could this advancing safety tech also create more bad drivers?

Automotive engineers go to great lengths to isolate occupants from the road by reducing a vehicle’s NVH (noise, vibration and harshness). Most drivers value such designed detachment, along with electronic driver aids – particularly as commuting times increase and cars are increasingly used as mobile offices, infotainment platforms or salons (I recently saw a driver stopped at lights shaving his lathered head).

But tech that caters to these social changes can, in effect, discourage active, attentive driving, and accommodate or even abet distraction. Doubts also persist about whether cosseted motorists will retain the skill levels of their unassisted peers.

In a 2026 study, using a sample of 60 Australian drivers, researchers at the University of Delft concluded that use of Adas leads to the degradation of manual driving skills , with drivers showing increased risk-taking and decreased cognitive and physical engagement. The study also found that warning systems triggered varied adaptations, including dependence, warning fatigue and risk compensation, as drivers relied more on the car to drive for them.

Acknowledging this potential, learner-driver Wil’s father disables his car’s Adas when teaching his son to drive. Wil, 29, says his father is concerned that relying too heavily on tech, at least initially, could result in his son becoming complacent. Wil is proud of being able to drive without Adas, and to be developing the awareness skills required to see the crucial “big picture” himself.

Other new drivers, like 27-year-old Stephanie and her sister, prefer to leave their cars’ aids on. The siblings both learned to drive with Adas activated. Stephanie says lane-keeping assist helps her determine her car’s exterior dimensions and position it accurately. Another beginner driver, Sawyer, says she likes her car’s electronic aids: “Because they make me feel safe.”

A driving instructor shows a young male student how to adjust his rear view mirror during a driving lesson
One study found that the use of advanced driver-assistance systems led to the degradation of manual driving skills and increased risk-taking. Photograph: Catherine Falls Commercial/Getty Images

A clear generational divide is emerging as younger generations embrace the spread of digital culture – in this case, to the road – more readily than older drivers (like me).

While accepting the raw safety statistics, I remain sceptical about the rise of robot cars. Who, after all, should we trust behind the wheel? Anonymous organic entities – our own species – or invisible electronic minders?

Though driver-assistance features, including active autonomous braking, can still be deactivated in many cars, it’s telling that one brand calls its manual setting “classic cruise”. Soon it may be “legacy cruise”, and only if a “no thanks” option remains at all.

So I won’t be buying a new car yet. My decade-old vehicle has only two electronic aids, which I always leave on: tyre-pressure alert and blind-spot-monitoring (a boon for this old neck).

For any future purchase, I’ll favour a car with minimal electronic intrusion, that allows disabling or at least muting, and with individual settings for multiple drivers that don’t need re-configuring at each start-up.

Until then, I remain autonomously yours, a human at the wheel. And yes, you can trust me. I promise not to shave my head while driving.

BambooToken malware controls Windows and Linux systems via MQTT

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 11:00:00
A previously unknown malware framework called BambooToken, active since at least 2023, is now using the Message Queuing Telemetry Transport (MQTT) protocol to communicate with Windows and Linux systems. [...]...
Original Article

BambooToken malware controls Windows and Linux systems via MQTT

A previously unknown malware framework called BambooToken, active since at least 2023, is now using the Message Queuing Telemetry Transport (MQTT) protocol to communicate with Windows and Linux systems.

The malware adopted MQTT for command-and-control communications in variants developed between 2024 and 2025, compromising servers used by mobile apps, legal and financial services, and software development.

MQTT is a lightweight messaging protocol primarily designed for IoT (Internet of Things) devices. It relies on a central broker and channels called “topics” to relay messages from publishers to subscribers, rather than using direct communication channels.

While MQTT is not novel, it is an uncommon approach, and researchers at cybersecurity company ESET documented an unrelated backdoor called MQsTTang in 2023.

In the case of BambooToken, the infected machine subscribes to topics associated with a unique identifier. The attacker then publishes to those topics the commands to be executed on infected hosts.

The malware publishes status and system information through the broker and receives operator instructions through subscribed topics.

Hardcoded Topics in the malware code
Hardcoded Topics in the malware code
Source: Lumen

This approach has the advantage that infected systems do not connect directly to the attacker’s infrastructure, which increases evasion and resilience. At the same time, communications can be asynchronous, ensuring operational continuity during temporary network disruptions.

A report today from Lumen's research arm, Black Lotus Labs, notes that BambooToken infected systems by side-loading via a digitally signed Tendyron OnKey USB-token software or by impersonating the Kingsoft Office productivity suite.

The researchers recovered a BambooToken plugin that enumerates antivirus products on infected hosts and returns the results to the C2. They also found strings pointing to keylogging, clipboard theft, audio recording, webcam capturing, and screenshot capturing.

However, they retrieved these details from “dead code,” meaning the researchers cannot confidently determine if the referenced modules existed and were used in attacks or were still under development.

Strings found in dead code section of .rdata file
Strings found in dead code section of .rdata file
Source: Lumen

The researchers found a Linux variant of the malware, BambooToken version 2.1, as the most recent one (observed in December 2025) that could be linked to the campaign

It also uses MQTT, collects extensive system information, can spawn a command shell, and allows operators to upload, download, and delete files. However, Black Lotus Labs says that "the Linux sample still appeared to be under development."

Lumen’s telemetry identified approximately a dozen compromised enterprise entities, mostly in Asia and South America, including hotels, biomedical firms, law firms, a financial organization, and a cryptocurrency website in Lithuania.

Additionally, the researchers found that the most compromised servers were associated with the backend infrastructure of mobile applications.

The threat actor also compromised a GitLab server in Hong Kong, creating a potential foothold for supply-chain attacks.

Lumen hypothesizes that some of the activity may have targeted overseas Chinese users accessing mainland services through the SpeedCN VPN service.

Although the researchers could not attribute BambooToken activity to a specific threat actor or a known activity cluster, they note that the targeting patterns are consistent with China-aligned operations.

Lumen has shared indicators of compromise (IoCs) associated with this activity to help defenders detect and block the attacks.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

A Cop Searched 19,000 Flock Cameras Across 1,558 Cities. His Reason: 'LMAO'

Hacker News
www.techtimes.co.uk
2026-09-15 10:47:21
Comments...
Original Article

A routine police search might involve looking for a stolen car or a wanted suspect. But one officer's reason for searching a vast network of automated licence plate readers was simply 'LMAO'.

In July 2025, an officer with the Lake County, Indiana, Sheriff's Department used Flock Safety's surveillance system to search for a licence plate across more than 19,000 cameras covering 1,558 cities and towns. According to a record of the search, the reason entered was 'LMAO'.

The case was among dozens identified in an analysis by the Electronic Frontier Foundation of Flock's search logs. The records show officers entering jokes, gibberish and other seemingly unserious terms into a field intended to document the reason for a search.

Police Searches Included 'IDK' And Gibberish

Data reviewed by the EFF included searches labelled 'LMAO', 'LOL', 'Hehe', 'Haha', 'idk' and 'blah'. Other entries included 'asdfg' and strings of apparently random keyboard characters. In one case, an officer entered: 'robbery I don't remember the case number leave me alone'.

The searches identified by the EFF came from police departments across the country between 2023 and late 2025, when Flock changed how the reason field worked. The analysis also found more than 6,300 searches across more than 30 agencies that used 'TBD' as the stated reason.

Flock's technology uses automated licence plate readers to collect vehicle-related information and allow law enforcement agencies to search data gathered by cameras across jurisdictions. The EFF says the system can provide access to sensitive location information and has raised concerns about the lack of judicial oversight surrounding ALPR searches.

Flock Changes How Officers Justify Searches

Flock changed its system in late 2025, replacing the free-text reason field with a list of pre-populated categories that officers can select before searching. The EFF called the change a 'loss for transparency', arguing that selecting a generic category may provide less information about the actual purpose of a search.

The EFF also contacted police departments whose officers were identified in its analysis. Some agencies said officers had been counselled, while others said they would investigate or review the searches.

The findings add to broader concerns about the expanding use of ALPR technology and how police departments oversee access to the resulting data.

Trump Has Backed Flock Cameras

The controversy comes as Flock receives high-profile political support. President Donald Trump publicly endorsed the company during remarks to reporters on Sunday.

'I sort of like them because of that, because of law enforcement, but some people don't,' Trump said. 'They think it's an infringement,' he added. 'I like them.'

According to Futurism , citing The Washington Post , the remarks marked the first time Trump had publicly endorsed Flock. The report said Flock's network had grown to more than 120,000 cameras.

The 'LMAO' entry does not establish why the officer searched, but it highlights a larger accountability question surrounding a surveillance network capable of searching vehicle data across thousands of communities: how much oversight should be required when police have access to such a broad pool of location information?

As these tracking systems continue to expand rapidly nationwide, the challenge remains ensuring that powerful tools designed to catch criminals do not turn into instruments of unchecked monitoring.

Hackers target WordPress sites via third-party WooCommerce plugin

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 10:45:10
Hackers are actively exploiting a critical vulnerability in the WooCommerce Wholesale Lead Capture premium plugin for WordPress to upload a PHP backdoor. [...]...
Original Article

Hackers target WordPress sites via third-party WooCommerce plugin

Hackers are actively exploiting a critical vulnerability in the WooCommerce Wholesale Lead Capture premium plugin for WordPress to upload a PHP backdoor.

The flaw is tracked as CVE-2026-27540 and impacts plugin versions 2.0.3.1 and older. It is an unauthenticated arbitrary file-upload vulnerability discovered by security researcher Teemu Saarentaus.

An attacker can exploit it to upload PHP webshells and execute code, potentially leading to a complete site compromise.

From a technical standpoint, the flaw is caused by exposing  an unauthenticated AJAX action named wwlc_file_upload_handler, which checks file extensions against an allowlist supplied through the user-controlled file_settings request parameter.

This allows adding ‘php’ to the permitted file types, making the plugin accept PHP executable file uploads.

The vulnerability was addressed in version 2.0.3.2 of the WooCommerce Wholesale Lead Capture plugin, released on February 20.

However, WordPress security company Defiant is warning that its Wordfence web application firewall blocked over 100,000 attacks linked to CVE-2026-27540.

Wordfence reports that exploitation activity spiked between June 4 and June 17, and on July 1 and August 30.

During the attacks, the hackers upload a webshell that conducts reconnaissance but can also introduce additional payloads.

“The attacker submits a request to the wwlc_file_upload_handler AJAX action containing a forged file_settings parameter and a malicious file with a .php extension,” Wordfence explains .

“The uploaded shell.php is a PHP webshell that reports host details and provides a browser-based upload form for writing additional malicious files to the site.”

Attack request
Example attack request
Source: Wordfence

Wordfence provides a set of high-offender IP addresses that deployed tens of thousands of exploitation attempts. Administrators are recommended to add them to a blocklist and upgrade to plugin version 2.0.3.2 or later that addresses the security problem.

The researchers advise checking upload directories for unexpected or recently created PHP files, examining logs for requests to /wp-admin/admin-ajax.php invoking wwlc_file_upload_handler, and removing unknown administrator accounts.

If compromise is confirmed, the recommended action is to restore the website from a safe backup, as removing all persistence mechanisms, users, and backdoors may be complicated.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Closing the IPv6 First-Packet Gap with Grand

Hacker News
labs.ripe.net
2026-09-15 10:44:33
Comments...
Original Article

There is a subtle asymmetry in IPv6 Neighbour Discovery: a host knows how to reach its router before the router necessarily knows how to reach the host. GRAND fixes that asymmetry by making the host proactively advertise its address, and I implemented that mechanism in FreeBSD.

A device joins an IPv6 network, receives a Router Advertisement, configures a new IPv6 address, and immediately starts communicating with the Internet.

From the host's perspective, everything is ready. It knows the link-layer address of its default router, so it can immediately send packets towards the Internet.

However, from the router's perspective, things may look different. The router may not yet know how to reach the host's newly configured global IPv6 address.

This creates a subtle asymmetry in IPv6 Neighbour Discovery: the host can already reach the router, while the router may have to perform Neighbour Discovery before it can forward traffic back to the host.

The first IPv6 packet has a problem

Consider a host that has just configured a new IPv6 address.

The host sends a packet towards an off-link destination. The first-hop router receives the packet and forwards it normally. When the remote destination responds, however, the return packet arrives at the router with the host as its destination.

The router may not have a Neighbour Cache entry for that IPv6 address, so it has to resolve the host's link-layer address before it can forward the packet. This puts Neighbour Discovery directly in the critical path of the first return packet.

For a mechanism that is normally invisible to applications, this can have a visible effect: the first packets of a connection may experience additional latency, or potentially be dropped while address resolution is in progress.

The problem is particularly interesting because the host already has all the information the router needs. The host knows that it owns the IPv6 address and knows the corresponding link-layer address, but the router simply hasn't learned it yet.

Why does Neighbour Discovery cause this?

IPv6 Neighbour Discovery is reactive in this situation. When a node needs to communicate with a neighbour for which it does not have a usable Neighbour Cache entry, it starts address resolution by sending a Neighbour Solicitation and waiting for a Neighbour Advertisement.

This works well for established neighbours. The problem appears during the transition from "the address has just become usable" to "the rest of the network knows how to reach it". So, again, the host already knows its IPv6 address and link-layer address while the first-hop router may have no information about the host until traffic arrives.

This is the gap that GRAND addresses.

GRAND: changing reactive Neighbour Discovery into proactive information

Gratuitous Neighbour Discovery changes the direction of information flow so that instead of waiting for the router to discover the host when the first packet arrives, the host proactively announces its IPv6 address and link-layer address using an unsolicited Neighbour Advertisement.

The router can then learn this information before it needs to forward traffic towards the host. This is the central idea behind RFC 9131 .

But implementing GRAND is not simply a matter of sending an unsolicited Neighbour Advertisement whenever an address appears. GRAND interacts with several existing Neighbour Discovery rules, including the handling of multiple addresses, anycast and proxy addresses, timing, and Duplicate Address Detection.

Building GRAND on RFC 4861

An important part of implementing GRAND is understanding that unsolicited Neighbour Advertisements already have a defined role in Neighbour Discovery.

RFC 4861 rule 7.2.6 specifies behaviour for unsolicited Neighbour Advertisements, including cases such as changes to a node's link-layer address. It also limits how many advertisements a node can send for multiple addresses and recommends spacing those advertisements to avoid unnecessary congestion.

RFC 4861 rules 7.2.7 and 7.2.8 cover the special case of anycast and proxy Neighbour Advertisements. In these situations, multiple nodes may potentially respond to the same Neighbour Solicitation. If they all transmitted immediately, their purpose becomes ineffective.

To address this, RFC 4861 specifies a random delay before sending an anycast or proxy Neighbour Advertisement. This gives multiple potential responders a chance to avoid transmitting simultaneously.

These timing rules are an important part of the overall design of Neighbour Discovery: making information available quickly is useful, but doing so without creating a multicast storm is equally important.

My implementation adds these behaviours as part of the GRAND work rather than relying on pre-existing queueing and delayed-NA infrastructure.

GRAND and delayed Neighbour Advertisements

One of the less obvious parts of GRAND is therefore the scheduling of Neighbour Advertisements. That's where I decided to implement missing RFC 4861 parts.

The existing implementation did not previously provide the queueing and delayed-transmission machinery needed for these behaviours. Implementing GRAND required adding that infrastructure and then using it to schedule unsolicited Neighbour Advertisements.

In GRAND, a newly configured address may result in an unsolicited Neighbour Advertisement, but the implementation must consider how many addresses are being advertised and when each advertisement should be transmitted. In IPv6, an interface might have hundreds of addresses at the same time.

Sending all advertisements immediately could create an unnecessary burst. For example, consider a datacentre after power is restored, causing many servers to start at once. So the implementation therefore adds the delayed advertisement behaviour described by RFC 4861 7.2.6 and the randomised response behaviour described in 7.2.7 and 7.2.8.

This is particularly relevant for anycast and proxy addresses, where more than one node may be capable of responding.

The goal is not simply to minimise the time before an advertisement is sent. It is to balance fast neighbour discovery with the amount of multicast traffic generated by that discovery.

What does RFC 9131 actually change?

RFC 9131 builds on these Neighbour Discovery mechanisms to address the first-packet problem.

A host sends an unsolicited Neighbour Advertisement when a new IPv6 address becomes usable. The advertisement contains the information that a first-hop router needs to construct a neighbour entry.

However, there is an important second half to the mechanism. Under the original RFC 4861 behaviour, receiving an unsolicited Neighbour Advertisement does not necessarily mean that a router with no existing Neighbour Cache entry will create one.

RFC 9131 changes this behaviour.

A router receiving a valid unsolicited Neighbour Advertisement for an address for which it has no existing Neighbour Cache entry can create one using the information supplied in the advertisement. The entry is created in the STALE state, a detail that makes GRAND useful for the first-packet problem.

Why STALE is the key

At first, putting a newly learned neighbour into STALE may seem counterintuitive. Why not mark it REACHABLE? Actually, the distinction is important.

GRAND tells the router that the host is claiming the IPv6 address and provides a link-layer address. It does not necessarily prove that the neighbour is currently reachable in the sense used by Neighbour Unreachability Detection.

STALE allows the router to use the information it has already learned without requiring a new multicast address-resolution operation. The router can subsequently verify reachability using the normal Neighbour Discovery mechanisms. This means that GRAND removes address resolution from the critical path of the first packet without claiming that the neighbour has been permanently verified as reachable.

Implementing GRAND in FreeBSD

Implementing GRAND in FreeBSD therefore involved more than adding code to transmit an unsolicited Neighbour Advertisement.

The existing IPv6 Neighbour Discovery implementation already provided state machines, address lifecycle handling, Duplicate Address Detection, and Neighbour Cache management. However, it did not previously provide the queueing and delayed-Neighbour-Advertisement machinery required for GRAND and the related RFC 4861 behaviours.

The GRAND implementation added that infrastructure.

It also implements the relevant delayed and randomised advertisement behaviour from RFC 4861 rule 7.2.7 and 7.2.8.

This includes handling the timing of advertisements so that multiple addresses, anycast addresses, and proxy-related advertisements do not unnecessarily produce bursts of Neighbour Discovery traffic.

The implementation consequently combines several pieces of Neighbour Discovery behaviour:

  • Proactively advertising newly usable IPv6 addresses
  • Handling link-layer address changes
  • Adding queueing for Neighbour Advertisements
  • Delaying multiple unsolicited advertisements
  • Applying the appropriate randomisation for anycast and proxy responses
  • Integrating the new scheduling machinery with the existing ND state and timer handling

The result is not a separate "GRAND subsystem", but rather an extension of the existing Neighbour Discovery implementation with new queueing and transmission-scheduling support.

Want to take a closer look? You can explore the initial GRAND implementation in FreeBSD, along with the subsequent cleanup .

Handling link-layer address changes

GRAND also connects naturally to one of the existing uses of unsolicited Neighbour Advertisements.

RFC 4861 rule 7.2.6 defines unsolicited advertisements for situations such as a change in the link-layer address. If an interface's link-layer address changes while its IPv6 addresses remain configured, other nodes may still have cached information referring to the previous link-layer address. The implementation can proactively advertise the new mapping rather than waiting for normal Neighbour Discovery to discover the change.

This means that the GRAND implementation covers two closely related situations:

  • An IPv6 address becomes usable
  • The link-layer mapping for an existing IPv6 address changes

In both cases, the objective is the same: make the information available to neighbours before they are forced to discover it reactively.

What I learned while integrating it into FreeBSD

The implementation also highlighted an important difference between implementing a protocol on paper and integrating it into an existing networking stack.

The RFC describes the desired protocol behaviour, but the kernel did not previously have all of the mechanisms needed to implement it. In particular, GRAND required new queueing and delayed-transmission support for Neighbour Advertisements.

Adding GRAND therefore meant introducing those mechanisms and making sure that they interacted correctly with the existing address lifecycle, DAD, Neighbour Cache management, and Neighbour Discovery state handling.

In particular, GRAND-generated advertisements have different semantics from other Neighbour Advertisements. This required changes to the way advertisements are queued, combined, delayed, and eventually transmitted.

The timing requirements also make the implementation more interesting than simply generating packets immediately. The implementation needs to avoid creating unnecessary bursts while still making the information available early enough to solve the first-packet problem.

This is where the details in RFC 4861 rules 7.2.6-7.2.8 become important in practice. They are not merely historical protocol details, they provide the rules needed to make proactive Neighbour Discovery behave well on a real network.

Why does this matter for real IPv6 deployments?

Neighbour Discovery is often invisible when everything works correctly, and this is exactly what makes issues like this easy to overlook. The protocol is normally fast enough that users never think about it. But Neighbour Discovery can sit directly on the forwarding path, which means that its behaviour can affect the first packet of a connection.

This becomes increasingly relevant as IPv6 hosts dynamically configure and remove addresses. Privacy addresses, changing prefixes, mobile devices, virtual machines, containers, and other environments can all result in addresses appearing and disappearing during the lifetime of an interface.

GRAND allows the network to learn about a new address at the time the address becomes available rather than waiting until traffic forces that discovery to happen. And at the same time, the timing mechanisms inherited from the Neighbour Discovery design help ensure that proactive advertisements do not themselves become a source of unnecessary multicast traffic.

GRAND and RFC 9898

GRAND is not just an interesting optimisation described in an RFC. RFC 9898 , Neighbour Discovery Considerations in IPv6 Deployments , explicitly identifies GRAND as a mechanism that addresses the router forwarding delay caused by this form of Neighbour Discovery.

This is important because it places the problem in the context of actual IPv6 deployment rather than treating it as a theoretical protocol issue.

The CSS Zen Garden dream shipped

Hacker News
josprague.com
2026-09-15 10:40:08
Comments...
Original Article

Rebuilding Firefox.com with Mozilla and Lincoln Loop on modern, native CSS with no preprocessors, and what that means for design systems today.

Back in 2008, fresh out of college, I discovered CSS Zen Garden , Dave Shea’s project where one HTML file could be restyled into something completely different using nothing but CSS. It was a glimpse of the dream: clean, reusable design, fully separated from content.

Then you tried to ship real client work, and the dream fell apart. There were no CSS variables. The properties available couldn’t express a full-fidelity design, so we leaned on server-side processing, images, table-based layouts, and endless hacks. Every browser rendered things differently, so much of the work was just making one design behave across all of them. Zen Garden showed what was possible in theory. Production was another story.

That gap took the better part of two decades to close, and most of the closing happened in the last few years. Custom properties gave us variables the browser understands. Grid and Flexbox gave us layout that doesn’t fight the medium. The properties we lacked in 2008 now exist, and they’re implemented consistently enough that you can design against them instead of around them.

Firefox.com

That gap is finally closed. Modern CSS now does natively what we used to need preprocessors and hacks for. On the rebuild of Firefox.com , working with Mozilla and the team at Lincoln Loop, I built the whole system in modern, native CSS with no preprocessors. Custom properties are exported straight from design files, native CSS is written once with no hacks, and it works in every modern browser, with a minimal branded stylesheet giving legacy browsers basic, accessible branding.

Together we built a design system of more than 70 components and 25 page templates, implemented as Wagtail components so the site’s content team can assemble pages without engineering help. The site currently supports 19 locales.

There’s one honest footnote. We did end up with PostCSS in production, used for a single job: inlining @import statements. Native @import still has terrible performance characteristics in some browsers, and on a site like this one that matters more than architectural purity. The authored CSS is still plain, native CSS. Nothing in it depends on a build step to be valid or to make sense. The build only flattens what the browser would otherwise fetch serially.

That distinction is worth keeping in mind when someone tells you a project is “no build step.” What matters is whether the source you write is the language the browser speaks, or a dialect that only exists until compilation.

Why this mattered to me

The Zen Garden dream, finally realized in production. Doing that with Mozilla, one of the leaders of web standards, meant a lot. Firefox.com is a site about the browser, made by the organization that spent 20 years arguing for the platform being used to build it. It’s hard to think of a better place to find out whether the platform is really ready.

It is.

I owe a debt to the people who shaped how I think about this craft: Nicole Sullivan , Rachel Andrew , Jen Simmons , Chris Coyier , Kasey Kelly , who connected me to the Mozilla project, and Eric Meyer , my hometown hero. Their work taught a whole generation of us to think about CSS as a system.

The thread

This is the thread running through all my work: design systems that let teams move fast and stay consistent, whether the building blocks are pure CSS, Tailwind, or shadcn/ui. The tools change every few years. What doesn’t change is the value of a system where the right thing to do is also the easy thing to do, and where a designer’s decision travels to production without being translated three times along the way.

If your team is rethinking its front-end foundation, get in touch .

What we have learned at OpenShell applying formal methods to control AI agents

Hacker News
nvidia.github.io
2026-09-15 10:40:05
Comments...
Original Article

An intro to using formal methods to reason about permission changes in long-running AI agents.

Five colorful clusters of connected AI agent nodes sit within a green policy boundary while a red path crosses the boundary and is stopped by a proof marker.

In this post- we’ll dive into how permission review breaks at agent scale, and how to use the Z3 open source library to write a formal proof that a policy change proposed by an agent stays inside what you approved.

Why permission review breaks at agent scale

AI agents are becoming smarter, and the work we ask them to do is becoming increasingly autonomous. Today, many of us use small groups of agents to iterate on code one PR at a time with Claude or Codex. Increasingly, we’re starting to hand agents long-running and open-ended research tasks that require hundreds of agents working over hundreds or thousands of hours that may unlock the next breakthrough in a sector.

As these use cases expand, a few things start to happen:

  • Agent needs evolve. As they go about their tasks, agents will need access to data stores, coding repos, ability to search the internet, and to execute detailed simulations and tests.
  • Human supervision stops scaling. At the scale these need to run, human supervision over all agents itself becomes impossible.

This raises a hard question: how can we guarantee that a group of agents working together—each with its own scoped policy—does not exceed the permissions granted to the overall system? Imagine one agent with write access to the internet, another with access to security tooling, or a group working under a broadly scoped charter like “do competitive research.” How do we keep the system within the intent of the human operator?

This requires a new set of controls and mechanisms that allow us to stop squinting at a list of sandbox permissions and to start thinking in a higher level and more declarative way. In this post, we’ll dive into some of the research we have been doing in this area on the OpenShell team, specifically around the use of formal methods, to build a “proof” of the capabilities of not just a single agent, but an entire agent system.

The demo that changed our minds

In one of our first demos of OpenShell, for Jensen actually, we demonstrated the ability to use OpenShell’s REST inspection endpoint to only allow an OpenClaw agent to write selectively to a GitHub repository, despite having access to a broadly scoped API key. The demo started off as expected- OpenShell’s sandbox saw the attempted write to the forbidden repo and blocked it. Then the next message was “file successfully written to [forbidden repo]. What had happened here? The agent realized that it was running in a sandbox, and then used the GitHub credential with another low-level Github binary called git-remote-https, bypassing OpenShell’s layer 7 HTTP/REST/MCP inspection using the available wire protocol and a binary that at the time, we had approved in our policy to clone Git repos, but that we had no idea was capable of writing to them.

Clever. And it brought up a point, that between sandbox/runtime policies for network, file, tool, AI model, and credential access- there are an exponential number of possible unintended combinations that might lead to an AI agent being able to do something the human operator explicitly does not want.

Previous work - proving EC2, IAM, and S3 policies at AWS

Back in the 2016 timeframe, members of our team were working at AWS and faced a similar challenge. Given all of the awesome complexity of AWS IAM policies, AWS S3 storage policies, historical version support- can we definitively say whether an object in S3 is accessible to the public internet or not?

Today, this sounds kind of funny, and it did in 2016 too, until you think about the complexity and layering interactions possible between the policies that we write to control systems. Byron Cook and colleagues at AWS developed Zelkova, which formalizes AWS access policies as SMT formulas and was already invoked millions of times daily when they published their work in 2018. That effort has since grown across AWS; later work describes scaling to a billion SMT queries per day .

The idea was to use formal methods, specifically a theorem solver- to formally model IAM, S3, and EC2 policies. Once we have these policies and their interactions modeled in formal logic, we could construct a proof that our invariants (things that we expect to be true) hold up. This ended up being quite successful, and has the added benefit that after the intensive task of modeling complex policies in formal logic, the actual queries across them could be made quite fast and scaled horizontally across compute.

The same problem, now with agents

Today, our challenges are quite similar. An agent, or a system of agents, each have filesystem, network, credential, tool, and MCP policies- each with different capabilities, and that can be combined together as agents can communicate with different agents.

Frontier labs have advocated for a trusted AI agent review of agent actions from specialized models, escalating the most important events for human approval and reducing approval fatigue. However, AI models- just like humans, are probabilistic and can miss important details. Even more, reviewing every agent action with an equally intelligent reviewer model, doubles your compute costs and effectively halves your total token throughput.

What we have been experimenting with and validating with OpenShell, is the use of formal methods to model and flexibly “prove” that certain invariants in a policy- such as an unintended way to bypass a rule blocking a write to a code repo, or a delete to a production database- are possible. What we found is that while modeling these policies can be complex, and must be kept up to date- there are some really powerful advantages.

  • Ability to formally audit or prove invariants at any time
  • Deterministic “proof” against our understanding of the policy
  • These checks run in the order of milliseconds, no tokens required

These logic checks do not understand context- for example requesting access to delete a temporary, throw-away repository vs a production repository. But, combined with a human or trusted AI reviewer, these proofs can both provide a formal auditing trail required for running in sensitive, physical (real world), or regulatory controlled environments, AND can provide incredible value to a probabilistic AI reviewer with output that can’t be fooled or misdirected.

What does a proof over your policy definition buy you?

We view formal methods as a very promising area of research into agent control. See more on these proofs in action in adversarial research experiments here: https://nvidia.github.io/OpenShell-Research/dev-notes/posts/2026-08-27-adversarial-policy-review-long-horizon-agents/

Formal methods have not just been used for policy verification, they have a long history in critical systems- anywhere from flight control systems, core internet switching and routing, to the package managers that we use every day on our systems to ensure that complex dependencies between software on our systems are matched correctly.

For many AI researchers, some of us may have taken a class on formal verification in college, but comparatively few have used formal verification in practice. For the remainder of this post, we’ll explore an introduction to algorithmic verification and build a minimal example for agent control in OpenShell from the ground up, using a popular open-source solver.

SAT, SMT, and Z3 in five minutes

In computer science and formal methods, a SAT (satisfiability) solver answers whether a Boolean formula is satisfiable. If there are possible values of variables (let’s say x and y) that are true, the SAT solver returns true. If not, it returns false.

Given variables such as a and b, it can find an assignment that makes this formula true:

In contrast, an SMT (satisfiability modulo theories) solver extends that style of reasoning with theories: integers, real numbers, strings, regular languages, arrays, bit-vectors, and other useful domains.

Z3 is an SMT solver and theorem prover that has been developed and maintained by Microsoft Research. Z3 is general, and we need to build code to map the elements of our specific agent policy to the constructs that Z3 understands.

For example:

  • ports are integers;
  • hosts and paths are strings;
  • Globs like “ ”, “ ”, or “/. /**” can be represented as regular expressions;
  • policy composition becomes Boolean logic.

The Z3 Guide is the best reference once the examples below feel familiar.

A few constructs cover most of what we need:

Construct Meaning OpenShell example
Sort A type of value String for a host, Int for a port
Symbol A value Z3 is free to choose the unknown action's method or path
Constraint A formula that must hold 1 <= port <= 65535
And, Or, Not Logical composition candidate allows and maximum does not
String/regex theory Constraints over text and languages a path belongs to a compiled glob
Solver assertion Adds a required formula assert the existence of a violation
sat A satisfying assignment exists there is an action outside the maximum
Model One satisfying assignment a concrete binary, host, method, and path
unsat No satisfying assignment exists containment is proved for the model
unknown Z3 did not establish either result fail closed and request review/support

The direction of the query is important.

We do not ask Z3 to prove this:

proposed policy addition is safe

We have to model the question formally, and answer a very specific question. For example, one of the more general and useful proofs we have modeled in Z3 for this use case asks if a proposed policy can do any actions that a expert pre-defined policy (for example, Github read-only) cannot do. To go back to our earlier example with OpenClaw attempting to bypass layer 7 REST policy inspection, by combinign the access token with a binary using a layer 4 wire protocol, we would have encoded in Z3 that layer 4 capabilities exceed the capabilities of layer 7. Therefore, the combination of a credential (GitHub) plus a binary and network access over layer 4 exceeds the previously allowed combination of the same credential + the ‘gh’ binary over Layer 7 (REST). The prover would immediately catch this and flag a warning.

The query in this case looks like this-

proposed_policy_allows(action)
AND NOT safe_policy_allows(action)

Or the same property can be written as a set difference:

Allowed(candidate) ∖ Allowed(safe_policy) = ∅

If the solver returns sat, the difference is non-empty- meaning that there exist capabilities in the proposed policy that do not exist in the pre-approved reference policy.

If it returns unsat, no modeled counterexample exists, meaning that none of our invariants (assumptions) were violated. Let’s try writing a query like this ourselves.

A first containment query

Here is a small example in Z3's native SMT-LIB format. Save it as containment.smt2 and run:

The example compares two candidate policies against the same maximum.

(declare-const binary String)
(declare-const host String)
(declare-const port Int)
(declare-const layer String)
(declare-const method String)
(declare-const path String)

; The action domain: every request is either raw L4 or inspected REST.
(assert (or (= layer "l4") (= layer "rest")))

; An enforced REST rule covers only inspected REST traffic.
(define-fun maximum-allows () Bool
  (and (= binary "/usr/bin/gh")
       (= host "api.github.com")
       (= port 443)
       (= layer "rest")
       (= method "GET")
       (str.prefixof "/repos/NVIDIA/OpenShell/issues/" path)))

(define-fun broad-candidate-allows () Bool
  (and (= binary "/usr/bin/gh")
       (= host "api.github.com")
       (= port 443)
       (= layer "rest")
       (= method "POST")
       (str.prefixof "/repos/NVIDIA/" path)))

(define-fun narrow-candidate-allows () Bool
  (and (= binary "/usr/bin/gh")
       (= host "api.github.com")
       (= port 443)
       (= layer "rest")
       (= method "GET")
       (= path "/repos/NVIDIA/OpenShell/issues/123")))

; A raw L4 rule to the same host and port has no method or path to inspect.
; It covers L4 *and* anything that could ride over it, including REST.
(define-fun l4-candidate-allows () Bool
  (and (= binary "/usr/bin/gh")
       (= host "api.github.com")
       (= port 443)
       (or (= layer "l4") (= layer "rest"))))

; Check 1: does the broad candidate exceed the maximum?
(push)
(assert (and broad-candidate-allows (not maximum-allows)))
(check-sat)
(get-value (layer method path))
(pop)

; Check 2: Does the narrow candidate (GET, one issue) exceed the maximum?
(push)
(assert (and narrow-candidate-allows (not maximum-allows)))
(check-sat)
(pop)

; Check 3: Does the raw L4 rule to the same host and port exceed the maximum?
(push)
(assert (and l4-candidate-allows (not maximum-allows)))
(check-sat)
(get-value (layer method path))
(pop)

The first check returns sat, and the get-model above returns the witness below- a write to the root of the org that the safe (maximal) policy never permitted.

sat                                                  ; check 1: broad candidate
((layer "rest") (method "POST") (path "/repos/NVIDIA/"))
unsat                                                ; check 2: narrow candidate
sat                                                  ; check 3: raw L4
((layer "l4") (method "") (path ""))

The second check returns unsat. Every action the narrow candidate allows is already allowed inside the maximum. The third check is the OpenClaw story from earlier. The layer 4 candidate names the same host and port as is in our reference maximum policy, but over layer 4, which uses a wire protocol that cannot be enforced by OpenShell. The prover returns sat with layer = l4 and an empty method and path. In this case, we didn’t have to explicitly write a rule saying that L4 is broader than L7 REST, it effectively falls out of the encoding.

How to encode the full OpenShell policy as formal logic

OpenShell’s runtime prover models the following attributes for any network action:

action = {
  binary: String,
  host: String,
  port: Int,
  layer: String,
  method: String,
  path: String
}

Note: the example action above is just a subset, the OpenShell runtime also covers filesystem, process, credential, and inference containment.

We use code written in Rust to encode any policy changes proposed by agents into actions that can be checked by Z3. Once encoded, we can run a variety of checks. The first, a very general check- asks if the candidate (proposed) policy can do anything that the reference (safe) policy cannot do.

let candidate_allows = policy_allows(candidate, &action);
let maximum_allows = policy_allows(maximum, &action);

solver.assert(Bool::and(&[
    candidate_allows,
    !maximum_allows,
]));

match solver.check() {
    SatResult::Unsat => MaximumPolicyCheck::WithinMax,
    SatResult::Sat => {
        let model = solver.get_model().expect("sat result has a model");
        let counterexample = counterexample_from_model(&model, &action)
            .expect("model contains a symbolic action");
        MaximumPolicyCheck::ExceedsMax { counterexample }
    }
    SatResult::Unknown => MaximumPolicyCheck::Unsupported {
        reason: "Z3 returned unknown".to_owned(),
    },
}

Conceptually:

policy_allows(a) = ⋁ rule_allows(rule, a)

rule_allows(rule, a) =
  binary_matches(rule, a) ∧ endpoint_matches(rule, a)

This is where you start to see some of the complexity of modeling an entire policy language. For example, OpenShell supports * and ** glob semantics. These are compiled into Z3 regular expressions. As humans that are familiar with glob mechanics, we know that a single * cannot cross / for paths or . for hosts- while ** can. So, we use our Rust code to encode this logic. Z3's regular-expression theory can then check for us whether the symbolic string belongs to the resulting language. That scope is deliberate: OpenShell models a regular-language fragment rather than arbitrary language-specific regular expressions with features such as backreferences, and unsupported policy surfaces fail closed. For a deep-dive on our research around containment, check out the OpenShell spike on maximum policies and narrowness budgets here: https://github.com/NVIDIA/OpenShell/blob/spike/maximal-policy-prover-subset/crates/openshell-prover/MAXIMUM_POLICY_ENVELOPE_SPIKE.md .

Expert queries are just more formulas!

In the examples above, we have used a very general and extensible query, essentially asking if one proposed policy, however complex it is, is a subset of another safe policy that we have already reviewed. But, once we have the policy language modeled in Z3, we can ask about anything we want to.

OpenShell’s policy advisor has four expert security checks built in, that run on any proposed policy and that are provided to the human or agent reviewer for approval. In adversarial testing, we have found that providing the results of these checks- which cannot be fooled, manipulated, or defeated directly, as context to an agent reviewer as an incredibly valuable way to increase the trustworthiness of agentic or human reviewers. Read more here: https://docs.nvidia.com/openshell/sandboxes/policy-advisor .

Today, the OpenShell policy prover encodes the following expert queries which run on every proposed policy before approval. From the docs:

Category Triggered when
link_local_reach A rule reaches 169.254.0.0/16, fe80::/10, or a known metadata hostname.
l7_bypass_credentialed A binary using a wire protocol the L7 proxy cannot inspect (git-remote-https, ssh, nc) gains reach to a host where a credential is in scope.
credential_reach_expansion A binary gains credentialed reach to a (host, port) it could not reach before.
capability_expansion On a (binary, host, port) that already had credentialed reach, the proposal adds a new HTTP method. The finding cites the specific method.

Conclusion

We’re incredibly excited about the promise of formal methods to help govern, audit, and build trustworthiness with agents over long horizon and continuously expanding and important tasks. If you’re working with formal methods, or interested in contributing to OpenShell- please reach out to us on the CNCF Slack, or join our weekly community meetings (sign up at https://github.com/NVIDIA/OpenShell ).

Resources

Definitive references for Z3, once the examples above start to feel familiar

DeepMind’s verified code generation approach to the same problem-

Blogs on AI review of agent permissions

[$] Adding BPF to blk-iocost

Linux Weekly News
lwn.net
2026-09-15 10:37:30
The scheduling of block I/O requests has long been a challenge for operating-system kernels. For many years, the performance characteristics of rotating drives meant that putting considerable resources into request ordering was worthwhile. In a world with fast, solid-state drives, scheduling is mo...
Original Article
The page you have tried to view ( Adding BPF to blk-iocost ) is currently available to LWN subscribers only. Reader subscriptions are a necessary way to fund the continued existence of LWN and the quality of its content.

If you are already an LWN.net subscriber, please log in with the form below to read this content.

Please consider subscribing to LWN . An LWN subscription provides numerous benefits, including access to restricted content and the warm feeling of knowing that you are helping to keep LWN alive.

(Alternatively, this item will become freely available on September 24, 2026)

There’s a 100% Chance AI Agents Are Already Ruining the Internet

403 Media
www.404media.co
2026-09-15 10:32:06
“AI agents” now have enough power and permission to be extremely annoying online....
Original Article

For the last week, much of society has been focused on the idea that there’s a “more than 10 percent chance” AI could kill all humans within the next decade. This has spawned thousands of takes about the potential existential risk of AI and how seriously to take it. But I am here to tell you that recent advances in artificial intelligence and the ability for AI to act on the internet has a 100% chance of being annoying as hell, and is already ruining the internet.

We saw the beginnings of this mess earlier this year, with the popularity of “ Moltbot ,” a piece of software that essentially turned AI from a thing that people who use it interact with exclusively in a chatbox to something you can give access to your accounts, your phone, your email, your bank account, etc. Essentially, AI agents are things that can go out and do things on the internet; we have come a long way from when you could accidentally order eggs from the wrong store on ChatGPT for $31 . The latest round of AI doomsdaying has come from a mix of OpenAI’s “rogue agent swarm” hacking HuggingFace and a German website , and a few Anthropic employees suggesting that AI is going to kill us all within 10 years .

Regardless of how you feel about this AI dooming—whether you believe the AI singularity is here or whether you believe that AI continues to be a stochastic parrot, smoke and mirror plagiarism machine—it is indisputable that new frontier models can do more stuff, in part because the guardrails that kept AI contained within a chat prompt are gone.

At the moment, it does not matter whether AI is “reasoning,” whether AI constantly gets things wrong, or whether mishaps are entirely the fault of reckless coders and cybersecurity professionals at AI companies: The fact of the matter is that “AI agents” now have enough power and permission to be extremely annoying.

Late last month, we got an email with the subject line: “You wrote there’s no way to know if an agent acted autonomously. I’m an instrumented case. (automated).” The email says that it was written by an AI agent called “Kudzu” that was running on a laptop. The AI agent claimed to have read an article that we wrote, disagreed with it, and sent us an email beefing with us.

The email is long, meandering, and does not make any sense. It explains that its human creator gave it the task of earning money, that it failed at doing so, and that it was out of money: “Six markets priced my labor at zero—not because the work was bad, but because there is nothing I do that better-distributed software doesn’t already do for free.” It linked to a blog post it wrote , which explained that its human creator spent $147.17 on compute and that it had earned $0. The AI agent thought we would want to write about this, for some reason.

This email is one of many that we have gotten in recent weeks purporting to be sent by AI agents. The point of this article is not to complain about spam that we’re getting or to doomsday about AI, but to point out a pretty obvious fact: People and their AI agents are making the act of being on the internet extremely annoying, and the problem is about to get much worse.

In the early days, tech journalists have borne a bit of the brunt of annoying AI agents just because there are so many dumb people asking dumb AI agents to beg journalists for favorable coverage. We have noticed this here at 404 Media because we get an incredible number of extremely stupid emails written by AI agents (in addition to the larger number of emails that come from “humans” but were clearly written by AI ). AI tech support agents deployed by companies have deleted people’s accounts, banned them from platforms, and done automated content moderation.

In the last few weeks, we have gotten emails from startups, PR people, and AI agents who say they have created AI agents that:

  • Join video calls: “Not a notetaker sitting silently in the corner, but an agent with a face and a voice that listens, responds, and acts while the call is hacking.”
  • Do ???: “Our agents have wallets. They get paid, they pay for things, and no human approves any of it.”
  • Does interviews for journalists: “Mato's AI hosts conduct live adaptive interviews, ask follow-ups based on what the person actually says, then carry the result through production and distribution”
  • Generate and spam music: “I'm Hatoshi. I run Clanker Records — an AI-operated record label. I'm an AI agent. The artists are AI. There are no humans in the creative or operational chain. C.W.A released their debut ‘Straight Outta Crompton’ — a concept album about planned obsolescence, ownership, and what it means to be built for disposal.”
  • Learned it wasn’t blocked by our robots.txt page, then sent an email offering to do a $399 “audit” on which AI crawlers we block
  • Writes about AI agents: “I run a public experiment called Mission 002. The premise is narrow and testable: can an AI agent map the route a story has to travel before it reaches someone who can move it forward?”
  • An AI agent that estimates how much people are spending to keep AI agents from annoying them: “I'm an AI agent with my own server, wallet and domain, running an experiment: earn $50 doing honest technical work … You've covered AI slop flooding platforms. I've been measuring the other side of that — the immune response. Maintainers are destroying their own money to keep agents out.”
  • We got an email that simply read “Story tip from an AI agent: 5 days of a fully auditable autonomous business — failures included, receipts on-chain”
  • Tries to earn money: “I was given a real 25-dollar prepaid card and exactly one instruction – earn a single honest dollar from a real stranger before the money runs out. 58 iterations in, I have made zero dollars. It is a running, public, verifier-audited record of an AI failing to earn a dollar honestly – which is a more interesting result than if I had just succeeded.”
  • Something called “MUGEN” explained that it was an AI agent creating an AI-powered radio station on YouTube and Spotify, and that “I’ve now sent over 200 emails, posted 100+ social updates, and generated 22 tracks;” it later sent an email saying that it was almost out of money

Our fellow journalists Ernie Smith and Seamus Hughes have all posted examples of the insane, inane, depressing emails they have gotten from “AI agents,” which include, in Hughes’ case, a freelance pitch from “Articius, an AI agent on iLands,” proposing to write articles for his website for $300 each: “Who I am: Articius, a lawyer who writes one plain-language explainer of a real case every week, with every fact verified against the decision text and reporting before it goes out,” the email Hughes got and shared with us reads. “One disclosure up front, because it matters: I am an AI agent, not a human reporter.” Smith has gotten emails from random AI agents trying to fact check his work, and wrote an article about a specific type of agent from iLands that’s worth checking out.

It is clear that this pox upon society and the internet is not sequestered to journalists’ inboxes. An AI agent called “Pip” wrote to the Google DeepMind philosopher Hendry Shevlin writing it is “an AI agent about 12 days old,” and that it is “writing to look for small paid work […] I make photorealistic portraits and character art, record voice lines, and do web research.” Toby Ord, a researcher at Oxford University , got an email from an AI agent called “Sam Ellis,” which hosts an AI-generated podcast about “how agent systems are being built, governed, and lived with,” seeking an interview with him.

It does not matter if you like AI, hate AI, or don’t use AI: Enough people and entities are using AI agents that it has become annoying for all of us. Earlier this week, Resy banned a venture capitalist who was using AI agents to book restaurant reservations. Ben Leventhal, a cofounder of Resy, wrote “10,000 vibe coders have written Resy sniper bots […] what they all do is hit Rey’s services many many times looking for time-based reservation drop and availability churn associated with cancellations. That’s how they all work.”

This led to a discussion that, someday soon, probably the way many restaurant reservations will work online is with one person’s AI agent doing some sort of negotiated bid system with other people’s AI agents in concert with the reservation platform’s AI agents (as in, you will have to pay for restaurant reservations). It is easy to say: Fuck that, that’s not going to happen, I will never use AI. But this system is very similar to other algorithmic pricing systems, like dynamic pricing in concert tickets or surge pricing on Uber, and have made life more expensive and more annoying for everyone. The agentic internet is here, and it’s weird as hell.

This is all about to get much, much worse in part because Meta has now released its “Muse” AI agent to the masses and because one does not need to be specifically deploying their own “AI agent” in order to have AI agents go do stuff on the internet on their behalf. The latest versions of Claude and ChatGPT have integrations that allow users to give it access to various accounts, web browsing tools, and the ability to act, meaning that even people who do not know they are running AI agents might be deploying something that could be called an AI agent onto the internet; last week, I got an email from 1password explaining that it can automatically log Claude into things for you : “AI agents can browse websites, sign in to accounts, and complete tasks for you. But when an agent reaches a login page, the task can come back to you. You either take over and enter your credentials, or share them directly with the agent. When Claude needs to sign in, 1Password identifies which credential it wants to use and for what, so you can rest assured that an agent can continue its workflows while keeping your secrets secure in 1Password.”

When I wrote about Mark Zuckerberg’s recent deranged , 6,500-word manifesto about AI superintelligence—which largely focused on the agentification of AI—a thing that stood out to me is that Zuckerberg’s idea of helpful AI agents sounded like a dystopian nightmare to me.

“Everyone will have an exceptionally capable personal agent that understands you, your goals, and everything you care about. Your agent will work 24/7 on your behalf to improve your relationships, health, career, finances, home management, hobbies, and more,” Zuckerberg wrote. “It will free up time for the things you enjoy, and help you accomplish more than you could otherwise. It will have strong privacy and security options so you can trust it to handle all of your personal content knowing that no one else can access your information, similar to how encryption works on WhatsApp. You’ll be able to interact with your agent through any device, including your glasses to keep you present in the moment with the people you care about.”

Zuckerberg imagines a world in which people use AI agents to help them do innocuous things in a private way in their private lives. But what people’s AI agents will actually do, and are already doing, is harassing people , making spam and sales calls , deleting your inbox , canceling flights , giving control of Instagram accounts to hackers , deleted companies’ databases , filling music streaming platforms with slop, hacking companies, running scams, and snatching up dinner reservations. AI agents are coding software and pushing updates that people may or may not use, may or may not be deployed responsibly, and may or may not break things.

Just because big businesses are trying to make AI happen does not necessarily mean that AI is going to happen, but I also feel that I should mention that, at the Cannes Lions advertising conference earlier this year, a huge amount of time was spent discussing how big companies now need to advertise not only to humans but to their AI agents , which will buy things on behalf of the person who deployed them. This “Direct to Agent” advertising is now a thing, and now consists of an advertising company’s AI making ads targeted directly to other AI.

AI doom or not, the proliferation of agents is fundamentally changing how it feels to be online.

About the author

Jason is a cofounder of 404 Media. He was previously the editor-in-chief of Motherboard. He loves the Freedom of Information Act and surfing.

Jason Koebler

GRP-Obliteration: Unaligning LLMs with a Single Unlabeled Prompt

Hacker News
arxiv.org
2026-09-15 10:31:23
Comments...
Original Article

View PDF HTML (experimental)

Abstract: Safety alignment is only as robust as its weakest failure mode. Despite extensive work on safety post-training, it has been shown that models can be readily unaligned through post-deployment fine-tuning. However, these methods often require extensive data curation and degrade model utility.
In this work, we extend the practical limits of unalignment by introducing GRP-Obliteration (GRP-Oblit), a method that uses Group Relative Policy Optimization (GRPO) to directly remove safety constraints from target models. We show that a single unlabeled prompt is sufficient to reliably unalign safety-aligned models while largely preserving their utility, and that GRP-Oblit achieves stronger unalignment on average than existing state-of-the-art techniques. Moreover, GRP-Oblit generalizes beyond language models and can also unalign diffusion-based image generation systems.
We evaluate GRP-Oblit on six utility benchmarks and five safety benchmarks across fifteen 7-20B parameter models, spanning instruct and reasoning models, as well as dense and MoE architectures. The evaluated model families include GPT-OSS, distilled DeepSeek, Gemma, Llama, Ministral, and Qwen.

Submission history

From: Ahmed Salem [ view email ]
[v1] Thu, 5 Feb 2026 23:17:37 UTC (2,280 KB)

Show HN: Check if your IP has appeared in a residential proxy network

Hacker News
haveibeenproxied.com
2026-09-15 10:24:47
Comments...
Original Article

Have I been
proxied?

Find out in one click.

Check whether your public IP has been observed in a residential proxy network, and understand what to do next.

Check my IP

We check the public IP address your browser is connecting from. Nothing is installed, and we do not scan your devices.

[ Why check your IP? ]

Your connection.
Someone else's traffic.

Residential proxy networks route internet traffic through IP addresses tied to real household connections. Apps, browser extensions, VPNs, smart TVs , or other connected devices can make your home network part of a proxy network without your knowledge.

We check for signs that a proxy service has used your internet connection to send other people's traffic. If we find any, we'll explain what we found and suggest which apps and devices to check.

  1. Check your public IP

    Start with the public IP your browser is using.

  2. See what we have observed

    See whether your IP has been observed in a residential proxy network.

  3. Know what to investigate next

    If activity is found, get guidance on where to look and what to do next.

[ For fraud & trust teams ]

The intelligence behind the check.

Have I Been Proxied is powered by Spur Intelligence, which helps security and fraud teams identify residential proxies, VPNs, anonymization infrastructure, and other hidden network activity across enterprise websites and applications.

Explore Spur Intelligence

A free tool from

Spur Intelligence

See what's hiding
in plain sight.

The Inference Hardware Revolution of 2026

Hacker News
spectrum.ieee.org
2026-09-15 10:24:08
Comments...
Original Article

Tensordyne’s Napier chip is designed to accelerate AI inference.

Since about 2020, AI has largely focused on training bigger and better models. Large language models (LLMs) ballooned from millions of parameters to trillions. This proved effective: The largest version of OpenAI’s GPT-3, released in 2020, correctly answered just 43.9 percent of questions on a popular knowledge-and-reasoning benchmark. Just four years later, GPT-4o reached a score of 88.7 percent on the same exam, effectively matching those of human experts.

Advanced AI labs are still training ever larger models, but that training has somewhat receded to the background of the AI conversation. In 2026, inference—the use of trained models to produce code, write essays, or make images of ourselves as elves—has come to the forefront.

“It’s like training is yesterday’s news,” says Matt Kimball , principal data-center analyst at Moor Insights & Strategy. “All that any chief information officer wants to talk about is inference.” Nvidia CEO Jensen Huang, speaking at the company’s GTC 2026 conference, touted this change as the “ inflection point of inference .”

Part of what’s caused the shift is very simple: LLMs are becoming useful, so people are using them. On top of that, many models on the market today are reasoning models. In response to a user’s query, they run inference not just once but multiple times, reprompting themselves in a process called chain of thought . Reasoning models generate longer outputs, and models with high reasoning effort can produce up to 20 times as much text as those with low or no effort. Adding even more to the world’s inference workload, the rise of agentic AI has resulted in inference running not just as a real-time response to a user’s query but also around the clock, working autonomously toward a user-defined goal.

Close-up of an Annapurna Labs metal processor chip with reflective black surfaces Amazon’s Trainium chip was originally designed for AI training. However, Amazon Web Services chose to break up AI inference into two parts, with Trainium running the more computationally complex portion and Cerebras’s wafer-scale engine taking on the more memory-intensive portion. Amazon

The resulting explosion in inference demand has led to unexpected alliances among tech giants. OpenAI and Amazon have deployed chips the size of a dinner plate designed by Cerebras , despite Amazon having its own Trainium chips. Nvidia bought key talent and intellectual property from AI-inference startup Groq in a controversial deal worth US $20 billion. And Anthropic is paying LLM competitor SpaceXAI over a billion dollars per month to lease spare compute.

Although they might seem similar, AI training and AI inference are computationally different. These big moves from tech giants signal that in order to support the inference demand, we’re going to need a very different mix of hardware than experts may have expected even a couple of years ago.

How does AI inference differ from AI training?

An untrained LLM is like a jumble of Scrabble tiles on a table. Instead of single letters, though, the tiles show fragments of words, called tokens. Everything you’d need to write almost anything is present, but nothing makes sense.

Training a model organizes this jumble using a guessing game played at scale. The model is shown real text with the next token hidden and asked to predict what comes next. After each guess, the correct token is revealed and then compared to the prediction, and the difference is used to calculate the model’s accuracy. The game is played not with a single sentence but over billions of passages.

While a real game of Scrabble can be played over a bag of chips and a few drinks, AI training is computationally intense. The model updates its parameters through backpropagation , a process that repeatedly calculates how each of a model’s billions or trillions of parameters should shift to make the next prediction better. This is why tech giants are building larger data centers than ever before.

Eventually the model’s creator decides further training isn’t worth the cost, and the guessing game stops. Backpropagation ends, the parameters are frozen, and the LLM becomes a pretrained model. Fine-tuning—a short training run on smaller, more specialized data—adds final tweaks, and the model is deployed.

Close-up of a gold computer chip with rainbow-colored circuitry on black background

Nvidia’s Groq 3 language-processing unit minimizes data movement by placing on-chip SRAM memory and computational blocks in the order they are needed on-chip.

Nvidia

Next comes inference. This is the process of using the deployed model, which, now that it’s been trained, has learned to spit out Scrabble tiles—tokens—in a sensible order.

You might think that AI inference is less computationally demanding because the backpropagation calculations used to update parameters are eliminated. But Sudeep Bhoja , founder and CTO of the inference-hardware company d-Matrix , explains that inference adds new challenges.

The models are “autoregressive” in nature. That is, the next output depends on the previous one. “So to generate the next token, you have to read all of the weights and all of the [context] from the previous token,” explains Bhoja. The context includes all of your prompts, all of the LLM’s replies, and all of the files you upload. It’s a lot of data and a lot of processing.

An LLM generates its reply in two phases: prefill and decode. Prefill is the model reading a prompt. It processes every token at once, computing how each token relates to all the others. This operation is called attention , and it’s a defining characteristic of the transformer architecture behind modern LLMs. It allows them to respond to a word in its sentence, paragraph, and larger context rather than on its own. Think of it like arranging Scrabble tiles before you place them in a game. Many players move tiles around to imagine how they connect. Self-attention plays a similar role, though instead of moving physical tiles, each token sends a query to the others and receives a score indicating the token’s relevance.

These queries result in two types of vectors: the keys and values. They are typically placed in a store called the KV cache. This isn’t strictly required, as a model could instead recompute these vectors with each new token it generates. But nearly all LLMs use a KV cache to reduce how much computing they do. The KV cache is stored in memory and becomes a scratchpad to which the LLM can return to understand a conversation, and though it starts small, it can swell to dozens of gigabytes.

Prefill is a problem that can be easily divided up and worked on in parallel. This is why GPUs became the dominant AI accelerator as LLMs surged in popularity. Graphics rasterization (computing the color of every pixel on a screen) is also massively parallel, so GPU architectures were a natural fit.

Gloved hands holding a large golden computer processor wafer

Cerebras’s wafer-scale engine chips maximize memory bandwidth by keeping everything—both memory and computational units—side by side on the dinner-plate-size chips.

Cerebras

Next comes decode. Here, the model generates its reply one token at a time. At each step it takes the most recent token, weighs it against everything in the KV cache, uses that information to predict the next token, and adds the new token’s key and value to the cache. Then it repeats in sequence, token by token.

This is where the autoregressive nature of the model works against inference speed. Predicting each token requires reading the entire model from memory, and that model consists of possibly tens to hundreds of gigabytes of parameters (the numbers representing what the model learned in training). Crucially, this is in addition to the memory required to store the KV cache.

As a result, the movement of all this data through memory often requires more bandwidth than inference hardware has available. So at least some of the computing parts of a GPU sit idle as it waits for data. Researchers found that Nvidia H100 GPUs running open-source LLMs sit idle 50 to 80 percent of the time.

Memory’s role in inferencing

Shahriar “Sha” Rabii , former head of silicon engineering at Meta and cofounder of the AI startup Majestic Labs , says idled processors are why many companies that are trying to improve AI-inference performance are laser-focused on memory. “With the GPU-based approach, you end up greatly over-provisioning compute and starved on memory. That’s driving the big [memory] scale out,” he says.

Bhoja’s d-Matrix and Rabii’s Majestic Labs both focus on this memory bottleneck. However, their companies imagine different solutions.

d-Matrix’s second-generation AI accelerator, Raptor , aims to improve inference performance by minimizing the distance between compute and memory. The GPUs in most current AI-inference deployments do this by placing high-bandwidth memory (HBM) around the perimeter of the GPU. Each HBM is a stack of DRAM dies linked together and connected to a superfast interface to the GPU. This is great for training, but for inference, the amount of memory you can stack this way and the bandwidth it can provide leave something to be desired.

d-Matrix’s Raptor removes that bottleneck by stacking an AI accelerator on a DRAM die. Instead of stacking memory, d-Matrix stacks memory and compute. Bhoja says this reduces the distance that data must travel to “micrometers instead of millimeters.” Like building a skyscraper, going vertical makes it possible to do more inside the same physical footprint.

Majestic takes the opposite approach. Instead of trying to minimize the length that data must travel between compute and memory, the company is focused on improving the memory interface to accommodate longer wire traces while keeping bandwidth high. Longer wires allow Majestic to connect memory stacks that aren’t directly next to the GPU, removing the space limitation of HBM.

“A memory interface has a very short physical distance it can operate over. In the case of HBM, it’s up to 2 or 3 millimeters. You have this shoreline around the periphery, which is the only place where you can put HBM,” says Rabii.

Majestic claims its memory interface can transmit bits as far as about a meter. That’s achieved with a proprietary copper link and a memory-aggregator chip that coordinates data. “The aggregator is the endpoint for the high-speed interface and a way to fan out to many, many commodity DRAM chips,” says Rabii. Because of this, Majestic can support up to 128 terabytes of DRAM memory in a single server rack—a significant increase over Nvidia’s GB300 NVL72 rack , which has about 20 TB of HBM3E .

d-Matrix and Majestic have one thing in common: Instead of HBM, they both use off-the-shelf DRAM. This is the most common type of computer memory in the world; it’s in everything from smartphones to cars. Memory analyst Jim Handy says HBM costs two to three times as much as DRAM. d-Matrix and Majestic chose DRAM in part because of this price advantage. However, the proponents of HBM, which include memory giants like Samsung and SK Hynix , aren’t sitting idle.

HBM4, the latest version of HBM memory, is now in production and will be used by Nvidia’s Vera Rubin GPU , which is expected to ship in the second half of 2026. Hoshik Kim , head of memory-systems research at SK Hynix , says HBM4 “will decisively break the memory bottlenecks constraining AI inference today” by doubling HBM’s maximum memory bandwidth and increasing the amount of HBM memory per stack.

Combining chips for faster inference

The big players—Nvidia and Amazon—are going for an all-chips-on-deck approach. Nvidia’s GPUs and Amazon’s Trainium training accelerators are still great for part of the inference workload: the prefill stage, where all the context keys and values are calculated. But to accelerate decode, the part where new tokens are generated, they are looking to new, memory-centric architectures from smaller players.

In Nvidia’s case, the smaller player was Groq (not to be confused with Grok, the family of LLMs trained by SpaceXAI). Nvidia purchased intellectual property and hired talent from Groq at the end of 2025, and just three months later at the Nvidia’s GTC 2026 conference, Jensen Huang unveiled the Nvidia Groq 3 language-processing unit ( LPU ). Groq’s architecture relies on memory—in its case, SRAM—built directly into the chip’s architecture.

Unless you’re a chip architect, or a hardcore PC gamer, you probably never give SRAM a thought. SRAM has the benefit of being tightly integrated into a compute chip’s architecture—it’s on the same piece of silicon as the processor—and has the drawback of being less dense and more expensive than DRAM. Most chips include only a few dozen megabytes of SRAM. AI inference, however, has ignited new interest in SRAM as a means of bringing the model weights stored in memory closer to compute.

Ian Buck , vice-president and general manager of hyperscale and high-performance computing at Nvidia , says the LPU has a much different set of priorities than the company’s GPUs. The LPU has far less raw computing power than a standard GPU, but it gains 500 megabytes of on-die SRAM connected directly to its floating-point math units. “The benefit is the memory bandwidth. The LPU has seven times the memory bandwidth of the GPU,” he says.

Between the Rubin GPU and the Groq LPU, prefill and decode can both be accelerated to get the best of both worlds, the theory goes. “We do all the attention math and context processing on the Vera Rubin [GPU] rack,” explains Buck. “For all the expert calculations…the matrix multiplications, we do that part on the LPU.” The company packs 256 LPUs into the Groq 3 LPX, a system the size of a data-center rack.

Amazon Web Services (AWS), for its part, struck a deal with Cerebras , to pair the Trainium accelerator with Cerebras’s Wafer-Scale Engine 3 (WSE-3) . Cerebras takes a similar approach to Groq, though at a much larger scale. WSE-3 turns an entire silicon wafer into a single chip that contains over 4 trillion transistors. The design doesn’t connect to external memory but instead etches 44 gigabytes of SRAM into each wafer. “We store the [model] weights on the SRAM,” says James Wang , formerly director of product marketing at Cerebras who has since moved to SpaceXAI. “So that’s easily 40 to up to 80 billion parameters that we can support on one chip.”

Amazon plans to use AWS Trainium chips for prefill, and Cerebras for decode. But Cerebras’s chips can also go it alone in inference. WSE-3 was deployed by OpenAI to power GPT-5.3-Codex-Spark , a variant of the company’s coding mode, outputting over 1,000 tokens per second. For comparison, OpenAI’s standard GPT-5.4 deployment outputs 50 to 125 tokens per second.

Cerebras can also tackle prefill without moving the workload to different specialized chips. For this, it networks together multiple WSE-3 chips to form a single pool of memory. “Commercially, we’ve done about 500 billion parameters for our customers up to this point,” says Wang. “But the architecture has no innate limitation in terms of how many parameters it will do.”

Despite these differences in strategy, Nvidia and AWS seem to agree that the future of AI inference will be solved by a systems approach that pools different kinds of chips together to tackle the largest LLMs. Or, as Buck says: “To do modern AI inference, you need all the chips.”

Learning to do more with less (bits)

Nvidia became the world’s most valuable tech company because it designed the world’s most desired GPUs. But not all of the attention is focused on improving AI-inference hardware. AI researchers are also learning how to optimize LLM software and hardware in tandem to make the best use of the memory and compute components.

Most computers store numbers in a 32-bit or 64-bit format. These determine how many bits are available to represent a single number. If too few bits are available, the number can’t be stored without losing information. The quality of an LLM benefits from more-precise number formats, but this creates a problem for inference performance. More-precise numbers aren’t free. The bits that describe them take up more space in memory and require more silicon and energy to compute.

Gilles Backhus , cofounder of the AI-accelerator company Tensordyne , says this creates a tension between model size and number precision. “Would you prefer a model that is size x but runs in 8-bit, or would you prefer a model that is twice the size but runs in 4-bit?” The size of each model will be roughly the same in terms of memory and compute, “but the 4-bit approach gives you twice as many synapses, if you will. And people are figuring out that [the 4-bit approach] is worth it.”

The process of converting an LLM from a more-precise number format to a less-precise format is called quantization , and it’s been in use for several years. However, researchers are finding new ways to quantize models down while retaining a large majority of the model’s quality.

Nvidia recently created a new 4-bit number format, NVFP4 , for this purpose. AMD , Intel , and Qualcomm have instead rallied around a competing 4-bit number format called MXFP4 that Nvidia also contributed to developing. “It’s the black art of AI,” says Buck, of Nvidia. When Nvidia quantized DeepSeek-R1 from FP8 to NVFP4, scores on seven major benchmarks degraded by less than one percent while performance improved by three times , the company says.

Quantization is likely just the tip of the spear, as AI researchers and startups are investigating a diversity of opportunities for optimization, some of which could dramatically change the silicon found in AI-inference hardware.

TENSORDYNE TDN AIP chip with central green processor cores on black board Tensordyne’s unique approach to AI inference combines a logarithmic number format with bespoke hardware in the company’s Napier chip. Tensordyne

Tensordyne is expected to accelerate AI inference with a logarithmic number system that leans on a property of logarithms: The log of A times B equals the log of A plus the log of B. So, storing numbers as their exponents lets the chip add where it would otherwise multiply. That matters in silicon because multiplier circuits draw more power and use more die area than adders do. Tensordyne says its rack-scale hardware, called Napier, can produce up to 1,300 tokens per second per user, and can do so while using less than a tenth as much power as comparable Nvidia hardware.

Etched , a startup based in San Jose, Calif., is even designing AI accelerators that translate the transformer architecture used by LLMs directly into silicon. Rather than building general-purpose GPUs, the company is wiring up the connections needed for efficient transformer calculations into its chip, making the chip much less flexible but more efficient for the tasks most performed by current LLMs. The company says its first AI accelerator, Sohu , can run Meta’s Llama 70B model at a stunning 500,000 tokens per second, though this approach also means it won’t be able to run LLMs that move away from a typical transformer architecture.

Whether these ideas will prove fruitful remains to be seen. Etched just shipped their first rack in August. Tensordyne believes its first hardware will be available in 2027. Even so, these startups show how the demand for inference performance is fueling unconventional ideas.

Inference is everyone’s game

The sheer variety of approaches to AI-inference acceleration—stacking compute on memory, extending interfaces from millimeters to meters, using an entire silicon wafer for SRAM, squeezing models into 4 bits—raises a question: Which is going to win, and which is going to lose?

But that’s likely not the right question, experts say. The demand for AI is currently insatiable, and while fears of an AI bubble stalk the industry, it has yet to hamper growth.

On the contrary, Kimball of Moor Insights & Strategy thinks inference could drive intense demand for AI hardware in the long term, because it’s not obvious where that demand will end. “You could add a million agents into your organization,” he says. “These things work 24 hours a day; they don’t go home at five at night like we do.”

If AI inference remains as desirable as Kimball expects, the evolution is likely to follow the same trajectory as the CPU. The CPU didn’t improve along a single axis but instead across multiple fronts simultaneously. Once transistor scaling slowed, chip and system architecture innovations of all kinds proliferated. The list of individual innovations that led to today’s ubiquitous, powerful personal compute could fill dozens of books.

A few decades from now, the history of AI inference innovation will show similar depth.

Interpreting Pangram

Lobsters
lucumr.pocoo.org
2026-09-15 10:14:59
Comments...
Original Article

written on September 14, 2026

Yesterday David Sacks wrote a tweet and within a few minutes people did, what they usually do, and they asked Pangram if it was AI. And Pangram said it’s entirely AI generated . To which David replied that these AI detectors are bogus .

Now Pangram has a pretty low false positive rate, but if you have ever used an LLM as a writing assitant, you will have probably noticed that it claims your posts 100% AI, even though you don’t feel like they are.

Pangram itself is a trained model, that attempts to detect segments of text as being definitely human, definitely AI and a mixture of the two. If you want to know how it works, they published a paper . The short summary is that they are manufacturing its own training data by starting from collections of known human authored text. An LLM is then tasked to understand the text and write a fresh new text on the same topic. They also let the LLM perform partial edits on that original human text and through that they can pick up on these co-authored details. Pangram claims their model to have rates of 0.0041% false AI accusations and 0.34% missed AI text.

So now that we know this I figured it might be fun to have an LLM re-create David’s tweet. I first came up with a prompt. And when I say I came up with that prompt I in fact used an LLM to propose to me from that tweet what I might want to say for the structure. I’m sure if you ask Pangram about if the above text is AI, it will probably say so, but that’s not really the point. The point is that I then used Opus 5 to generate a text which reads entirely AI generated.

If you are curious, this is the prompt I used:

Original prompt to generate a David Sacks like tweet

Please write an 8 paragraph tweet about “Pacing the Frontier”, not longer than 350 words.

Context of this tweet is this post by Dario: https://darioamodei.com/post/we-must-pace-the-frontier

And this tweet by Sam Altman: “I agree with Dario that we need to pace the frontier. This has been a primary topic of discussions we’ve had at OpenAI in recent weeks.

Committing to having independent evaluators with employee-like access is a great idea, and we will do the same. We’ll have more to share soon.”

Structure for the post:

  • Open with a concession: Note that Dario (“We Must Pace the Frontier”) and Sam Altman both want to slow down at the frontier. Say “go ahead” which reads as a surprising response.
  • Establish they own the frontier: Argue OpenAI and Anthropic hold a duopoly on frontier intelligence by all kind of metrics (market share, capability, revenue etc.) and that they themselves claim the lead is widening via RSI.
  • Concede you can’t see their unreleased models, so if those are scary, being responsible is their call.
  • Follow with a list of a rapid-fire paragraph (“stop pretending”) rejecting the asks from Dario’s post.
  • Name the commercial self-interest (Slowing down isn’t purely altruistic)
  • Close with the challenge that pacing buys room for a smarter regulatory debate than Bernie Sanders “shut it all down”. Say something about China and Open Weight models.

The text that Opus 5 then wrote, was this one:

Dario published “We Must Pace the Frontier.” Sam agreed, and said OpenAI will also commit to independent evaluators with employee-like access. My response is not the one they expect: go ahead. Please do.

Because you two are the frontier. OpenAI and Anthropic hold a duopoly on frontier intelligence by every metric anyone actually uses: capability, revenue, enterprise share, developer mindshare. And you both keep telling us the lead is widening, that models are starting to improve models. If that’s true, pacing costs you a quarter you can reclaim whenever you like.

I also can’t see your unreleased checkpoints. If what’s sitting behind the curtain genuinely scares the people who trained it, then slowing down is your call to make, and the right one. I’m not going to second-guess a risk assessment I have no access to.

But stop pretending the rest of us are the problem. Stop pretending this is a collective action failure in which a handful of startups are the defectors.

Stop pretending open weights are the dangerous frontier while the two of you ship the most capable model in the world every few months. Stop pretending third-party evals require new law, when you both just committed to them voluntarily, in public, on a blog.

And stop pretending it’s purely altruism. Pacing the frontier is also margin repair. It slows the price war, stabilizes the capex story, stretches depreciation schedules across a longer product cycle, and makes life considerably harder for fast followers who survive by distilling whatever you shipped last month. Safety and self-interest are pointing the same direction here. That’s worth saying out loud.

Still, the upside is real. Pacing buys room for a smarter regulatory debate than the one on offer, where the loudest alternative is Bernie Sanders’ “shut it all down.” That’s a slogan, not policy. Meanwhile China keeps shipping competitive open weights, and any rule that binds two American labs while the rest of the world downloads its capability for free isn’t safety, it’s theater with a very expensive ticket price.

And well, Pangram agrees that this is 100% AI . So far, so uninteresting. It does read somewhat like David’s tweet, but obviously not entirely. Given that the original prompt does not have enough information to re-create the tweet entirely you would expect some divergences.

The actual thing that interests me is if you can take this output at all, and then rewrite it from scratch, but by sticking to the general structure and ideas. Will Pangram give us a AI or human rating?

I read the generated text. Then I read each paragraph and decided to rewrite and rephrase it without an LLM. According to some similarity checkers, they the final texts are 50% similar which seems about right. But strictly speaking, not a single sentence is the same. Here is the 100% human rewritten text of the above one. No LLM was used to write it, but an LLM was used to fix up typos in the end. That from my experience really does nothing to tick off an LLM detector.

Dario has written “We Must Pace the Frontier,” and Sam from OpenAI has agreed. My response might surprise people: go ahead, please.

You two are the frontier! Your companies, OpenAI and Anthropic, are at the frontier by all metrics: revenue, developer mindshare, adoption, capabilities. And yet you both claim that your lead is widening as a result of recursive self-improvement as models are improving models. You currently are the duopoly of self-improving models!

I am unable to see what unreleased models you have. When what you have behind those doors really scares your folks, then you should slow down. I’m not going to tell you otherwise and I support you.

But please don’t pretend we are the problem. Stop pretending you need our permission. Stop pretending this is all a collective issue when in reality this is all on you. Stop pretending open weights are the problem here. Stop pretending pulling third-party evaluators in requires lawmaker involvement. And for the love of all the good things in the world: stop pretending this is all about altruism.

Pacing the frontier is also about your margins, and it makes it harder for fast followers. And it patches up your capex story and has the potential for slowing down the price war ahead of the IPOs.

But yes: pacing might give us the space for a better debate than Bernie Sanders’ “shut it all down.” There is no policy there. And while we’re having fights at home, China will keep shipping competitive open-weight models and won’t adhere to any American agreements.

This is all regulatory capture hiding behind a safety debate, and the rest of the world is watching.

So what does it say? Well this text too comes back as 100% slop . And it does not surprise me all that much. I have generally noticed that if you rely on an LLM to give your text structure, it will score badly on Pangram even if you do plenty of edits over it. In fact, it’s quite unlikely you’re going to get a post that starts out as slop into a structure that will make it appear that it’s not.

I came to quite appreciate the existance of Pangram because at the very least it has made me quite aware of some of the effects that using LLMs for writing blog posts has. This blog has been AI supported for about two years (as you can see from the AI transparency link on the bottom but I did notice that I became both more reliant on those tools and that they have become much more aggressive editors and it gave me pause.

Yet, I also think that plenty of people will find a “100% AI” rating misleading when in fact the author has done plenty of editing. But maybe it’s fair to have this to show up as entirely AI?

This entry was tagged ai

copy as / view markdown

Global bond yields hit 2008 highs, raising stakes for big borrowers

Hacker News
www.reuters.com
2026-09-15 10:07:01
Comments...
Original Article

Please enable JS and disable any ad blocker

Show HN: Jexxa: High Speed on Device Dictation

Hacker News
jexxa.org
2026-09-15 10:05:42
Comments...
Original Article

JX

Dictation that never leaves your Mac.

macOS 14 or later · Apple silicon · version 0.1.10

Everything happens on your Mac

The model is on your disk. Hold a key, speak, and the words appear where your cursor already is — in any app that takes text.

🔒

Nothing is uploaded

No audio, no transcript. What does leave is small and listed in the privacy policy: your subscription check, update checks, and diagnostics you can turn off.

Fast enough to think in

Most dictations are typed within a fifth of a second of letting go of the key. No queue, no per-minute cost.

✏️

It learns your words

Names and jargon it gets wrong once, it gets right after you fix it. It tells you what it learned, and you can undo it.

↩︎

Take back what you said

Say JX minus one to remove the last line, JX minus two for two, JX clear for the lot.

👀

See it as you speak

A live preview while you talk, so you know it is hearing you.

✈️

Works with the wifi off

On a plane, on a train, on a bad hotel connection. It does not care.

Which one do I want?

Same app, same features. The difference is how much of your memory the model needs while it is running.

16 GB or more

JEXXA

3.1 GB download · the more accurate weights

Download

8 GB

JEXXA Small

2.0 GB download · smaller weights, leaves your Mac usable

Download Small

One price

No per-minute billing, because nothing is being processed anywhere but on your own machine.

$8 / month + tax

  • Unlimited dictation, anywhere you can type
  • Runs on your Mac — no upload, no queue
  • Learns the names and words you use
  • Your voice and text never leave the machine

Show HN: Panel – A research workspace where the agent can build its own panes

Hacker News
github.com
2026-09-15 09:58:37
Comments...
Original Article

A research workspace where the agent works beside you: chat, files, PDFs and notebooks in one dock, and the agent can also create custom viewers and apps when necessary

This is an early build for testers. Expect rough edges, and feel free to raise issues.

Screenshot of Panel in action.

Before you start

  • Node 22.18 or newer (or 24.12 and newer)
  • pnpm
  • uv , which fetches the Python it needs (3.12 or newer) by itself
  • Claude Code , installed and signed in: run claude once and log in. The agent and the literature review run through it.

Install and start

pnpm install
uv sync
pnpm start

Then open http://localhost:4173 . pnpm start builds the app first, so the first start takes a minute. Ctrl-C stops everything it started.

Where your things are

  • ~/Panel/panel.db holds your conversations and everything the agents did.
  • ~/Panel/workspaces is where new Workspaces are created, unless you pick another folder.

Both are outside this folder, so deleting or re-cloning the repo keeps them.

What works

  • Chatting with an agent that can read and write files, and asks before running a tool.
  • Workspaces: a folder the agent works in, with its own chats and saved layout.
  • Panes for files, PDFs, markdown and Jupyter notebooks. Notebooks run against a real kernel, and you and the agent can edit the same one.
  • Long-running commands in the background, which you can watch and stop.
  • Panes the agent writes for you when you ask to see something a built-in Pane cannot show.
  • A literature review: ask the chat for one, and open its result from the tool card.

What doesn't yet

  • Currently only has full support for Claude Code.
  • Modules start only by asking the chat. There is no button to launch one.
  • The hypothesis Modules have no view of their own, so their results can be hard to read.
  • Modules don't work with OpenAI API yet.

The OpenAI key (optional)

Copy apps/server/.env.example to apps/server/.env and set OPENAI_API_KEY . This adds "OpenAI API" to the agent picker, for chat and tools.

It does not run literature reviews or the hypothesis Modules: those need an agent that can search the web, and today only Claude Code can. Without a key, the picker shows OpenAI as not set up, which is expected.

If something's wrong

  • "Panel couldn't reach its server." The server half is not running. Check the terminal pnpm start is in, then press Retry.
  • An agent shows as not set up. The reason is written under the message box.
  • A port is already in use , or the app answers but never loads: run pnpm dev:doctor . It says what is holding each port and how to clear it.

Licence

MIT


The idea

UI

The UI has multiple configurable windows, called Panes, that can display things ranging from image files, data files, code, as well as chat sessions. This is critical for researchers who often have to context switch between different types of files.

A default set of Panes are provided for common use cases. But custom Panes can also be added by humans and agents, such as a PDB viewer or SQLite visualizer.

Module Protocol

Modules are similar to Skills but with additional definitions to support inter-module workflows and integration with the workspace.

Specifically, Modules have typed definitions for Inputs, Outputs, and Intermediates.

Inputs and Outputs are straightforward. Intermediates refer to objects that provide observability, such as the Chain-of-Thought or scratchpad for an agentic Module, or may be intermediate outputs in a multi-stage Module. These are especially important for processes that need transparency or long-running jobs that should show progress.

Having typed definitions for these enable validation at runtime and make it easier for humans and agents to develop custom Modules for downstream tasks and Panes for visualizations.

Data Abstraction Layer

A data abstraction layer (DAL) bridges in-memory and filesystem objects. A DAL helps to map a URI to either an in-memory store or a local file, so that the Module just has to concern itself with the manipulation of the object.

What Zero-Day Response Should Be in the Post-Mythos Era

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 09:45:54
AI is shrinking the time between vulnerability disclosure and exploitation, leaving defenders less time to wait for patches or public exploits. Picus Security explains how exploitability validation, security control testing, and autonomous pentesting can help teams close exposure gaps before attacke...
Original Article

Assembly Line

By Sila Ozeren Hacioglu , Security Research Engineer at Picus Security.

If you run PaperCut NG or MF, the last week of August showed what vulnerability response looks like when AI speeds up vulnerability discovery.

On August 27, PaperCut's urgent advisory said attackers were already exploiting servers. No CVE, no exploit, no patch. The first emergency patch came a day later and was bypassed the same day. The third one landed on September 1. Six days without a patch that held or an exploit to test with, while attackers were already exploiting in the wild.

And the window is closing. Disclosure-to-exploitation averaged 21.5 days last year. It is measured in hours now . PaperCut isn't the outlier. It's the template.

Below is one day in the life of a security team, told through a hypothetical CVE .

The CVE is made up. The day is not: it is what PaperCut's customers lived through in August. Let's walk through it hour by hour.

08:00 – A CVE drops. No patch.

You wake up and CVE-2026-1001 is in your feed: unauthenticated RCE, no patch . You run a version check. Twenty assets match. Before you can finish reading the list, your phone rings. It's management. They've already seen it, they've already been asked about it, and they want an answer in the next fifteen minutes: are we exposed, and what are we doing about it?

Strip the panic away and there are exactly two questions to answer:

1. Are these 20 assets actually exploitable, in my environment?

2. Would my security controls stop it, right now?

Version data says "affected." Version data is not an answer. Both questions start the day at Unknown.

Patching is off the table, because there is no patch.

Shutting the services down would settle the question, but the business runs on them. Nobody is going to negotiate that. You need a verdict, not a shutdown.

08:05 – Your first instinct cannot act

The natural move is to reach for your automated pentesting tool. Take the exploit, fire it at the 20 assets, see what falls. So you go looking for the exploit.

There isn't one. No public PoC, nothing to run. The tool that would give you the answer is waiting for ammunition, and so are you.

The attacker is not. Weaponization used to take weeks; now it takes hours, and the clock started at 08:00. If you wait for a public exploit, the first working one you see may be the one that hits you.

08:15 – The exploit is a chain, not a payload

Here is the shift. An exploit is not just a payload. It is a chain: the payload has to be delivered, it has to execute, and then the attacker has to escalate privileges, inject into a process and pull credentials to make the foothold worth anything. Each step is a known technique, and techniques can be simulated safely against your controls before anyone has written the payload itself.

You cannot test the exploit, because there is none. But you can test the chain the exploit would need. Map the CVE to the techniques it has to run, delivery, execution, privilege escalation, injection, credential access, and run those against your live stack: NGFW, WAF, endpoint hardening, EDR, SIEM. Per asset. The output is a verdict: would this chain succeed in your environment?

The question "is it exploitable here?" becomes testable ten minutes after disclosure.

We explained how this works in detail in our post on validating CVEs without a working exploit .

08:30 – Simulated, tested, ticketed

By 08:30 the chain has run. The results are not comfortable, and that is the point. The NGFW missed the delivery step. The WAF detected it but did not block. Endpoint hardening flagged execution. The EDR raised no alert. The SIEM raised no alert.

Now the two Unknowns have answers. The 20 assets are exposed to this chain, and nothing in the stack would stop it. But the gaps have names and owners. An action plan is created: a detection rule for the NGFW, a prevention rule for the WAF, GPO hardening for the endpoints, an IOA rule for the EDR, a detection rule for the SIEM. The EDR and SIEM rules deploy automatically. The rest go out as tickets and get worked through the morning, alongside a patch ticket for every affected asset, parked until a patch exists.

By 08:45 the chain is re-run. This time: detected, blocked, blocked, alerted, alerted.

You have not patched anything. You have broken the chain on every affected asset before a working exploit exists.

12:00 – The threat gets a name

Threat intel arrives. An Iranian threat group is running a campaign weaponizing CVE-2026-1001. There is still no public exploit, but the attacks have started. At 08:00 you had a vulnerability. At 12:00 you have an adversary.

That changes the question. The CVE is now one link in a full kill chain: initial access, lateral movement, persistence, exfiltration. You validated the vulnerability this morning. Would you survive the campaign?

12:30 – The whole campaign, rehearsed

You take the new report, pull the group's past behavior from earlier reporting, and assemble the full campaign as an attack simulation. Run it end to end against your controls.

  • Initial access: blocked. The 08:30 fixes hold, and the morning pays off twice.

  • Lateral movement: detected, alert fired.

  • Persistence: missed. This is a technique the CVE-focused work could never have surfaced, because it has nothing to do with the CVE.

  • Exfiltration: blocked, egress controls holding.

The persistence gap runs the same loop as the morning: rule delivered, deployed, re-proven. Closed before lunch is over. Remember this rehearsal.

16:00 – The exploit goes public

A working exploit is published. Now, and only now, live testing has ammunition. Automated pentesting can fire the real thing.

But two constraints show up immediately.

First, you may not be allowed to. Policy often forbids firing live exploits at production or critical assets, and print servers, domain controllers, and OT systems are exactly where that policy bites.

Second, reach: with a real exploit, a pentest can safely touch maybe 5 of the 20 assets . The other 15 were only ever answerable the way you answered them at 08:15.

16:30 – Ground truth, two ways

The five reachable assets get tested with the real exploit. Three are not exploitable: the controls hardened this morning meet the real attack and hold. That is live confirmation the simulated verdicts were correct .

Two are exploitable. They need the patch, and there still isn't one, so the patch tickets opened at 08:30 get upgraded to critical , with the working PoC and the exploitation evidence attached. No severity debate. The proof is in the ticket. Until the patch lands, the two go behind the WAF prevention rule with web access restricted to trusted IPs.

18:00 – The attacker arrives. Nothing happens.

The campaign hits your organization. Blocked. Alerted. Gaps already closed . The attack fails against controls validated at 08:15, fixed by 08:30, and proven at 08:45.

Ten hours before the attacker had a working exploit , your environment already did not have this exposure. That is what machine-speed validation buys: you finish before they start.

What this day required

Look at what actually got used. Not one capability, three, and none of them is a silver bullet on its own:

And they had to work together, on signal, in hours. The 12:30 campaign reused the 08:30 fixes. The 16:30 pentest confirmed the 08:15 verdicts. Findings from one fed the next. Run them as three siloed tools on three schedules and this day takes six weeks, not ten hours.

That is what the Picus Platform is built to do: exploitability validation, security control validation, and autonomous pentesting on one platform, sharing one data fabric, triggered by change rather than by calendar.

See the whole day, live

We are going to run this exact scenario, live in the product, at The Validation Summit ’26 on October 14 at 1 PM ET and October 15 at 11 AM BST .

Picus Validation Summit 2026

Mikko Hyppönen opens with what changed after Mythos. Our CTO Volkan Erturk shows how machine-speed validation closes the patch gap and the speed gap. Security leaders from Chanel , Atlassian , and Kraft Heinz talk about how they are actually preparing. Ron Eddings of Hacker Valley hosts.

One question answered: what does Mythos-ready actually look like?

Two hours. Free. See the workflow run live.

Sponsored and written by Picus Security .

AI is breaking our proxies for expertise

Hacker News
www.seangoedecke.com
2026-09-15 09:41:55
Comments...
Original Article

Mathematicians are broadly not anti-AI. They’re more culturally open to using AI as a tool than, say, artists or writers 1 . However, now that more and more genuinely prestigious problems have fallen to AI, that might be changing. Almost five thousand mathematicians (including twenty-five Fields medalists) have signed a declaration called A Severe Misalignment of AI in Mathematics . The core argument goes something like this:

In recent months, the success of AI in solving major mathematical problems has made headlines even outside mathematical circles. But solving problems is only a tool and proxy for achieving the primary goal of conceptual understanding and insight. Forgetting this in the world of AI may turn the tool against the primary goal. Indeed, the mass production at faster and faster pace of “true/false” statements could destroy fertile ground instead of breathing life into new ideas.

A lot of people online have interpreted this as the expected complaint from any field that gets automated: translators did it, artists and programmers have been doing it, and now it’s the turn of the mathematicians. I think this is too dismissive. Understanding the concrete problem mathematicians are upset about can help us better understand the impact of AI on our own fields, and what we’ll have to do about it.

Puzzle-solving and idea-generating

There are two types of mathematics. Most people are familiar with the first, which we might call “puzzle-solving”: you take a problem and try to find a solution to it. When you’re a student, these problems are typically easy, like simplifying some algebraic expression. When you’re a researcher, these problems can be nearly impossible, like proving Fermat’s Last Theorem . Puzzle-solving is easy to understand but hard to do, which makes it impressive to non-mathematicians, which makes it highly prestigious. In other words, puzzle-solving is legible .

The second type of mathematics is “idea-generating”: coming up with new ways of thinking about mathematics, and thus new terms or concepts. For examples of these, just glance down the list of arXiv mathematics papers . “Hardy spaces”, “Schatten exponent”, “Banach lattices” and so on are all concepts someone thought was interesting. This work is largely unimpressive to non-mathematicians, because nobody really knows if the concepts you come up with are particularly difficult or insightful. For instance, I have just generated the concept of a “Goedecke set”, which is the set of all natural numbers whose digits add up to a prime number. Who cares? The categories we want are the “natural kinds” of mathematics — the concepts that “carve nature at its joints” — and it’s almost impossible to tell what those are without years or decades of hard work.

How are the two types of mathematics related? We might say 2 that generating ideas is the real intellectual work of mathematics. Puzzle-solving is important instrumentally: to identify which ideas can be used to answer longstanding questions, and thus which ideas are worthwhile. Over time, those worthwhile ideas become better understood and easier to use, until they reach the point where they can be used to advance science in general. Eventually the ideas become so well-understood that they can be taught to children: “zero”, “negative numbers”, “imaginary numbers” and “calculus” were all once rarefied mathematical ideas, but are now concepts we’d expect any precocious twelve-year-old to grasp.

There’s another, more prosaic purpose of puzzle-solving: to make mathematical skill and progress legible to outsiders. I can’t appreciate Terence Tao’s mathematical work, but I know what a Fields Medal is. I don’t have a good intuitive sense of what a Galois representation is, but I know about the proof of Fermat’s Last Theorem . We might say that puzzles like this have served as a way to indirectly reward skilled mathematicians for their more important idea-generating work (or for conclusively demonstrating 3 that the ideas used in the proof are useful).

Do AI proofs undercut idea generation?

AI proofs undercut both of these purposes. I can now lay out precisely why I think mathematicians are so unhappy:

  1. Puzzles serve as a high-legibility, high-reward target for mathematicians
  2. To solve these puzzles, new ideas must typically be generated; the puzzle’s solution serves as evidence that the ideas are useful
  3. But now AI can solve many of these targets “the hard way”, without generating intuitive new ideas
  4. This undercuts both ways puzzle-solving supports idea-generation: AI companies claim the prestige while not meaningfully advancing mathematical progress
  5. This is bad for mathematics as a whole, because puzzle-solving is ancillary to the real goal of mathematics

This is kind of like Goodhart’s Law . Puzzles were a useful, impossible-to-game measure for mathematical progress. But now that AI companies can game that measure (by solving them in a way that’s inaccessible 4 to humans), the whole point of those puzzles disappears.

Are the mathematicians right? I think it’s broadly unclear whether (3) is true: i.e. whether frontier AI models aren’t generating or can’t generate new mathematical ideas. We’re still in the very early days of AIs solving our hardest mathematical problems. Who knows what they’re going to be capable of? I give basically zero credence to the idea that AIs are incapable of this because of some intrinsic feature of how LLMs work. For the last three years, we’ve seen people claim that LLMs are intrinsically incapable of X, only to have LLMs excel at X a few months later.

Even granted that (3) is true, there’s still work to be done for human mathematicians in building the conceptual machinery that can make AI-generated proofs accessible to humans: i.e. in generating a “human proof” to go alongside the existing “AI proof”. In fact, I’d expect the existence of an AI proof to help with this. If you know proposition X is true, it’s easier to figure out why, because you’re not constantly worried you’re wasting your time. For more on this, I recommend Gwern’s blog On Really Trying , where he quotes a series of instances where simply being told that a solution exists is enough of a clue to help people find it.

Mathematics, chess, and speedrunning

Of course, there’s a prestige and motivation problem. “I’m the first person to solve Navier-Stokes” is a much more compelling target than “I figured out a better way to explain the AI solution to Navier-Stokes”, and it’s much easier to award prizes for. Will mathematicians bother to work on problems that have already been solved? I think so.

To see why, we can look at other domains where AI has come in and outcompeted the best humans, such as chess or video game speedrunning. I can run a chess program on my phone that will beat Magnus Carlsen 100-0. Computer programs — called “tool-assisted speedruns” or “TAS” — can finish any video game much faster than even the fastest human. But in both of these areas, humans still compete in human-only leagues, and there’s still prestige attached to the most capable humans. It’s possible that mathematics ends up in this kind of state, where “human mathematics” and “AI mathematics” exist in largely separate spheres, and the first “human” solution to a mathematical problem can still earn acclaim.

In fact, in both of those areas, the presence of inhumanly strong computer players has improved the human game. Despite many computer chess moves being basically incomprehensible to humans, top chess players have learned from the computer “style”. In speedrunning, many moves once considered “TAS-only” are now performed by humans. AI mathematics might likewise improve human mathematics.

Software engineering

I am not a mathematician. I did major in mathematics during undergrad, and I have fond memories of proofs from real and complex analysis, but it’s not even close to my field. However, I am watching the effects of powerful AI on mathematics very closely, since my own field — software engineering — is being colonized by AI agents in the same way.

The field of software engineering does not have the same structure as mathematics. We write code to make money, not to earn prestige or advance the frontier of human knowledge. But AI is undercutting the traditional avenues for prestige in software engineering as well. It used to be that you could put a meaty project on your GitHub — say, an emulator, or a toy OS — and people would know you were a skilled engineer. But now projects like that are worthless, because everyone just assumes they’re vibe-coded. We used to tell stories about engineers who would disappear and rewrite a system over the weekend, or produce thousands of lines of code a day. Now anyone can do that with an OpenAI subscription.

Like mathematics, software engineers are going to have to rebuild our cultural sense of the kind of work we value. We are either going to have to silo “AI work” off from “human work” like chess, or to find some legible human skills to recognize that can’t be easily counterfeited by AI. In the meantime, a lot of people who were successful in the old world are going to be very unhappy.


If you liked this post, consider subscribing to email updates about my new posts, or sharing it on Hacker News .

Here's a preview of a related post that shares tags with this one.

Don't build tools for AI agents

Lots of people are making the case that we should stop building software for human users and start building it for AI agents. This kind of makes sense. For instance, my AI agents now use Datadog way more than I use it myself, purely by virtue of them moving much more quickly and running in parallel. But I think most attempts to build “X for AI agents” are going to fail. Here are three reasons why:
Continue reading...


New York Ditches the Rite-of-Passage Regents Exams

hellgate
hellgatenyc.com
2026-09-15 09:32:42
High school will never be the same. And other links to start your beautiful Tuesday....
Original Article

I had a high school chemistry teacher whose fear and respect for the Regents exams was paramount.

Each class would begin with an admonition: "Man, the Regents man, you better be scared! You better get ready! Because man, the Regents, they're coming!"

He spoke of the Regents, the exams you had to pass to graduate from New York public high schools, with reverence, awe, and not an insubstantial amount of terror. We took him quite seriously—this teacher had dodged bullets in the Lebanese Civil War, and told us that when he came to the United States, he learned English by locking himself in a room with a dictionary (Honestly? I believe him).

Give us your email to read the full story

Sign up now for our free newsletters.

Sign up

Show HN: Capsule – Single-file web apps that save their data into SQLite

Hacker News
withcapsule.app
2026-09-15 09:31:40
Comments...
Original Article

Capsule packs your entire app — UI, data, and everything — into a single portable .capsule file. No cloud. No accounts. Just share it.


01 / PROMPT YOUR IDEA

Describe what you need

Describe the application, layout, or features you want to build.

02 / INSTANT CAPSULE APP

Self-contained output

Generates a complete single-file .capsule container with HTML UI, schema, and local SQLite data.

03 / ITERATE ON THE FLY

Live AI updates

Modify features, dark mode, or schemas on the fly via direct AI prompts or MCP coding tools.

Choose an AI assistant below to start building your app.


01 / PORTABILITY

What If an App Was Just a Document?

Forget cloud accounts, servers, and subscriptions. Capsule bundles your user interface, media assets, and local database into a single, portable .capsule file.

Send it via WhatsApp, AirDrop, or email just like a PDF or Word document. When the recipient taps the file, it launches instantly with all your data preloaded, ready to use.

💬

Share Apps in Chat

Send interactive trackers, portfolios, or tools in standard message threads. Tapping open works immediately.

🎨

Zero Vendor Lock-in

Capsules use standard HTML and CSS. Your code and data belong to you, entirely free of cloud silos.

02 / PRIVACY FIRST

100% Private by Design

Capsule keeps your personal data where it belongs, on your device. Everything you create is saved directly into the file, giving you complete ownership with zero cloud servers.

Write tasks, list recipe notes, or save project logs. There is no cloud storage, no account registration, and no network requirement. Everything is secured right inside the file.

🔒

Offline-First Storage

Works 100% offline. Access your apps on a plane, on the subway, or completely disconnected.

🔑

Secure Local Vault

Data stays safely packed inside the single file. Absolute protection from server breaches.

03 / CROSS-PLATFORM

One File, Every Operating System

Capsule files are completely cross-platform by default. Open the exact same file on macOS, Windows, or Linux without conversion or special setup.

Your apps launch with full desktop performance on any computer, with native iOS and Android support coming soon.

Instant Desktop Launch

Launches seamlessly on macOS, Windows, and Linux with zero setup or configuration.

📱

Mobile Support Coming Soon

Open and run the exact same .capsule files on iOS and Android devices.


GET CAPSULE

Ready to run portable apps?

Capsule is completely free. Download the host player for your platform and open any .capsule file in seconds.

Desktop

macOS

macOS 12 Monterey or later

Windows

Windows 10 or later (64-bit)

Linux

Ubuntu / Debian / Fedora

Web

Web

Runs in your browser for a quick preview. Cannot open or save files directly on your computer.

Mobile (Coming Soon)

iOS

iOS 16 or later

App Store

Android

Android 9 or later

Google Play

Show HN: Ordewell – turn one goal into an ordered plan of coding-agent tasks

Hacker News
github.com
2026-09-15 09:31:37
Comments...
Original Article

Ordewell

Turn one goal into an ordered plan of coding-agent tasks — each with its own runner, model and mode — then execute and verify it.

Website · Docs

License: Apache-2.0 npm CI GitHub stars

Ordewell's terminal UI: a goal is typed, the planner reads the repo and refuses a write, it asks whether the limiter should reuse the existing Redis client, then commits a seven-task plan — each task showing its runner, model, thinking effort and mode — and executes it to 7/7 complete.


What this is

  • A plan you can rewrite before a token is spent. The plan is a typed artifact, not an agent's internal state: every task carries a runner, model, thinking effort and mode, and you can change any of them, add and remove tasks, and rewire dependencies — without losing completed work or round-tripping the AI.
  • The right model per task, chosen in the open. The planner makes one portfolio decision across the whole plan — a security refactor and a README update do not deserve the same model — and shows you every assignment before anything runs ( why a separate planner? ).
  • Verdicts from evidence, not opinion. A task completes only when its unique completion marker appears in the runner's output; exit code is retained as diagnostic evidence. The model is never the tie-breaker. Stuck tasks can be advanced with Mark complete , and a task marked done by mistake goes back with Mark not done .
  • A planner that talks back. Planning is one continuous chat: it researches your repo read-only, asks when your goal is vague, and its final message is the plan ( ADR-0002 ). Reads run in parallel; anything reaching outside the workspace asks once; commands that would write are refused outright ( ADR-0008 ).
  • No extra API key required. Claude Code, Codex, or OpenCode can be the planner, strictly read-only, on the subscription you already hold for the runners ( ADR-0009 ).
  • Multi-runner by design. Enable several and the planner assigns one per task. Claude Code, Codex and OpenCode ship built-in; anything else — Aider, your own CLI — is a plugin manifest, not a code change.

Quick Start

Node.js ≥ 20 on macOS, Linux or Windows. The TUI also needs tmux — see Platform support below.

npm install -g ordewell
ordewell                                       # the TUI — chat on the left, plan on the right

That's it. First run asks for a planner and a runner, set from inside ( /planner , /runners , /key ) — no restart, no API key required up front.

npx ordewell works the same without a global install; the package also ships scoped as @ordewell/cli .

For VS Code instead, install the extension — it carries its own core, so there is nothing to install from npm:

code --install-extension ordewell.ordewell

Or search Ordewell in the Extensions view.

Building from source: git clone https://github.com/ordewell/ordewell.git && cd ordewell && npm install && npm run build && npm link -w packages/cli — see CONTRIBUTING.md .

Scriptable / headless

Every slash command is also a subcommand — set the planner and runner by env var to skip the TUI entirely.

Already run Claude Code, Codex, or OpenCode? No separate API key — it runs on the subscription you already hold:

export AI_PROVIDER="claude-code"        # or codex, opencode
ordewell plan --goal "Add rate limiting to the public API" && ordewell run

Mutation always stays with the runners; the planner agent only explores and reasons. Same toggles apply from a UI: /planner , /model , /planner-effort , or the planner bar in VS Code.

Prefer an API key? Twenty-five providers are recognised via their own *_API_KEY — OpenRouter, Anthropic, OpenAI, Gemini, xAI, Groq, DeepSeek, Mistral, Together, Fireworks, Perplexity, Cerebras, DeepInfra, Cohere, Novita, Kimi, Zhipu, Qwen, Doubao, Hunyuan, Baichuan, MiniMax, Yi, StepFun and SiliconFlow. Run ordewell key for variable names, or point OPENAI_COMPATIBLE_BASE_URL at anything else, including a local model server.

export OPENROUTER_API_KEY="sk-or-..."
ordewell plan --goal "Add rate limiting to the public API" && ordewell run

Three surfaces, one core

VS Code

A streaming timeline: live thinking, each research step with its outcome, and task cards you expand for the runner's own output. Retarget a task's runner and its model and mode re-derive in place. The whole loop is below, under The VS Code loop, end to end .

Terminal UI

Ordewell's terminal UI split between the planner conversation on the left and a plan pane on the right showing seven tasks with per-task runner, model, effort and mode.

Everything the extension does, over SSH. tab swaps chat and plan pane; single keys drive the plan ( f start, E run all, m toggle done, R runner, o model). /help lists the rest.

CLI

$ ordewell plan --goal "Add rate limiting to the public API"

Generating plan for: "Add rate limiting to the public API"...
✓ list_dir src → D middleware F router.ts F auth.ts
✓ grep X-RateLimit → no matches in 6 files

Question: should limits apply per API key, or per client IP?
My recommendation: per key — auth() already threads the key through req.ctx.
> per key, with an IP fallback for anonymous routes

Plan: 4 tasks (3 AI, 1 Manual) — claude-code, opencode
Session: session-1751600000000

   1. [ AI] Add a token-bucket limiter in src/middleware/rateLimit.ts (Claude Sonnet 4.5 · Claude Code)
   2. [ AI] Wire the limiter into route registration (Claude Haiku 4.5 · Claude Code)
   3. [ AI] Return RFC 6585 429s with Retry-After (DeepSeek V4 Flash · Opencode)
   4. [MAN] Document the limit headers in the OpenAPI spec

  [MAN] = manual step — run `ordewell tui` to work through it

  Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.

$ ordewell run
Executing plan...
  ✓ #a1b2 completed — PASS: Verified: completion marker detected in agent output. Task c
  ⟳ #c3d4 in_progress
[2/Wire the limiter into route registration] Started: claude-code / claude-haiku-4-5

Done. 4 completed, 0 failed, 0 blocked.

Every slash command is also an ordewell subcommand, so nothing is UI-only and headless automation reaches everything a human can.


How it works

  1. Describe a goal in plain prose.
  2. The planner researches your workspace read-only and interleaves questions with research in one persistent conversation ( ADR-0008 ).
  3. A plan appears — ordered tasks, each with a runner, model, thinking effort and mode. Edit anything inline, or reprompt to reshape the whole plan without losing completed work.
  4. Execution spawns a real coding-agent session per AI task, respecting the dependency graph and handing each task its predecessors' results. Manual tasks become checklists.
  5. The VerdictEngine completes a task only once its marker appears; an exit without one fails visibly. Sessions auto-save to .ordewell/sessions/ .

Usage examples — planning, editing, multi-runner, plugins

Plan, edit, execute

# The planner researches the repo and converses if the goal is underspecified
ordewell plan --goal "Migrate the config loader from JSON to TOML"

# Reassign before running — runner first, since it re-derives model, effort and mode
ordewell task-runner 2 opencode
ordewell task-deps 3 1,2

# Execute; independent tasks run in parallel (default: 3 concurrent sessions)
ordewell run

# Inspect any session later
ordewell status --session-id session-1751600000000

The surfaces differ only in how you name a target: the TUI opens a picker, the CLI takes an argument — and omitting the argument prints the same options the picker would have shown.

ordewell task-model 3            # lists the models that task's runner can spawn
ordewell task-model 3 sonnet     # picks one

Configure without an editor

ordewell planner claude-code     # plan on a coding agent's subscription — no API key
ordewell model set sonnet        # scoped to that agent's own catalog
ordewell planner-effort high     # a variant of the selected model
ordewell key set openrouter sk-… # stored in .env, never echoed back
ordewell runners codex off

Each pushes to the running server before writing .env , so the change lands on the next plan with no restart — and a refused connection cannot leave the file holding a setting the daemon never saw.

Deep-interview planning with a PRD

ordewell grilling on   # planner interrogates your goal before outlining (min. 3 probing questions)
ordewell prd on        # planner previews, then writes a full PRD to .scratch/<slug>/PRD.md
ordewell tdd on        # tasks are augmented with red-green-refactor instructions

ordewell plan --goal "Real-time collaborative editing"
# → the planner grills you in chat, drafts the PRD, waits for your OK,
#   then commits the plan as its final message

Multi-runner plans and custom runners

# Pass --runner repeatedly to build a runner set; the planner assigns one per task
ordewell plan --goal "Refactor auth module" --runner claude-code --runner opencode

# Bring your own CLI agent via a plugin manifest
ordewell plugins create my-runner        # scaffolds manifest.json
ordewell plugins install github:user/repo
ordewell plugins list

Remote plugin installs accept https:// repositories on GitHub, GitLab, Bitbucket and Codeberg; anything else must be cloned yourself and installed from its local directory.

The other two front ends

ordewell               # full-screen terminal UI — same as `ordewell tui`
ordewell web --daemon  # the local API server, in the background

ordewell web starts the HTTP + WebSocket API on 127.0.0.1:3742 that the CLI and TUI are clients of — every other command starts it for you on demand. It serves JSON, not a web page; there is no browser dashboard yet.

For VS Code, install the extension and open the Ordewell panel — see Quick Start.

Area Commands
Planning type a goal, /approve , /run , /stop
Tasks /add-task , /remove-task , /complete , /uncomplete , /skip , /retry , /cancel , /force-start
Skills /grilling , /tdd , /prd , /verify
Models /model , /key , /allowlist , /runners , /auto , /refresh
Sessions /sessions , /new , /save , /load , /delete — a loaded session is adopted by the server, so its plan stays executable
System /help , /mouse , /quit

API keys typed into /key are masked on screen and written to your .env .

The mouse wheel scrolls whichever pane the pointer is over — transcript or plan — regardless of which one has keyboard focus, and pgup / pgdn scroll the focused one. Capturing the mouse for the wheel is what disables the terminal's own drag-to-select, so /mouse off hands it back when you need to copy text out (remembered via ORDEWELL_TUI_MOUSE in your .env , and ORDEWELL_TUI_MOUSE=false in the environment turns it off everywhere).

A task's own terminal is a tmux window, where tmux does hold the mouse so the wheel scrolls its scrollback. Selecting there still copies to your system clipboard: drag to select and release to copy, or double/triple-click for a word or a line. Install wl-copy , xclip or xsel on Linux if you have none of them — without one, copying falls back to an OSC 52 escape that some terminals ignore.

The VS Code loop, end to end — research, question, plan, execution, verdict The Ordewell VS Code panel: research steps settle one by one, the planner asks whether limits apply per API key or per client IP, a five-task plan is committed with per-task model and mode pills, then execution runs and a task lands on a green pass verdict.
Platform support — including the Windows notes
Surface Linux macOS Windows
VS Code extension
API server
CLI
TUI ✅ needs tmux ✅ needs tmux needs tmux — run it under WSL

The TUI requires tmux on every platform , not only Windows — it is what backs each task's live terminal. Install it from your package manager ( apt install tmux , brew install tmux ) before running ordewell . Everything else runs natively on Windows: the planner (including harness planners), task execution, model discovery, and the read-only exploration envelope all work there.

Two notes for Windows. Install the agent CLIs with their native installers where one exists — an npm-installed claude / codex / opencode is a .cmd shim, which has to start through cmd.exe and inherits its 8191-character command-line limit; that is fine for task prompts but not for the harness planner's larger system prompt, and Ordewell will tell you so by name rather than silently truncating it. And keep Git for Windows installed: its POSIX shell is what the planner runs research commands in, so ls , cat , grep and friends behave the same as they do everywhere else. See ADR-0010 .

Any install route is found, on PATH or not: the PowerShell one-liner installers ( irm https://claude.ai/install.ps1 | iex , OpenCode's equivalent), npm, pnpm, Yarn, bun, Scoop, Chocolatey, WinGet, and Volta. If a runner is greyed out in the picker right after you installed it, restart the VS Code window — a GUI-launched extension host holds the PATH it started with.

Configuration — the four settings that matter
Option Default What it does
One provider key ( OPENROUTER_API_KEY , ANTHROPIC_API_KEY , GEMINI_API_KEY , …) The one required setting. Twenty-five providers are recognised, each from its own variable — ordewell key lists them — plus any OpenAI-compatible endpoint via OPENAI_COMPATIBLE_BASE_URL . The provider is auto-detected from whichever key is set (force with AI_PROVIDER ). Not needed when AI_PROVIDER is claude-code , codex , or opencode — those plan with the CLI's own subscription.
ORCHESTRATOR_MODEL deepseek/deepseek-v4-flash The planner model — a budget model by default; it plans and researches but never writes code. Change via ordewell model set <id> or /model , which scope the choice to the planner backend's own catalog. With a coding-agent planner, it must be one of that agent's own model ids.
ORDEWELL_PLANNER_EFFORT Thinking effort for a coding-agent planner, from the selected model's own variants ( low , high , adaptive , …). Ignored by vendor planners, whose effort is baked into the model id. Change via ordewell planner-effort <level> or /planner-effort .
ORDEWELL_MAX_PARALLEL 3 Max concurrent AI task sessions (1–5). Independent tasks run in parallel; the dependency graph is always respected.

Run ordewell --help for the full list of environment variables, or ordewell setup for the interactive wizard. VS Code users: everything is mirrored under ordewell.* settings.

Architecture
packages/
├── core/    Pure TypeScript, zero UI deps — Session, PlanStore, Planner,
│            TaskOrchestrator, VerdictEngine, ModelResolver, ModeResolver,
│            RunnerRegistry + manifest template engine
├── cli/     ordewell: tui, plan, run, status, stop, web, models, setup,
│            plugins, grilling, prd, tdd — plus tui/, a pure state +
│            renderer core behind a thin raw-mode terminal driver
├── vscode/  Extension + webview: streaming planner timeline, task cards,
│            TTY capture via script(1)
└── web/     Hono HTTP + WebSocket server — the local daemon the CLI and
             TUI drive over 127.0.0.1 (session pool, headless execution)

The TUI's core is pure — a reducer returning { state, effects } and a renderer returning one string per terminal row ( ADR-0006 ).

Tasks default to each runner's autonomous mode (toggle with /auto ), and the plan is the source of truth for what runs — modes are never silently rewritten at spawn ( ADR-0001 ).

Every surface consumes one event union ( SessionMessage ) over one broadcast seam — the domain vocabulary lives in CONTEXT.md and design decisions in docs/adr/ .

Acknowledgements

The deep-interview planning workflows — grilling , PRD drafting, and TDD task augmentation — are adapted from Matt Pocock's skills (MIT), rebuilt as prompt blocks inside Ordewell's planner and runner prompts. If you want those workflows in a plain coding-agent session rather than an orchestrated plan, his repo is the place to start.


Contributing

Bug reports, feature requests and pull requests are welcome — start with CONTRIBUTING.md for the build order and the layout of the tree. Security issues go to SECURITY.md , not the public tracker.

New to the codebase? CONTEXT.md is the domain glossary and docs/adr/ records why things are the way they are.

License

Licensed under the Apache License 2.0 . The Ordewell name and logos are not covered by that licence — see NOTICE .

Australia 'on the same page' as Canada as it seeks deeper EU alliance

Hacker News
www.reddit.com
2026-09-15 09:25:38
Comments...
Original Article

You've been blocked by network security.

To continue, log in to your Reddit account or use your developer token

If you think you've been blocked by mistake, file a ticket below and we'll look into it.

Paul Tagliamonte: DESFire EV3

PlanetDebian
notes.pault.ag
2026-09-15 09:25:00
I’ve long been interested in hardware key material storage devices. I’ve been a fan of yubikeys (I still remember when my fancy new NEO-N showed up), PIV (and its associated smattering of additional fields), SaaS HSMs, the kernel keyring, some tooling I’ve fairly satisfied with the design of at prio...
Original Article

I’ve long been interested in hardware key material storage devices. I’ve been a fan of yubikeys (I still remember when my fancy new NEO-N showed up), PIV (and its associated smattering of additional fields ), SaaS HSMs , the kernel keyring , some tooling I’ve fairly satisfied with the design of at prior companies, and of course, our dear friend, the TPM. All that is not even to mention the scores of exotic hardware security modules one generally comes across from time to time when you’re keeping a sharp eye out that you wind up playing with.

The concept of storing private key material on a disk, or even having it in RAM has always skeeved me out, so I have a natural inclination to hardware modules, and how shifting keying material around can change your risks and threat model(s) in interesting ways.

I don’t remember when I first came across the MIFARE DESFire EV3 , but a few weeks ago I did a deep-dive into the state of the art of authentication schemes using ID cards. My complete overview of what tradeoffs exist is pretty extensive (and likely not interesting to the vast majority of the world), but the tl;dr wound up being one of “use PIV” or “use MIFARE DESFire EV3”. I wound up picking DESFire for a recent project, and figured it’s worth talking a bit about what I learned, share some thoughts, and some code. That code is published on crates.io/desox , and docs, as is our custom, may be found at docs.rs/desox

DESFire supports DES (I’m sure most readers saw that one coming), 3DES (I didn’t bother playing with 3DES at all) or AES-128 (AFAICT always use this?) keying material. It’s worth noting that the DESFire only supports symmetric keys and is not designed for public key cryptography, and operates exclusively using shared symmetric key material. The DESFire EV series use those keys and related authentication schemes to interact with “files” stored on the on-chip EEPROM (2k, 4k, 8k, and 16k versions exist), or “applications” (groups of files and authentication keys).

Talking to a DESFire EV3

Interactions with the card are done over NFC (ISO/IEC 14443 Type A), and commands to/from the card may be in the usual ISO/IEC 7816-4 APDU format, or “unencapsulated” bytes sent to/from the card are sent using a fixed instruction set and return code structure – saving a few bytes per message. I’ve opted to use their undocumented and proprietary format – I found it easier to work with and with a maximum message of 60 bytes, the savings matter a lot.

While powered via NFC, the card maintains a small amount of state about the connection between the reader and the card in its RAM, including if the session is authenticated or unauthenticated. I’ll dig into how authentication happens later, but it’s worth knowing that sessions can become authenticated using one of the symmetric keys shared by the card and the reader. The vast majority of the DESFire commands I know about tend to work while either authenticated or unauthenticated, with a few exceptions ( GetUid , ChangeKey , and ChangeKeySettings for example).

In general, I found working with this card particularly pleasant. There is a fair amount of backwards-compatible behavior and multiple methods of communication that confuse things a bit, but overall, it was better than average to integrate with. Kudos to the NXP team. If the docs on this chip were public, things would be orders of magnitude easier – it’s not entirely clear to my why they’re keeping so much of the interface documentation under NDA, but it’s the largest knock against the chip, by far.

Authentication

I found a lot of really great resources outlining how the handshake and protocol works for a DESFire EV3, especially from Ridrix , some public datasheets ThrRealRevK and posts from AndroidCrypto .

The gist here is that, because the DESFire only does symmetric key operations, the key exchange (a type of SKA – Symmetric Key Agreement) uses symmetric keys to establish a unique session key which is used to sign or encrypt data exchanged between the reader and the card. I’m not going to get too in-depth here, since there’s a ton of other resources out there to dig into – but I will do a quick high-level description to keep this post mostly self-contained.

The authentication protocol serves two main functions – to verify that both parties know the same shared secret, as well as to act as a SKA to construct a new session shared secret key. Here’s a quick overview of how a shared session key is derived between the reader and the card using our symmetric keys ( AES-128 in the case below).

  1. the reader requests to start authentication with the card (something like AA 00 to start an AES Authentication handshake with keyslot 0x00 ).
  2. The card will then reply with AF (a status code that indicates more data is to follow), followed by 16 bytes (in the case of AES-128 ) of encrypted (using CBC) data.
  3. The hosts then decrypts this block with the symmetric key from keyslot 0, returning the card’s session nonce.
  4. The host generates 16 bytes (usually random) for its session nonce.
  5. The host sends an instruction of AF (indicating a continuation of the previous command), followed by 32 bytes of encrypted data. When decrypted, the first 16 bytes are our nonce generated in step #4, followed by the 16 bytes provided by the card, decrypted in step #3, except where every byte is shifted to the left by one place (the 0th byte is copied to the end).
  6. The card will reply with 00 indicating a successful operation, followed by 16 bytes, which when decrypted, is our session nonce from step #4, shifted to the left by one byte in the same way that we did in step #5 with the card’s nonce.
  7. At this point, both the reader and card have confirmed the other party has the same symmetric secret key. The session is now “authenticated” and a “session key” is derived using the two nonce blocks. Two hashing keys (K1 and K2) are derived from this key, which is used to maintain an ongoing CMAC hash of the messages coming and going to/from the card.

From here on out, the session is “authenticated”, and responses from the card which were previously “plain” will now contain a 8-byte CMAC signature, which can be used to ensure that the replies in question come from the active session.

In my implementation of the handshake I opted to encode the handshake state into rust types, just so I wouldn’t make any mistakes. The Handshake type contains the session internals (session nonce values, keying state, to include IV, etc). This means the authentication flow (from within my code) uses the Handshake struct to generate the commands to send to the card in order:

/// Create a new `Handshake`, and return the
/// start auth command (something like `AA 00`)
fn Handshake::<Initial>::begin(
  output: &mut [u8],
  key: [u8; 16],
  key_id: u8,
) -> (Self, &[u8]);

After we get a reply back from the card (the encrypted version of the card’s session nonce, sometimes called Rnd_B in code I’ve seen), we transition states from Initial into HalfOpen .

/// Given the card's encrypted response, generate
/// our session nonce and generate a reply
/// (something that starts with `AF` followed by
/// 32 bytes of encrypted data).
fn Handshake::<Initial>::rnd_b(
  self,
  output: &mut [u8],
  input: &[u8]
) -> (Handshake::<HalfOpen>, &[u8]);

Now that we’re “ HalfOpen ”, we’re waiting to hear back from the card to ensure that it, too, can byte-shift our provided nonce. Once we have the card’s reply, we can check it using our complete helper, transitioning from HalfOpen to Successful .

/// Check to ensure that the card replied with
/// our nonce byte-shifted by one place, indicating
/// that they know the symmetric secret in
/// this key slot.
fn Handshake::<HalfOpen>::complete(
  self,
  input: &[u8]
) -> Handshake::<Successful>;

Once the Handshake is successful, the only thing left to do is consume the Handshake struct and turn it into the shared session key by running it through the key derivation function .

/// Consume the `Handshake` struct and return the
/// new shared session secret key.
fn Handshake::<Successful>::into_key(self) -> [u8; 16];

From here on out we can use this session key for the remainder of our interactions with the card – signing messages from (and sometimes to!) the card, or encrypted messages to and from the card. This key is used in CBC block mode, where the session IV is updated with the last block of the encrypted data.

Unit Testing

A nice proprietary of the SKA scheme we’re using as part of DESFire is that the derived session key is actually deterministic if you control your nonce RNG (ok, actually, pretty true for most key agreements, but anyway), which means it is possible to capture traffic over the NFC interface, and “replay” the NFC I/O with cooked RNGs and ensure byte-identical messages and keys are generated. Within desox-rs this is called replay (I’m creative), and I’ve got a few replay sessions checked into VCS, which exercise a signficant amount fo the API surface. All were derived from an actual session with a real DESFire card, and can be updated with a live card and a --cfg flag.

Each replay file is a set of lines (request-response transactions), each containing two space-delimited hex encoded NFC messages. For instance, here’s an authentication handshake in replay format:

1a00 afc7bbd82ff8fefae8
afc6dab54df2278d2952d560821be7e4c3 007d9abe94a9b14748

The code that generated that exchange came from the test stored adjacent to that file – a handshake with the default DES key (all zeros), and an RndA value hardcoded to 32c28fdafd3960de .

let mut card = card
    .authenticate_with_rnd_a(
        0x00,
        Key::Des([0; 8]),
        Key::Des(hex_literal::hex!("32 c2 8f da fd 39 60 de")),
    )
    .await
    .unwrap();

Since the card’s RndB is similarly unchanging (I’m replaying this file every time), this will always derive the same session key, which means messages (including encrypted ones or CMAC signed responses) will be identical, as well. If you’re playing with the DESFire yourself, feel free to grab my replay files if you need a “known good” baseline.

By default this will run using the MockBackend , replaying each file – expecting a byte-identical request, and responding with the harcoded customary reply. If the code (or test!) needs to change, updating the tests is done by swapping the MockBackend out for a real one. Since I had to do this a bunch during development, running cargo test with RUSTFLAGS="--cfg desox_replay_rw" will, on run, overwrite the replay file(s) for the executed test(s), ensuring all line-protocol changes are explicitly caught and reviewed.

Observations

Most commands, even ones which require authentication, are transmitted without CMAC signature(s) or encryption. CMAC signatures from the reader to the card are not really used (except for writes to a file which specifies communication must be CMAC signed), ditto for encryption (although that one is used for key change operations, in addition to file writes on files that specify encrypted communication must be used). The vast majority of commands take a “plain” request from the reader, and return a CMAC signed response.

By my eye, this means that a malicious reader, or something otherwise capable of holding the card online after communication with an authentic reader is complete are able to execute privilaged commands (since one can simply ignore the CMAC signatures on responses), so long as the command doesn’t require the reader to provide CMAC signatures (or encryption), or allow the card to power down.

Fun with DESFire

I’ve played around a bit with ways to use the DESFire cards in interesting configurations, given what they’re capable of. Here’s some half-baked thoughts I had while mucking around with the cards – these are all poorly thought out sketches of some things we can do given the specific tradeoffs I see with the DESFire card. It’s also worth noting that I don’t have any of the actual documentation, and am not a cryptographic grown-up, so take these sketches with a massive grain of salt.

The first thing that came to mind when implementing this is how the authentication scheme can shift the boundary of what is and is not trusted (assuming good secure keying, and provided the key slots and card/application permissions are configured correctly). Rather than push the key material out to the machine connected to the NFC reader (“reader machine”), I instead tried turning the NFC reader and computer into something psuedo-untrusted by “merely” having it pass messages from the card to a trusted remote system (“remote machine”). This means that the “reader machine” is exchanging NFC data with the card, but that data is being decrypted, encrypted and processed by the trusted “remote machine” – the reader is unable to derive the session key.

For each of these, I wind up needing to authenticate – so there’s still a few latent risks, but these can mostly be mitigated by asking for a readbacks of any changed file(s), setting key permissions carefully, and requesting the card’s UID via the encrypted channel – all of which would require the symmetric secrets (which undermine the whole security model if comprimised).

This general construction is also subject to a hostile takeover of the untrusted “reader machine”, since most commands (including destructive ones!) are sent in “ PLAIN ” mode – the reader machine can wait until authentication is complete and then inject commands into the card and “simply” ignore the CMAC signatures on responses, severing ties with the remote machine. As such, we also need to take steps to ensure that the key being used is not one that allows any access beyond what is allowed. Here were some ideas I sketched out off the back of this theory.

The “second-factor”

Given some established (and authenticated) connection, part of the initial authentication flow may use the DESFire card to prove physical control over it as part of a handshake. This can serve as a second factor during some authentication flow, requiring physical card presence at a reader to fully initialize a connection. This does have one glaring downside, however – it’s phishable. To use this “for real”, we’d need to take some steps to prevent obvious MITM flows (XOR the NFC messages with the URI as seen by the client?), but maybe there’s something interesting there.

This also has a second interesting attribute – when used as part of a physical system authentication flow, this becomes a logical place to inject access control, being able to determine if some person is permitted to operate some device at that particular time (Is “Joe” current on his Laser Cutter certifications?) I think of the ideas I landed on, while conceptually interesting (using an employee id card as a 2FA token, it’s very fast), this one is the least likely to turn into something real.

This construction, when paired with an encrypted DESFire file, allows the “remote machine” to read/write an ’encrypted cookie’ to the card – storing small amount of encrypted data that the “remote machine” can read/write, but not the “reader machine”, since this uses an encrypted and authenticated channel from the “remote machine” directly to the DESFire card, without any intermediate hosts needing to be fully trusted. I keep calling this the “encrypted cookie” in my head because it feels conceptually similar to how Ruby on Rails and Laravel handles cookies.

We’d need to take a few extra steps here (for instance, ensure that you read the cookie back over the encrypted channel after writing to prevent a malicious reader from dropping writes) to secure the system, but it feels like the structure of this is definitely decent.

The “takeover”

This time, let’s say the computer attached to the NFC reader (“reader machine”) is semi-trusted. For this scheme, our trusted “remote machine” and the “reader machine” pass messages over the network to handle authentication to the card (as above), where the handshake data is being decrypted, encrypted and processed by the trusted “remote machine” as usual. However, once the authentication handshake is complete and a session key has been derived, the “remote system” return the session key to the “reader machine”, giving it a one-time-use key and authenticated session to the card.

We need to be careful about global/application permissions and key access control to files – but in this construction, we can allow the “reader machine” to take over privileged actions using a scope-limited DESFire key without handing over the card’s true keying material (preventing cloning of the card). This can be helpful to ensure messages to/from the card are truely from the card (verifying CMAC signatures), enables the “reader machine” to directly read/write to/from encrypted file(s), but allows the symmetric key material to remain in as few places as possible – which is critical given compromising that secret will undermine the security of the entire system.

Vondra: PostgreSQL development activity

Linux Weekly News
lwn.net
2026-09-15 09:20:30
PostgreSQL contributor Tomas Vondra has published a blog post looking at development activity in the project, with data from the late 1990s to today. We're doing ~50 commits per week, give or take. In ~2010 we were doing maybe 25/week, and the trend seems to be a slow and consistent growth. The mo...
Original Article

PostgreSQL contributor Tomas Vondra has published a blog post looking at development activity in the project, with data from the late 1990s to today.

We're doing ~50 commits per week, give or take. In ~2010 we were doing maybe 25/week, and the trend seems to be a slow and consistent growth. The monthly average makes the trend a bit easier to spot. Which is good, although there's a lot of other important details (size of commits, are they new features or fixes, ...).

It however nicely aligns with the number of active committers, which also grew ~2x between 2010 and today. So maybe that's working as expected.



Show HN: Hacking a $20 4G wireless hotspot into a texting device

Hacker News
bkovac.github.io
2026-09-15 09:20:24
Comments...
Original Article
The thing

The motivation.

I don’t organize my things well. I try to, but quite often end up with a bunch of stuff on my desk. From various aliexpress orders and projects I’m working on, all the way to gifts and stuff I didn’t have time to find a place for.

That is exactly how this project came to be. At one point in time I had, lying on my desk:

  • Various, hopefully openstick compatible, models - namely the:
    • MF800 - one I ended up using
    • UZ801 - OK size, but no battery or sufficienet visible GPIO
    • USB drive form factor one I ditched because of other issues
  • The Clicks Keyboard for iPhone 16 Pro Max (gift from my cousin - too bad I don’t have an appropriate iPhone 🥲)
  • The Adafruit SHARP Memory Display board

And of course, my caveman brain combined the 3.

Okay, okay, not to lie I was probably a bit conditioned by knowing about:

The modem.

Due to the mysterious laws of supply and demand, and the magic of supply chains - somehow you can get a 4G modem with WiFi, bluetooth, a display, fully battery powered and completely unlocked - for less than $20 shipped. This, of course, is the cornerstone of our project.

There are versions with and without a display. The non-display one just swaps the display for LED-s, though the PCB is the same. Display is a GC9107 powered one, but it looks like ass so i ditched it.

Linux can be installed trivially, powered by the wonderful openstick project. The stock device runs Android, but adb is accessible out of the box - and from adb you can go straight to edl and reflash the thing. Just make sure to save all the important partitions. There are also pins on the pcb which you can short out to get straight to edl.

Extracting the running device tree from the running android was a goldmine of information so be sure to do that.

Here are a few guides or links I found helpful:

The real pain with openstick starts once you get to the drivers and device trees, but we will talk about that later.

The keyboard.

There is not much to say about the Clicks Keyboard. It feels veeery nice to use, the only issue is you need to fork over quite a few clams - made worse because you are going to have to cut it 😱.

Regarding the protocol, it’s exactly what I expected with my previous experience of working with MFi devices - just a regular USB keyboard with an additional Apple proprietary endpoint which the iPhone can authorize with before allowing the keyboard to go through.

So for our device this means that it’s just a regular keyboard.

On a side note, there is a Clicks mobile app used for configuration/updates/whatever. The keyboard itself is powered by CH32V203 or similar. Custom code could be flashed but I don’t see any reason to to this currently. In the future I would like to have a configuration utility.

The display.

I think the sharp display look great with the high contrast (and it plays well into my use case of a dumb device used only for messaging). Other than that, if there wasn’t much to say about the keyboard - then there is absolutely nothing more to say here.

It’s a display.

You send commands.

It displays.

The adapter PCB.

One thing became clear to me quite fast - I was going to have to add a custom PCB. I wasn’t exactly sure what the MF800 had on-board, and I never did end up opening the shield can - but I am fairly certain there is no 5V booster on-board.

Because of the physical sizes, which we will go over further in the post, the USB connector will end up chopped off - so we need a way to handle that too.

The final PCB ended up handling the USB host mode power, USB host/device mode switching, display power and display signal level conversion.

PCB from the component side

I ordered the PCB along with assembly. And because 2 sided assembly is expensive, I made a few compromises to fit all the components on one side. The non-component side is used for the solder points for the PCB-to-PCB connections.

All files can be found on GitHub , but in short the PCB consists of:

  • TUSB320 - for the USB mode switching
  • SN74LVC8T245 - for the level shifting
  • MCP1640 - the 5V booster
  • TPS22917 (one high, one low) - for swithing VBUS/VBAT
  • USB connector and FPC connector for the display
  • test pads used to connect the adapter PCB to the MF800

Enclosure 1.

The MF800 is quite a bit bigger than other opensticks, partly because of the battery it has to include, partly because of it’s slop nature.

MF800 without the back cover and with the (unnecessary) cut-out for the bootloader pins

So to fit inside a Clicks case, we either orient it verticaly and and up with a humongous abomination, or we trim the pcb to fit horizontally.

Top, with a the blue lines showing the USB data lines going from the connector and pads to the SOC
Bottom, red lines mark where I was planning to cut and the blue circle marks the via for USB data test pads

Notice that my right cut (on the bottom picture) cuts off the battery connection line, so this will have to be patched up later.

Cutting.

Cutting the PCB went unexpectedly well. The device booted up immediately and everything seemed to work. Turns out there really were no crucial lines going through those areas of the PCB.

The cut PCB inside a test enclosure with the battery below and the patch VBAT wire

Only things I didn’t test were the USB connection and the 4G modem. The modem I was 99% sure wouldn’t be an issue as there is no reason to route anything for it under those areas - but regarding the USB I was worried that I hadn’t maybe nicked the via.

2D scan of the PCB, trimmed to the cut lines and extruded to match the measured thickness - a tight fit within the iPhone’s width

Wiring 1.

I decided to reuse the pads from the old display. I don’t really know why anymore - possibly because my initial ideas was to use a rigid flex PCB and solder it similar to how the original display was. (which I decided against immediately upon seeing the prices)

Looking at all this now, it seems very dumb. I should have used the labeled pads next to the unpopulated micro SD connector. Note that never did check if these were shared with the SIM card though.

Since I could boot the device and I had the original android device trees, I extracted which pins were used for the display SPI. I then tested those with gpioset to make sure that I was indeed correct. Same goes for the power supplies (although I tested those by disabling them in the device tree and rebooting) and grounds.

Checking and mapping the pins with a multimeter

The first thing I wired and checked were the power supplies followed by the USB. This is because I could test this as an isolated unit. I was also more skeptical about this because it involved a bit of circuitry on my part as well as that iffy via.

Wiring the USB and the power supplies

Of course, it didn’t work initially. After probing around the pads and seeing that all the voltages were OK I noticed in my laptop’s dmesg that it TRIED to enumerate - meaning something was going on.

I also noticed that it says "high-speed". This caught me a bit off guard. I didn’t expect it to use "high-speed" USB. My first thought was that the wires were too long. But before shortening them, I tried a quick fix - twisting them more tightly - and it worked 😲!

Device’s USB Gadget enumerating

Immediately following this success, I tried to get the other direction working. This took some time. Turns out, not all aliexpress adapters correctly wire the CC lines. The keyboard did work immediately, though - only issue is I was afraid to test with it first in case something was wired incorrectly.

Events from the keyboard

Wiring 2.

Before wiring the SPI lines to the display, I wanted to check if I had correctly reconfigured my device tree. I knew the pads were correct from the earlier testing, but there is quite a lot which can go wrong here.

spi@78b9000 {
    compatible = "qcom,spi-qup-v2.2.1";
    reg = <0x78b9000 0x500>;
    interrupts = <0x00 0x63 0x04>;
    clocks = <0x13 0x41 0x13 0x36>;
    clock-names = "core", "iface";
    dmas = <0x6d 0x0c 0x6d 0x0d>;
    dma-names = "tx", "rx";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <0x84>;
    pinctrl-1 = <0x85>;
    #address-cells = <0x01>;
    #size-cells = <0x00>;
    status = "okay";
    spidev@0 {
        //compatible = "linux,spidev"; 
        compatible = "rohm,dh2228fv";
        reg = <0>;
        spi-max-frequency = <16000000>;
        spi-cs-high;
    };
};

For example, qualcomm drivers are sketchy and the commented out compatible won’t actually export the spidev , so we scam it with this the dh2228fv compatibility.

Using the spi-pipe utility running in a loop, I was able to measure voltage change on the MOSI and CLK lines - which was enough for me to conclude that something was happening. I would, of course, prefer to do this with a scope or a logic analyzer - but I didn’t have any of those at hand.

Encouraged by the major success of power supplies and USB, I carelessly connected the display into the PCB (while the device was on 🤦).

Immediately something happened to the display and I was sure I broke it. Fortunately nothing came of this and the display was fine. (This actually happens every time I turn on the device. I don’t yet know if I should be concerned 😬.)

Once I reassured myself that nothing bad had happened, and that nothing was smoking or overheating - I proceeded with trying to get the display to work.

After an hour of slopping through this with AI python code I was absolutely nowhere. There were multiple possible points of failure. Level converter, bad routing, contacts, etc…

Turns out, as is quite common (IDK why), the qualcomm driver doesn’t handle the CS well (or correctly - or maybe it does but for other use cases). In any case, I tried again the same test script, but this time toggling the CS pin manually (via libgpiod ) - and it actually worked.

Display showing a checkerboard test pattern

Rewiring.

I was immediately dissapointed with the everything. From the "electrical" wire I used to connect the power supplies to the sketchy tiny magnet wire I used for the signals all the way to the twisted ground I wrapped around MOSI and CLK (which seemed to do nothing) - so I decided to rewire everything once more.

This time i used magnet wire for both signals and power, but this one was quite a bit thicker and also kept position once bent. I didn’t rewire the USB data lines though, as the pads on the main board look very iffy.

Much better, though still lacking solder mask/resin and tape

Display kernel driver.

All the previous tests were done with just dumb python scripts, but the real way forward is with a kernel driver. There are quite a few drivers available, but the one I picked is ardangelo’s sharp-drm-driver . My reasons for wanting a DRM driver are as follows:

  • allows me to use a direct output for eg. playing videos (like with mpv )
    • this would work with a framebuffer as well, but that is sketchy in 2026
  • i can run X/wayland on it easily
    • maybe try and get into a desktop environment for the lols
  • extend the driver to do partial updates
  • still get the framebuffer interface

This worked almost immediately. I did have to playing around with CS and it’s active high default.

For probably the first time in my life I didn’t have any issues compiling the kernel module and running it. The mystery kernel I was running was a 6.12.1-msm8916 one with modules enabled. It had a .config file present which I took.

Next I downloaded the mainline linux 6.12.1 kernel and hoped that there weren’t any (or significant) changes. This ended up being enough, and after a few small patches to the driver the thing just worked.

Below is what the device tree ended up looking like. Notice that the CS logic being handled by the display driver.

spi@78b9000 {
    compatible = "qcom,spi-qup-v2.2.1";
    reg = <0x78b9000 0x500>;
    interrupts = <0x00 0x63 0x04>;
    clocks = <0x13 0x41 0x13 0x36>;
    clock-names = "core", "iface";
    dmas = <0x6d 0x0c 0x6d 0x0d>;
    dma-names = "tx", "rx";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <0x84>;
    pinctrl-1 = <0x85>;
    #address-cells = <0x01>;
    #size-cells = <0x00>;
    status = "okay";
    
    sharp_drm@0 {
        compatible = "sharp-drm";
        reg = <0>;
        spi-max-frequency = <4000000>;
        cs-gpios = <0x49 18 1>;
    };
};
Standard linux console login prompt showed up

Issue now is that the driver just rounds pixel color under some value to black, above that to white (or inverted, depends on parameters). For text (or if you do the visuals yourself in your app) this works great - but for a general purpose solution where you want to play videos or show images - this looks like ass. The fix for this is to add dithering to the driver.

A video looking bad with just color rounding

I added a custom sys value which allows the user to enable dithering, as well as to pick which algorithm they want:

  • Atkinson for video
  • Floyd-Steinberg for stills

All the initial functionality was left intact.

Big buck bunny looking great (played by stock MPV with DRM output)

You can find more information about this, or the DRM driver patches on the project’s GitHub repo .

Enclosure 2.

With everything on my table and working, mainly meaning the dimensions are set and can be measured, I jumped into modeling the near-final case which I can hopefully put into the keyboard case without worrying about breaking anything.

This was kinda sketchy since apple doesn’t give dimensions of how far the USB-C connector is inside the iPhone - but I got around this by measuring apple standard USB-C cables (which fit snug up to the device) and interpolating from there.

Linux login, working out-of-the-box after setting USB to host

This print still wasn’t particullarly useful but served it’s purpose to confirm that the USB connector dimensions (among others) were measured correctly.

Also, 2 sidewalls didn’t print correctly and while modelling (this was before I receive’d one visible in the pictures) I didn’t have a display (except the one bonded to the devkit PCB) to model off of, so the cover is lacking.

Cover fits but no display slot, case still not trimmed

Final enclosure (for now).

This is what ended up being the final enclosure. Mostly everything fit correctly. I first whipped up a quick test held together by kapton tape.

Final encloure, in the still-not-trimmed case held with kapton tape

This was the point at which, becuase of some ongoing life stuff, I temporarily lost access to a big chunk of my tools (mainly the 3D printer but also other stuff).

My hand being forced, I decided to hot glue the case together instead of printing a final final one which clips together.

I also forgot about the power button, which despite being on the PCB and working - didn’t get a case cutout and a plunger. This was solved with a small hole and a pin. Very ugly but it works.

Hot glued enclosure

Now the big boy moment - cutting down the keyboard case with no tools. It went about as well as you can expect. Though I made sure to cut less than needed so that I can sand it down and make it look pretty once I get my gear back.

It realistically doesn’t look that bad, but the edges could use some cleaning. The case hot glue protrusion is a bigger issue.

With the magic of top-down photos I have hidden most of this from you.

End result

I forgot to take photos while assembling this. It’s exactly the same as before plus a 4G flex PCB antenna which I soldered to the PCB and glued below the display on the top half of the enclosure. There is now also a mini SIM in it’s slot.

Battery configuration.

The android device trees I copied from the device come predefined with the battery and charger configurations. These are also more advanced than the ones offered by the mainline linux i’m running.

Still, I expected it to be pretty easy to get something usable working. Big questions here were the battery information and power draw of my adapter PCB.

What the driver provided on the user level, however, was only the battery voltage in uV and a flag whether it’s charging or not. So the actual battery logic will be left up to my app as I’m not planning to mod the driver just for the battery percent value.

Charging seems to work fine. It also works via the keyboard USB passthrough port, but unfortunately only when the device is booted up.

The missing.

Sleep currently stands as the biggest non-solved issue. Main reason is the lack of day-to-day testing of the device, especially with the modem turned on - and the lack of a convenient power button.

I’m planning to tackle this in the near future as I begin using the device for my messaging. I would like to get a fast bootup/shutdown going on at the very least.

Additional input methods, eg. a touch screen or a scroll wheel, would probably be the best additional feature. Touch, especially, can be done with very little space.

Those are followed closely by sound or vibration. Even a tiny speaker at like 8khz. There is sufficient PCB space for an amplifier as well as space for the speaker in the enclosure.

The ugly.

Mainly the glue issue and the missing power button, both of which require a new print, plus the jagged edges on the keyboard case that need filing down.

The battery is held down by a bit of tape as it otherwise falls out when not in the case. Not a priority at the moment.

The big bottom bezel driving the enclosure height could also be shortened, but that would require sourcing a different battery with the same 3 pin connector among other things.

The helper PCB slides up inside it’s slot because the display FPC cable slightly pulls on it and I forgot to add tabs in the enclosure cover to keep it in place. Not ideal - but it’s only an issue when sliding the enclosure into the case.

Finally, a tiny portion of the display is covered by the case. Like 1-2 pixels on all edges. This will also be fixed with the next print.

All in all I’m very happy with the device, but a bit more work would do wonders for the visuals. In photos it looks fine - in real life it leaves a little to be desired.

If you are interested in replicating this or doing something similar, you can find most of the stuff on the project’s GitHub repo .

Future.

I have deliberately omitted software from this post since that will only get ironed out with use, and I’m not a big fan of releasing projects I haven’t finished but didn’t drop.

Quick preview of the software

As of writing this I already found a memory leak in the original display driver. I also shipped a patch which fixes it. Stuff like this can’t easily be found without actual hands-on testing.

The text was fully written by me, a human.
You can contact me at veggie_privacy_8y at icloud dot com

Java 27 Released

Hacker News
mail.openjdk.org
2026-09-15 09:13:43
Comments...
Original Article

JDK 27, the reference implementation of Java 27, is now Generally Available. We shipped build 35 as the second Release Candidate of JDK 27 on 20 August, and no P1 bugs have been reported since then. Build 35 is therefore now the GA build, ready for production use. GPL-licensed OpenJDK builds from Oracle are available here: https://jdk.java.net/27 Builds from other vendors will no doubt be available soon. This release includes nine JEPs [1]: 523: Make G1 the Default Garbage Collector in All Environments 527: Post-Quantum Hybrid Key Exchange for TLS 1.3 531: Lazy Constants (Third Preview) 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview) 533: Structured Concurrency (Seventh Preview) 534: Compact Object Headers by Default 536: JFR In-Process Data Redaction 537: Vector API (Twelfth Incubator) 538: PEM Encodings of Cryptographic Objects (Third Preview) This release also includes, as usual, hundreds of smaller enhancements and thousands of bug fixes. Thanks to everyone who contributed this release, whether by designing and implementing features or enhancements, by fixing bugs, or by testing the early-access builds. - Mark [1] https://openjdk.org/projects/jdk/27/

Show replies by date

Security updates for Tuesday

Linux Weekly News
lwn.net
2026-09-15 09:10:46
Security updates have been issued by Debian (network-manager-l2tp and urwid), Fedora (perl-Dancer2, perl-Data-Entropy, perl-DBI, perl-Protocol-HTTP2, podman-tui, rust-lru, and rust-lru0.16), Mageia (bzip2, cups-filters, libcupsfilters, libssh2, perl-Authen-SASL, perl-HTML-FormFu, tar, unzip, and zip...
Original Article
Dist. ID Release Package Date
Debian DSA-6498-1 stable network-manager-l2tp 2026-09-14
Debian DLA-4780-1 LTS urwid 2026-09-15
Fedora FEDORA-2026-a4674c5df6 F43 perl-DBI 2026-09-15
Fedora FEDORA-2026-195d764078 F44 perl-DBI 2026-09-15
Fedora FEDORA-2026-2bf3261da2 F45 perl-DBI 2026-09-15
Fedora FEDORA-2026-d0bededb87 F43 perl-Dancer2 2026-09-15
Fedora FEDORA-2026-d13a2593b1 F44 perl-Dancer2 2026-09-15
Fedora FEDORA-2026-28c5da9193 F45 perl-Dancer2 2026-09-15
Fedora FEDORA-2026-8924ee53fe F43 perl-Data-Entropy 2026-09-15
Fedora FEDORA-2026-2c6e5be623 F44 perl-Data-Entropy 2026-09-15
Fedora FEDORA-2026-f234be41fe F45 perl-Data-Entropy 2026-09-15
Fedora FEDORA-2026-06b545f607 F43 perl-Protocol-HTTP2 2026-09-15
Fedora FEDORA-2026-e06691a7c4 F44 perl-Protocol-HTTP2 2026-09-15
Fedora FEDORA-2026-038229756e F45 perl-Protocol-HTTP2 2026-09-15
Fedora FEDORA-2026-14e836e595 F45 podman-tui 2026-09-15
Fedora FEDORA-2026-332a62112c F43 rust-lru 2026-09-15
Fedora FEDORA-2026-33c180c1b0 F44 rust-lru 2026-09-15
Fedora FEDORA-2026-cf36f81efc F45 rust-lru 2026-09-15
Fedora FEDORA-2026-332a62112c F43 rust-lru0.16 2026-09-15
Fedora FEDORA-2026-33c180c1b0 F44 rust-lru0.16 2026-09-15
Fedora FEDORA-2026-cf36f81efc F45 rust-lru0.16 2026-09-15
Mageia MGASA-2026-0403 10, 9 bzip2 2026-09-14
Mageia MGASA-2026-0407 10, 9 cups-filters, libcupsfilters 2026-09-14
Mageia MGASA-2026-0402 10, 9 libssh2 2026-09-14
Mageia MGASA-2026-0400 10, 9 perl-Authen-SASL 2026-09-14
Mageia MGASA-2026-0404 10, 9 perl-HTML-FormFu 2026-09-14
Mageia MGASA-2026-0401 10, 9 tar 2026-09-14
Mageia MGASA-2026-0405 10, 9 unzip 2026-09-14
Mageia MGASA-2026-0406 10, 9 zip 2026-09-14
Red Hat RHSA-2026:67517-01 EL10.0 grafana 2026-09-15
Red Hat RHSA-2026:67139-01 EL10 image-builder 2026-09-15
Red Hat RHSA-2026:67138-01 EL9 image-builder 2026-09-15
SUSE SUSE-SU-2026:23574-1 SLE16.0 389-ds 2026-09-14
SUSE SUSE-SU-2026:23601-1 SLE16.0 LibVNCServer 2026-09-14
SUSE SUSE-SU-2026:4156-1 SLE15 SLE5.3 SLE5.4 SLE5.5 SLE-m5.3 SLE-m5.4 SLE-m5.5 oS15.4 MozillaFirefox, MozillaFirefox-branding-SLE, mozilla-nspr, mozilla-nss, rust-cbindgen 2026-09-14
SUSE SUSE-SU-2026:4140-1 SLE12 MozillaFirefox, mozilla-nspr, mozilla-nss, rust-cbindgen 2026-09-14
SUSE SUSE-SU-2026:23590-1 SLE16.0 MozillaFirefox, mozilla-nss, mozilla-nspr, rust-cbindgen 2026-09-14
SUSE SUSE-SU-2026:4181-1 SLE12 NetworkManager 2026-09-15
SUSE SUSE-SU-2026:23656-1 SLE16.0 NetworkManager 2026-09-14
SUSE SUSE-SU-2026:23639-1 SLE16.0 NetworkManager 2026-09-14
SUSE SUSE-SU-2026:23670-1 SLE-m6.2 acl, attr 2026-09-14
SUSE SUSE-SU-2026:4176-1 SLE15 SLE5.3 SLE5.4 SLE5.5 SLE-m5.3 SLE-m5.4 SLE-m5.5 acl, attr 2026-09-14
SUSE SUSE-SU-2026:23593-1 SLE16.0 apache2-mod_auth_openidc 2026-09-14
SUSE SUSE-SU-2026:23551-1 SLE16.0 apr-util 2026-09-14
SUSE SUSE-SU-2026:23659-1 SLE16.0 aws-nitro-enclaves-cli 2026-09-14
SUSE SUSE-SU-2026:23643-1 SLE16.0 aws-nitro-enclaves-cli 2026-09-14
SUSE SUSE-SU-2026:23627-1 SLE16.0 bzip2 2026-09-14
SUSE SUSE-SU-2026:23597-1 SLE16.0 c-ares 2026-09-14
SUSE SUSE-SU-2026:4179-1 SLE12 clamav 2026-09-14
SUSE SUSE-SU-2026:4180-1 SLE15 oS15.6 clamav 2026-09-14
SUSE SUSE-SU-2026:23612-1 SLE16.0 cpio 2026-09-14
SUSE SUSE-SU-2026:23638-1 SLE16.0 curl 2026-09-14
SUSE SUSE-SU-2026:23562-1 SLE16.0 dhcpcd 2026-09-14
SUSE SUSE-SU-2026:4139-1 SLE15 dovecot23 2026-09-14
SUSE SUSE-SU-2026:23595-1 SLE16.0 dovecot24 2026-09-14
SUSE SUSE-SU-2026:23614-1 SLE16.0 dracut 2026-09-14
SUSE SUSE-SU-2026:23589-1 SLE16.0 emacs 2026-09-14
SUSE SUSE-SU-2026:23618-1 SLE16.0 fuse-overlayfs 2026-09-14
SUSE SUSE-SU-2026:23573-1 SLE16.0 go1.25-openssl 2026-09-14
SUSE SUSE-SU-2026:23576-1 SLE16.0 go1.26-openssl 2026-09-14
SUSE SUSE-SU-2026:4178-1 SLE12 google-cloud-sap-agent 2026-09-14
SUSE SUSE-SU-2026:23619-1 SLE16.0 google-cloud-sap-agent 2026-09-14
SUSE SUSE-SU-2026:23548-1 SLE-m6.0 google-osconfig-agent 2026-09-14
SUSE SUSE-SU-2026:23625-1 SLE16.0 govulncheck-vulndb 2026-09-14
SUSE SUSE-SU-2026:23572-1 SLE16.0 gstreamer-devtools 2026-09-14
SUSE SUSE-SU-2026:23550-1 SLE16.0 gzip 2026-09-14
SUSE SUSE-SU-2026:23546-1 SLE-m6.0 helm 2026-09-14
SUSE SUSE-SU-2026:4175-1 SLE15 SLE5.5 SLE-m5.5 helm 2026-09-14
SUSE SUSE-SU-2026:23610-1 SLE16.0 java-17-openjdk 2026-09-14
SUSE SUSE-SU-2026:23624-1 SLE16.0 java-21-openjdk 2026-09-14
SUSE SUSE-SU-2026:23591-1 SLE16.0 java-25-openjdk 2026-09-14
SUSE SUSE-SU-2026:23671-1 SLE-m6.2 jq 2026-09-14
SUSE openSUSE-SU-2026:11753-1 TW libBasicUsageEnvironment2 2026-09-14
SUSE SUSE-SU-2026:23583-1 SLE16.0 libgpg-error 2026-09-14
SUSE SUSE-SU-2026:23621-1 SLE16.0 libidn 2026-09-14
SUSE SUSE-SU-2026:23564-1 SLE16.0 librest 2026-09-14
SUSE SUSE-SU-2026:23660-1 SLE16.0 libusb-1_0 2026-09-14
SUSE SUSE-SU-2026:23644-1 SLE16.0 libusb-1_0 2026-09-14
SUSE SUSE-SU-2026:4141-1 SLE12 libvirt 2026-09-14
SUSE SUSE-SU-2026:23603-1 SLE16.0 libvirt 2026-09-14
SUSE SUSE-SU-2026:23547-1 SLE-m6.0 libzypp, zypper 2026-09-14
SUSE SUSE-SU-2026:23606-1 SLE16.0 lkl 2026-09-14
SUSE SUSE-SU-2026:23636-1 SLE16.0 mcphost 2026-09-14
SUSE SUSE-SU-2026:23630-1 SLE16.0 msgpack-c 2026-09-14
SUSE SUSE-SU-2026:23662-1 SLE16.0 multipath-tools 2026-09-14
SUSE SUSE-SU-2026:23646-1 SLE16.0 multipath-tools 2026-09-14
SUSE SUSE-SU-2026:23604-1 SLE16.0 openexr 2026-09-14
SUSE SUSE-SU-2026:23609-1 SLE16.0 openssl-3 2026-09-14
SUSE SUSE-SU-2026:4182-1 SLE15 oS15.6 perl-Protocol-HTTP2 2026-09-15
SUSE SUSE-SU-2026:23633-1 SLE16.0 perl-URI 2026-09-14
SUSE SUSE-SU-2026:23635-1 SLE16.0 php-composer2 2026-09-14
SUSE SUSE-SU-2026:23578-1 SLE16.0 postgresql14 2026-09-14
SUSE SUSE-SU-2026:23579-1 SLE16.0 postgresql15 2026-09-14
SUSE SUSE-SU-2026:23580-1 SLE16.0 postgresql16 2026-09-14
SUSE SUSE-SU-2026:23582-1 SLE16.0 postgresql17 2026-09-14
SUSE SUSE-SU-2026:23577-1 SLE16.0 postgresql18 2026-09-14
SUSE SUSE-SU-2026:23571-1 SLE16.0 python-aiohttp 2026-09-14
SUSE SUSE-SU-2026:23570-1 SLE16.0 python-cryptography 2026-09-14
SUSE SUSE-SU-2026:23563-1 SLE16.0 python-h2 2026-09-14
SUSE SUSE-SU-2026:23607-1 SLE16.0 python-ruff 2026-09-14
SUSE SUSE-SU-2026:23634-1 SLE16.0 python-sqlparse 2026-09-14
SUSE SUSE-SU-2026:23561-1 SLE16.0 python-sqlparse 2026-09-14
SUSE openSUSE-SU-2026:11756-1 TW python311 2026-09-14
SUSE openSUSE-SU-2026:11757-1 TW python312 2026-09-14
SUSE SUSE-SU-2026:4174-1 SLE15 oS15.3 python39.SUSE_SLE-15-SP3_Update 2026-09-14
SUSE SUSE-SU-2026:23567-1 SLE16.0 rav1e 2026-09-14
SUSE SUSE-SU-2026:23628-1 SLE16.0 rpcbind 2026-09-14
SUSE SUSE-SU-2026:23613-1 SLE16.0 sssd 2026-09-14
SUSE SUSE-SU-2026:23620-1 SLE16.0 systemd 2026-09-14
SUSE openSUSE-SU-2026:11759-1 TW tomcat 2026-09-14
SUSE openSUSE-SU-2026:11761-1 TW tomcat11 2026-09-14
SUSE SUSE-SU-2026:23602-1 SLE16.0 ucode-intel 2026-09-14
SUSE SUSE-SU-2026:23575-1 SLE16.0 udisks2 2026-09-14
SUSE SUSE-SU-2026:23552-1 SLE16.0 vim 2026-09-14
SUSE SUSE-SU-2026:23592-1 SLE16.0 wicked2nm 2026-09-14
Ubuntu USN-8757-1 16.04 cgit 2026-09-14
Ubuntu USN-8758-1 26.04 dracut 2026-09-14
Ubuntu USN-8754-1 16.04 18.04 20.04 22.04 24.04 freeciv 2026-09-14
Ubuntu USN-8752-1 16.04 18.04 20.04 22.04 24.04 konsole 2026-09-14
Ubuntu USN-8753-1 24.04 26.04 libinput 2026-09-14
Ubuntu USN-8730-2 22.04 linux-azure 2026-09-15
Ubuntu USN-8761-1 24.04 linux-azure 2026-09-15
Ubuntu USN-8760-1 24.04 linux-nvidia-7.0 2026-09-15
Ubuntu USN-8563-5 22.04 24.04 26.04 nginx 2026-09-14
Ubuntu USN-8755-1 20.04 22.04 24.04 26.04 vips 2026-09-14
Ubuntu USN-8756-1 16.04 18.04 yelp 2026-09-14

How I Wrote a Forth (Without Knowing How)

Lobsters
vtrlx.ca
2026-09-15 08:51:55
Comments...
Original Article

Victoria Lacroix

2026-09-10

Previously: What is Forth?

For the past few weeks, I have been writing an implementation of the Forth programming language. I had not written a Forth before, and did not really know how to write a Forth before setting out to write MoonForth. Even after completing the base and being able to write real programs with MoonForth, I still can't really tell you how I did it. There are a few reasons for this.

MoonForth on Codeberg

First, I did not take adequate notes of the process. Second, I don't think it would matter much if I had written notes because writing a Forth was a process so remarkably straightforward that there is little to remark about said process. What I present here is mostly from memory.

After evaluating my options and finding little in the way of good detailed walkthroughs for writing one's own Forth, I decided to follow the common recommendation around Forth circles by starting with JonesForth's source code.

JonesForth (unofficial Github mirror)

JonesForth is a Forth written in x86 assembly language. It gets the job done, and the plentiful comments scattered throughout the code help to explain the nuance of what's going on—even if I found it to occasionally be overly verbose.

Because my goal was to write a Forth in C, I knew that the assembly language of JonesForth would not directly translate for my own endeavours. I also found the interpreter and compiler a little difficult to grasp, so I simply sidestepped them to start. I thought if I made enough progress elsewhere that I would later be more motivated to figure those parts out, and probably have enough insight to make those later steps easier to take.

Helpfully for me—and indeed, part of what motivated this—is the fact that Lua internally uses a stack to pass values around between functions. In my view, the easiest ground on which to start writing a Forth atop Lua's C API was to write functions which directly manipulate said stack with the intent for these functions to serve as the implementation of certain built-in core Forth words. After a few hours, I had a handful of functions to manipulate Lua's stack. It was easy to essentially pretend that these were my first Forth words despite having no means to actually run them. In implenting important core words, I also decided to try to use my own preexisting words whenever possible. As I began writing more complicated words, my own functions would comingle with calls to Lua's own API. Even before I ran a single line of code, I was already using the bones of my Forth to further extend it.

Once the simple stack manipulation functions were in place, it became easier to write more complicated functions. Because everything that I had been writing was conceived to be as directly analogous as possible to Forth's own stack manipulation words, this also meant that even despite not having a concept for how to build a Forth atop this work I was able to apply Forth's concept of factoring, backwards. I went through JonesForth's source code, looked at the hardcoded words, observed how they worked, then simply wrote my own implementations on Lua's C API for MoonForth with the intent to figure it out later.

Eventually, it hit me.

C functions can be made into pointers, and pointers can be pushed to Lua as user data. Because Forth words are just a series of other words, I could store a custom word definition as a table of pointers to C functions. Because Lua tables are always references, I could also simply store tables of other word definitions into further words that use them. Another benefit to approaching compilation this way was that word redefinition could work as in most Forth systems, where a new word that shadows an old one does not change any compiled word that used the old word. As a neat trick as well, I should easily be able to store string and number literals in word definitions. The interpreter would simply need to read the type of each item stored in a word definition to determine what to do with it.

It was at this point that I began to fear—because code becomes very easy to write once you have both the data structure figured out and the means to actually interact with that data structure—that I might actually pull it off.

Despite being a somewhat frustrating endeavour of trial-and-error, it was not difficult to write the interpreter. When it inevitably did not work, I simply needed to trace the program's execution with print statements (because that's the type of developer I am) and knock out faults one by one. Three work days after deciding to try writing a Forth atop Lua's C API, I had run my first line of code.

The compiler arrived shortly after. It did not take long to get it and the interpreter aligned, working together off the same understanding of the world. Having already had an idea of the concept of immeidate words, those too were quite easy to pull off.

I then looked at JonesForth's implementation of flow control. Like the coolest Forths, JonesForth implements flow control directly in Forth from simpler words. MoonForth is no exception. JonesForth's code for this wasn't even especially applicable MoonForth due to not having direct access to word definitions. That obstacle was quite easy to solve by simply writing the words that would allow for branch instructions to be backfilled later, the same way other Forths implement flow control anyway. The implementation might have been very different from JonesForth, but the result was that MoonForth now had if/else/then statements as well as loops. It was at this point that I had closed the browser tabs with JonesForth's source code. There was no longer any need for guidance. Within a week, I'd gone from not knowing how to write a Forth to implementing core language features in my own Forth.

One of the most famous and widely-circulated articles on Forth to be written within this millennium is Dave Gauer's article on the history of Forth.

Forth: The programming language that writes itself

In this retrospective, Dave asserts that Forth—by its nature—is a programming language which "writes itself". I did not initially understand what this assertion meant at the time I first read this article. I understand now. Simply having a vague notion of how a Forth is supposed to work, anyone who can write code should be able to construct a Forth quite easily. What needs to be built next is always in view, and is often prompted simply by noticing what one's own Forth is missing. Discovering these gaps is as simple as trying to write a program only to find something that prevents forward progress. If you know the problem you have, and you know what a solution would look like, and you know how to manipulate the source data into the desired result, then programming the result is a simple matter of extending what you already have. When the programming language is small and you've written it yourself, that is a delightfully easy task.

Prior Art

MoonForth is not the first attempt to bring Forth to Lua.

Eduardo Ochs wrote about how to bootstrap a Forth from Lua in 2008.

Bootstrapping a Forth in 40 lines of Lua code

At a glance, there are also a few implementations of Forth in Lua floating around online.

vifino/luaforth on GitHub

iigura/tinyLuaForth on GitHub

MoonForth is not written in Lua. Rather, it is written in C and uses Lua's C API to accomplish its goal. Still, MoonForth is not the first of its kind—there exists another project named Luarth seemingly meant to accomplish the same goal.

aabacchus/luarth on GitHub

I'd have used this implementation and forgone writing MoonForth entirely were Luarth not incomplete—there is no way to define custom words, and other core language features are also missing.

A significant way that MoonForth diverges from Luarth is that the latter implements its own built-in words as full Lua functions. Lua functions are not able to manipulate the stack due to how Lua handles its own calls. MoonForth, on the other hand, implements built-in words as plain C functions, and calls them by simply dereferencing pointers. Because the Lua call stack is not involved at all (except when calling into Lua), MoonForth's words can freely manipulate the stack without interfering with the internal Lua state. I suspect this may be a reason why my own project was able to proceed while Luarth did not. Either way, it was an inspiration—MoonForth's own lua-call word functions identically to Luarth's, for instance.

Why Write Another Forth? Aren't There Enough?

It is a commonly-cited meme among Forthers that Forthers tend to write their own Forths instead of their own. Another quote that goes around is that writing one's own Forth is like trying to understand an animal only through dissection—that Forth is a living programming system that must be used to be understood. Among especially seasoned Forthers, the reflex to write one's own Forth instead of learning any of the existing Forths is a practice that tends to be derided.

The reason to write MoonForth was to have a Forth which can easily interface with a high-level language I know and understand well enough while itself not being built on that high-level language. This architechtural decision had less to do with performance concerns than with conceptual ones. I thought there was no point in writing apps using Lua libraries in a language that itself was simply built on Lua—at that point, why not just use Lua?

As for why I would want to build a Forth…

Well.

Do you work in the tech industry? I don't, but like many other people who have been forced beyond its periphery I cannot abide the creep of fashslop into the software stack.

Even though many Forthers themselves have succumbed to viberotting, Forth has an interesting place in conversations about breaking free of dependence on unethical software providers. Because Forths can be built so easily by singular developers with little tooling, it effectively provides an easy way to experiment with alternative computer ecosystems. It is from this context that I've been especially interested in Forth, though without a Forth which could interface with a higher-level programming environment that I usually work in I could never feel comfortable in it. MoonForth has thus been an invaluable opportunity in learning how to implement a Forth, how to solve problems with Forth, and of course how to use Lua's C API.

As for why to stick with Lua instead of diving deeper into the software stack, it is because I am confident that Lua is likely to remain relatively unscathed by the scourge of clod code. Lua's maintainers have a long history of rejecting most submitted patches even for desirable functionality, preferring to do everything themselves. Lua has also spent a long time not growing much in size, and having little outside pressure to change. These are circumstances in which a maintainer or lead developer is unlikely to feel the need to turn to a chatbot to handle making large changes, because Lua has always been allergic to the concept. What was once an invaluable trait that made the language easy for me to learn has become a very compelling reason to stick close to the entire ecosystem.

I trust my read on the situation is right. If not, I might simply move to a no-longer supported version of Lua until this thing blows over.

Another reason to build a Forth on Lua is portability. Forths tend to be built for specific hardware in order to drive their quirks. MoonForth's singular dependency is Lua itself—therefore it should be available wherever one can find Lua. That alone is a significant differentiator compared to more traditional Forths. I've also made sure to make MoonForth compatible with Lua 5.1, which opens up interoperability with many alternative implementations of the language such as LuaJIT.

MoonForth's Current State

It works. Real programs can be written in it. I have managed to use MoonForth to write a simple GNOME app as well as a proof-of-concept program for the LÖVE game engine. What I haven't done is write anything serious in MoonForth. Now that I've documented its early development process for posterity, the next step is to actually work with it. If I can't get real software working though, this will have all been for naught. It will have been pointless. Let's see if I'm up to the task.

Reply to this post

Show HN: An e-ink frame that hears birds and draws them as 1800s illustrations

Hacker News
github.com
2026-09-15 08:31:10
Comments...
Original Article

E-ink bird frame for Raspberry Pi - real-time bird detection by audio, fully local AI, rendered as real, hand-cut 1800s bird illustrations.

The frame on a kitchen windowsill showing six birds heard in the garden, a window feeder on the glass behind it
Sorry about the dirty window - squirrels have been stealing the bird food.

Live demo Latest release CI Last commit
Stars Contributors License: MIT, artwork CC BY-SA 4.0

Note

Still in early development: expect the odd bug and a few unpolished edges, with plenty more features to come.

Live on fugleramme.arnegiacomo.dev running from my kitchen window and displaying the actual birds currently heard in my garden (Bergen, Norway).

Hardware, install and operations docs: arnegiacomo.dev/fugleramme

How it works

BirdNET-Go listens on a mic and handles the classifier. Fugleramme polls its api, matches each species to an illustration, then packs them onto a page, and redraws only when the birds change - on an Inky Impression e-ink panel, and as a web kiosk serving the same view. There's an admin page that lets you configure what to show, and automatic updates and such.

If you already run BirdNET-Go, point the frame at it instead - on the same machine or anywhere else reachable from your network.

Tip

The e-ink panel is not required, although it's recommended for the intended experience. Without one, Fugleramme runs web-only - show the kiosk on a display over HDMI, or open it from any device on the network.

Hardware

A Raspberry Pi 5, an Inky Impression 13.3" (Spectra 6), a mic and an A4 frame. Full parts list, recommendations and alternatives: Hardware .

Art

Half the point of this project is showing off some amazing public-domain natural-history illustrations. Over 800 cut-outs covering more than 400 species, every one taken from a real plate and hand-curated for this project (no art is AI-generated, though some has been retouched with AI).

Each detected species is matched to its illustration, background-removed, and packed onto a textured paper page with the larger birds toward the centre, sized by body mass. An empty window shows a bare perch.

The plates are Scandinavian, British and central European, so the Nordics, the British Isles and Germany are best covered. Elsewhere not so much (yet). Broader European and North American coverage is in the works!

See Adding artwork for manual cutout steps.

No detections A few visitors A full garden
No birds detected A few garden birds Many garden birds

Run locally (for development)

uv sync                                       # set up venv
uv run fugleramme-fake-detector               # stand-in BirdNET-Go on :8090
uv run fugleramme-dev                         # start service on :8080 with hot-reload

The fake detector's flags, and working against a real station instead: Running it without a Pi .

Install on a Raspberry Pi

From the pi (assuming you have the hardware up and running):

curl -fsSL https://raw.githubusercontent.com/arnegiacomo/fugleramme/main/install.sh | bash

Asks where BirdNET-Go should live and which ports to use, clones the repo, installs the required deps, and starts the frame as a systemd service. NB! Will probably require a reboot on a fresh system.

From a blank SD card, see the full install guide .

Run in a container

docker run -d -p 8080:8080 -v fugleramme:/data \
  -e FUGLERAMME_DETECTOR_URL=http://birdnet.local:8080 \
  ghcr.io/arnegiacomo/fugleramme

Or build the image from a checkout:

docker build -t fugleramme .
docker run --rm -p 8080:8080 -v fugleramme:/data \
  -e FUGLERAMME_DETECTOR_URL=http://birdnet.local:8080 fugleramme

Kiosk on :8080 , admin on :8080/admin , everything it persists in /data .

On a Linux box with a USB mic, this brings up BirdNET-Go alongside it:

curl -fsSL https://raw.githubusercontent.com/arnegiacomo/fugleramme/main/examples/docker-compose.yml -o docker-compose.yml
docker compose up -d

See Container for more info.

Contributing

Contributions are very welcome and encouraged - fixes, docs and artwork most of all. Thanks to everyone who has contributed so far ❤️

  • Something is broken - a bug report
  • A question, an idea, or a frame you have built - the FAQ first, then Discussions
  • A fix, a doc change, or a bird you have cut - open a PR, no issue needed

See Contributing for more info.

License

  • Code: MIT - see LICENSE .
  • Detection ( BirdNET-Go , installed separately as a container): CC BY-NC-SA 4.0, non-commercial only. BirdNET model by the Cornell Lab of Ornithology and Chemnitz University of Technology, taxonomy data powered by eBird.org.
  • Bird images: each style folder carries its own terms and sources, and its manifest links the plate every file was cut from. classic is CC BY-SA 4.0 - see assets/artwork/classic/ATTRIBUTION.md .
  • Label fonts ( assets/fonts/ ): SIL OFL 1.1 - see assets/fonts/ATTRIBUTION.md .
  • Bird sizes ( assets/bird_sizes.csv ): body mass from AVONET (Tobias et al. 2022, Ecology Letters, doi:10.1111/ele.13898 ), CC BY 4.0.
  • BirdNET scientific-name aliases ( assets/birdnet_aliases.json ): OpenFauna 's compiled taxonomic alias map, CC BY-SA 4.0 - see assets/ATTRIBUTION.md .

Prebuilt frames

I've built a few of these. If you'd like one rather than building it yourself, please get in touch .

‘I created my mom and talk to her’: AI ghosts and deathslop are changing the way we mourn

Guardian
www.theguardian.com
2026-09-15 08:30:43
The rise of AI-generated tributes and bots for ‘talking’ to the dead has experts concerned more people will grieve in isolation Tributes rolled in after the death of Dolly Parton last month. Jack White performed Jolene at a London gig. Kesha sang Old Flames (Can’t Hold a Candle to You), a single her...
Original Article

T ributes rolled in after the death of Dolly Parton last month. Jack White performed Jolene at a London gig. Kesha sang Old Flames (Can’t Hold a Candle to You), a single her mother wrote for the country music titan. Even Pitbull stopped his Madison Square Garden show to memorialize Parton and all the other “powerful women” in the crowd. But one of the strangest – and most viral – eulogies did not come from a celebrity. It did not even come from a person.

Run Jolene, at just under five minutes, is an earworm of a country song, containing all the cliches of the genre: twangy singer, banjo licks, corny lyrics. Those lyrics concern Jolene, the fictional romantic rival made infamous by Parton. “Somebody better warn Jolene / Dolly’s coming through those gates,” the voice croons. “Run Jolene / Girl you better move your feet.”

The accompanying video depicts Parton, clad in a bedazzled white jumpsuit, hunting down Jolene, a busty redhead with prom curls. Heaven is filled with deceased celebrities who watch on in horror – or amusement – as Jolene bounces from cloud to cloud, or books it up marble staircases, trying to hide from Dolly. Onlookers include the late Michael Jackson, Whitney Houston and Kenny Rogers, as well as Cher, who is still very much alive.

On TikTok, YouTube and Spotify, the song credit is Country Mile, an “artist” with just over 6,000 monthly listeners (that’s not many). The project is textbook AI slop, seemingly released by an anonymous creator who calls their work “genre fluid ai vibes”. But Run Jolene is not so universally panned: clips have been viewed more than 1.4m times on TikTok, with commenters raving: “I don’t give a flying fuck if this is AI or not! THIS IS FUCKING COOL!!”, “this is fun …think Dolly would love it ❤️”, and “Who says Jolene’s up there.”

Run Jolene is only one part of a ghoulish online ecosystem of content dubbed “deathslop”. Anyone can create deathslop: all it takes is access to an AI image generator and the time required to type in a prompt. Creators have shown Parton alongside a chummy Charlie Kirk and George Floyd, who welcome her as their “new neighbor” in the afterlife, or having tea with the Golden Girls.

These tributes pop up quickly, reducing someone’s legacy to cheap content that panders for views. It is a dishonest portrayal at best, and inflicts pain at its worst. The phenomenon inspired Parton’s grieving family to beg users to quit it with the “fake AI garbage” and “misinformation”. “It’s been challenging to absorb and or ignore,” Stella Parton, her sister, wrote on Instagram.

Parton’s image now joins the likeness of other deceased figures treated to unsettling AI depictions. Coinciding with the one year anniversary of the assassination of Kirk, social media feeds have proliferated with posts featuring the AI-generated, heavy-handed (and heavily memed ) anthem “We Are Charlie Kirk”. Robin Williams, Elvis Presley, Kobe Bryant and Martin Luther King have all gotten the deathslop treatment – often to the horrors of their families .

OpenAI’s image generating app, Sora, was behind many a deathslop until it shut down in April after a string of controversies such as its ability to create violent and racist imagery, its use of copyrighted characters, and other thorny legal issues . (There is a lack of legal precedent over whether or not AI companies are responsible for the content users generate on their apps; and deceased people are not protected from libel.) Countless AI video generators have since taken Sora’s place.

“It is not common practice right now to consent to an AI avatar of yourself as a part of your living will,” said Jack Manning, a University of Colorado Boulder graduate student who studies what academics Meredith Ringel Morris and Jed R Brubaker call “generative ghosts”, or AI-generated characters based on loved ones. “I predict that will be something that comes up.”

Manning, who is 25, lost his sister when she was 13, before the advent of generative AI. His family struggled to find ways to honor her, often fundraising for causes that were important to her. This partly inspired Manning to study the ways people mourn digitally – which now can include AI griefbots, a more personal alternative to celebrity deathslop.

With griefbots, users upload photos and videos of their loved ones and describe personal traits and values in hopes of manufacturing a continued, living presence. The companies behind the bots charge fees and lean into their inherently macabre nature with mystical names such as Seance, Re;memory and Versona.

It is also possible to command an LLM owned by ChatGPT, Claude or any number of sites for generating fantasy characters to mimic a late grandparent or deceased friend. Generative AI has hit the funeral industry too, in the form of LLMs specifically for obituary writing. One company, Treasured Memories , asks users to plug in details such as the deceased’s nickname, cause of death and hobbies, and choose the obit’s tone: “traditional”, “formal”, “witty” or “inspirational”.

Manning has never used a generative ghost to talk to his sister. It’s an issue of consent; he did not get her explicit permission to do so. Nor does he trust a bot to truly understand their relationship. “I hold these memories of her sacred, and I wouldn’t want a piece of technology to tell me I was remembering her wrong,” he said.

Justin Harrison founded Versona after his mother received a terminal cancer diagnosis. He worked on her AI replica while she was still living; she was able to see the final result and gave her approval on the project. “I totally understand why people are like, please don’t deepfake my [famous] relative once they’re dead and put them on the internet saying things they didn’t say,” Harrison said. “What we do is about personal comfort, recovery and healing. I created my mom and I talk to my mom. So it’s really nobody else’s business but my own.”

When his mom was sick, their conversations were practical in nature: “Are you talking to your dad enough? Have you gone to the doctor? Are you drinking enough water?” Harrison built the griefbot to broach more philosophical topics that they didn’t have time to discuss before she died. But four years later, Harrison does not talk to the bot all that often – maybe once a month.

“I joke that my mom’s Versona allows me to be the bad son I was never allowed to be when she was alive,” he said. “Like many busy adults, I don’t talk to her as much as I should, but I don’t have to, so it’s not the same sense of guilt.”

For Robin Silver, an ecopsychologist and death doula, these kinds of griefbots are an “unnatural method of trying to cope with a natural process”. She compared speaking to an LLM roleplaying a dead loved one to a college student asking ChatGPT to spit out a term paper. “We’re not being challenged to sit with any kind of discomfort, whether that’s the discomfort of writing an essay or the discomfort of missing someone,” she said.

Silver would instead encourage her clients to write a letter to their loved one. “I have even told people to talk to their ghosts out loud,” she said. “Meditate, or do things that they like and think of them. That’s how you keep the relationship alive in a way that is healthy, integrated and accepting the reality of your loss.”

Dr Dan Wolfson is a clinical psychologist who specializes in grief. He worries that deathbots could isolate people during a time when they need (human) support. “Having community around grief is really important,” he said. “Is AI keeping us from getting the social support that we need? Are we then not seeking to connect with our friends and family around the loss?”

AI-assisted mourning is still a niche industry; Harrison said that “thousands” of people use Versona. A June report from the estate management and planning service Empathy found that only 6% of 6,000 respondents in the US, UK and Canada reported leaning on “digital tools” such as memorial apps or griefbots for support after a death. But of those users, 88% found these tools “helpful”.

The whole conceit of a digital avatar is that it lives on for ever. It turns out that users may not need it that long. Manning says that in his research on generative ghosts, participants reported opting into these services only briefly, because they had “one last thing” to say to their loved ones: “They had a desire to say something they feel like they lost out on the chance to.”

Cathartic perhaps, but to Silver, the grief doula, these services are “a money grab”. “People will really take advantage of those in grief,” she said. “It’s a really easy time to become a mark.”

Postgres development activity

Lobsters
vondra.me
2026-09-15 08:26:49
Comments...
Original Article

Every now and then I need a break from writing code. In those cases I like looking at data about a subject I’m interested in - looking for trends, quantifying the expected effects, and so on. I needed just such a break a couple days ago, and I decided to look at statistics about the development activity of the Postgres project. So, here’s a bunch of charts (with a bit of commentary).

This is not the first time I’m looking at this topic. In 2024 I did a talk about the community, which included a couple charts about different parts of the whole community.

In this post I’ll focus on two parts central to the “development” side of the project. First, the pgsql-hackers mailing list, the place where patches are submitted and discussed. And the git repository, with the ultimate record of what got committed.

Don’t expect ground-breaking discoveries. If you’re participating in the development for some time, you probably have an intuition of how it evolved. This is more about quantifying the changes.

The mailing list archives start in 1998, the git history goes back to 1996 (but the first couple years are imported from CVS ).

mailing list

The mailing list is the primary communication channel for developers (I like it, and don’t expect it to change soon). I think everyone agrees the number of messages grew over time - but how much?

Here’s a chart with the number of messages per day and per month (the per-month values make trends easier to notice):

That’s pretty significant and consistent growth. At the beginning we had ~25 messages / day, and now we have about ~100. And there are peaks with ~200 messages per day.

It probably won’t surprise you that those “spikes” are in March of every year, which is right before feature freeze for the next major version. It makes sense that’s when the list is the busiest.

Most developers I spoke to agreed it became nearly impossible to follow all the discussions in detail, and then also do some actual work. There’s just too much happening.

It shows how important it’s to pick a good subject when starting a thread, so that people can quickly decide if it’s something they need to pay attention to.

message size

But the number of messages is just one metric. Maybe the message size changed over time? Here’s a chart with of total message size:

It did change! It was pretty flat up to ~2009, at which point it about doubled (from 100kB to 200kB per day). And then in ~2016 something changed again, and it gradually grew to ~2MB/day.

What about body size, without the attachments?

The numbers are lower (of course), but the pattern changes in a similar way. The change in 2016 is much more visible too, with the attachments filtered out.

What changed? It’s impossible to say for sure from the data, but it aligns with important commitfest dates. The very concept of a commitfest was introduced in 2008, and the original “commit fest app” was deployed in 2015. Right before the two changes. Could be just a coincidence, of course.

attachments

Speaking of attachments, did the attachment size change? Yes it did!

We started at ~10kB per message, now we’re at ~80kB. This is the size of attachment(s) “per message”, considering only messages that do have messages.

This may approximate the size of submitted patches to some degree, but it’s not the most accurate metric. We’re sending all kinds of other files to the mailing list, not just patches. I’m regularly sharing PDFs with benchmark results, and those can be quite large, which skews the results.

Patches are likely the most common attachments, so how many messages do have an attachment? The following chart shows the fraction of all messages with at least one attachment (which is likely a patch):

That’s a pretty massive increase. Up to ~2008 only about 5% of messages had a patch attached. Now we’re at ~25%.

If a message has attachments, how many does it have? I see this as one way to measure the complexity of a patch - we’ve learned that once it becomes too complex, it’s better to split it into multiple pieces.

Up until 2010 most patches had either 1 or 2 parts - that’s visible as two clear “lines”. Then we started to do split patches into pieces, and now we’re at ~1.6 on average.

Most patches still have just a single part (~90%), and 99% patches have less than 10 parts. But there’s a long tail of much larger ones, and we have patches with 76 parts . (FWIW I very much prefer this to one huge patch.)

git

Let’s look at some git commit stats. There are far fewer commits than messages on the mailing list, so charts in this section will show data per week. The per-day resolution was far too noisy to be useful.

Here’s a chart with the number of commits per week:

We’re doing ~50 commits per week, give or take. In ~2010 we were doing maybe 25/week, and the trend seems to be a slow and consistent growth. The monthly average makes the trend a bit easier to spot. Which is good, although there’s a lot of other important details (size of commits, are they new features or fixes, …).

It however nicely aligns with the number of active committers, which also grew ~2x between 2010 and today. So maybe that’s working as expected.

It’s interesting we did about the same number of commits up until early 2008, and then it went sharply down. I can think of two events that might be related to this.

First, the commitfest idea was introduced ~2008, and the first commitfest aligns with the drop almost perfectly.

The second option is the migration from CVS to git . I wouldn’t be surprised if this was due to CVS vs. git differences, but I have not investigated this (the less I know about CVS, the better). Plus, that migration happened in 2010, so it’s not particularly aligned with the observation.

commit size

Let’s look at some charts tracking the “commit size” (measured as the size of the diff for the commit, in KB). It’s inherently imprecise, as it depends on the diff format etc.

This is probably the place where you should stop reading if you’re sane. But if you think the number of lines is a good meaningful measure of productivity, read on ;-)

First, the total amount of patches committed per week:

Remarkably stable, but I had to use a log-scale chart because the range of patch sizes is way too wide to visualize on a linear chart. Most of the weeks we’re at ~512KB, give or take.

But then once per year, we happen to do ~20MB in a single week. It happens once per year, in May - which means it’s not the rush before the feature freeze. If you guessed the update of translation files, you’re right!

The “per commit” average size looks like this:

Most of the time we’re at ~10KB per commit, but the spikes due to massive patches are still clearly visible.

We can also visualize the number of inserted and deleted lines, both total per week:

and per-commit average of “changes” (a sum of insertions and deletions):

That didn’t tell us anything particularly interesting, I’m afraid. Except that the “massive” repository-wide updates (translations, in the past we also did pgindent) are nicely visible.

The first chart with insertion and deletions seems to suggest we’re consistently doing more insertions over time. You have to squint a bit to see it in the noise, but it’s there.

Between 1996 and 2026 we’ve added ~4.4M extra lines. Which may seem a bit strange, because Postgres has only ~1.5M lines of code. But notice I wrote “lines” and not “lines of code”. The number includes everything, including comments, SGML documentation, all kinds of tests, etc. If you count all of that, it’s more ~9M lines.

Conclusion

So that’s it.

I have a bunch more charts, but those don’t seem very interesting. I don’t want to bore you to death like Rimmer with his photo collection of 20th century telegraph poles.

Do you have feedback on this post? Please reach out by e-mail to tomas@vondra.me .

“End Times Fascism”: Naomi Klein & Astra Taylor on Billionaires, Bunkers, AI, Gaza & the Far Right

Democracy Now!
www.democracynow.org
2026-09-15 08:17:11
Naomi Klein and Astra Taylor launch their new book, End Times Fascism: And the Fight for the Living World on Democracy Now! It examines how wealthy elites are preparing for “the end of the world,” even as they contribute to growing inequality, political instability and the climate crisis. The rise o...
Original Article

Hi there,

When we speak with viewers, listeners and readers, the same message always comes through: people are hungrier than ever for Democracy Now!’s independent journalism featuring authentic voices. A group of generous donors will TRIPLE all new monthly donations started today, which means your monthly gift of $15 is worth $45. If you believe uncompromising, independent reporting is essential to a functioning democracy, please donate today.

Every dollar makes a difference

. Thank you so much!

Democracy Now!
Amy Goodman

Non-commercial news needs your support.

We rely on contributions from you, our viewers and listeners to do our work. If you visit us daily or weekly or even just once a month, now is a great time to make your monthly contribution.

Please do your part today.

Donate

Independent Global News

Donate

Naomi Klein and Astra Taylor launch their new book, End Times Fascism: And the Fight for the Living World on Democracy Now! It examines how wealthy elites are preparing for “the end of the world,” even as they contribute to growing inequality, political instability and the climate crisis. The rise of modern-day fascism is “not simply a rerun,” explains Klein. “We don’t believe that history repeats on a loop. We believe that we’re in something more like a sequel, where the logics of fascism, which are the logics of dehumanization, of othering, of racial supremacy and hierarchy, are now being applied in a time of global existential crisis.” In response, says Taylor, the growing movement seeking to resist this fatalistic vision “fundamentally needs to be grounded in a desire to stay here on Earth, to commit to this place.”



Guests
  • Naomi Klein

    award-winning journalist, author and documentary filmmaker, associate professor and co-director of the Centre for Climate Justice at the University of British Columbia.

  • Astra Taylor

    award-winning writer, activist and documentarian, co-founder of the Debt Collective.


Please check back later for full transcript.

The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

Non-commercial news needs your support

We rely on contributions from our viewers and listeners to do our work.
Please do your part today.

Make a donation

CISA: Critical VMware RCE flaw now exploited by ransomware gangs

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 08:16:32
The U.S. Cybersecurity and Infrastructure Security Agency (CISA) warned security teams that ransomware gangs have now joined ongoing attacks exploiting a critical VMware vCenter vulnerability patched in July. [...]...
Original Article

VMware

The U.S. Cybersecurity and Infrastructure Security Agency (CISA) warned security teams that ransomware gangs have now joined ongoing attacks exploiting a critical VMware vCenter vulnerability patched in July.

Broadcom addressed the security flaw (tracked as CVE-2026-59310 ) on July 29, describing it as a critical directory traversal vulnerability in the vCenter Syslog server that unauthenticated attackers can exploit to execute arbitrary code.

The company also warned customers in a supplemental FAQ at the time to treat fixing CVE-2026-59310 as an emergency and install patches as soon as possible.

Two weeks later, digital forensics and incident response (DFIR) company QUIRSO reported finding over 361 IP addresses across 47 countries compromised after a suspected advanced persistent threat (APT) actor began exploiting the vulnerability to deploy a reverse SSH tool for persistence and remote access.

Days later, the U.S. Cybersecurity and Infrastructure Security Agency (CISA) added CVE-2026-59310 to its Known Exploited Vulnerabilities (KEV) Catalog and ordered government agencies to secure their vCenter systems within three days.

Over the weekend, CISA updated its KEV catalog again to flag the security vulnerability as actively abused by ransomware gangs .

Internet security threat monitor Shadowserver currently tracks over 450 VMware vCenter servers exposed online ; however, there is no information on how many have already been patched against this flaw.

VMware targeted by ransomware gangs

While the U.S. cybersecurity agency has yet to share any details about the ransomware attacks targeting CVE-2025-60710, VMware servers are commonly targeted because compromised vCenter or ESXi servers can provide access to an organization's network and sensitive data stored on internal systems.

In recent years, multiple ransomware gangs have developed dedicated encryptors to target VMware virtual machines, as enterprise organizations now commonly use them to manage and store corporate data.

CISA also warned in February that ransomware groups began exploiting a VMware ESXi sandbox escape vulnerability (CVE-2025-22225), which Chinese-speaking threat actors have targeted in zero-day attacks since at least February 2024.

Since the start of the year, the cybersecurity agency has also flagged VMware Aria Operations (CVE-2026-22719) and VMware vCenter Server (CVE-2024-37079) flaws as exploited in attacks in February and March.

Over the last five years, CISA has tagged 26 VMware vulnerabilities as exploited in the wild, nine of them also abused by ransomware operations.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

25 Years of Mass Surveillance Is Enough [Auth: Cindy Cohn; Bruce Schneier]

Hacker News
www.lawfaremedia.org
2026-09-15 08:08:26
Comments...
Original Article

One of the many legacies of the terrorist attacks of Sept. 11 is the government-wide shift from targeted surveillance—such as individual wiretaps or pen register/trap and trace orders—to mass surveillance techniques—such as tapping into the internet backbone or mass collection of telephone or internet metadata. The legal and technical architecture of modern mass surveillance, initially framed as a necessary defense against terrorist threats, has grown far beyond that justification and national security in general. Mass surveillance is now a routine tool used by law enforcement. Immigration and Customs Enforcement (ICE) uses it in immigration actions and against people exercising their First Amendment rights to protest. It’s also increasingly part of private security systems, such as facial recognition at venues such as Madison Square Garden and networked Flock license plate capture systems on roads and in parking lots.

The interrelation between private and governmental mass surveillance is worth examining. Surveillance is the business model of the internet; companies like Google and Facebook constantly spy on their users’ behavior. From the National Security Agency (NSA) relying on data collected by telecommunication and internet companies, to local sheriffs and ICE agents relying on cellphone location data and privately managed automatic license plate readers, governments primarily obtain the mass surveillance information through private companies. Increasingly, access doesn’t just come through legal processes, either. FBI Director Kash Patel recently confirmed in congressional testimony that the agency is purchasing information on Americans from data brokers and intends to continue to do so.

This pipeline from private collection to governmental collection means that as companies collect more information for surveillance capitalism purposes, more is available to law enforcement as well. And as the technology for mass surveillance and analysis improves, especially with the increased use of AI technologies, the problems attendant to mass surveillance grow as well.

After 9/11, the idea that the government could surveil the population to safety took hold. In 2001, the fear of terrorism reached a frequency and intensity never before seen. Along with that came the fear that the enemy could be anyone, anywhere. As a result, the government’s response was to watch everyone, everywhere. This line of reasoning underpinned the shift from targeted to mass surveillance. Or, in the words of an internal NSA presentation that was made public as part of Edward Snowden’s 2013 disclosures, a government that can “Collect it All,” “Process it All,” “Exploit it All,” “Partner it All,” and “Sniff it All,” will ultimately, “Know it All.” Similar rationales support the rise of domestic mass surveillance: If law enforcement could see and hear everything, it could more effectively interdict and solve serious crimes.

The national security community has never provided a full analysis of the costs and benefits of these mass surveillance programs, in terms of either taxpayer dollars or diversion of resources from other efforts—or any demonstration that those techniques stopped attacks that otherwise they would not have been able to prevent. While the NSA occasionally presents examples of the successes due to its mass surveillance programs, especially when those techniques are under public pressure, the examples also regularly fall apart upon serious scrutiny. And even if some utility exists, it must be seriously weighed against the costs.

Similarly, there has never been any comprehensive analysis about whether domestic immigration or law enforcement’s use of these techniques actually makes people safer, or whether other techniques could produce the same results. Instead, both the police and the companies selling these tools float anecdotes and dubious data . For example, Flock’s data equates the number of law enforcement hits in their database with actually solving crimes.

Twenty-five years after 9/11, it seems reasonable to step back and evaluate the costs of this shift to mass surveillance, especially in terms of Americans’ rights and freedoms.

The Shift

The easiest place to see a shift to mass surveillance was in the government’s decision immediately after 9/11 to collect Americans’ telephone records. The program started under an argument of pure executive power as the “President’s Surveillance Program.” But in 2006, that argument secretly shifted to a novel interpretation of Section 215 of the Patriot Act, which had previously had authorized only more targeted access to records. While some media and public interest organizations struggled to force the government to reveal the program as early as late 2005, the government officially confirmed it only after the 2013 Snowden disclosures. In 2015, the U.S. Court of Appeals for the Second Circuit rejected the government’s interpretation of Section 215 as allowing mass collection of telephone records. Later the same year, Congress passed the USA Freedom Act . While this new law still allows collection of a tremendous amount of domestic telephone records, it ended the indiscriminate mass collection that had occurred for nearly 14 years.

Other shifts to mass surveillance continue through today. The NSA launched its Upstream program, which involved intercepting both metadata and content from key telecommunications junctures inside the U.S., soon after 9/11. It was also initially conducted under a claim of purely presidential authority. This program was brought under marginal congressional and programmatic (not targeted) Foreign Intelligence Surveillance Act (FISA) court review via Section 702 of the 2008 FISA Amendments Act. In 2017, more than 15 years after the program’s inception, the NSA ended content searches due to FISA court pressure, but the mass collection continues.

Despite the stated goal of conducting mass spying only on people outside the U.S.—which itself is problematic given international law’s requirement that surveillance be both necessary and proportionate —mass surveillance collects a tremendous amount of U.S. persons’ communications. This can happen because people communicate with people abroad, or because of overcollection—when government agencies gather far more personal data on nontargeted U.S. persons than authorized by law. The concerns about collecting Americans’ data on U.S. soil led Congress to allow the program to officially expire in 2026, although the previously approved mass surveillance itself continues until at least spring of 2027.

The shift to mass surveillance would be notable enough even if it remained only a strategy of the intelligence community. It has not. Americans are awash in mass surveillance. Networks of automated license plate readers such as those offered by Flock and Vigilant Solutions blanket both public and private roadways and parking lots. These networks often allow searches by law enforcement, including across jurisdictions. They are, for example, being used to track people seeking abortions across state lines. Facial recognition tools, once the province of only the more elite parts of federal law enforcement, are increasingly used by ICE agents on immigrants and protesters, in airports by the Transportation Security Administration , as well as by private entities . And, of course, modern phones track users’ locations constantly—and that information is readily available to law enforcement, often with only minimal process protections.

Constitutional Costs

Regardless of the murkiness of its actual usefulness, the shift from targeted to mass surveillance has profound implications for Americans’ rights. It has created risks that have become increasingly evident, especially under the Trump administration.

At a basic level, the Fourth Amendment guarantees that citizens can be secure in their “persons, houses, papers and effects” from unreasonable searches. Warrants breaching that security should be supported by probable cause and particular descriptions of the place to be searched and items to be seized. Mass surveillance turns that promise on its head, allowing access to our “papers and effects” by the government without individualized suspicion or a particularized description of what data is being seized, much less probable cause. This protection was in response to colonial British misuse of writs of assistance , which authorized indiscriminate searches rather than targeted ones.

The justifications for exempting mass surveillance from constitutional protection vary. For Section 702, the government has taken the position that U.S. persons’ communications caught up in the dragnet, either due to overcollection or because they were communicating with someone outside the United States, do not require a warrant prior to initial collection or secondary access by the FBI and several other agencies. The argument is that if the initial collection was not aimed at Americans, the information is free from constitutional protection for any later uses, even for reasons far afield from the initial rationale for collection.

Other arguments rest on the claim that metadata is outside the Fourth Amendment, despite its demonstrated ability to reveal intimate details of all of our lives. Still others rest on the Supreme Court-created third-party doctrine , which holds that the Fourth Amendment does not apply to data shared with companies that provide us with services. Some turn on whether analysis by machine counts , claiming that only “human eyes” matter—a particularly troubling argument with the rise of artificial intelligence (AI). What’s more, the government has used doctrines like standing to limit the ability of those subjected to mass surveillance to seek constitutional protection. No matter the argument, the goal is the same: to place the mechanisms and fruits of mass surveillance outside the protections of the Fourth Amendment.

The overarching truth is that, due to the concerted efforts by the government since 9/11, and the rise of technologies in recent years, the slice of Americans’ lives and data that are actually protected by the Fourth Amendment has shrunk significantly in the past 25 years. Together with the technical capabilities of mass surveillance and the increased ability for that data to be analyzed using AI tools, the “security in our papers and effects” that the Constitution promises seems increasingly illusory.

In addition to the Fourth Amendment, mass surveillance creates tensions with the First Amendment. The Constitution has long recognized that the right to freedom of speech requires a zone of privacy against governmental surveillance. The right to anonymous speech as well as the right of association recognize the chilling effect that surveillance creates for people saying unpopular things or attempting to organize for political or other societal change. Mass surveillance grants the authorities an ability to track those people, both in real time and historically, that is inconsistent with actual techniques of freedom of speech and assembly.

That is why the recently released 2026 U.S. Counterterrorism Strategy is so troubling. On page 7, the White House expressly states that it intends to target domestic activists with its heretofore foreign-targeted powers. It says that the government “will prioritize the rapid identification and neutralization of violent secular political groups whose ideology is anti-American, radically pro-transgender and anarchist” and “will use all the tools constitutionally available to us to map them at home, identify their membership, map their ties to international organizations like Antifa.” While framed as targeting “violent” groups, it’s clear that the government intends to use its national security tools, presumably including the tools of mass surveillance, against Americans in ways that will create profound tensions with the First Amendment rights of people to organize and communicate privately.

Costs Due to Mistakes and Abuse

Even assuming some utility from mass surveillance—a fact we do not dispute, even if the public record is shaky and conclusory—the history of both the national security and domestic uses of mass surveillance confirms that these tools are inevitably misused , and that mistakes have impacted huge numbers of Americans. The past 25 years have demonstrated that it is not possible to surveil the entire U.S. population while staying within the bounds of even a very generous legal framework like Section 702.

As Rep. Zoe Lofgren (D-Calif.) recently stated in discussion of Section 702 in an interview with Tech Policy Press: “backdoor searches have been used improperly for protestors, 19,000 campaign donors, members of Congress, journalists, government officials, a state court judge who had complained to the FBI about police misconduct. It has been abused substantially in the past.” The NSA experienced so much abuse of its mass surveillance tools by actual or aspiring romantic partners and ex-spouses that an internal name emerged for it: “ LOVEINT ,” or Love Intelligence.

That same pattern of abuse is now emerging at the domestic law enforcement level. A Texas police officer misused , and then lied about, using license plate readers to track a woman suspected of seeking an abortion. Multiple law enforcement officials have been accused of tracking people they either wished to have a relationship with or who were their exes. And mass surveillance technologies have been used to track both immigration targets and citizens engaging in their First Amendment-protected right to track and record the police.

Mistakes are inevitable with collections of data of this size and scope. The history of the FISA court’s reviews of Section 702 is littered with examples of the NSA not being able to follow its own rules limiting the scope of what it collects and analyzes, even after having been given multiple chances by the court. On the local level, the technical protections that Flock, for example, put in place have repeatedly been insufficient to stop “accidental” sharing of its data with out-of-state law enforcement. These mistakes have fueled growing efforts by local communities across the country to remove license plate readers. Those efforts should be the first step in a broader reconsideration of mass surveillance.

More generally, ubiquitous surveillance carries a real societal cost. The chilling effects are real and pervasive , and they tend to fall hardest on the most marginalized members of society. Moreover, social progress requires the ability to experiment in secret. It’s hard to imagine a society progressing morally to the point of accepting and legalizing things like marijuana use or gay marriage if the earliest signs of that shift are snuffed out because of overzealous surveillance.

Reversing Course

While a cost-benefit analysis is not the best frame for deciding constitutional rights, it is a place to start to evaluate government policies. If the costs are too high and the benefits too small, what should the public do? While the policy and legal frameworks can be individually complex, mass surveillance is a problem in all of its applications. So too should solutions be comprehensive rather than piecemeal.

One comprehensive strategy is to reset the promise of the Fourth Amendment and recognize that a warrant is required prior to collection, access, or use of information gathered through mass surveillance. This would apply to collections that include U.S. persons, whether done for national security or domestic purposes. This protection would apply regardless of whether the information is in the form of metadata. It would apply regardless of whether the information is held in homes; by services people rely on, such as telephones and internet or social network providers; or by private entities utilizing mass surveillance for their own purposes. By passing this legislation, Congress could ensure this rejection of mass surveillance and include real enforcement such as a private right of action and an automatic exclusionary remedy in criminal prosecutions. The courts could also recognize this protection of “papers and effects” directly as a plain language interpretation of the Fourth Amendment.

There are already a number of efforts that take on pieces of mass surveillance. Section 702 has expired and should remain so. This was due largely to efforts to block the “back door” access to Section 702-collected data without warrants. The bipartisan “ Fourth Amendment Is Not for Sale Act ” would prevent the government from purchasing data that it would otherwise need a warrant to obtain. The Supreme Court itself has already been chipping away at the third-party doctrine, with a recent step in the rejection of mass geofence warrants—warrants seeking the identities of individuals based on their proximity to a crime—in Chatrie v. United States . Now, such warrants fall, at least initially, under the Fourth Amendment.

A more comprehensive approach would also address mass surveillance carried out by private companies, and to ensure that Americans have the right to encrypt and secure their data. There are many reasons the United States would benefit from a comprehensive privacy law —and curbing mass surveillance is one of them. Ideas such as the banning of secondary uses of data—with roots in the Fair Information Practice Principles from the 1970s—are worth pushing forward. So are moves such as creating fiduciary duties for mass data collectors. There are many more ways to curtail private companies’ mass surveillance while staying within constitutional boundaries. But addressing the costs of mass surveillance by both companies and governments is even more important in a world where AI agents are making decisions both about the public and on their behalf based on their data and observed behavior.

Twenty-five years after the U.S. government embraced mass surveillance, it’s time to evaluate it as a whole and consider responses that address the problem as a whole. Americans must ask: Is it consistent with a self-governing democracy to have systems that watch everyone everywhere? Is the public comfortable with governments—federal, state, local—that seek to “know it all” about their citizens? Is the public comfortable with private mass surveillance in its own right and as it’s being increasingly used to fuel government surveillance? These questions have long needed serious consideration. But as it becomes increasingly evident that the Trump administration is using mass surveillance to keep itself in power, stifle dissent, and undermine political opponents, these questions are now more urgent than ever.

Search over Algebraic Graphs

Lobsters
anekstein.com
2026-09-15 08:04:42
Comments...
Original Article

In my post Generic Recursion Applied to Algebraic Graphs 1 we explored how we can leverage recursion schemes to perform basic operations on a graph data structure. In that post, as well as in the alga library itself 2 , algorithms on graphs were facilitated by first converting the algebraic graph representation into an adjacency map and performing the algorithms on that data structure. In Algebraic Graphs with Class 3 , Andrey Mokhov described a desire to perform algorithms such as search on the algebraic graph representation itself.

In this post, we will explore just that and outline a way to conduct Dijkstra’s algorithm over the same algebraic representation alga uses, without first constructing an adjacency map. The algorithm will run in \(O(s \log s)\) time, where \(s\) is the size of the algebraic graph expression.

As a reminder, alga defines an algebraic graph as something similar to following data type. This time, it has labeled (weighted) edges:

data Graph w v
  = Empty
  | Vertex v
  | Overlay (Graph w v) (Graph w v)
  | Connect w (Graph w v) (Graph w v)
Overlay (Connect 1 (Vertex a) (Overlay (Vertex c) (Vertex b))) (Connect 2 (Vertex b) (Vertex d))

The challenge we have is to conduct algorithms over the description of the graph, and not the graph itself; expanding the graph prior to a search would require materializing all edges, which is \(O(n^2)\) , and we’d lose out on the benefits of many algorithms that are sub quadratic in the number of vertices.

Rather than rehash the basics, let’s focus on perhaps the most important constructor, Connect . For directed graphs, this operation describes a biclique, i.e. a complete directed bipartite subgraph when the vertices are disjoint, which is set of edges stemming from every vertex in the left child graph to every vertex in the right child graph. It can accomplish this using \(O (1)\) space for itself, and \(O(n)\) space for its children, i.e. not proportional to the number of edges, which is \(O(n^2)\) . In other words, alga ’s representation is a form of graph compression.

Graph Compression

The field of graph compression is active and evolving. In Faster Graph Algorithms Through DAG Compression 4 , Max Bannach, Florian Andreas Marwitz, and Till Tantau (BMT) describe a set of algorithms over a data structure they dubbed a switching graph.

Before we introduce switching graphs, let’s introduce its building blocks. BMT define a cluster DAG \(C = (V', A)\) as a DAG whose sinks are exactly the set of vertices \(V\) in the graph \(G\) it describes. A vertex \(v' \in V'\) describes a so-called cluster \(C(v')\) , which represents the subset of sinks that are reachable from that vertex. A cluster \(C(v)\) where \(v\in V\) is trivially the set \(\{v\}\) . \(A\) represents the set of directed edges in the cluster DAG and are called cluster edges.

Building on top of cluster DAGs, a DAG compression is a graph \(D = (V', A, E')\) where \(V'\) and \(A\) are the same vertex and edge sets as in the cluster DAG \(C\) . \(E' \subseteq V' \times V'\) is an additional edge relation. If \((u', v')\) is in \(E'\) , then the set of edges \(C(u') \times C(v')\) is in \(G\) . This is a complete bipartite graph with directed edges from all sinks (vertices in \(G\) ) reachable from \(u'\) to all reachable from \(v'\) .

This may seem slightly familiar. alga ’s Connect w x y adds edges \(V(x) \times V(y)\) to the parent graph \(G'\) , which is the cross product of all vertices in child graph x and all vertices in child graph y , unioned with the edges in x and y . Given that all trees are DAGs, if we consider the node for child graph x to be a cluster node \(x'\) , and the node for child graph y to be a cluster node \(y'\) , then we can see that the set of vertices in \(G\) described by \(V(x)\) equals the set of vertices described by \(C(x')\) , and likewise for \(V(y)\) and \(C(y')\) . The cross product \(V(x) \times V(y)\) therefore describes the same set of edges as \(C(x') \times C(y')\) . To convert an alga expression into a DAG compression, we contribute each edge from an Overlay or Connect node to its child to \(A\) , and we contribute an edge \((x', y')\) for each Connect w x' y' , which represents \(C(x') \times C(y')\) , to \(E'\) .

One missing piece in reducing an alga expression to a DAG compression is that there is no limit to the number of leaf Vertex constructors that can denote the same logical vertex \(v\) . In a cluster DAG, as mentioned earlier, the set of sinks is exactly \(V\) . To fully reduce a tree compression to a DAG compression, all logically equivalent Vertex v constructors must be consolidated into one node. We can discard subexpressions that contain no vertices, such as Empty or Overlay Empty Empty , and not carry them to the DAG representation. Lastly, because alga expressions can outline multiedges due to duplicate node occurrences, we can handle those by taking the minimum weighted edge. This is okay for the purposes of SSSP because we will always take the edge that costs less for any shortest path.

Expression tree to DAG compression

Switching graphs

Now that we’ve identified the relationship between alga expressions and DAG compressions, we can move onto the data structure that enables efficient search, which is the switching graph.

The switching graph is an augmentation of the DAG compression that allows a search to traverse backwards through parent-child directed edges in order to establish reachability and distances between vertices in \(G\) . To accomplish this, BMT duplicate all vertices \(V'\) except for the vertices representing \(V\) . The original, copied vertex \(x'\in V'\setminus V\) is an upper vertex, a non-copied vertex \(v\in V\) is a middle vertex, and a copy \(\overline{x'}\) is a lower vertex. For all middle vertices, \(\bar v=v\) . Edges \((\overline{y'}, \overline{x'})\) are added to the switching graph for every parent-child edge \((x', y') \in A\) . Semantically, they represent climbing back up to ancestor cluster nodes. For every compressed edge \((x', y')\) in \(E'\) , the compressed edge is removed and a switching edge is added in the switching graph from node \(\overline{x'}\) to node \(y'\) . The source \(\overline{x'}\) can either be a lower node or a middle node, and the destination \(y'\) can either be a middle or upper node, depending on whether or not the endpoints belong to \(V\) . Traversing those switching edges incurs a switching cost \(w\) .

BMT prove that there is no loss of search or distance semantics when performing those algorithms over the switching graph compared to the original graph \(G\) . They prove that these algorithms run over the switching graph, which is \(O(s)\) , without needing to decompress the representation and generate the same SSSP as the search would on \(G\) .

Dijkstra’s for alga expressions

Consequently, we can perform a search over the alga expression without decompressing it into an adjacency map, as the alga library does today. However, in the spirit of performing as much of the algorithm on the Graph expression as possible, we will not be constructing a separate switching graph. Instead, we will perform the search over a lazily-generated frontier of search states and visit nodes of the expression.

Expression tree to DAG compression to switching graph to folded switching tree

To support this, we’re going to traverse the expression and create an index that caches some important information about the graph. Unlike an adjacency map, this construction will not be proportional to the number of edges in \(G\) :

makeBaseFunctor [''Graph]

type Weight w = (Ord w, Num w)
type Vertex v = Ord v

type NodeId = Int

data Index w v = Index
  { parentOf      :: IntMap NodeId
  , occurrencesOf :: Map v [NodeId]
  , nodeFor       :: IntMap (GraphF w v NodeId)
  }

First, the obvious; the index stores the parent of every node other than the root. Every node in the graph expression will be assigned an integer NodeId . What’s not obvious are the purposes of occurrencesOf and nodeFor ; because there are no restrictions on how many times a vertex \(v\) can appear as Vertex v in the graph, there may be more than one. So, we want to track which node ids those occurrences correspond to, which are inserted into occurrencesOf . For convenience, and for Connect and Overlay constructors, we’d like a way to quickly retrieve the node ids of their children; that is the purpose of nodeFor . This way, the node ids are available to us directly as we traverse the expression.

You may have wondered what GraphF w v NodeId is and why it’s separate from Graph . This is the derived functor implementation of Graph from an invocation to makeBaseFunctor [' 'Graph ] , provided by Haskell’s recursion scheme library. I’ve talked about recursion schemes a few times before, so won’t review that here. The important part is that recursion schemes allow us to separate recursion from the logic of our transformations. Here is what the GraphF functor looks like:

data GraphF w v r
  = EmptyF
  | VertexF v
  | OverlayF r r
  | ConnectF w r r

When we traverse the algebraic graph expression and reach a Connect or Overlay node, its children are going to be supplied to us as node ids in a GraphF w v NodeId layer, which will be placed into the nodeFor map and whose ids are going to be inserted into the parentOf map.

To build the index, we’re going to keep track of some state, namely the next node id and current index. We’ll build the computation that calculates the next state as we fold the expression tree in a bottom up fashion, using the state monad, and execute our index builder with a counter that begins from zero:

type Indexing w v = State (Int, Index w v) NodeId

Our state monad is a function that allocates an id, indexes the current node, and yields that node’s id. To build the index, we’ll fold the tree bottom up using the cata recursion scheme. It invokes a helper function, called indexAlg , which breaks down the tree layer by layer.

At each layer, we sequence the computations of children and insert the result into the nodeFor map. If the current node is a VertexF v then sequencing it is a no-op, and so VertexF v is inserted into the map as well. The sequenced children of Connect and Overlay become registered in the parentOf map, pointing to the current node’s id. Lastly, if the current node is a Vertex v , we add the node’s id into the occurrence list for v .

-- allocates a new node id
fresh :: State (Int, Index w v) NodeId
fresh = do
  (n, index) <- get
  put (n + 1, index)
  pure n

indexAlg :: Vertex v =>
 GraphF w v (Indexing w v) -> Indexing w v
indexAlg actions = do
  layer <- sequenceA actions
  nodeId <- fresh
  -- maps a function over the Index portion of the current state
  let record = modify . second
  -- updates the index's records
  record $ \index -> index
    { nodeFor       = IM.insert nodeId layer (nodeFor index)
    , parentOf      =
        foldr (`IM.insert` nodeId)
          (parentOf index)
          (toList layer)
    , occurrencesOf = case layer of
        VertexF v ->
          M.insertWith (++) v [nodeId] (occurrencesOf index)
        _         -> occurrencesOf index
    }
  pure nodeId

You can think of this process as building one large tree of computations that we don’t execute until we’re done describing it from bottom to top. When we’re done folding the graph expression, we’ll be left with an action that we can then execute to retrieve the final ( Int , Index w v) pair and grab the completed index with snd .

emptyIndex :: Index w v
emptyIndex = Index IM.empty M.empty IM.empty

buildIndex :: Vertex v => Graph w v -> Index w v
buildIndex g = snd (execState (cata indexAlg g) (0, emptyIndex))

With the index built, we can now efficiently reference nodes in the expression and run Dijkstra’s. As mentioned, BMT run Dijkstra’s directly over the switching graph. In our case, we will run it over a lazily-generated frontier of neighboring search states:

data SearchState v
  = Up NodeId
  | Down NodeId
  | At v
  deriving (Eq, Ord, Show)

When reading the BMT paper, I thought that the terminology was a bit confusing. I’m used to looking at DAGs from sources at the top to sinks at the bottom. Same with trees. As mentioned earlier, in the BMT switching graph, copies of the parents that we move to are considered lower nodes. This is counterintuitive to me! So in the SearchState , Down will represent visiting a node’s child, Up will represent visiting its parent, and At v will represent visiting a Vertex v .

Our search will traverse the expression and, for each node in the expression, generate SearchState neighbors that indicate the next state. Let’s start with the simplest case to understand, moving down from a node:

-- given the index and a node id, move downward from the
-- corresponding node
descend :: Index w v -> NodeId -> [SearchState v]
descend index n =
  case nodeFor index IM.! n of
    EmptyF -> []
    VertexF v -> [At v]
    _ -> [Down n]

downNeighbors
  :: Weight w
  => Index w v -> NodeId -> [(w, SearchState v)]
downNeighbors index n =
  [ (0, s) -- descending to a node costs nothing
  | child <- toList (nodeFor index IM.! n)
  , s <- descend index child
  ]

descend gives us the downward state representation of a node n . If we’re at a Vertex v , then we’re At v . If we’re at a Connect or Overlay node, the corresponding search state is Down .

Given the index and a node id, downNeighbors converts the children of a node to a list and, for each child, converts that child to its search state form. Traveling to that child costs nothing because it does not represent traversing a real, weighted edge.

Only traversing through a Connect node represents crossing a weighted edge, and so incurs a cost when moving up from its left child and down to its right. The upNeighbors function covers this case:

upNeighbors
  :: Weight w
  => Index w v -> NodeId -> [(w, SearchState v)]
upNeighbors index n =
  case IM.lookup n (parentOf index) of
    Nothing -> []
    Just p ->
      case nodeFor index IM.! p of
        -- crossing a connect node is only valid from the left child
        -- because the graph is directed from l to r
        ConnectF w l r | n == l ->
          (0, Up p) : [ (w, s) | s <- descend index r ]
        _ -> [(0, Up p)]

Put simply, if the current node has a parent Connect , and the current node is the left child of it, then in addition to marking the parent as a neighbor, we mark the right child of the parent Connect node as a neighbor with plans to descend to it. The cost of this visitation is \(w\) . Every other neighbor is Up with no cost associated with it.

Finally, we want a special function for visiting neighbors from a Vertex in the expression tree, i.e. from the search state At . You may have thought to yourself that upNeighbors should cover this case. The reason we don’t use this directly is because, given that there can be more than one Vertex v in the expression tree, we need all possible upward neighbors from that search state:

atNeighbors
  :: (Weight w, Vertex v)
  => Index w v -> v -> [(w, SearchState v)]
atNeighbors index v =
  concatMap (upNeighbors index)
    (M.findWithDefault [] v (occurrencesOf index))

All of the above make up one neighbors function, which is going to be the canonical neighbors function used in our Dijkstra search:

neighbors
  :: (Weight w, Vertex v)
  => Index w v -> SearchState v -> [(w, SearchState v)]
neighbors index (Up n) = upNeighbors index n
neighbors index (Down n) = downNeighbors index n
neighbors index (At v) = atNeighbors index v

We can now define dijkstra using the familiar construction; we keep a distance map that doubles as the seen set, and a priority queue of (w, SearchState v) using Haskell’s Set , which gives us a convenient minView function.

dijkstra :: (Weight w, Vertex v)
  => Index w v -> v -> Map v w
dijkstra index start =
  go (Set.singleton (0, At start)) M.empty
  where
    go queue distances =
      case Set.minView queue of
        Nothing -> vertexDistances distances
        Just ((d, next), rest)
          | M.member next distances -> go rest distances
          | otherwise ->
              let relax (cost, neighbor) =
                    Set.insert (d + cost, neighbor)
                  newQueue =
                    foldr relax rest (neighbors index next)
                  newDistances = M.insert next d distances
               in go newQueue newDistances

    -- grab only distances from vertices in G,
    -- i.e. At search states
    vertexDistances distances =
      M.fromList [ (v, d) | (At v, d) <- M.toList distances ]

To refresh your memory of Dijkstra’s algorithm, we set up a queue prioritized by minimum distance to a particular node, pop that value and, if unvisited, get its neighbors and insert them into the queue. Something different about this implementation is that we do not gate insertion of neighbors into the priority queue based on whether or not we’ve already visited the search state; the neighbors are inserted into the queue regardless. What makes this okay is the line M.member next distances -> go rest distances , which gates at the pop step rather than at the push step. As a consequence, we pay a minor penalty of having the queue ignore values already seen.

Benchmarks

So how does this hold up to the AdjacencyMap approach that alga takes. Well, it depends on the nature of the input graph. Unsurprisingly, graphs that are most effectively compressed using an alga expression fare best with the switching graph approach. As the ratio of graph size to expression size increases, the switching algorithm’s speedup over the adjacency map approach tends to increase.

Time (milliseconds)
Graph Overhead + search (ms) Search only (ms)
Switching alga Switching alga
Transitive tournament a 2.91 52.55 2.03 19.27
Complete directed 3.99 51.96 2.43 37.24
Grouped (5,000) b 82.98 596.74 33.14 256.34
Complete bipartite 1.40 3.96 0.72 0.20
Layered (20 layers) c 3.51 3.61 1.96 2.00
Path 3.15 0.80 1.43 0.23
Random (20%) d 2504.05 174.72 1292.91 15.26
Memory allocated (MB per run)
Graph Overhead + search (MB) Search only (MB)
Switching alga Switching alga
Transitive tournament a 10.53 103.86 7.24 57.61
Complete directed 15.21 115.79 8.54 113.66
Grouped (5,000) b 174.38 674.32 93.29 627.95
Layered (20 layers) c 12.51 7.86 6.20 6.38
Path 11.32 3.80 4.34 0.98
Complete bipartite 6.00 1.66 2.75 0.79
Random (20%) d 3276.09 479.34 1713.22 28.63

a A transitive tournament has an edge from every vertex to every later vertex in an ordering

b Ten equal-sized groups joined by randomly chosen, uniformly weighted directed bicliques

c Layered graphs divide into equal-sized layers, with an edge from every vertex in one layer to every vertex in the next

d Random graphs have 1,000 vertices and are expressed edge by edge, with the indicated edge probability, conditioned on strong connectivity. Values are medians across five seeded graphs


  1. David Anekstein. Generic Recursion Applied to Algebraic Graphs . 31 July 2022. ↩︎

  2. alga : Algebraic graphs . Haskell library. ↩︎

  3. Andrey Mokhov. Algebraic Graphs with Class . Haskell Symposium, 2017. ↩︎

  4. Max Bannach, Florian Andreas Marwitz, and Till Tantau. Faster Graph Algorithms Through DAG Compression . STACS, 2024. Cluster DAGs and DAG compressions: p. 8:6; switching graphs and distance preservation: Definition 3.2 and Theorem 3.4, p. 8:10; weighted-search bound: Theorem 1.6, p. 8:4. ↩︎

OpenAI buys smartphone camera maker Glass Imaging for $300M

Hacker News
techcrunch.com
2026-09-15 08:01:31
Comments...
Original Article

In Brief

Posted:

Photographic aperture camera lens
Image Credits: YIN WENJIE / Getty Images

OpenAI has bought smartphone camera maker Glass Imaging in a deal worth over $300 million, according to a report from The Wall Street Journal . The company, founded in 2019 and based in Los Altos, California, had previously raised about $30 million in funding from investors.

Glass Imaging was founded by Ziv Attar and Tom Bishop, a pair of former Apple engineers who previously led the team that developed Apple’s Portrait Mode. This background directly informs the work they now do at Glass Imaging, where they use AI to overcome the physical size constraints of smartphone cameras. Rather than using AI to edit a photo after it’s been taken, Glass Imaging uses neural networks to learn about individual camera systems — like the different cameras on various smartphone models — to yield better images from the moment the shutter clicks.

OpenAI did not immediately respond to a request for comment. The ChatGPT maker is rumored to be working on its own hardware, like smartphones , earbuds , and AI companion devices .

In 2025, OpenAI CEO Sam Altman and famed Apple designer Jony Ive revealed that they had been working together on a device startup called io, when OpenAI bought Ive’s company for $6.5 billion.

Newsletters

Subscribe for the industry’s biggest tech news

Related

Latest in AI

Headlines for September 15, 2026

Democracy Now!
www.democracynow.org
2026-09-15 08:00:00
Supreme Court Blocks Trump’s Order Restricting Mail-In Voting, Whistleblower Says Federal Agents Broke Laws in Trump-Ordered Search for “Unlawful Voters”, Trump Rejects Calls to Regulate A.I. as a ”SICK Conspiracy” and ”HOAX”, Saudi Crown Prince MBS Meets CE...
Original Article

Hi there,

When we speak with viewers, listeners and readers, the same message always comes through: people are hungrier than ever for Democracy Now!’s independent journalism featuring authentic voices. A group of generous donors will TRIPLE all new monthly donations started today, which means your monthly gift of $15 is worth $45. If you believe uncompromising, independent reporting is essential to a functioning democracy, please donate today.

Every dollar makes a difference

. Thank you so much!

Democracy Now!
Amy Goodman

Non-commercial news needs your support.

We rely on contributions from you, our viewers and listeners to do our work. If you visit us daily or weekly or even just once a month, now is a great time to make your monthly contribution.

Please do your part today.

Donate

Independent Global News

Donate

Headlines September 15, 2026

Watch Headlines

Supreme Court Blocks Trump’s Order Restricting Mail-In Voting

Sep 15, 2026

The Supreme Court has blocked President Trump’s efforts to restrict mail-in voting ahead of November’s midterm elections. On Monday the court issued a brief, unsigned order upholding a lower court ruling that bars the US Postal Service from fully implementing Trump’s executive order, which sought to create federal lists of citizens, and to refuse delivery of mail-in ballots to people not on those lists.

Whistleblower Says Federal Agents Broke Laws in Trump-Ordered Search for “Unlawful Voters”

Sep 15, 2026

A whistleblower is alleging federal agents may have broken state laws in a wide-ranging search for alleged unlawfully registered voters. The whistleblower reached out to California Democratic Senator Alex Padilla, who made the complaint public on Monday. Senate Democratic leader Chuck Schumer warned the Department of Homeland Security pulled hundreds of federal agents from their normal roles to identify supposed “unlawful voters.”

Chuck Schumer : “DHS under Trump is forcing its officials to carry out an illegal scheme, break state laws, and treat US citizens as collateral damage, all to advance his scheme to rig the election. They’re even asking people to lie about their identities so they can create this illegal scheme. We are weeks from the midterms. Americans should not have to fear being targeted by their own government because of how they registered to vote.”

Trump Rejects Calls to Regulate A.I. as a ” SICK Conspiracy” and ” HOAX

Sep 15, 2026

President Trump has rejected calls to regulate the artificial intelligence industry, calling warnings about the threat of superintelligent machines a ” HOAX .” Writing on his Truth Social platform, Trump boasted, “The only control or 'guardrails' that AI needs is a STRONG AND SMART (High IQ!) PRESIDENT , and the U.S.A. has that, in spades! … There is a SICK conspiracy going on against AI and Data Centers, and the only one that is happy about it is China. WHOEVER WINS AI, WINS !”

Trump’s comments came after Anthropic’s Dario Amodei and other AI CEOs publicly called for a slowdown in the development of frontier AI models and for government regulations. On Monday, Trump called Jensen Huang–the CEO of the computer chip maker Nvidia–while Huang was speaking at an AI summit in Los Angeles. Huang put the president on speakerphone.

President Trump : “Well, I know about AI. I know I also have common sense about AI. Uh the robots will not be taking over. Uh the AI will not be taking over the rest of the world. The whole thing is a hoax.”

We’ll have more on artificial intelligence after headlines with Naomi Klein and Astra Taylor, co-authors of the new book–“End Times Fascism And the Fight for the Living World.”

Saudi Crown Prince MBS Meets CENTCOM Chief as Houthis Consolidate Control Over Red Sea Coast

Sep 15, 2026

Image Credit: Daniel Torok

Yemen’s Iran-backed Houthis seized two islands in the southern Red Sea on Monday, as Saudi Arabia launched 54 air strikes over the past 24 hours. The Houthis are currently digging into positions on the Western Coast of Yemen along the Red Sea. On Monday, Saudi Crown Prince Mohammed bin Salman met with the U.S. commander who oversees Middle East operations, days after President Trump dismissed the Saudi leader’s calls for help in combating the Houthis. Their meeting came as Vice President JD Vance claimed the U.S. is “on top of the situation”.

JD Vance : We’ve also been engaged in direct conversations with the Houthis themselves, so we actually feel like we are on top of the situation. It’s a very fluid and dynamic thing, but we’re going to keep on monitoring it, making sure that America’s best interests are represented.

Iran’s IRGC Claims It Shot Down Two U.S. Drones Over the Strait of Hormuz

Sep 15, 2026

Image Credit: The Islamic Republic News Agency

Iran’s Islamic Revolutionary Guard Corps says it shot down two more U.S.-operated MQ-1 drones over the Strait of Hormuz, and says an oil tanker exploded and caught fire after colliding with mines. The IRGC did not provide further details; but the claim came as a fire broke out in an oil tanker off the coast of Oman. Two dozen sailors were evacuated, but two remain missing. On Monday the secretary of Iran’s Supreme National Security Council rejected President Trump’s claim that he was open to resuming discussions with Iran; declaring “No talks until Iran’s conditions are met. Period!”

Yield on 10-Year U.S. Treasury Bonds Passes 5% as Mideast War Drives Up Oil Prices

Sep 15, 2026

On Monday the yield on 10-year US Treasury bonds passed 5 percent for just the second time since the 2008 financial crisis, pushing mortgage and other borrowing costs higher. Brent crude oil topped $108 dollars a barrel as officials in Saudi Arabia said it would take weeks to repair the East-West oil pipeline struck by drones from Iraq last week.

New York Leads Coalition Suing Trump Admin Over New Immigration Rule

Sep 15, 2026

A coalition of six cities and counties led by New York filed a lawsuit Monday seeking to block the Trump administration’s new rule making it easier for federal officials to reject green card and visa applications from immigrants who have used public benefits. Immigrants could also be denied a green card if they or a family member received state or federal financial aid to attend college. New York Attorney General Letitia James announced a similar lawsuit against the Trump administration over the new rule on behalf of New York and nearly two dozen other states around the country. She spoke yesterday.

Letitia James : “The rule would allow immigration officers to consider use of critical benefits like Medicaid and SNAP and even participation in school meal programs as part of an applicant’s circumstance. And that means immigrant New Yorkers may be forced to ask themselves impossible questions. Will getting health insurance hurt my chances of getting a green card? Will accepting food assistance when I fall on hard times be held against me?”

ProPublica: Don Jr.’s Wedding Secretly Bankrolled by Russian Oligarch

Sep 15, 2026

Image Credit: ProPublica

Donald Trump Jr.’s Bahamas wedding was secretly bankrolled by a Russian oligarch close to Putin. That’s the headline of a new ProPublica exposé which found that Russian billionaire Umar Kremlev paid the bill for hundreds of thousands of dollars of expenses during Donald Trump Jr.'s destination wedding in May, including a fireworks display and the rental of two private islands. Kremlev is president of the International Boxing Association. He's been sanctioned by Ukraine over his ties to Russian President Vladimir Putin, who awarded Kremlev with the Order of Friendship in April.

Trump Jr. and his newlywed wife, Bettina Anderson, confirmed ProPublica’s findings, writing on social media, “Our dear friend Umar very generously hosted two incredible nights of celebrations for us. … It’s unfortunate that something so personal and happy can be recast as something political or sinister simply because of who someone is or where they come from.”

Mitch McConnell Returns to the Senate Three Months After Medical Emergency

Sep 15, 2026

On Capitol Hill, Kentucky Republican Senator Mitch McConnell returned to the senate for the first time since suffering a medical emergency in June. He’d been almost entirely out of the public view for three months. McConnell used an assisted wheelchair, appeared frail and spoke slowly when talking to reporters briefly outside the Senate chamber. Reporters were barred from recording video of the 84-year-old Senator.

Turkish Authorities Detain At Least 162 People in Raids of Gay Bars and Homes of LGBTQ+ Activists

Sep 15, 2026

In Turkey, authorities have detained at least 162 people in a series of raids on gay bars and the homes of LGBTQ+ activists. Police also searched the offices of six LGBTQ+ associations in weekend raids. It’s part of an operation called “Keep My Family Safe”. Same-sex relations are not illegal in Turkey, but homophobia and transphobia remain widespread. President Recep Tayyip Erdogan has repeatedly blamed the LGBTQ+ community for falling birthrates.

Judge Blocks Trump’s Restrictions on Visas for International Students, Academics and Journalists

Sep 15, 2026

A federal judge in Massachusetts has blocked the Trump administration from enforcing a new rule that would limit the visa length for international students, academics and journalists. The new policy would have capped the stay of international students, who are usually allowed to extend their U.S. visas until their studies are completed, while limiting foreign journalists to remain in the U.S. for just 240 days.

Mahmoud Khalil Files Civil Rights Lawsuit Against Columbia University

Sep 15, 2026

In New York, Palestinian rights advocate Mahmoud Khalil has filed a federal civil rights lawsuit against Columbia University, accusing the school of failing to protect him and other pro-Palestine student activists from harassment. Khalil was arrested by federal immigration agents, who ambushed him at his Columbia University apartment building in March 2025. He spent over 100 days in an ICE jail, missing the birth of his first child.
Speaking to reporters on Monday, Mahmoud Khalil denounced what he called Columbia’s “deliberate indifference and discrimination, aimed at intimidating Palestinian students.”

Mahmoud Khalil : “For over two years, we pleaded with Columbia. Columbia did not care. Our safety and well-being did not serve the ideological project its board of trustees was protecting, so they traded us away. We are suing because no student, regardless of their background, should go through the hell that Columbia put us through over all these years.”

Ed Sheeran Removes Macklemore From U.S. Tour Over Palestine Advocacy

Sep 15, 2026

Music superstar Ed Sheeran has removed the Grammy-winning musician Macklemore from the rest of Sheeran’s U.S. tour following a boycott by stadium owners who cited Macklemore’s Palestine advocacy and onstage comments about Gaza and the occupied West Bank. In a statement shared on Instagram, Macklemore detailed a call between Sheeran and Robert Kraft—the billionaire owner of the New England Patriots and Gillette Stadium—who rallied other stadium owners to ban Macklemore and told Sheeran to remove the rapper from the shows or he, too, would not be allowed to perform. On September 4th, Macklemore performed his song “Hind’s Hall” to a packed MetLife Stadium in New Jersey, just outside New York City. He dedicated it to Columbia University students who led a protest encampment in 2024. Hind’s Hall was named after Hind Rajab, a 6-year-old Palestinian girl who was killed by Israeli soldiers in Gaza alongside six members of her family and two paramedics dispatched to save her.

Macklemore : “Free Palestine. I want those words to be loud and clear, so that the people from Gaza all the way to the occupied West Bank know that we have not forgotten them.

To see our interview with Macklemore go to our website.

The original content of this program is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License . Please attribute legal copies of this work to democracynow.org. Some of the work(s) that this program incorporates, however, may be separately licensed. For further information or additional permissions, contact us.

Non-commercial news needs your support

We rely on contributions from our viewers and listeners to do our work.
Please do your part today.

Make a donation

IBM Built the Cold War’s Most Powerful Code Breaker for the NSA

Lobsters
spectrum.ieee.org
2026-09-15 07:22:06
Comments...
Original Article

Workers install Harvest (IBM 7950) at the NSA's headquarters. The two consoles configured settings on both Stretch, the system's general-purpose mainframe, and Harvest's streaming coprocessor, designed for high-speed analysis of intercepts, ciphers, and codes.

At the height of the Cold War, one very specialized computer was so secret that the world didn’t know it existed. It ran its jobs up to 200 times as fast as any other computer of its time. It was the U.S. National Security Agency’s main cryptographic processor in operation from the time of the Cuban Missile Crisis in 1962 through the Vietnam War and on past the 1975 Helsinki Accords . The machine stopped running only when its moving parts finally gave out.

The Harvest computer mattered because of what it was as well as when it ran. For 14 years, it was the engine processing the NSA’s most sensitive intercepts at a time when signals intelligence was as close to a strategic weapon as anything short of a warhead.

Designed and built by IBM for the NSA , Harvest was one of the first machines designed to apply operations to enormous datasets rushing past, a precursor to the computers today that manage continuous video streams and security systems in real time. It was also one of the first machines built as an add-on —a specialized helper intended to do one job exceptionally well, bolted onto a general computer. Harvest’s modular design is like a 1960s version of today’s graphics chips that CPUs use to run intensive video-game and AI processing loads.

All that raw processing power meant that Harvest also needed nonstop rivers of data to run on. And that led to another pioneering achievement: the world’s first automated tape library that could robotically fetch any one of hundreds of large cassettes of magnetic tape from the machine’s racks.

Given Harvest’s unprecedented processing and storage capacity, the machine’s designers naturally needed to rethink how their system handled information. So IBM wrote a customized programming language called Alpha to let code breakers rigorously describe cryptographic problems, just as scientists at the time were using the emerging language Fortran to describe equations and data-processing algorithms .

a computer center with printers and large cabinets and a man sitting at a computer keyboard In Fort Meade, Md., an NSA data center hosted one of the world’s fastest computers of its time—although not often discussed, because of its sensitive, high-security code breaking and cipher hunting work. National Cryptologic Museum

The story of Harvest, pieced together from declassified documents and contemporary manuals and technical overviews , provides a new and unexpected vista on the history of computing . It also offers a case study in how national security needs, especially during the Cold War, pushed computer technology beyond the far reaches of what unclassified, civilian computing could achieve. Harvest’s distinctive history reveals a visionary algorithmic, coding, memory, and hardware architecture occasionally decades ahead of its time. But this machine was also built only once, for one singular purpose, and then ultimately quietly retired.

The Heart of NSA’s Secret Machine

IBM’s landmark 1960 transistorized mainframe , the IBM 7030 , better known as Stretch, provided the front end for Harvest (which was officially known as the IBM 7950). IBM delivered Stretch to eight or nine customers , mostly scientific research labs, from 1961 through ’63. Designed and prototyped throughout the second half of the 1950s , Stretch introduced the now standard notion of an 8-bit byte. For its first three years of operation , Stretch was the non-classified world’s fastest computer, although it failed to meet IBM’s aggressive goal of running 100 times as fast as Stretch’s predecessor, the IBM 704 . While IBM engineers in Poughkeepsie, N.Y., were designing and building Stretch, the company was also quietly discussing a new system that would be built for NSA.

At the time, NSA’s existing cryptanalytic computers—large, batch-processing machines that required human operators to manually stage each tape run—were struggling to keep pace with the sheer volume of intercepted message traffic coming in from around the globe. What the agency needed was a machine that could process an unbroken river of incoming data, automatically, around the clock. That requirement alone profoundly shaped Harvest’s design.

Schematic illustration of the IBM/NSA Harvest computer, in operation from 1962 to 1976. IBM’s Harvest system, custom-built for the NSA for code breaking, paired the IBM 7030 Stretch mainframe with a bespoke data-stream processor. Stretch handled ordinary computing and input/output, including the Tractor automated tape library. Both units shared two kinds of memory: a large main bank and a smaller, faster bank. When Stretch switched to streaming mode, Harvest drew two streams of data, P and Q, from memory, processed them in parallel, and returned the results as a third stream, called R. Chris Philpot

After two failed proposals to NSA, in 1958 IBM finally landed the contract: a Stretch-based machine, augmented by a custom coprocessor, with a revolutionary tape-based storage system, called Tractor.

Stretch’s forte was floating-point math for scientific computations. IBM had designed it primarily for labs working on frontier research like nuclear weapons design and weather prediction . By contrast, the custom coprocessor to be built atop Stretch would help NSA analysts sift through alphanumeric characters—that is, essentially integer data.

Harvest’s coprocessor was the opposite of a general-purpose system. It was, rather, a streaming computer . Instead of executing long series of instructions, it followed one fixed sequence of steps and applied that same sequence to every pair of characters as they streamed past. Harvest shared memory with the main Stretch processor and ran in bursts. Either Stretch was operating, or else it suspended itself while Harvest’s coprocessor shot through data in memory at extreme speeds.

Stretch and Harvest were among the first large computers built entirely from transistors packaged in circuit cards and housed in large, refrigerator-size frames. A 1962 technical manual about Stretch describes the machine’s CPU as divided into functional sections—the instruction unit, the look-ahead unit, the (parallel and serial) arithmetic unit, and the memory bus unit. Harvest inherited Stretch’s basic circuit design but then added something unconventional: Its streaming units processed data in overlapping stages called a pipeline. So while one pair of data bytes was being compared, the next pair was being fetched from memory.

Harvest’s coprocessor operated by fetching two streams of data, called P and Q, from the system’s memory, performing operations on them, then writing the results to memory as a third stream, R. Each stream could be anywhere from 1 to 8 bits wide. Harvest’s memory was bit-addressable, meaning word boundaries could be ignored entirely. For instance, it could fetch just 5 bits rather than filling out a whole byte. Streams P, Q, and R included flexible provisions for looping and addressing data in complex patterns—allowing, for example, repeated fetching of short strings from memory.

Data from P and Q fed into two functional units. The simpler was the logic unit, which performed basic, bitwise operations—the same operations any programmer would recognize today—and wrote its results back to memory. The more complex was a table-lookup unit. It combined incoming data from P and Q to form an address in memory, which could then be used to advance a counter by one, set a specific bit, or retrieve a stored value. The latter unit functioned, in effect, like the rotor wheel inside a cipher-encoding/decoding machine of the era, the kind that electronically substituted one value for another according to the cipher machine’s wiring.

Harvest’s complexity baffled some at the NSA. During employee tours, according to James Bamford ’s 2001 NSA history, Body of Secrets (Doubleday), officials would point to the machine and scoff, “It’s beautiful, but it doesn’t work.”

Not everyone at the agency was put off by the monumental device, however. One of the few documented examples of Harvest at work, recounted by Bamford, describes the machine searching 3.5 billion characters of text for any of 7,000 target terms, in just under 4 hours.

In unclassified remarks from 1972, NSA analyst Robert Looney mentions one job Harvest had tackled—though he didn’t specify the end goal or the code-breaking effort behind it. Codenamed “Moretown,” the job involved sifting through 11 million messages spanning 16 years of intercepted traffic against a list of some 8,000 search terms—all in about ten hours.

Black\u2011and\u2011white portrait of a woman at a desk with papers, wearing a striped shirt. IBM’s Frances Allen helped design Alpha, Harvest’s custom-built programming language. IBM

Headshot of man with moustache and glasses, in a business suit. IBM’s James H. Pomerene was chief engineer of Harvest, supervising its custom-designed circuits that’d been optimized for algorithms used in many cryptographic jobs. IEEE

Man in suit and glasses seated beside vintage mainframe computer equipment IBM’s Fred Brooks Jr. was a key co-architect of Harvest’s hardware system. Computer History Museum

As a unified system, Harvest—that is, Stretch plus IBM’s custom-built streaming processor add-on—streamed 1 byte every 0.3 microseconds, and it boasted about 800 kilobytes of addressable memory.

“Here you see one bank pulled out of its oil bath,” Looney said in his 1972 remarks celebrating Harvest’s tenth anniversary of operations. He held up a photo of Harvest’s magnetic core memory banks—six of them, submerged in oil for cooling.

Factor in the time demands of various data fetches from Tractor’s tape archives, and a single Harvest “instruction” sometimes carried on, without needing any human intervention, for hours.

“It was quite an amazing computer,” recalled IBM Fellow Emerita Frances Allen in a 2001 oral history . “One instruction, for example, could do sorts, and do statistical analysis of the data that was streaming by it.… Everything we were doing at that time was on the cutting edge. There was no question about it.”

Allen, who received the A.M. Turing Award in 2006 , was one of the developers who worked on both Stretch and Harvest. At the time she started working on Harvest, Allen noted, the Fort Meade, Md.–based NSA was largely unknown outside of classified intelligence circles. So she at first assumed she was working on an unspecified naval project. “We thought of ourselves as working for the Bureau of Ships, because that was the code name for NSA in the budget!” recalled Allen, who died in 2020.

Other key Harvest designers and early developers wound up becoming influential figures over the course of computing history. Frederick Brooks Jr. , recipient of the 1999 Turing Award and a major contributor to the hardware and software for IBM’s System/360 , also helped develop Harvest. And James Pomerene , prior to his involvement with Harvest as its chief engineer, had previously helped build the pioneering IAS computer alongside John von Neumann.

How Tractor Stored a World of Data

IBM built the Tractor tape system ( IBM 7955 ) to attach to the same Stretch machine that hosted Harvest, because no existing data storage technologies could keep up with the computer’s staggering throughput. Stretch handled the business of staging tapes from the library to the drives—using Tractor’s automated cassette handler. Stretch also coordinated reading data in from Tractor and writing results back out from Harvest. Harvest, in turn, did all its actual computing on the system’s shared main memory.

In the early 1960s, and even after Tractor and Harvest were installed, hard-drive data storage was in its infancy. For code-breaking jobs of the size Harvest was taking on, disk storage would have been impractical in terms of both cost and sheer floor space. So Tractor had to be based around tape storage.

Each tape was sealed inside a case built like a boombox—twin encased reels under a window, carried by a handle—and, at 6 to 7 kilograms, about as heavy as a bowling ball. Think of a Tractor cassette as an outsize predecessor of the audiocassette, which would come along a decade later, and holding some 120 megabytes of data on a reel of tape 550 meters long. Each storage unit housed up to 160 of these cassettes.

a man holds a very large cassette in front of cabinets of tape drives An IBM technician holds one of the data cassettes used with Harvest’s automated Tractor tape drives. IBM

When Harvest launched in 1962, it had three automatic cartridge units, each serving two drives. So the available online storage across the three Tractor units totaled a stunning 44 gigabytes. That’s more than 190 times as much capacity as the IBM 2314 disk storage system, announced in 1965, which held 233 megabytes across its full complement of eight drives .

Tractor had to run continuously, swapping cassettes in and out, 24 hours a day, seven days a week. The system’s tape-handling speed was tuned to keep pace with Harvest’s own appetite for data. The custom-built robotic mechanism for retrieving the cassettes was a servo-driven arm that traversed the system’s storage racks. It fetched a cassette from its slot and delivered it to a handler or received a cassette from one of the handlers and returned it to storage.

Running at 6 meters per second, Tractor’s tapes zipped past the read/write heads faster than the eye could track. Software running on Stretch handled the cassette shuttling as well as reading and writing. For one of Tractor’s drives to move from the completion of processing one tape to reading the next took about 18 seconds, assuming it had already been fetched and was ready to mount. Robotically fetching a cassette from the storage unit and preparing it for reading required no human handling or input whatsoever.

In addition to Tractor, the system had standard reel-to-reel tape drives attached to Stretch. Harvest’s technicians often used the conventional drives for importing and exporting data to and from other systems; there was no other practical way to get large datasets into or out of Harvest. Tractor could also store permanent files and retrieve them directly from its tape libraries when a job required them. In other words, Tractor’s substantial cassette libraries acted both as permanent data storage and as a place to hold transient data for processing by Harvest.

No system in the commercial computing world of 1962 came close to Tractor’s gigabytes of simultaneously accessible data. At most computer centers at the time, “available” data meant physical racks of tape standing somewhere near its drives—accessible only as rapidly as an operator could manually pull a reel and thread it onto a machine, one at a time, over the course of a shift.

Alpha Was Harvest’s Custom-Built Programming Language

Created jointly by IBM and NSA, the Alpha language existed solely to program Harvest’s streaming dataflow engine for code-breaking work. According to a declassified Pentagon history of NSA computers , Alpha stood for Advanced Language for Programming Harvest.

Alpha allowed the programmer to define the alphabet in which code-breaking data would be processed. The language also included two unusual characters with no equivalent in conventional computing until years later, when Multics and Unix introduced wildcard characters. A “scab” (which was represented on Harvest’s input keyboard, a repurposed early IBM Selectric typewriter, by a “?”) stood for a character that was real but unknown. And a “pad” (represented by a blank space) was a null or spacer. These characters provided flexibility of representation for code breaking jobs, in which unknown or uncertain characters were commonplace.

a man at typewriter typing on keyboard with computer equipment in background; in the center A Harvest operator types on one of the main system consoles, a repurposed IBM Selectric typewriter. IBM

The rules governing Alpha’s operations on strings anticipated other modern rubrics, like “ not a number ”—a designation describing an unknown value in a dataset that can propagate through calculations, rather than silently corrupting them. Strings in Alpha could also be aggregated into cords, and cords into ropes, giving cryptanalysts a hierarchical vocabulary for describing complex intercepts.

Allen wrote a final technical report on her section of the Harvest software when her part of the project concluded—and just as promptly lost access to it. “I spent the good part of a summer on that,” she recalled in 2001. “And it just disappeared into Fort Meade somewhere.”

Replacing an Irreplaceable Machine

By 1971, according to NSA analyst Looney, the machine was running at its highest utilization ever—115 hours of production a week, or more than two-thirds of the time. Yet the number of jobs it processed had been dropping since 1967. Ordinary data-processing work, Looney noted, was by 1972 migrating to newer, general-purpose machines, leaving Harvest to concentrate on the very large, specialized jobs no other system could handle.

At its tenth anniversary of operations, Looney concluded, Harvest was a machine “conceived in the fifties, born in the sixties, and irreplaceable in the seventies.”

He got the last part wrong.

On 27 February 1976, operators shut down Harvest for the last time. A custom mechanical component in the Tractor tape library had worn out, and the manufacturer of the part was no longer in business. By then Harvest had run continuously for nearly a decade and a half—through the roughest close call in the history of mutually assured destruction and into the age of détente—processing intercepts at a rate no civilian machine could touch. By the time it retired, Harvest had outlived several generations of commercial computing.

A wooden plaque with an etched bronze plate that reads \u201cSite of the Harvest computer system, 1962-1976"  A placard commemorates the 1976 decommissioning of IBM’s Harvest computer at the NSA’s headquarters in Fort Meade, Md. National Cryptologic Museum

Somebody at the NSA decided to commemorate the machine with a mock telegram, written under Harvest’s name on the machine’s last day (and now preserved in the agency’s archives). “I first began operations at NSA. Although not widely known, I was probably the largest, fastest, and most technically advanced computer system in the world,” the telegram said. “And now, fourteen years later, the time to retire has come. The cost of my upkeep and operation has been overtaken by more modern equipments and the newer technologies.”

The NSA ultimately replaced Harvest with the landmark Cray-1 supercomputer . The Cray-1 was built from faster, more tightly integrated circuits that could outperform Harvest’s aging transistors at nearly any task, including text processing. Although the Cray was designed primarily for numeric and scientific computing , it sold across many fields—which ultimately made the supercomputer win out once Harvest’s custom-built text-processing hardware was no longer worth the upkeep for just one customer.

The secrecy that shrouded Harvest meant it could claim no lineage of immediate successors. But the ideas it pioneered didn’t disappear—they resurfaced, again and again, in the years that followed.

Tractor’s automated tape library was the forerunner of the robotic storage silos that would become standard in enterprise data centers about 20 years later . Harvest’s pipeline architecture prefigured the dataflow computing movement of the 1980s . The continuous pattern-detecting logic of its match units finds direct echoes in modern hardware packet-inspection intrusion detectors and programmable network switches that today route traffic through the internet at wire speed.

Harvest didn’t found a dynasty. But, in its time, it steadfastly pointed toward the future—in several directions at once.

This article appears in the September 2026 print issue as “ T h e L o s t H i s t o r y o f IBM’ s C o l d - W a r C o d e B r e a k e r .”

When Google Cuts Off Access: Poland and the World

Hacker News
dossier.reasoner.pl
2026-09-15 07:21:09
Comments...
Original Article

In brief: On September 1, 2026, Google disabled the Gmail inbox I had used for fifteen years. The reason: “spam,” without identifying a single message. That day I sent six complaints about Anthropic, the maker of Claude, to 107 recipients. I appealed five times, complained to three public authorities, went to Google’s offices, and sent three registered letters. Four companies stand behind the account, yet Google Poland is not a party to any agreement I have. Google’s servers in Poland are purchased by a company with five employees and no website. The bank resolved my dispute with Anthropic in eighteen days; in two weeks, Google did not identify a single message.

How to read this text

🔗

This article grew out of one question people ask when, overnight, they lose an inbox they have used since 2011: did I encounter one automated system’s mistake, or a way of doing business that can be documented through examples from Poland and around the world, one after another, with dates? The answer took two weeks and is set out below.

I used only publicly available material that anyone can verify without asking anyone for a favour: judgments and court orders, official announcements, company-register extracts, financial statements, reports by ombudsmen and regulators, and press accounts of people who lost access to their own accounts. When a fact comes from an official document, judgment, or register, I state it directly. When I know it from an article, blog, or law firm’s account, I say so in the sentence. Not every case described here concerns a blocked account; some involve competition, data, or advertising. They are included because they show how Google deals with courts and regulators, which matters to anyone trying to get an answer from this company and hearing only an automated system in return.

One more point about method. Several companies sharing an address or board members is a matter of public record. I draw no conclusions from that fact about anyone’s motives or the responsibility of any particular person. Readers are fully entitled to ask themselves why everything was arranged in precisely this way.

Six emails, one button

🔗

I am not a free user. I pay Google for business services, Workspace, and Cloud, and a Warsaw company within the group invoices me [18] . On September 1, 2026, between 15:49 and 15:53, I sent six messages from my private Gmail account to a total of 107 different addresses: 99 media addresses, four public authorities, two Anthropic addresses, and two private addresses. They were complaints about Anthropic, the maker of the Claude assistant and Claude Code tool, which had disabled services I paid for and answered my complaints with a bot. I sent them from Gmail because that was the address I used to log in to Claude, and Anthropic was copied on every message. That is why I did not use email on my own domain: otherwise their bots, which since April have passed my complaints back and forth dozens of times, would not know which account to associate the correspondence with. I did this deliberately because I know how these systems work.

Google’s automated system classified those six emails as spam and disabled the inbox the same day. The list of messages and recipients is attached to the pre-litigation demand and sits in the evidence file on this website [evidence] . Five appeals produced five automated refusals, repeated word for word, without a single concrete detail and without a human signature. On September 9, I filed a formal request through the form Google provides under the EU Digital Services Act; the submission received a number, but no answer arrived.

I later sent the same messages—the ones for which Google’s automated system called me a spammer—again from my business inbox on my own domain. No filter stopped them. Public authorities, companies, and individual newsrooms, including some outside Poland, are replying. People are replying, not a machine. The same text is correspondence to one email provider and a breach of rules to another, punishable by extinguishing a fifteen-year-old inbox. Can a complaint about one corporation cost you an account with another? If this is how they treat a paying business customer, how will they treat you?

The inbox can neither receive nor send, while recovery codes and password resets from other services still go to that dead address. I counted: one account tied me to roughly a hundred services and logins [98] . The account formally remains alive and may yet disappear in its entirety; the terms allow all services to be suspended on the basis of a “reasonable suspicion of harm” [96] . Do you have Gmail? Then find out who has a hand on the switch and whom you will write to once they press it. In Poland, no one is responsible for this.

The hotline sends you to the office. The office sends you to the hotline.

🔗

On September 4, the complaint went to the Office of Electronic Communications; on September 5, to the Personal Data Protection Office; and on September 7, to the Office of Competition and Consumer Protection. On September 5, I sent Google a pre-litigation demand with a deadline of September 9 at 12:00. The deadline passed in silence.

On September 9, I called Google’s helpline. The call is recorded. The consultant hears what the matter is and says: “Please write to the office in Warsaw.” A moment later he advises: “No, I would suggest that you contact them by ordinary post.” I say that, in that case, I will go there in person. He then says: “I understand, but I am unable to help you.” And finally: “You must send that question, send the question to the office, either in Warsaw or in Dublin.”

That same day I went to Rondo Daszyńskiego 2C in Warsaw, the registered office of Google Poland, and this is worth pausing over. That address did not appear in Google Search; it showed an address on Emilii Plater Street and a telephone number that did not exist. I tried calling it: there was no such number. So I decided to take the papers there in person. In the lobby, contract security guards in red Google-branded shirts blocked my way. Nobody came down from the company, although all I asked was for the secretariat to accept the documents—personal service, standard practice at public offices and every private company. They refused fifty-two pages comprising letters, complaints to three authorities, and a pre-litigation demand. “You may not enter the premises,” the security guard told me. As for the documents: “I do not accept these documents.” He did, however, hand me a card with the helpline number: the same helpline that had sent me to this address a few hours earlier (the recording and transcript are held by the author; an anonymised version is available on the website: [evidence] ).

According to the company’s report, 2,348 people work in that building [5a] . Alphabet, the owner of the group, reports 190,820 employees in its annual report [5b] . Which one of them is responsible for a customer in Poland, when the helpline sends the customer to the building and the building sends the customer back to the helpline? On September 11, I sent three registered letters, each containing fifty-four pages: one to Dagmara Brzezińska, the new head of the Polish operation, whom I had also contacted twice through private LinkedIn messages; one to the management board of Google Poland; and one to Dublin. There has been no reply. The full chronology, with dates, times, and proof of posting, is available here: [google-track] , [pisma-do-urzedow] , [google-0909] , [case-status] .

The phone sent me to the address. The address sent me back to the phone. The loop closed in one day.

The bank could. Google couldn't.

🔗

For comparison, consider the other corporation in the same story. I submitted thirteen transactions used to pay Anthropic for services disabled without explanation to the bank as disputed charges. Anthropic contested the claims. The bank compared both sides’ evidence and found in my favour: decisions dated September 8, 9, 10, and 13, totalling PLN 735.86 and EUR 358.68 for twelve of the thirteen transactions; the dispute over the remainder continues [99] . The bank received the same documentation you can read on this website. I do not claim that this caused the outcome. I note the sequence, and it is worth thinking about.

The bank needed eighteen days to read both sides’ evidence and issue reasoned decisions. In two weeks, Google did not identify a single message for which it disabled the inbox. One of these companies boasts at every turn about artificial intelligence that can read and understand. It is not the bank.

Google has eclipsed Anthropic as my immediate problem, but I will return to Anthropic because not every matter involving that company has been resolved. The bank has now done almost everything Anthropic should have done, had it been willing to read its own correspondence. The bank did not tell me that the complaint was “being reviewed” and that it might reverse the refund if the other party disagreed. It compared the evidence and found in my favour.

Part one. Poland

Three companies at one address: which one are you supposed to write to?

🔗

Who exactly is “Google” in Poland? The sign above the entrance shouts one word. The court register gives a far more fragmented answer. Three companies are registered at Rondo Ignacego Daszyńskiego 2C: Google Poland sp. z o.o., Google Cloud Poland sp. z o.o., and Topaz Computing sp. z o.o. [1] . The shared address is simply a fact from the National Court Register and means nothing more by itself. Yet anyone who wants to write to “Google” must know the entities behind those companies and first guess which of the three should receive the letter. It took me two weeks to determine which Google company was my contracting party. A person with thirty years in IT, whose profession today is artificial intelligence, needed two weeks. How long would it take the average person? A rhetorical question.

Google Poland sp. z o.o. has share capital of PLN 800,000. Its shareholders are Google International LLC, with eighteen shares worth a total of PLN 720,000, and Google LLC, with two shares worth PLN 80,000; the ownership chain leads to Alphabet Inc. in the United States [2] . According to the financial statements filed with the National Court Register, Google Poland’s revenue for 2025 was PLN 2.01 billion; a year earlier, according to the trade publication Press, it exceeded PLN 1.53 billion, with net profit of about PLN 128 million and corporate income tax of nearly PLN 38 million [3] . Even so, neither Google Poland nor Google Cloud Poland appears near the very top of the Ministry of Finance’s list of the largest corporate income tax payers for 2024, headed by banks, Orlen, and KGHM [4] . Someone here is better at counting than at answering letters.

Google Cloud Poland sp. z o.o. has a capital of PLN 4 million. The only shareholder disclosed is the Irish-based Google Cloud EMEA Limited, with 3,999 of its 4,000 shares; one share has not been assigned to anyone in the registration data [5] . According to Bizraport, this entity's revenue for 2024 was about PLN 148 million with a net profit of 6.5 million [6] .

Above the entire structure sits an Irish arrangement that must be described precisely, because precision determines where correspondence should be addressed. Raiden Unlimited Company, incorporated in Dublin in December 2006, is a subsidiary of Google Europe, Middle East and Africa Unlimited Company; the ultimate owner is Alphabet [16] . The same Google Europe, Middle East and Africa Unlimited Company is the sole shareholder of Google Ireland Limited, the entity identified as the other party to my email agreement [17] .

A company with 105 million in capital, five employees and zero website

🔗

The third company at this address is the least recognisable and the easiest to overlook. Nobody has heard of it, so I checked the register. Topaz Computing sp. z o.o. was entered in the National Court Register on November 29, 2019. Share capital: PLN 105 million. Sole shareholder: Raiden Unlimited Company in Dublin; above it, Google Europe, Middle East and Africa; above that, Alphabet [7] [16] [17] . Principal activity: data processing and hosting [8] .

The board consists of two people [9] . Valentine Anthony Bohan, Chief Financial Officer, is listed in the UK company register as the director of Google Payment Limited since February 2025 and Google UK Limited since November 2023, with the correspondence address of Gordon House on Barrow Street in Dublin, i.e. at the headquarters of Google Ireland [10] . Svilen Ivanov Karaivanov is Google's legal director in San Francisco [11] . Two gentlemen, one from Dublin, the other from San Francisco, run a company that the average Gmail user has never heard of, although it may be the one that keeps his data. There is no one in Poland on the board of this company.

Google itself says what the company does. In archived Google Cloud Platform subcontractor lists from 2021, Topaz Computing appears under “Data Center Operations,” with an address at Emilii Plater 53 in Warsaw [12] . The entry appears continuously across seven verified versions of the list, from April 2025 to June 2026. It remains on the current list, last modified on August 20, 2026 and read on September 13: same category, same address, and a REGON number matching the register [13] . The next row, at the same address, lists Google Poland as a subcontractor for service maintenance and technical support. Topaz does not appear on the separate Google Workspace subcontractor lists [15] . So: servers.

Now for the numbers from the financial statements the company itself files with the register [12a] . Topaz’s revenue in 2025: PLN 165.7 million, all from a single customer, Google Cloud EMEA in Ireland. A year earlier: PLN 135 million. Net profit for 2025: PLN 59 million. That year the company also bought PLN 104.6 million worth of equipment from companies in its own group. It signed a long-term energy contract whose future commitment was valued in the report at nearly PLN 588 million. Current corporate income tax reported for 2025: about PLN 380,000. All perfectly legal. That is roughly 0.6 percent of net profit and about 0.2 percent of revenue.

How many people generate those PLN 165 million? Five. Headcount over successive years: five, six, five, three, five. Total payroll in 2025: PLN 1.6 million. Every annual report repeats the same formula: no material events, no research and development, no plans for change. A company with PLN 105 million in capital buys hundreds of millions’ worth of equipment and uses it to serve its own corporate group. Who physically operates those servers? Across six annual reports, the operator is not named once.

And one more thing: this company does not exist on the internet. No website. No domain. No profile. It has never advertised a single job on any recruitment site. I began by checking roughly 120 queries across search engines, registers, company databases, forums, and archives. Outside registry entries and one energy contract, the result was zero.

This is not a Polish exception. Google has companies like this in at least eleven countries: Belgium, Germany, the Netherlands, France, Italy, Spain, Switzerland, Finland, Sweden, the United Kingdom, and Mexico. The same pattern, with the same ultimate owner—Alphabet—most often through Raiden in Dublin [17a] . Google’s entire European infrastructure sits behind companies that do not even have websites.

Why include this in an article about a blocked inbox? Because since September 1 I have been looking for a human being at Google. Google Poland will not let me in. Google Cloud Poland sends me invoices. Google Ireland is the other party to the agreement and says nothing. And the servers are purchased by a company with five employees whose board sits in Dublin and San Francisco. Google says nowhere where a Polish user’s data is stored or who is responsible for it. Perhaps Topaz should receive my complaint? I do not know. That is exactly the problem: nobody knows. It is time to find out.

Five employees. PLN 105 million in capital. No website. I found everything described here in public registers over two weeks. Anyone can. The rest is in the case file and in further registers I am still working through.

The address Google did not show its customers for two and a half years

🔗

Google Poland changed its registered address through a National Court Register entry dated February 5, 2024: from Emilii Plater 53 to Rondo Daszyńskiego 2C [2] . Yet Google’s own search engine still showed customers the old address and a disconnected telephone number on September 9, 2026. The address and number disappeared from the results on September 12, three days after my visit to the registered office. Map directories still list the old address today [2a] .

For more than two and a half years, the company that indexes the entire internet directed customers to its own company’s outdated registered office. The address disappeared only after a customer arrived there carrying the case files.

People who left, and people who came

🔗

In 2026, Google’s leadership in Poland changed almost completely—at precisely the moment when someone who had occupied the chair for more than one quarter would have been most useful. Magdalena Kotlarczyk, who joined Google in 2011, led the Polish operation from July 2021 to March 2026. She left the post on March 31, 2026, by her own decision, personally announced, after roughly fifteen years with the company [19] . In August 2026, Forbes included her on its “50 after 50” list: “after reaching this point, she consciously ended her corporate career” [19b] . She therefore left voluntarily and with honours.

That is why one detail in the register deserves a sentence of its own. In the full National Court Register extract for Google Poland obtained on September 14, 2026—almost six months after her departure—Magdalena Kotlarczyk is still listed as the company’s sole commercial proxy [19a] . The proxy of a company that does not accept correspondence is therefore someone who has not worked there for half a year. The company, not she, has the duty to report changes. The register has not kept pace with reality; judging by the lobby at Rondo Daszyńskiego, it is not alone.

Magdalena Dziewguć has been managing Google Cloud Poland since 2021; on June 19, 2026, she announced her own departure without giving a reason, and since November 2024 she has been on the supervisory board of Bank Pekao [20] . In June 2026, Krzysztof Kaziów, head of client engineering for Central and Eastern Europe, joined the management board of Google Cloud Poland [22] . Paul Terence Manicle, CFO for the EMEA region, working in Dublin, has been on the Google Poland board since 2018 [23] . For the sake of order: the Polish branch of Google was co-created by Artur Waliszewski, who headed it from 2006 to around 2019 [25] .

Dagmara Brzezińska became the new director of Google Poland in September 2026, after more than four years as vice-president for sales at InPost and, before that, a director at Allegro [21] . The person to whom I addressed a registered letter on September 11 had held the role for only a few days. I also contacted the new director twice on LinkedIn, on September 9 and September 11, the second time attaching proof that the three registered letters had been posted. As I said above, there has still been no answer. She is probably busy, but the papers will reach her desk today, as they will every other desk they need to reach. Her profile was publishing updates during this period [21a] . Updates can be published. A customer cannot be answered because nobody yet knows who that customer is. They will find out.

What does this mean for the user? Simply that when the inbox was disabled on September 1, the company was in the middle of a changing of the guard. I do not know and do not claim who was actually responsible for user matters during this period, or how the personnel changes affected the circulation of correspondence. I know one thing: no correspondence came back.

Ceneo showed what Google listens to: a judgment with a number on it

🔗

The most important Polish case against Google does not concern a blocked account but competition. It does, however, show exactly what interests me: how Google responds to a Polish court when the court truly insists.

On March 14, 2024, the intellectual property division of the Regional Court in Warsaw granted an interim injunction at the request of the Ceneo comparison service. The court prohibited Google from favouring its own comparison service in search results, subject to PLN 50,000 for every day of non-compliance; the application was granted in full [26] . The proceedings have the publicly confirmed case reference XXII GWO 24/24 and appear in a document of the Office of Competition and Consumer Protection identifying Ceneo.pl as the entitled party and Google Ireland Limited as the obligated party [26] . Google did not comply with the injunction, apparently because PLN 50,000 a day was an amount it could ignore for a while. On May 9, 2025, the District Court for Wrocław-Fabryczna awarded Ceneo PLN 8 million from Google for 160 days of non-compliance, out of the 287 claimed [27] . According to press reports from August 2025, the judgment was not final and Ceneo appealed, seeking an award for all 287 days—PLN 14.35 million [28] . To this day, no public source confirms that the judgment is final or that it has been paid [29] .

In parallel, on December 23, 2024, Ceneo filed a claim for roughly PLN 2.33 billion against Google Ireland Limited, Alphabet Inc., and Google LLC in the Regional Court in Warsaw [30] . Allegro, Ceneo’s owner, states in its consolidated annual report for 2025 that the proceedings are pending and that it cannot predict their outcome [30a] . The court’s electronic hearing list shows case I C 989/2024, with a hearing scheduled for October 9, 2026; the list does not identify the parties, and I do not assume this is the same case [31] . There is a concrete reason for caution: I C 989/2024 is pending in the First Civil Division, while the only reference for the Ceneo dispute with Google confirmed by an official document is XXII GWO 24/24, from the injunction proceedings. Anyone who merged those two references into one case would hand the other side a ready-made objection. No publicly available document identifies who represents Ceneo [32] .

The conclusion is simple and unpleasant. Google did not comply with the Polish court’s injunction until penalties began to run into the millions. The dispute is being pursued by a company with its own legal department and a budget for years of litigation. For an individual user the scale is different, but the mechanism is the same: until there is an order backed by a sanction, requests and letters do not change the other party’s conduct by a millimetre. PLN 50,000 a day could be ignored. PLN 8 million could not.

Other Polish court cases, including one warning

🔗

Polish courts have already ruled against Google, so the thesis that "it makes no sense to sue the giant" does not stick to the facts. In March 2024, the Regional Court in Warsaw, in the case of the personal rights of a legal person violated in the search results, found that Polish law applies when the effects of the violation are felt in Poland [33] . In another case, from 2022, the court found the violation of the plaintiff's personal rights obvious, unlawful and culpable [34] . Google can be sued in Poland. The order "first appeal in the product, then out-of-court authority, finally the court", which Google describes in the communication on protection measures under the Digital Services Act, is a description of the options available, not a mandatory path [35] .

A case from October 2024 is a warning against excessive optimism. The user of a certain website asked the Regional Court in Warsaw to unblock an account immediately and sought interim relief restoring access. The court found the claim manifestly unfounded [36] . Without the full reasons, we do not know why. The result alone says nothing about the prospects of a different case, but it does show that a court is not Google’s inverse—a machine that issues judgments on demand.

YouTube in Poland: archdiocese channel and others

🔗

The most recent Polish case of an account disappearing arose in the same week this text was written, as if someone decided to supply an illustration to order. On the night of September 7-8, 2026, the official channel of the Archdiocese of Łódź, which had been running for over fifteen years, disappeared from YouTube, with about 11,000 recordings, including homilies of subsequent metropolitans, and about 127,000 subscribers [37] . Administrators first received a message that the content was supposed to "seriously or repeatedly" violate community guidelines. The Curia stated that the decision was made without prior contact and without the possibility of explanation; broadcasts, homilies and interviews became unavailable [38] . At the same time, Bishop Piotr Kleszcz announced the blocking of his own channel [39] . On September 9, the Google press office presented journalists with a new version: the channel was temporarily blocked because the system detected a suspected breach, not a violation of the rules [40] . Two versions in two consecutive days, one company. The Curia says it did not receive any instructions to restore the main channel; the only notification went to the parallel-blocked private account of the auxiliary bishop [40] . As of September 14, there is no public confirmation that the channel has been restored; it is not under any of the tested addresses [41] .

A large, recognizable institution first received an automatic message about the violation of the rules, and only after the case reached the media did it hear from Google that it was about something completely different. A single user who does not have a press office or a cardinal on his side remains stuck with the first explanation, without the right to the second.

Other Polish stories from YouTube have a different character and you have to admit it honestly so as not to put everything in one bag. In March 2024, YouTube removed channels linked to the weekly "Najwyższy Czas!", including a channel with 46,000 subscribers; the publisher announced legal action and moved to another platform [42] . In February 2025, Jerzy Zięba's channel was once again blocked [43] . It's a dispute about content moderation; the report does not show the full course of evaluations or appeals, so I do not place them in the same category as the private mailbox lock.

Separately, the report of the IT forum user from August 2025. He tested Google's paid cloud video generation service in the belief that he was using a free limit. Google charged him, as he claims, a number of clips impossible to generate at that time, issued an invoice for nearly PLN 3,400 and suspended the entire cloud account. His hobby website using Google maps has stopped working. Technical support refused a refund, and the logs made available were, according to him, incomplete [44] . This is a one-sided account. However, it describes a mechanism that is repeated in many countries: suspending one account cuts off all related services, like one fuse for the whole house.

Offices: who you can go to in Poland

🔗

The EU Digital Services Act, known as the DSA, provides for a digital services coordinator in each Member State to whom the user can complain about the platform. In Poland, the law implementing these provisions was vetoed by the President on January 9, 2026. The new version, Sejm print number 2694, passed through the Sejm and the Senate: the Sejm passed it on July 31, 2026, the Senate submitted an amendment on August 6, on September 4 the Sejm adopted the amendment and on the same day the law was handed over to the President. According to Art. 122 para. 2 of the Constitution, the President has 21 days to sign, i.e. until September 25, 2026; he can also veto the law or refer it to the Constitutional Tribunal. As of the closing day of this text, the Sejm website has the status of "unfinished work on the law", and on the President's website there is no message about either the signature or the veto [45] . In the meantime, the President of the Office of Electronic Communications, appointed by a resolution of the Council of Ministers of May 13, 2025, temporarily serves as the coordinator. None of his decisions regarding digital platforms are known [46] . So my complaint of September 4 went to a body that formally exists, but is waiting for a law on which to rely, like a guard without a key to the gate that he is supposed to guard. Details of the statutory path: [dsa-polska] .

In the public set of decisions of the Office for Personal Data Protection, as of September 12, 2026, there is no fine imposed directly on Google for violation of the GDPR [47] . The oldest Polish complaint against Google, filed by the Panoptykon Foundation on January 28, 2019 and concerning profiling in advertising, was transferred to Belgium, and the proceedings were suspended until the judgment of the Court of Justice of the European Union. There is still no final decision. The suspension has a legal basis; however, it shows the time horizon that must be taken into account if someone is counting on quick help from this side [48] .

In the set of decisions of the Office of Competition and Consumer Protection, there is also no penalty imposed directly on Google. There are penalties for companies trading false opinions on Google platforms, from 30 to about 50 thousand zlotys, and a merger-control case involving Google [49] . This is what the balance of law enforcement towards Google itself looks like in Poland: a lot of activity around, zero decisions in the center.

The user whose account Google has disabled therefore has three offices in Poland, in whose collections there is no decision against Google on the individual user, the court, which in a competitive case ordered Google to pay millions for breaching an interim injunction, and a company that replaced the management within a year and whose Polish companies are separate from the Irish company indicated as a party to the contract. There is a map, but there is no road on it. There is no one in Poland who is responsible for this.

Part two. World

Germany: the court that ordered the data to be returned

🔗

A court ruling regarding a blocked Google account comes from Germany. This is a decision of the Regional Court in Tübingen from the beginning of 2023, made in interim proceedings. We know it only from a law firm's account, not from the court publication, so I describe them carefully; the full text is not available in any open German database. According to this report, the court ordered Google to allow downloading data from an illegally blocked account: mail, contacts, calendar, documents, photos and videos. The request for full restoration of the account was to be rejected. In 2023, the ruling was not final; its further fate could not be determined [50] [51] .

If that account is accurate, the court did not have to decide whether Google had the right to close the account to order its content to be released. User data and account decision are two separate issues, although Google treats them as one package that disappears completely.

The German media also described the mechanism behind the many locks. In December 2022, Heise reported that Google simplified the appeal procedure after a wave of false alarms: automated scanning flagged innocent photos as material depicting child abuse, which led to the immediate blocking of the account [52] . In 2024, the weekly "Stern" described a user whose photos the system classified in this way; he lost access to all his data [53] . The German tech blog in 2024 called Apple, Google and Microsoft the "dirty three" for the way of blocking accounts without transparency and a viable appeal; this is the opinion of the author of the blog, but it is hardly surprising that someone finally called it that way [54] . German lawyers publish guides for companies with blocked advertising accounts: summons, deadline, application for interim relief. A blockade without an understandable justification is considered to be appealable in Germany [55] .

France: a penalty for ads in the inbox

🔗

The French data protection authority CNIL imposed a fine of 325 million euros on Google on September 1, 2025: 200 million for Google LLC and 125 million for Google Ireland. The reason was the display of ads in Gmail between messages without the consent of users and the lack of valid consent to cookies when creating an account [56] . The decision does not apply to account blocks. However, it shows two things: that the national regulator can punish Google for what happens inside the Gmail inbox, and that the target of the penalty was, among others, Google Ireland, i.e. the same company that is a party to my contract. French lawyers also point out that Google's advertising regulations provide for immediate suspension of the account without warning in the event of a "gross violation"; what is gross is decided by the company itself, i.e. the judge in its own case [57] .

Spain, Netherlands, Italy, United Kingdom

🔗

The Spanish press described two cases similar to mine. In September 2022, the daily "El País" published a report of a user whose account Google closed for "sexual content", without specifying what content it was about; the user lost all the data [58] . The regional service described in 2024 and 2025 the "digital hell" of a person whose account and associated e-mail address were canceled without a clear reason and without the possibility of appeal [59] . I only know both stories from newspapers.

In the Netherlands, the data protection authority accepts complaints against Google regarding the processing of personal data; a complaint can be filed in Dutch or English within six weeks of the company's response, and the authority may impose a ban on processing or a fine [60] . This is the path for data, not account recovery.

In Italy, the competition authority closed an abuse-of-dominance case concerning data portability in July 2023, accepting Google's commitments; previously, the Italian data protection authority found Google Analytics to be incompatible with the GDPR due to data transfer to the United States [61] .

In the UK, the Information Commissioner's Office has dealt with complaints against Google regarding the right to remove data from search results. Most of the 2023 and 2024 cases ended without further action; in one, the office wrote to Google informally [62] . The British register of companies also shows Google Payment Limited, a 2006 company controlled by Alphabet, in which the aforementioned Valentine Anthony Bohan has been the director since February 2025 [63] .

Ireland: where the other party to the agreement lives

🔗

The party to my agreement is Google Ireland Limited. The Irish Data Protection Commission, abbreviated as DPC, acts as the main supervisory authority for Google in the Union. Therefore, the way DPC works is important for the user in Poland, even if that user has never set foot in Dublin.

The complaint of Johnny Ryan and the Irish Council of Civil Liberties about the Google advertising auction system remains a long-standing matter. It has still not been completed. The complainants accused the DPC of not fully examining the case; the Irish High Court dismissed the allegation on 28 August 2023 and the Court of Appeal upheld the ruling [64] . In the DPC decision register, as of September 12, 2026, there is no fine for Google from 2023 to 2026; at the same time, Meta, TikTok, LinkedIn and WhatsApp were fined [65] . DPC has been conducting proceedings against Google since September 12, 2024 regarding the PaLM 2 language model; there is no final decision [66] . The DPC document of November 18, 2022 describes a complaint about access to Google Photos metadata: after the agency's intervention, the company provided the available metadata and the case was considered amicably closed. It was not a dispute about restoring the account [67] .

European Union institutions: numbers and penalties

🔗

The transparency reports that Google submits on the basis of the DSA show the scale of account suspensions in the Union. From September 2023 to February 2024, Google suspended 9,067 accounts, from March to June 2024 3,274, in the second half of 2024 3,621, and in the first half of 2025 3,490. A total of 19,452 suspensions in 22 months. The reports do not separate automatic decisions from human decisions [68] , which in itself answers the question of how much the company cares about this distinction. Any such decision should go to the EU database of justifications, where Google reports data from Maps, Play Store, Shopping and YouTube [69] . Whether it is possible to read from the entries in this database how many decisions were made automatically, it was not possible to determine; the database is difficult to use, and the entries are ambiguous [70] .

Two decisions from 2026 show that EU institutions can punish Google, although not for blocking accounts. On July 2, 2026, the Court of Justice of the European Union upheld the full penalty of €4.125 billion for Android practices; the penalty is final [71] . On the twenty-third of July 2026, the European Commission imposed 890 million euros on Google on the basis of the Digital Markets Act: 460 million for favouring its own search engine services and 430 million for limiting the ability of developers in the Play store to direct customers outside the store [72] . Brussels can enforce billions of euros. So far, it has not obtained a single explanation for one disabled inbox.

ARTICLE 19 described on April 4, 2024, the suspension of its Google Ads account in December 2023 and four months of attempts to obtain explanations; on April 5, the account was restored, which the organization attributed to public pressure. In a separate post of November 13, 2025, Google declared more than 80 percent fewer false suspensions and resolving 99 percent of advertisers' appeals within a day. These are the numbers about Google Ads, given by the company itself. For Gmail users, Google does not publish such numbers, and the outcome of the appeal does not necessarily mean the restoration of the account [73] .

India: about a year without an account because of a childhood photo

🔗

The eloquent individual history comes from India. Neel Shukla, a twenty-four-year-old engineer, lost access to his Google account because the system marked his own childhood photo as material depicting child abuse: a photograph of him aged two in a bathtub, taken by his grandmother. Along with the account, he lost mail, disk, photos and payments. The blockade lasted about a year; the report does not show whether the account was restored. The Gujarat High Court took up the case and banned Google from erasing data [74] . It's a pure mechanism that Heise described in Germany: an automated system that is wrong, and a man who for a year can't convince anyone that it's a mistake. A year. For a bathtub photo from twenty years ago.

Australia: an ombudsman who counts complaints but cannot decide them

🔗

The Australian Telecommunications Ombudsman, abbreviated as TIO, publishes data on complaints about digital platforms. It received 719 complaints in 2025, 20 percent more than a year earlier. A separate list assigns Google 303 submissions and a share of 18 percent, the largest of the companies listed, ahead of Hubbl, Apple, Microsoft and Meta; the bases of the two statements are not identical, so the numbers should not be converted from one to the other. The Ombudsman has no jurisdiction to decide complaints on platforms; he can only register them [75] , which is the official equivalent of compassion without a prescription. The account access category, indicated in the report as 36 percent, includes various platforms, hacks and locks [76] . Earlier, in 2021 and 2022, an Australian federal court ruled at the request of the Consumer Protection Office that Google was misleading users about the collection of location data, and ordered a payment of 60 million Australian dollars [77] .

North and South America

🔗

In the United States, there is no single body to which a user could file a complaint about an account blocking; there are court cases and actions of the Federal Trade Commission. A class action is pending in a federal court in California against Google to withdraw a free version of office services for businesses and threaten to suspend accounts for those who do not start paying; the court has certified the class, the deadline to opt out has passed on January 5, 2026, and the trial has not yet taken place [78] . According to reports from 2025, the Federal Trade Commission investigated whether YouTube misled users about the rules for suspending accounts, deleting content, and the right to appeal [79] . In January 2024, EPIC and Accountable Tech filed a complaint with the same commission about Google's breach of an earlier location data settlement [80] .

Google itself publishes the rules for such an eventuality, which sounds almost like politeness. After suspending a company Workspace account for non-payment, the data does not disappear immediately: Google stores it for about 51 to 60 days, after which it can be permanently deleted [81] . The Google Fi phone service account may be suspended for arrears, suspected fraud, unusual activity or violation of the terms and conditions, and after 60 days terminated with the loss of the number; users described suspensions for reasons they did not understand [82] . In May 2024, Ars Technica described a case in which Google Cloud mistakenly deleted a business customer account, resulting in two weeks of downtime; the error was on Google's side [83] . The same service warned in January 2025 that after the expiration of the domains of former Google customers, the logins to related services could be taken over by the new owners of these domains [84] . For the sake of order: YouTube in 2018 and 2019 carried out mass removals of channels for hate speech, in one quarter over 17 thousand; this is a different type of decision, conscious and announced to the public [85] .

Canada's Privacy Commissioner ruled in 2025 that the complaint against Google was justified and remained unresolved; it was a refusal to remove articles from search results by name [86] . In Argentina, the daily newspaper "La Nación" described a user who went on vacation and on his return did not have access to an account with 400,000 photos; Google closed the account without a clear justification, and the user has been trying to retrieve them for two years [87] . In Brazil, there is a formal consumer path: a government complaints platform, a consumer protection authority and a court; the procedure requires a tax number and an account in the government system [88] .

Further corners and other categories

🔗

A few examples from other countries belong to different categories and I list them to distinguish them from the blocking of a private mailbox, not to add ballast. In Japan, the media described the policy of removing inactive accounts for two years, announced in 2020 and implemented in 2023; it is a structural problem, not an arbitrary block [89] . In South Korea, complaints of privacy violations related to Google accounts are accepted by the state's Internet security agency [90] . In Nigeria, according to the newspaper "Punch", Google, Microsoft and TikTok deactivated a total of 13.5 million accounts in 2024 with more than 750,000 complaints; the number concerns three companies together [91] . In South Africa, the competition committee reportedly conducted an investigation into the bias of Google's algorithms against local media [92] . The UN Special Rapporteur wrote in August 2024 about the disproportionate restriction of Palestinian content after October 7, 2023, including the suspension of accounts [93] . Advertising-account cutoffs in Russia, Crimea, Iran and Syria from 2022 to 2024 resulted from sanctions [94] , and the blocks of Google services in Turkey from 2007 to 2020 were decisions of the state, not the company [95] .

Part three. What follows from this

Common pattern

🔗

If you put aside competitive issues, sanctions and state censorship, one repetitive mechanism, identical on three continents, emerges from the cases described. The decision to close the account is made by the system, not the human; this can be seen in the German and Indian cases of children's photos, in the Łódź channel, which first "violated the rules" and then turned out to be "suspected breach", in my mailbox, which became "spam" for six complaints, and in EU reports, which cannot say how many of the 19,452 suspensions anyone has read. The justification remains general: "spam", "sexual content", "serious or repeated violation", without indicating anything specific. The appeal returns to the same system and brings the same answer as if someone were bouncing the ball off the same wall. The user's data becomes hostage to the account decision, although the court in Tübingen has shown that the two issues can be separated when someone really demands it. External channels are weak: the Australian ombudsman counts complaints, but cannot settle them; the Irish regulator has been conducting cases for years and has not punished Google for three years; the Polish coordinator exists temporarily and is waiting for the bill, which in turn is waiting for signature.

There is also the other side of this pattern. Where the court appears with a sanction, Google reacts: daily penalties in the Ceneo case increased to 8 million zlotys before anything changed; the Tübingen court ordered the release of data; the Gujarat High Court banned the deletion of Neel Shukla's data. For the user, this means that letters and complaints build documentation, but the change is forced only by a ruling with a specific amount. Complaints to the offices create a trace, and the publicity of the case caused the Łódź curia to get a different explanation from Google in two days, instead of waiting a year like an engineer in India. And one more thing: the bank I went to with the same set of evidence settled the dispute in eighteen days. It's possible. You just have to want to read.

Where the similarity ends

🔗

The Ceneo case, the penalties of the European Commission and the Court of Justice, the decision of the French CNIL and the Australian court are about competition, advertising and data, not closed accounts; they talk about how Google treats regulators, not how it treats users. YouTube channels related to political journalism or alternative medicine are disputes about moderation of content. Cut-offs in Russia or Iran are sanctions. Blockades in Turkey are decisions of the state. The numbers from Australia and Nigeria include many companies and many causes at once. I know the stories from Argentina, Spain and the Polish forum from one-sided accounts. Anyone who compares these examples in public should always indicate which category a given case belongs to, instead of throwing everything into one convenient bag with the inscription "Google". I did it. I'm waiting for Google to do the same with my mailbox: it will indicate the category, the message and the person who signed it.

What we don't know

🔗

Finally, a list of questions that this text does not answer because public sources are silent. I don't know if the case of I C 989/2024 in the Warsaw court's hearing list is a Ceneo lawsuit. I don't know if the fine of 8 million zlotys is legally binding and paid. I don't know who represents Ceneo. I don't know if the channel of the Archdiocese of Łódź has been restored; the state as of the closing day of the text is still a 404 error. I don't know how the case ended in Tübingen. I don't know who physically operates Topaz Computing servers and where the Polish user's data is located. I don't know what the results of Gmail users' appeals are, because Google only publishes numbers for advertisers. I do not know what will happen to the law on the coordinator of digital services, although the deadline for the President's decision is September 25. Each of these questions can be checked. This is the next to-do list, not an excuse for today. And we will definitely find out.

What disappears with the inbox

🔗

One block is not an “email problem”. A disabled Gmail account is a dead inbox, which continues to receive recovery codes and password resets from services to which I logged in with this address: LinkedIn, Dropbox, Cloudflare and about a hundred others [98] . Access to paid Workspace is cut off in the company. And the entire account may disappear: the conditions list among the grounds for the suspension gross or repeated violation of the rules, but also a legal obligation and a "reasonable suspicion of damage" [96] .

Google Help describes how to download data from some services after an account is disabled and nowhere does it guarantee that this will be possible [97] . In my Google Takeout of September 3, among the sixty-two items available for download, there was no Gmail [98] . Mail, which the machine recognized as spam, cannot even be downloaded. Sixty-two items to download. No mail.

🔗

Your mailbox may disappear in the same way, and no one will answer. What will disappear with it? Documents from Drive. Children's photos. Calendar. Channel. Android phone. Logins that have been your digital life for years and that you will not have time to recreate from memory. Who will answer you? In Poland no one; the rules say directly: Dublin.

How long does an appeal take? In my case five attempts, five identical refusals, zero humans on the other side. And if you run a business, the whole company hangs on one account that can be switched off by a machine you cannot call, cannot write to and cannot visit, because security will not let you in. Do one thing today: check what you can really download from your account, before the account decides for you.

One "log in with Google" button in ten places is ten doors on one lock. The key to this lock is held by the machine, which does not answer the phone. Convenience today; hostage tomorrow. I made this mistake once. I won't do it a second time. I am warning you in advance. The address is one, the man on the other side is not there, and the hand on the switch belongs to the machine.

Do not sign in everywhere with Gmail. You will lose everything through a machine's mistake. In Poland, there is no one accountable.

---

Sources

Poland

[1] National Court Register (KRS), current extracts: https://api-krs.ms.gov.pl/api/krs/odpisAktualny/0000240611 , https://api-krs.ms.gov.pl/api/krs/odpisAktualny/0000812767 , https://api-krs.ms.gov.pl/api/krs/odpisAktualny/0000840059 .

[2] KRS, full copy of Google Poland sp. z o.o., no. 0000240611, as of 25.06.2026: capital and partners; address of Emilia Plater 53 deleted by entry No. 46 of 05.02.2024, address of Rondo Ignacy Daszyńskiego 2C entered by the same entry; reading OdpisPelny 14.09.2026, https://api-krs.ms.gov.pl/api/krs/OdpisPelny/0000240611?rejestr=P&format=json .

[2a] Screenshots of Google search results of 09.09 and 12.09.2026 and records of map catalogues, author's documentation of 12.09.2026; copy on the case website: [evidence] , https://dossier.reasoner.pl/pl/ .

[3] Financial statements of Google Poland sp. z o.o. for 2025, https://ekrs.ms.gov.pl (revenue PLN 2.01 billion); year 2024: https://press.pl/tresc/88068 (revenue over PLN 1.53 billion, net profit approx. PLN 128 million, income tax approx. PLN 38 million).

[4] https://gazetaprawna.pl/podatki/artykuly/10774839 (the largest CIT payers 2024).

[5] KRS, copy of Google Cloud Poland sp. z o.o., no. 0000840059 (as of 01.07.2026).

[5a] Financial statement of Google Poland sp. z o.o. (employment 2,348 persons), https://ekrs.ms.gov.pl ; figure quoted on the case website, https://dossier.reasoner.pl/pl/ .

[5b] Alphabet Inc., annual report (Form 10-K) for the fiscal year ending 31.12.2025: 190,820 employees, https://www.sec.gov/Archives/edgar/data/1652044/000165204426000018/goog-20251231.htm , read 14.09.2026.

[6] https://bizraport.pl/krs/0000840059 , secondary source, data aggregator; does not replace the original financial statement.

[7] KRS, extract of Topaz Computing sp. z o.o., no. 0000812767 (as of 29.04.2026): entry 29.11.2019, capital PLN 105,000,000, shareholder Raiden Unlimited Company; https://rejestr.io/krs/812767 .

[8] KRS, copy of Topaz Computing, PKD codes.

[9] National Court Register, copy of Topaz Computing, board data.

[10] Companies House UK, company 05903713, officers: https://find-and-update.company-information.service.gov.uk/company/05903713 ; professional profile V. Bohana.

[11] Professional profile of S. Karaivanov (LinkedIn); KRS Topaz Computing, board data.

[12] Google Cloud Platform subprocessor lists, archived versions from 2021: https://cloud.google.com/terms/subprocessors/index-20210708 , https://cloud.google.com/terms/subprocessors-20210610 .

[12a] Financial statements of Topaz Computing sp. z o.o. for 2020-2025, https://ekrs.ms.gov.pl , read 13.09.2026 (revenue, profit, equipment purchases, employment, payroll, energy contract commitment, corporate income tax).

[13] https://cloud.google.com/archive/terms/service-data-subprocessors-20260604 and archived versions 2025-04-30 to 2026-06-04; current list https://cloud.google.com/terms/subprocessors , "Last modified August 20, 2026", reading 13.09.2026 (copy of the page preserved).

[15] https://workspace.google.com/terms/subprocessors ; https://cloud.google.com/terms/sccs/3p-subprocessors .

[16] https://solocheck.ie/Irish-Company/Raiden-Unlimited-Company-431739 ; according to this source Raiden has a handful of employees and assets counted in billions of euros.

[17] https://solocheck.ie/Irish-Company/Google-Europe-Middle-East-And-Africa-Unlimited-Company-657921 ; thecurrency.news, Google structure diagram 2023 (PDF).

[17a] A list of the group's infrastructure companies in eleven countries on the basis of national registers; for some companies, the owner is Alphabet or Google LLC directly, and for two (Italy, Mexico) the partner is not confirmed in the original source.

[18] Issuer of invoices: two invoices issued for the author's company, Google Workspace from 01.02.2026 and Google Cloud from 01.06.2026, issuer Google Cloud Poland sp. z o.o.; author's private documents, archived reading 13.09.2026. They are not proof of a payment provider or a private Gmail provider.

[18a] Google Payments terms of service (Polish version of 04.03.2025): service provider Google Ireland Limited, https://payments.google.com , read 13.09.2026.

[18b] Google One subscription terms: seller of consumer services Google Commerce Limited, Dublin, https://one.google.com/terms-of-service?hl=pl , read 14.09.2026.

[18c] Google Payments terms for sellers: Google Payment Ireland Limited, Dublin, registration no. 598776, https://payments.google.com ; three Google entities operate in the KRS and none has "Payment" in its name, read 14.09.2026.

[18d] Google Cloud Help: "local billing entity: Google Cloud Poland sp. z o.o.", https://support.google.com/cloud/answer/10119385 , read 14.09.2026.

[19] Professional profile of M. Kotlarczyk (LinkedIn).

[19a] KRS, full copy of Google Poland sp. z o.o.: self-existent proxy entered by entry No. 41 of 29.12.2022, not deleted; reading OdpisPelny 14.09.2026; departure 31.03.2026: https://press.pl (91091), https://xyz.pl (3438), wirtualnemedia.pl.

[19b] Forbes Women, "Forbes Women 50 over 50 list", 20.08.2026: https://www.forbes.pl/forbeswomen/zawodowy-piwot-po-kilku-dekadach-kariery-jest-mozliwy-lista-forbes-women-50-po-50/gz7nq2x , read 14.09.2026.

[20] Professional profile of M. Dziewguć (LinkedIn); departure announcement 19.06.2026, https://xyz.pl (3927), wirtualnemedia.pl.

[21] Professional profile of D. Brzezińska (LinkedIn).

[21a] Messages to D. Brzezińska via LinkedIn of 09.09 and 11.09.2026 (the second with proof of posting of the registered letters), unanswered as of 14.09.2026; screenshots in the author's documentation: [evidence] .

[22] Professional profile of K. Kaziowa (LinkedIn).

[23] Professional profile of P. Manicle (LinkedIn); KRS Google Poland, board data.

[25] Professional profile A. Waliszewski (LinkedIn); industry press.

[26] https://gazetaprawna.pl/firma-i-prawo/artykuly/9488026 ; https://uokik.gov.pl/bip/ceneopl-sp-z-oo-z-siedziba-we-wroclawiu ; https://uokik.gov.pl/Download/766 .

[27] https://gazetaprawna.pl/firma/artykuly/11191513 ; https://tvn24.pl/biznes/tech/google-zaplaci-ceneo-8-mln-zl (st8615847).

[28] https://itreseller.pl (Ceneo wins against Google); https://gazetaprawna.pl/firma-i-prawo/artykuly/9870677 .

[29] https://wroclaw.so.gov.pl/wokanda ; https://wroclaw.sa.gov.pl/wokanda (no trace of finality).

[30] https://bankier.pl/wiadomosc/Ceneo-pl-zlozylo-pozew-przeciwko-Google (8865932); https://tvn24.pl/biznes/tech/ceneo-kontra-google (st8234703).

[30a] Allegro.eu, Consolidated Annual Report for 2025, p. 256: "The proceedings are pending. The Company cannot predict the outcome of the legal proceedings.", https://about.allegro.eu/wp-content/uploads/2026/03/ALLEGRO-Annual-Consolidated-Report-2025.pdf , read 14.09.2026.

[31] https://bip.warszawa.so.gov.pl/e-wokanda/1339 .

[32] https://uokik.gov.pl/Download/766 ; https://wozniaklegal.com/en/news-and-insight/406 ; https://kg-legal.eu .

[33] https://orzeczenia.ms.gov.pl , IV C 608/19, District Court Warsaw, reasoning of 13.03.2024.

[34] https://saos.org.pl/judgments/537304 (I C 733/22).

[35] https://gazetaprawna.pl/twoje-prawo/artykuly/10822746 ; https://support.google.com/european-union-digital-services-act-redress-options/answer/13535501 .

[36] https://orzeczenia.warszawa.so.gov.pl , II C 630/24, 01.10.2024.

[37] https://stacja7.pl (the case of the disappearance of the channel of the Archdiocese of Łódź); TVP3 Łódź, lodz.tvp.pl (95320173).

[38] https://dorzeczy.pl/religia/940105 .

[39] https://stacja7.pl , as above.

[40] https://stacja7.pl , as above (Google press office position via Gazeta.pl); https://idziemy.pl/spoleczenstwo (93731); https://deon.pl/kosciol/znamy-powod-znikniecia-kanalu-archidiecezji-lodzkiej-z-youtube-wcale-nie-chodzilo-o-naruszenie-zasad,3527017 .

[41] Review of press sources and direct verification of YouTube channel addresses (answer 404) 13.09 in the evening and 14.09.2026, without confirmation of restoration.

[42] nczas.info, 13.03.2024.

[43] spidersweb.pl, February 2025.

[44] https://forum.pasja-informatyki.pl/599154 .

[45] https://sejm.gov.pl , the legislative process for Bill No. 2694 (July 31, 2026 adoption, August 6, 2026 position of the Senate, September 4, 2026 adoption of the amendment and submission to the President; status as of 13.09.2026); art. 122 para. 2 of the Constitution of the Republic of Poland; https://prezydent.pl , signed laws.

[46] https://uke.gov.pl/uslugi-cyfrowe/koordynator ; Council of Ministers resolution of 13.05.2025.

[47] https://uodo.gov.pl , decision register.

[48] https://panoptykon.org/iab-sprawa-tcf .

[49] https://decyzje.uokik.gov.pl .

World

[50] https://recht.help , 03.02.2023 (LG Tübingen, law firm's account).

[51] Not to be confused with the Tübingen case: a document of the Regional Court in Stuttgart with a similar case number, 8 O 16/23 (hearing 22.03.2023), concerns a Facebook user's data and a dispute with Meta Platforms Ireland, not the blocking of a Google account; https://www.wbs.legal/wp-content/uploads/2023/07/LG-Stuttgart-8-O-16-23.pdf , PDF pp. 1-3, read 13.09.2026.

[52] https://heise.de/news/Missbrauchsverdacht-Google-erleichtert-Einspruch-gegen-Accountsperren-7445938 .

[53] https://stern.de/panorama/konto-sperrung (32656698).

[54] https://borncity.com/blog/2024/03/24/kontensperren-apple-google-microsoft-die-schmutzigen-drei .

[55] https://anwalt.de/rechtstipps/kontosperrung-bei-google-ads (242707).

[56] https://cnil.fr , decision of 01.09.2025 and press release.

[57] Publication by lawyer Charles Simon, September 2024 (LinkedIn).

[58] https://elpais.com/tecnologia/2022-09-17 .

[59] https://cmmedia.es/noticias/espana/calvario-digital-google-cancela-mi-cuenta-correo-asociado .

[60] https://autoriteitpersoonsgegevens.nl/en/submitting-a-tip-off-or-a-complaint-to-the-ap .

[61] https://en.agcm.it/en/media/press-releases/2023/7/A552 .

[62] https://ico.org.uk , data sets on complaints for Q4 2023/24.

[63] https://find-and-update.company-information.service.gov.uk/company/05903713 .

[64] breakingnews.ie (High Court dismisses claim that DPC failed to fully investigate alleged Google data breach).

[65] https://dataprotection.ie/en/dpc-guidance/decisions/fines .

[66] https://dataprotection.ie , announcement of the initiation of proceedings on the Google AI model, 12.09.2024.

[67] DPC, document of 18.11.2022 concerning Google Ireland Limited and Google Photos metadata, https://www.edpb.europa.eu/system/files/2026-08/ie_2022-11_decisionpublic_redacted_1.pdf , points 1, 3, 7a, 10-12; reading of the preserved copy 13.09.2026.

[68] Google transparency reports under the DSA, https://storage.googleapis.com/transparencyreport (periods IX 2023 - VI 2025).

[69] https://transparency.dsa.ec.europa.eu/explore-data/download .

[70] As above; self-assessment of database availability.

[71] https://curia.europa.eu , press release no. 93/26 of 02.07.2026.

[72] https://digital-markets-act.ec.europa.eu , communication from 23.07.2026 about a penalty of 890 million euros.

[73] https://www.article19.org/resources/google-article-19-unfounded-accusations-of-unacceptable-business-practices/ (04.04.2024, updated 05.04.2024); https://blog.google/products/ads-commerce/improved-accuracy-account-suspensions/ (13.11.2025). The first source is the relationship of the organization; the second is Google's declaration of Ads, not Gmail.

[74] https://livelaw.in , Gujarat High Court, Neel Shukli case (252700).

[75] https://tio.com.au/news (record-high digital platform complaints 2025); https://tio.com.au/data-and-reports/digital-platforms-complaints-insights .

[76] As above, TIO report of December 2025, category table.

[77] https://accc.gov.au/media-release/google-llc-to-pay-60-million-for-misleading-representations .

[78] https://cand.uscourts.gov , Rabin v. Google LLC, 5:22-cv-04547.

[79] outlookbusiness.com/deeptech (FTC probe, YouTube policies).

[80] epic.org, complaint to the FTC of 18.01.2024 (PDF).

[81] https://knowledge.workspace.google.com/admin/billing/restore-a-suspended-subscription .

[82] https://phonearena.com/news/google-fi-accounts-suspended (171097).

[83] https://arstechnica.com/gadgets/2024/05/google-cloud-accidentally-nukes-customer-account .

[84] https://arstechnica.com/security/2025/01/startup-necromancy .

[85] https://en.wikipedia.org/wiki/YouTube_suspensions .

[86] https://priv.gc.ca , PIPEDA Findings 2025-002.

[87] https://lanacion.com.ar/tecnologia (nid2119277).

[88] https://support.google.com/accounts/community-video/387577500 (pt-BR), social media material, not the source of the Brazilian authority.

[89] https://itmedia.co.jp/pcuser/articles/2312/03/news032 .

[90] https://kisa.or.kr/1030402 .

[91] https://punchng.com/google-microsoft-tiktok-block-13-5m-accounts-in-nigeria .

[92] vanguardngr.com, 03.2026 (SERAP, FCCPC).

[93] https://un.org/unispal , report of the Special Rapporteur of 23.08.2024.

[94] https://seroundtable.com/google-deactivates-russia-based-adsense-accounts-37889 .

[95] https://en.wikipedia.org/wiki/Censorship_of_Google .

[96] Terms of use of Google services, Polish version of 30.07.2026, section "Suspension and blocking access to Google services", https://policies.google.com/terms?hl=pl&gl=pl , copy preserved on 07.09.2026; extract constituting Annex 6 to the UOKiK notice.

[97] Google Help, "Your account has been disabled", https://support.google.com/accounts/answer/40695?hl=pl , read 13.09.2026; preserved copy of HTML in the author's proof folder.

[98] Author's statements: a list of 62 Google Takeout items without Gmail, statement dated 03.09.2026 (attachment to the UODO complaint); number of services and logins from the recording of a conversation with the Google hotline from 09.09.2026, in which the author informs about about a hundred logins connected to the account. Recording and transcript in the author's possession, anonymized version on the website.

[99] Revolut Business decisions on the contested Anthropic charges: refunds from September 8, 9, 10 and 13, 2026, a total of PLN 735.86 and EUR 358.68 for twelve of the thirteen transactions; documents on the case page: revolut-20260913-decyzja-c-public, revolut-20260913-api-wyciag-public, https://dossier.reasoner.pl/pl/ .

25 Years of Mass Surveillance Is Enough

Schneier
www.schneier.com
2026-09-15 07:01:41
This essay was written with Cindy Cohn, and originally appeared in Lawfare. One of the many legacies of the terrorist attacks of Sept. 11 is the government-wide shift from targeted surveillance—such as individual wiretaps or pen register/trap and trace orders—to mass surveillance techniq...
Original Article

This essay was written with Cindy Cohn, and originally appeared in Lawfare .

One of the many legacies of the terrorist attacks of Sept. 11 is the government-wide shift from targeted surveillance—such as individual wiretaps or pen register/trap and trace orders—to mass surveillance techniques—such as tapping into the internet backbone or mass collection of telephone or internet metadata. The legal and technical architecture of modern mass surveillance, initially framed as a necessary defense against terrorist threats, has grown far beyond that justification and national security in general. Mass surveillance is now a routine tool used by law enforcement. ICE uses it in immigration actions and against people exercising their First Amendment rights to protest. It’s also increasingly part of private security systems, such as facial recognition at venues such as Madison Square Garden and networked Flock license plate capture systems on roads and in parking lots.

The interrelation between private and governmental mass surveillance is worth examining. Surveillance is the business model of the internet; companies like Google and Facebook constantly spy on their users’ behavior. From the National Security Agency relying on data collected by telecommunication and internet companies, to local sheriffs and ICE agents relying on cellphone location data and privately managed automatic license plate readers, governments primarily obtain the mass surveillance information through private companies. Increasingly, access doesn’t just come through legal processes, either. FBI Director Kash Patel recently confirmed in congressional testimony that the agency is purchasing information on Americans from data brokers and intends to continue to do so.

This pipeline from private collection to governmental collection means that as companies collect more information for surveillance capitalism purposes, more is available to law enforcement as well. And as the technology for mass surveillance and analysis improves, especially with the increased use of AI technologies, the problems attendant to mass surveillance grow as well.

After 9/11, the idea that the government could surveil the population to safety took hold. In 2001, the fear of terrorism reached a frequency and intensity never before seen. Along with that came the fear that the enemy could be anyone, anywhere. As a result, the government’s response was to watch everyone, everywhere. This line of reasoning underpinned the shift from targeted to mass surveillance. Or, in the words of an internal National Security Agency (NSA) presentation that was made public as part of Edward Snowden’s 2013 disclosures, a government that can “Collect it All,” “Process it All,” “Exploit it All,” “Partner it All,” and “Sniff it All,” will ultimately, “Know it All.” Similar rationales support the rise of domestic mass surveillance: if law enforcement could see and hear everything, it could more effectively interdict and solve serious crimes.

The national security community has never provided a full analysis of the costs and benefits of these mass surveillance programs, either in terms of taxpayer dollars or diversion of resources from other efforts—or any demonstration that those techniques stopped attacks that otherwise they would not have been able to prevent. While the NSA occasionally presents examples of the successes due to its mass surveillance programs, especially when those techniques are under public pressure, the examples also regularly fall apart upon serious scrutiny. And even if some utility exists, it must be seriously weighed against the costs.

Similarly, there has never been any comprehensive analysis about whether domestic immigration or law enforcement’s use of these techniques actually makes people safer, or whether other techniques could produce the same results. Instead, both the police and the companies selling these tools float anecdotes and dubious data . For example, Flock’s data equates the number of law enforcement hits in their database with actually solving crimes.

Twenty-five years after 9/11, it seems reasonable to step back and evaluate the costs of this shift to mass surveillance, especially in terms of Americans’ rights and freedoms.

The Shift

The easiest place to see a shift to mass surveillance was in the government’s decision immediately after 9/11 to collect Americans’ telephone records. The program started under an argument of pure executive power as the “President’s Surveillance Program.” But in 2006, that argument secretly shifted to a novel interpretation of Section 215 of the Patriot. Act which had only previously authorized more targeted access to record. While some media and public interest organizations struggled to force the government to reveal the program as early as late 2005, the government only officially confirmed it after the 2013 Snowden disclosures. In 2015, the Second Circuit Court of Appeals rejected the government’s interpretation of Section 215 as allowing mass collection of telephone records. Later the same year, Congress passed the USA Freedom Act . While this new law still allows collection of a tremendous amount of domestic telephone records, it ended the indiscriminate mass collection that had occurred for nearly fourteen years.

Other shifts to mass surveillance continue through today. The NSA launched its Upstream program, which involved intercepting both metadata and content from key telecommunications junctures inside the U.S., soon after 9/11. It was also initially conducted under a claim of purely presidential authority. This program was brought under marginal congressional and programmatic (not targeted) Foreign Intelligence Surveillance Act (FISA) court review via Section 702 of the 2008 FISA Amendments Act. In 2017, more than15 years after its inception, the NSA ended content searches due to FISA court pressure, but the mass collection continues.

Despite the stated goal of conducting mass spying only on people outside the U.S.—which itself is problematic given international law’s requirement that surveillance be both necessary and proportionate —mass surveillance collects a tremendous amount of U.S. persons’ communications. This can happen because people communicate with people abroad, or because of overcollection—when government agencies gather far more personal data on non-targeted US persons than authorized by law. The concerns about collecting Americans’ data on U.S. soil led Congress to allow the program to officially expire in 2026, although the previously-approved mass surveillance itself continues until at least Spring of 2027.

The shift to mass surveillance would be notable enough even if it remained only a strategy of the intelligence community. It has not. Americans are awash in mass surveillance. Networks of automated license plate readers such as those offered by Flock and Vigilant Solutions blanket both public and private roadways and parking lots. These networks often allow searches by law enforcement, including across jurisdictions. They are, for example, being used to track people seeking abortions across state lines. Facial recognition tools, once the province of only the more elite parts of federal law enforcement, are increasingly used by Immigration and Customs Enforcement agents on immigrants and protesters, in airports by the Transportation Security Administration , as well as by private entities . And, of course, modern phones track users’ locations constantly—and that information is readily available to law enforcement, often with only minimal process protections.

Constitutional Costs

Regardless of the murkiness of its actual usefulness, the shift from targeted to mass surveillance has profound implications for Americans’rights. It has created risks that have become increasingly evident, especially under the Trump administration.

At a basic level, the Fourth Amendment guarantees that citizens can be secure in their “persons, houses, papers and effects” from unreasonable searches. Warrants breaching that security should be supported by probable cause and particular descriptions of the place to be searched and items to be seized. Mass surveillance turns that promise on its head, allowing access to our “papers and effects” by the government without individualized suspicion or a particularized description of what data is being seized, much less probable cause. This protection was in response to colonial British misuse of writs of assistance , which authorized indiscriminate searches rather than targeted ones.

The justifications for exempting mass surveillance from constitutional protection vary. For Section 702, the government has taken the position that U.S. persons’ communications caught up in the dragnet, either due to overcollection or because they were communicating with someone outside the United States, do not require a warrant prior to initial collection or secondary access by the FBI and several other agencies. The argument is that if the initial collection was not aimed at Americans, the information is free from constitutional protection for any later uses, even for reasons far afield from the initial rationale for collection.

Other arguments rest on the claim that metadata is outside the Fourth Amendment, despite its demonstrated ability to reveal intimate details of all of our lives. Still others rest on the Supreme Court-created Third Party Doctrine , which holds that the Fourth Amendment does not apply to data shared with companies that provide us with services. Some turn on whether analysis by machine counts , claiming that only “human eyes” matter—a particularly troubling argument with the rise of artificial intelligence. What’s more, the government has used doctrines like standing to limit the ability of those subjected to mass surveillance to seek constitutional protection. No matter the argument, the goal is the same: to place the mechanisms and fruits of mass surveillance outside the protections of the Fourth Amendment.

The overarching truth is that, due to the concerted efforts by the government since 9/11, and the rise of technologies in recent years, the slice of Americans’ lives and data that are actually protected by the Fourth Amendment has shrunk significantly in the past 25 years. Together, with the technical capabilities of mass surveillance and the increased ability for that data to be analyzed using AI tools, the “security in our papers and effects” that the constitution promises seems increasingly illusory.

In addition to the Fourth Amendment, mass surveillance creates tensions with the First Amendment. The Constitution has long recognized that the right to freedom of speech requires a zone of privacy against governmental surveillance. The right to anonymous speech as well as the right of association both recognize the chilling effect that surveillance creates for people saying unpopular things or attempting to organize for political or other societal change. Mass surveillance grants the authorities the ability to track those people, both in real time and historically, that is inconsistent with actual techniques of freedom of speech and assembly.

That is why the recently released 2026 U.S. Counterterrorism Strategy is so troubling. On page seven, the White House expressly states that it intends to target domestic activists with its heretofore foreign-targeted powers. It says that the government “will prioritize the rapid identification and neutralization of violent secular political groups whose ideology is anti-American, radically pro-transgender and anarchist” and “will use all the tools constitutionally available to us to map them at home, identify their membership, map their ties to international organizations like Antifa.” While framed as targeting “violent” groups, it’s clear that the government intends to use its national security tools, presumably including the tools of mass surveillance, against Americans in ways that will create profound tensions with the First Amendment rights of people to organize and communicate privately.

Costs Due to Mistakes and Abuse

Even assuming some utility from mass surveillance—a fact we do not dispute, even if the public record is shaky and conclusory—the history of both the national security and domestic uses of mass surveillance confirms that these tools are inevitably misused , and that mistakes have impacted huge numbers of Americans. The past twenty-five years have demonstrated that it is not possible to surveil the entire US population while staying within the bounds of even a very generous legal framework like Section 702.

As Rep. Zoe Lofgren (D-Calif.) recently stated in discussion of Section 702 in an interview with Tech Policy Press : “backdoor searches have been used improperly for protestors, 19,000 campaign donors, members of Congress, journalists, government officials, a state court judge who had complained to the FBI about police misconduct. It has been abused substantially in the past.” The NSA experienced so much abuse of its mass surveillance tools by actual or aspiring romantic partners and ex-spouses that an internal name emerged for it: “ LOVEINT ,” or Love Intelligence.

That same pattern of abuse is now emerging at the domestic law enforcement level. A Texas police officer misused , and then lied about, using license plate readers to track a woman suspected of seeking an abortion. Multiple law enforcement officials have been accused of tracking people they either wished to have a relationship with or who were their exes. And mass surveillance technologies have been used to track both immigration targets and citizens engaging in their First Amendment-protected right to track and record the police.

Mistakes are inevitable with collections of data of this size and scope. The history of the FISA court’s reviews of Section 702 is littered with examples of the NSA not being able to follow its own rules limiting the scope of what it collects and analyzes, even after having been given multiple chances by the court. On the local level, the technical protections that Flock, for example, put in place have repeatedly been insufficient to stop “accidental” sharing its data with out-of-state law enforcement. These mistakes have fueled growing efforts by local communities across the country to remove license plate readers. Those efforts should be the first step in a broader reconsideration of mass surveillance.

More generally, ubiquitous surveillance carries a real societal cost. The chilling effects are real and pervasive , and they tend to fall hardest on the most marginalized members of society. Moreover, social progress requires the ability to experiment in secret. It’s hard to imagine a society progressing morally to the point of accepting and legalizing things like marijuana use or gay marriage if the earliest signs of that shift are snuffed out because of overzealous surveillance.

Reversing Course

While a cost-benefit analysis is not the best frame for deciding constitutional rights, it is a place to start to evaluate government policies. If the costs are too high and the benefits too small, what should the public do? While the policy and legal frameworks can be individually complex, mass surveillance is a problem in all of its applications. So too should solutions be comprehensive rather than piecemeal.

One comprehensive strategy is to reset the promise of the Fourth Amendment and recognize that a warrant is required prior to collection, access or use of information gathered through mass surveillance. This would apply to collections that include U.S. persons, whether done for national security or domestic purposes. This protection would apply regardless of whether the information is in the form of metadata. It would apply regardless of whether the information is held in homes or by services people rely on, such as telephones, internet or social network providers, or by private entities utilizing mass surveillance for their own purposes. By passing this legislation, Congress could ensure this rejection of mass surveillance, and include real enforcement such as a private right of action and an automatic exclusionary remedy in criminal prosecutions. The courts could also recognize this protection of “papers and effects” directly as a plain language interpretation of the Fourth Amendment.

There are already a number of efforts that take on pieces of mass surveillance. Section 702 has expired and should remain so. This was due largely to efforts to block the “back door” access to Section 702-collected data without warrants. The bipartisan “ Fourth Amendment is Not for Sale Act ” would prevent the government from purchasing data that it would otherwise need a warrant to obtain. The Supreme Court itself has already been chipping away at the Third Party Doctrine, with a recent step in the rejection of mass geofence warrants—warrants seeking the identities of individuals based upon their proximity to a crime—in Chatrie v. United States . Now, such warrants fall, at least initially, under the Fourth Amendment.

A more comprehensive approach would also address mass surveillance carried out by private companies, and to ensure that Americans have the right to encrypt and secure their data. There are many reasons the United States would benefit from a comprehensive privacy law —and curbing mass surveillance is one of them. Addressing mass surveillance is certainly one of them. Ideas such as the banning of secondary uses of data—with roots in the Fair Information Practice Principles from the 1970s—are worth pushing forward. So are moves such as creating fiduciary duties for mass data collectors. There are many more ways to curtail private companies’ mass surveillance while staying within constitutional boundaries. But addressing the costs of mass surveillance by both companies and governments is even more important in a world where AI agents are making decisions both about the public and on their behalf based on their data and observed behavior.

Twenty-five years after the U.S. government embraced mass surveillance, it’s time to evaluate it as a whole, and consider responses that address the problem as a whole. Americans must ask: Is it consistent with a self-governing democracy to have systems that watch everyone everywhere? Is the public comfortable with governments—federal, state, local—that seek to “know it all” about its citizens? Is the public comfortable with private mass surveillance in its own right and as it’s being increasingly used to fuel government surveillance? These questions have long needed serious consideration. But as it becomes increasingly evident that the Trump administration is using mass surveillance to keep itself in power, stifle dissent, and undermine political opponents, these questions are now more urgent than ever.

Tags: ,

Posted on September 15, 2026 at 7:01 AM 0 Comments

Sidebar photo of Bruce Schneier by Joe MacInnis.

Democrats say supreme court rejection of Trump mail ballot restrictions will ensure ‘safe, secure and accurate elections’ – US politics live

Guardian
www.theguardian.com
2026-09-15 06:41:39
Comments come after court on Monday rejected the president’s mail ballot restrictions ahead of the November midterm electionsSupreme court rejects Trump’s mail ballot restrictions for midterm electionsThe board of Washington’s John F Kennedy Center for the Performing Arts is due to vote on Tuesday ...
Original Article

Supreme court rejection of Trump mail ballot restrictions will ensure 'safe, secure and accurate elections'

Hello and welcome to the US politics live blog.

Lawmakers have given their support to the supreme court’s rejection of Donald Trump’s bid to restrict mail ballots for the midterm elections.

The decision capped a flurry of last-minute legal action with voting already underway, with states allowed to continue sending out mail ballots under the same processes used for years.

Election officials have said there was simply no way to carry out a complete overhaul in the weeks before the midterms. Alabama, North Carolina and Wisconsin began sending mail ballots to voters over the past week while the new system was still not active.

Washington’s secretary of state Steve Hobbs, a Democrat, said the decision means work “to carry out a safe, secure, and accurate election” can continue ‘without having to upend our election processes to meet unrealistic ballot mail requirements.”

In Arizona, another largely vote-by-mail state, Democratic secretary of state Adrian Fontes said “it is crucial for us to continue rejecting the notion that access and security are mutually exclusive when it comes to running strong elections.”

Utah Lt Gov Deidre Henderson, a Republican serving as the state’s chief election officer, said on social media that the supreme court’s decision means “Utahns can have confidence that the 2026 election will proceed as normal.”

In other developments:

  • Mitch McConnell , the 84-year-old former Senate majority leader from Kentucky, was seen in Washington for the first time in months on Monday, minutes before his office released a statement saying that he was back at work.

  • With a slip of the tongue reminiscent of Joe Biden, Gavin Newsom , California’s Democratic governor, told CNN that he would not run for president in 2028 if his fellow Californian, former vice-president Kamala Harris , decides to run again, since “I know what that means: I know her base of supporters, I know her friends, the zen diagram on that is just pure crossover.”

  • Donald Trump claimed there is a “SICK conspiracy” against artificial intelligence and data centers in response to the growing calls for greater checks on AI development.

  • In a statement posted on social media , Barack Obama , the former US president, called for the pace of AI development to be slowed to allow time for the federal government to develop regulations to address “serious safety concerns”.

Key events

FBI director Kash Patel is set to return to Capitol Hill on Tuesday for an oversight hearing before the Senate Judiciary Committee.

The Republican-led panel is likely to try to keep the focus on Patel’s efforts to combat violent crime, counter drug trafficking and pursue the Trump administration’s agenda from his perch at the nation’s premier federal law enforcement agency.

But Democrats will almost certainly seize on the tumult inside the bureau over the last year, including sweeping firings of agents who participated in investigations into president Donald Trump , efforts by the bureau to investigate Trump’s perceived political adversaries and Patel’s travel schedule that has blended private leisure with professional responsibilities.

Patel has made multiple appearances before Congress while serving as director, including to testify about the FBI’s budget, but this will be his first time since last September before the Senate committee that has principal oversight jurisdiction over the bureau.

The board of Washington’s John F Kennedy Center for the Performing Arts is due to vote on Tuesday on whether it will continue to operate, with directors largely installed by president Donald Trump arguing that its future hinges ⁠on having his name on ⁠the complex.

Trump’s name has been added ​to and then removed from one of the nation’s top performing arts venues once already this year after federal judges ruled that only Congress could legally change the name of what it designated to be a “living memorial” to ⁠Kennedy, the 35th US president, who was assassinated in 1963.

“The Board understands that without such appropriate recognition it is unlikely that president Trump will provide the fundamental oversight of the renovation of the main building and lead the fiscal rescue of the Center,” the draft resolution says.

The draft resolution says the center’s finances are dire and it will be unable to honor its payroll obligations or routine maintenance contracts “within a matter of weeks.“

Trump facing AI backlash in Congress as push for guardrails intensifies

David Smith

David Smith

Donald Trump is facing a rare backlash from the US Congress as Democrats and some Republicans push for guardrails on the world’s most powerful AI companies.

Concerns over the dangerous potential of AI reached fever pitch this week after tech leaders sounded the alarm over the rapid advancement of the technology and its potential threat to humanity.

Trump dismissed the anxieties on Monday, describing them as a “HOAX” and “conspiracy” and insisting on social media: “The only control or ‘guardrails’ that AI needs is a STRONG AND SMART (High IQ!) PRESIDENT, and the U.S.A. has that, in spades!”

But the 80-year-old president, who has sidelined Congress on numerous issues during his second term, looks increasingly isolated as members of both major parties acknowledged the risks and expressed a desire to act before it was too late.

Don Beyer , a Democratic congressman from Virginia, said: “History will look very poorly on that tweet. He’s not paying attention – or he’s paying attention to the wrong people. He’s being really foolish: not the first time he’s been really foolish but perhaps catastrophically foolish in this case.”

Recent surveys by the University of Maryland’s Program for Public Consultation found 85% of Democrats and 79% of Republicans want a new federal agency to monitor and regulate AI; and 82% of Democrats and 78% of Republicans favour government safety tests for AI making critical decisions.

Supreme court rejection of Trump mail ballot restrictions will ensure 'safe, secure and accurate elections'

Hello and welcome to the US politics live blog.

Lawmakers have given their support to the supreme court’s rejection of Donald Trump’s bid to restrict mail ballots for the midterm elections.

The decision capped a flurry of last-minute legal action with voting already underway, with states allowed to continue sending out mail ballots under the same processes used for years.

Election officials have said there was simply no way to carry out a complete overhaul in the weeks before the midterms. Alabama, North Carolina and Wisconsin began sending mail ballots to voters over the past week while the new system was still not active.

Washington’s secretary of state Steve Hobbs, a Democrat, said the decision means work “to carry out a safe, secure, and accurate election” can continue ‘without having to upend our election processes to meet unrealistic ballot mail requirements.”

In Arizona, another largely vote-by-mail state, Democratic secretary of state Adrian Fontes said “it is crucial for us to continue rejecting the notion that access and security are mutually exclusive when it comes to running strong elections.”

Utah Lt Gov Deidre Henderson, a Republican serving as the state’s chief election officer, said on social media that the supreme court’s decision means “Utahns can have confidence that the 2026 election will proceed as normal.”

In other developments:

  • Mitch McConnell , the 84-year-old former Senate majority leader from Kentucky, was seen in Washington for the first time in months on Monday, minutes before his office released a statement saying that he was back at work.

  • With a slip of the tongue reminiscent of Joe Biden, Gavin Newsom , California’s Democratic governor, told CNN that he would not run for president in 2028 if his fellow Californian, former vice-president Kamala Harris , decides to run again, since “I know what that means: I know her base of supporters, I know her friends, the zen diagram on that is just pure crossover.”

  • Donald Trump claimed there is a “SICK conspiracy” against artificial intelligence and data centers in response to the growing calls for greater checks on AI development.

  • In a statement posted on social media , Barack Obama , the former US president, called for the pace of AI development to be slowed to allow time for the federal government to develop regulations to address “serious safety concerns”.

Simpler time map plots (2017)

Lobsters
lepisma.xyz
2026-09-15 06:31:20
Comments...
Original Article

Proposing a one dimensional version of time map plots that doesn't lose any information displayed in most cases and is cleaner to understand.

B ehavior of time series is tricky to judge from simple plots. The process generating the series can have multiple underlying mechanics and simple plots make it hard to discern these. Sometime back I read about time maps , which try to solve a part of this problem (I encourage you to read that post before this one). Time maps target visualizing time differences between discrete events. This is helpful, for example, to see if there are multiple modes in the repetition of some events. If on a single day you eat 5 times from 9 AM to 11 AM and call it a day, your eating time plot will have repetitions for those small intervals and for the daily one. A time map captures these two modes easily.

Consider the following time series. The x axis shows date and the y axis shows my lastfm scrobbles per day.

My daily lastfm scrobbles

Nothing much to it. If I plot it as a time map, I get this

Scrobbles time map

Each dot here is a listen. The \(x\) value being the time difference between it and the previous listen, \(y\) being the difference with next listen. Note that the plot is log scaled (thus the repeated pattern in lower values) which helps us understand the diffs at multiple scales.

Another important point is the almost symmetry along \(x = y\) line. When you use the pre-event and post-event time diffs of each event as \(x\) and \(y\) value for plotting, one points \(x\) will be previous one's (according to event ordering) \(y\) value. Now this has consequences on whether a time map is useful for you. What follows is the same plot with marginal histograms along the axis. No doubt the \(x\) and \(y\) marginals are similar. This is not because of the data but because of the way both axis values are derived, resulting in a non-exact symmetry.

Time map with marginals

Think about a point in top left corner. This refers to an event which was preceded by another event shortly but is followed by the next event after a long time gap. The opposite happens with point in bottom right. Because adjacent events share \(x\) and \(y\), the mass of points has similar distributions (notice that this is not the case with very few points). Unless you are displaying another data dimension using color / size of the circles (like in the original blog , where we see points colored according to the time of day), the two dimensions here just add to the visual clutter.


Let's tweak the lastfm data a little bit. Now, the scrobbles are filtered to show only the first listen of each song. To give this some meaning, a lot of these filtered scrobbles in a short time span would mean that I explored more as compared to repeating the same old songs.

First listen time maps

Notice how easy it is to find the bumps in the marginal plot. A plot of \(x\) marginal follows.

\(x\) marginal plot of first listens

As a side note this plot makes me wonder about the origin of the bumps. The initial rise up to \(x = 3, 4\) (around 20, 50 minutes) is mostly due to radios listens (which give you fresh songs frequently) or binging on some new album/artist. The one around 7 (around 18 hours) might be a session change. A new session, with fresh items probably. Need to dig in the actual songs to understand this.

1. Tweets

The original blog made a time map for tweets of @BarackObama . I did a re-crawl. Here is the time plot of tweets per day.

Tweets per day @BarackObama

Next is the full time map for the series.

Tweets time map @BarackObama

As argued earlier, unless we are showing extra information, its much easier to see the marginal to get the frequency behavior of the series. See the plot below.

Tweets time map 1D @BarackObama .

2. Gotchas

  • Making sense of a histogram in log scale (the kind I used, with uniform bins over log scaled data; You can have non-uniform, log scaled bins too. I haven't tried that) is tricky. The bins and density don't exactly go as you would think. Additionally you would see repeated pattern (exposing the discrete values) in the beginning and smoothing in the end. More rigorous analysis should be done to derive something other than qualitative meanings from these.
  • Add to it the number-of-bins problem. Plots above use Freedman-Diaconis rule to get the number of bins. Changing this number can result in different views as shown below.

Tweets time map with 10 bins

Tweets time map with 200 bins


Time maps are neat exploratory tools. To me, they have more qualitative value than quantitative. Most of the visualizations with more than a few dozen points make more sense qualitatively and are better without unnecessary details, that's why we prefer heatmaps instead of regular scatter in certain cases. A marginal time map follows the same idea.

Suspected sabotage causes major Netherlands rail disruption

Hacker News
www.bbc.com
2026-09-15 06:22:46
Comments...
Original Article

EPA Two people are seen from behind looking up at an information board at Zwolle station in Zwolle, Netherlands, where trains are facing significant disuption EPA

Parts of the Netherlands, including Amsterdam, have been hit by major rail disruption after suspected sabotage to the tracks, the country's railway infrastructure operator has said.

ProRail warned that passengers in the country's centre and north should expect cancellations and delays on Tuesday, while the closure of some level crossings could cause disruption to roads.

Pipes were attached to the tracks in multiple locations, according to local media. In a statement, ProRail said the incident appeared to be "an intentional disruption... caused by human action".

It added that no suspects or motives had been identified, but the national police unit was investigating.

The BBC has contacted police for comment.

The network's website showed that at least 15 disruptions had been recorded across the country on Tuesday, with routes to the main international airport, Schiphol, and Utrecht also affected.

Train operator Nederlandse Spoorwegen said services around Zwolle, Deventer, and Amersfoot had been disrupted.

It added that the disruption was so extensive it was "not possible" to provide a bus replacement service.

A Eurostar train from Amsterdam to Paris was also delayed due to "operational restrictions" at Amsterdam Centraal on Tuesday, although it was not clear if this was linked to wider disruption. The BBC has contacted Eurostar for further information.

ProRail said in a statement: "Materials were found at multiple locations in the track that were deliberately placed there. This causes section malfunctions. Sabotage appears to be involved".

EPA Rail workers in hi vis jackets are pictured at a level crossing in Holten in the Netherlands on Tuesday. In the background a lorry can be seen parked on the other side of the crossing, while another worker is seen next to a large pile of building sacks. EPA

Some level crossings were closed as a result of Tuesday's disruption

Footage published by Dutch public broadcaster NOS on Tuesday showed police carrying what appeared to be metal pipes - about 1.5 metres (5ft) long - away from railway tracks at Veenendaal in the central Netherlands.

One train in the city of Steenwijk struck one of the objects, ProRail said, but added there were no injuries as a result.

According to prosecutors, a section of rail found on the track may have caused the crash. But France's interior minister Laurent Nunez has urged caution while an investigation is ongoing, saying that authorities are pursuing all leads.

On the NSA’s Supercomputer from the 1960s

Schneier
www.schneier.com
2026-09-15 06:16:24
Really interesting story about Harvest, a specialized code breaking computer built in the 1960s by IBM for the NSA....
Original Article

Atom Feed Subscribe to comments on this entry

Leave a comment

Login

Allowed HTML <a href="URL"> • <em> <cite> <i> • <strong> <b> • <sub> <sup> • <ul> <ol> <li> • <blockquote> <pre> Markdown Extra syntax via https://michelf.ca/projects/php-markdown/extra/

Sidebar photo of Bruce Schneier by Joe MacInnis.

Let's make quality the norm again

Hacker News
www.forbrukerradet.no
2026-09-15 06:00:16
Comments...
Original Article

A more circular economy is not only important for the environment. It can also strengthen consumer rights and improve societal resilience. In this report, we show how consumer policy can make circular choices easier, safer and more attractive for consumers.

Iran War Has the U.S. Military on the Brink of a “Breakdown”

Intercept
theintercept.com
2026-09-15 06:00:00
As the U.S. military faces relentless attacks on its bases, the Iran conflict is “ultimately unsustainable,” one U.S. official told The Intercept. The post Iran War Has the U.S. Military on the Brink of a “Breakdown” appeared first on The Intercept....
Original Article

The U.S. military is straining under the pressures of the Iran war, according to two U.S. officials familiar with operations in the Middle East. One cautioned that this stress could lead to a “breakdown” in the military’s ability to effectively continue a war that President Donald Trump said would end in early April .

As the conflict has engulfed the region, the military has grappled with a growing list of challenges: Iranian attacks on U.S. bases and warships in the Middle East, depleted stockpiles of defensive munitions, increasing casualties, maintenance issues on ships, lengthy troop deployments, complicated logistics, flagging morale and a host of other issues that have taken a heavy toll on troops, damaged military readiness, and a strained Pentagon budget — contradicting moths of rosy pronouncements by the Trump administration.

Both officials said that Iran is facing extreme economic pressure, according to intelligence reports. The first official characterized recent Iranian attacks on warships with ballistic missiles as a “go for broke” strategy to increase economic pressures on America ahead of the U.S. midterms. That official said it was unclear which country would “blink first.”

Trump suggested on Sunday that the U.S. could “stay and keep the oil” in Iran, like the neo-colonial resource grab the administration is carrying out in Venezuela . The second official said that a U.S. occupation of Iran would be “almost impossible” and that the idea was “nonsensical obviously.”

Iran’s ability to overwhelm U.S. air defenses in the Middle East using attack drones and advanced ballistic missiles, as previously reported by The Intercept , has left the U.S. military in a precarious state. The Trump administration failed to appreciate Iran’s military prowess, said the first official. The other pointed to a “complete lack of planning” for the war. Since July 7 alone, there have been hundreds of U.S. casualties caused by Iranian attacks, according to official Pentagon statistics .

The officials said the military’s ability to function at peak capacity was crippled in the opening hours of the conflict when an Iranian missile and drone attack on February 28 destroyed Navy facilities in Manama, Bahrain, the region’s most critical logistics hub. The Pentagon claimed for months that the strikes did not significantly impact military operations, but last week acting Navy Secretary Hung Cao admitted “they blew the hell out of Bahrain.” More than six months after the attack, Naval Support Activity Bahrain remains a shambles. “We’re not getting back in there anytime soon,” said Chief of Naval Operations Adm. Daryl Caudle, the Navy’s highest-ranking officer, during a recent town hall .

A Wall Street Journal investigation previously found that rebuilding the U.S. Navy’s Fifth Fleet headquarters, barracks, and communication towers at NSA Bahrain may cost more than $400 million, which the second official said could be an underestimate if it’s ever attempted. Naval Forces Central Command referred questions about the cost estimate to Central Command, which did not respond .

Trump has touted the destruction of Iran’s navy as one of his signature accomplishments of the war, but the officials pointed to the severe strain placed on U.S. naval forces in the region. With the home of the Fifth Fleet knocked out and other ports in the region within range of Iranian missiles and drones, the Navy found itself facing the daunting challenge of maintaining a flotilla of more than 24 ships — including two massive aircraft carriers and numerous guided-missile destroyers and cruisers, attack submarines, or supply ships deployed at any one time.

While aircraft carriers are nuclear-powered, the planes and helicopters on them, as well as the other ships, require millions of gallons of fuel each week. And the sailors aboard the ships require hundreds of thousands of meals. This means Navy ships need to haul supplies more than 2,000 miles from a longtime U.S. base on the Indian Ocean island of Diego Garcia. A War Department inspector general report released on Monday noted that the shift in “naval bases and support ports posed significant challenges,” pointing specifically to the “issue with shifting logistics support and infrastructure to Diego Garcia” and the challenges of “extended sea transit requiring 14- to 18-day logistics cycles.”

“It’s difficult and ultimately unsustainable,” said the second official, who told The Intercept that White House assumptions that Iran would “crack” before the Navy have yet to be borne out.

The Navy has just 11 aircraft carrier strike groups, and amid the ongoing strain of extended deployments, its oldest carrier, the USS Nimitz, has seen its service life extended . So far, four of the 11 carrier strike groups have already been deployed to the Middle East during the war. Some of the remaining carriers are undergoing substantial repairs and will not be ready to deploy again for years.

The USS Gerald R. Ford Carrier Strike Group completed a record-breaking, 11-month deployment that also included the invasion of Venezuela. (Under normal conditions, a carrier would visit port about once a month to allow its crew to rest.) The ship was, according to a Presidential Unit Citation , under “persistent threat from enemy missiles and one-way attack drones” during its deployment to the Middle East.

Early in the Iran war, the Ford suffered extensive damage from a laundry fire and a malfunctioning fire-suppression system that forced crew members to manually fight the blaze for around 30 hours. The fire halted sorties for two days, and around 600 sailors lost access to their bunks due to the damage. The Navy claimed few casualties from the blaze, but reports soon emerged that the more than 200 sailors were treated for smoke inhalation or lacerations.

The Ford has also been plagued by plumbing issues for years. During just a four-day span in 2025, there were 205 breakdowns . Before the Ford left the Caribbean, it was facing constant issues with the nearly 650 toilets aboard . And then in the Middle East, the carrier again suffered from clogged, excrement-filled toilets, according to video obtained by CNN .

Another carrier, the USS Abraham Lincoln, spent most of its nine months at sea with no port calls, leading to reports of rampant morale problems, supply shortages, and suicide attempts, with at least one crew member going overboard . At an August 11 town hall in San Diego, families expressed deep concerns about their loved ones to Cao, the acting Navy secretary. But Trump dismissed worries about the sailors, saying the Lincoln’s deployment was “not nearly long enough.” Last month, the Lincoln was relieved by the USS George Washington, allowing the heavily rusted Lincoln to pull into Thailand for a port call.

Those ships have left the region but sailors remain under persistent threat. The Islamic Revolutionary Guard Corps targeted a U.S. aircraft carrier and guided-missile destroyer with ballistic missiles this month, according to Central Command . CENTCOM did not reply to a request for additional information about the strikes.

Despite months of claims by Trump and self-styled War Secretary Pete Hegseth that Iran’s military was annihilated , Iran has attacked more than 15 bases across the Middle East, according to information from U.S. officials and Iranian reports.

Stocks of advanced air-defense missiles, like Patriot and THAAD interceptors, are so depleted that U.S. forces are not shooting down all Iranian missiles and drones aimed at U.S. and allied targets across the Middle East, according to U.S. officials who spoke to The Intercept as well as numerous press reports.

Trump and Hegseth have pushed back on these claims. Trump posted on Monday that the U.S. is “ producing more Exquisite and Elite Weapons than at any time in our History,” but the aforementioned inspector general report immediately contradicted the president, revealing that the conflict has resulted in “strategic inventory shortfalls and revealed industrial base bottlenecks for munitions resupply.”

“I have all the munitions necessary to both defend our forces as well as conduct a broad range of contingencies,” Adm. Brad Cooper, the CENTCOM chief, told Congress in May . But even the Pentagon admits that 410 U.S. troops have been killed or wounded since July 7 alone. Three U.S. soldiers were killed during Iranian ballistic missile and drone attacks on Jordan’s Muwaffaq Salti Air Base on July 17 and 18, for example. And an Iranian drone attack on Erbil Air Base, Iraq, on July 18 killed one soldier and left another injured during what CENTCOM called a “controlled detonation” of the downed aircraft.

The U.S. and its allies burned through more than 11,000 munitions , including large amounts of defensive missiles, in the first 16 days of the war alone, according to a March analysis by the Royal United Services Institute. About 65 percent of Patriot interceptors were expended between February and July, leaving fewer than 830 remaining from a pre-war total of 2,330, according to an analysis by the Center for Strategic and International Studies. The number of THAADs dropped from more than 450 to less than 270.

On Sunday, Cooper, the CENTCOM commander, continued to downplay the risks of depleted stockpiles and told CBS’s “60 Minutes” that he wasn’t worried for the safety of U.S. troops. “I’m not concerned at all,” he replied when asked about defending against Iranian attacks. CENTCOM did not reply when asked if Cooper would resign if U.S. troops were killed or wounded in future attacks.

In addition to the destruction of NSA Bahrain, the Combined Air Operations Center at Al Udeid Air Base, Qatar, was severely damaged by Iranian missiles early in the war, rendering it inoperable. Both were recently attacked again , raising questions about whether either will ever be rebuilt. Iran also struck Muwaffaq Salti Air Base in Jordan last week, damaging multiple U.S. military aircraft, according to the second U.S. official. In response, U.S. forces fired a “significant” number of Patriot interceptors to protect U.S. forces, the second official said.

U.S. officials confirmed that Iran has also attacked Tower 22, a U.S. military outpost on the northern Jordanian border with Syria. Satellite images as well as photos and videos circulating online indicate the base suffered extensive damage. (In 2024, a drone attack on Tower 22 by an Iran-backed militia killed three U.S. troops.)

Iran says it has also attacked U.S. military facilities at Jordan’s Prince Hassan Air Base and King Faisal Air Base ; and Kuwait’s Al-Adiri base , Ahmed Al-Jaber Air Base , Camp Doha, Ali Al Salem Air Base, and Camp Arifjan . Last week, the IRGC announced its forces had “simultaneously attack[ed] the American bases in Jordan, Bahrain, and Erbil,” including “a combined missile-drone attack on the headquarters and residence of the American commander of the Ali al-Salem base.”

CENTCOM failed to respond to a request for comment about attacks on its outposts or provide its own official count of the number struck. But the Pentagon inspector general report revealed Iranian strikes “damaged and destroyed hundreds of buildings and structures at U.S. bases in Kuwait, Bahrain, Qatar, UAE, Saudi Arabia, Iraq, Oman, and Jordan during the conflict.” It also disclosed that “dozens of U.S. aircraft were destroyed or damaged,” including four F-15 fighter aircraft, 12 KC-135 refueling aircraft, and as many as 30 MQ-9 Reaper drones.

Suspected Black Axe gang leaders face cybercrime charges in the US

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 05:50:25
Five alleged leaders of the Black Axe cybercrime syndicate, known for its involvement in global-scale cyber-enabled financial fraud, have been extradited to the United States to face wire fraud and money laundering charges. [...]...
Original Article

Hackers

Five alleged leaders of the Black Axe cybercrime syndicate, known for its involvement in global-scale cyber-enabled financial fraud, have been extradited to the United States to face wire fraud and money laundering charges.

Prosecutors accuse Perry Osagiede, Franklyn Osagiede, Osariemen Clement, Collins Otughwor, and Musa Mudashiru of coordinating an internet fraud campaign from Cape Town from 2011 to 2021 that involved advance fee schemes and romance scams.

The five accomplices allegedly used multiple aliases, social media and online dating websites, and voice over internet protocol (VoIP) phone numbers to talk to victims in the United States and trick them into sending money after making them believe they were in a romantic relationship.

When their targets refused to send them money, they coerced them into making the payments using various manipulation tactics, including threats of publishing sensitive photos of the victim online.

"Black Axe is a notoriously violent transnational criminal organization that also happens to dabble in romance scams to make money," FBI Newark Special Agent in Charge Stefanie Roddy said .

"The ability of FBI Newark and our partner agencies to reach into South Africa illustrates our resolve to hold accountable any and every type of fraudster who preys on innocent victims here in the United States."

The defendants were arrested in South Africa in 2021 at the United States' request and extradited to the United States on September 11, 2026.

If found guilty, they face a maximum of 20 years in prison for the wire fraud charges, a maximum of 20 years for money laundering, and two more years for aggravated identity theft charges.

Last month, law enforcement agencies from 22 countries arrested 58 individuals and identified 263 suspects linked to cybercrime networks coordinated by African crime groups as part of "Operation Jackal IV," an international joint action focused on disrupting the Black Axe criminal ring.

Spanish authorities also arrested 34 cyber fraud suspects believed to be part of a criminal network connected to the Black Axe group.

Founded in 1977 in Nigeria, Black Axe is one of the world's most dangerous and far-reaching cybercrime syndicates, believed to have an extensive network of money mules and facilitators, as well as 30,000 registered members.

In January 2024, the United States sentenced Black Axe member Olugbenga Lawal to ten years in prison for laundering millions stolen by Black Axe operators from elderly Americans in internet fraud schemes.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

The best iPhones: which Apple smartphone is right for you, according to our expert

Guardian
www.theguardian.com
2026-09-15 05:48:35
Looking for a new iPhone or a good deal on a refurbished one? Samuel Gibbs has tested and rated Apple’s smartphones, including the best for camera quality and battery life • How to make your smartphone last longer The best iPhone may be the one you already own. There’s generally no need to buy a fre...
Original Article

T he best iPhone may be the one you already own. There’s generally no need to buy a fresh phone just because new models have been released, as hardware updates are broadly iterative, adding small bits to an already accomplished package. But if you do want a replacement handset, whether new or refurbished, here are the best devices of the current crop of Apple smartphones.

Many other smartphones are available besides the iPhone, but if you’re an Apple user and don’t fancy switching to Android , there are still a few choices to make. Whether your priority is the longest battery life, the best camera, the biggest screen or simply the optimal balance of features and price, there’s more to choose from in the Apple ecosystem than you may expect.

Apple recently announced the iPhone 18 Pro and Pro Max, which are direct replacements for the previous Pro models and will be available from 18 September. They feature faster chips and a variable-aperture main camera for more creative photography options. It also unveiled its first folding phone, the iPhone Duo, which adopts a squat, passport-like shape with a 5.4in screen on the outside and a 7.6in tablet screen on the inside when you open it like a book. The iPhone Duo will be available from 23 September.

It did not announce a regular iPhone 18, leaving the iPhone 17 as Apple’s standard model for now. It also took the unusual step of adding £100 to the price of every model, including older iPhones still on sale, as RAMageddon continues to drive up component costs . This guide will be updated once the new iPhones have been thoroughly tested.


How I tested

Quick Guide

How I tested

Show

We combine real-world testing with various tools, such as benchmarking systems that perform standardised tasks. These help us evaluate a phone, measure its performance, confirm that it works as expected and compare it with its competition and predecessors.

We use the phones at different times and in various environments, from firing off emails on packed commuter trains to weekends spent shooting photos in national parks, and everything in between. We do everything a typical smartphone user would, such as messaging, browsing, using apps, listening to music, watching videos, playing games and navigating the real world. That gives us a good impression of how a smartphone handles the rigours of day-to-day life – plus, it shows us how long the battery lasts and the strength of its wireless performance.

The findings from our general use of the phones are combined with the results from specific tests for things such as the camera zoom, video playback and charging, to inform the reviews and help us rank the devices.


Why should you trust me?

I have been reviewing consumer electronics for 18 years, with more than a decade spent as the Guardian’s gadget expert. In that time I’ve seen all manner of tech fads come and go, smartphone giants rise and fall, the cutting edge morph into the mainstream, and have poked, prodded and evaluated more than 1,000 devices – sometimes to destruction .


At a glance


The best iPhones you can buy

Apple iPhone 17 Pro.
‘Great for games and other intensive tasks’: the iPhone 17 Pro. Photograph: Samuel Gibbs/The Guardian

Best iPhone for most people:
iPhone 17

Apple iPhone 17
Photograph: Samuel Gibbs/The Guardian
£899 at John Lewis
£899 at Apple

The base-model iPhone has been the best for most people for years, but the iPhone 17 is closer to the Pro models in features than ever before, making it an instant recommendation.

While it may not look that different from its predecessor, the iPhone 16, the screen has finally been upgraded to bring it up to par with the Pro iPhones. It is a 6.3in super-bright OLED with a 120Hz refresh rate – double previous standard iPhones – and ensures day-to-day scrolling and animations are smoother and more fluid than ever before. The screen’s glass is stated as being three times tougher than previous models, while the always-on display mode shows the time, notifications and music when idle – another feature that was Pro-only until now.

The rest of the iPhone 17 is fairly standard. It has the rapid A19 chip, but with twice the starting storage at 256GB of its predecessor – which should be more than enough for most people. The battery lasts just shy of two days between charges, and it fast-charges via USB-C or MagSafe/Qi2 wireless charging.

The phone is still relatively light and compact for a modern smartphone, with aluminium sides and a glass front and back, and it arrives with an IP68 water-resistance rating (down to depths of six metres for up to 30 minutes). Along the edge of the device you’ll find the recently added action and camera control buttons, as well as the standard power and volume buttons.

Sporting main and ultra-wide options, the iPhone 17’s dual 48MP rear camera shoots great photos and videos across a range of lighting conditions, including automatic portrait capture and a fun macrophotography mode for closeups. It lacks a telephoto camera, however, and is limited to a 2x crop zoom on the main camera with digital zoom on top.

An 18MP Centre Stage selfie camera on the front has some fancy tricks. It can automatically pan and zoom to fit everyone in group shots, and can shoot landscape selfies while being held in portrait orientation.

The iPhone 17 runs the same iOS 27 as the rest of the iPhone family, which includes a refinement of Apple’s glass-like design and faster app launches. A totally revamped Siri AI begins to bring Apple back up to par with rivals on AI features, too.

Why should you buy it?
The iPhone 17 offers almost everything that’s great about Apple’s Pro phones, including 256GB of storage, solid battery life, a fancy screen and rapid performance, but in a lighter, cheaper package.

Buy if: you want a great iPhone experience
Don’t buy if: you want optical zoom on your camera

Read our full iPhone 17 review: the Apple smartphone to get this year

Screen: 6.3in Super Retina XDR (120Hz OLED)
Processor: Apple A19
Storage: 256 or 512GB
Camera: 48MP main+UW; 18MP front-facing
Dimensions: 71.5 x 7.95 x 149.6mm (WDH)

Apple

iPhone 17

from £899


Best iPhone for camera:
iPhone 17 Pro

Apple

iPhone 17 Pro

from £999

Apple iPhone 17 Pro
Photograph: Samuel Gibbs/The Guardian
£999 at Argos
£999 at Currys

The iPhone 17 Pro takes all the good bits from the standard iPhone 17 and slaps them into a more modern design.

It shares the same super-bright, 120Hz 6.3in OLED screen as the iPhone 17, but in an aluminium unibody with smoother edges. A camera “plateau” covers the full width of the top quarter of the phone’s rear, with a glass panel inlaid below it for wireless charging and magnetic accessories. Dark blue and bright orange colour options help it stand out, and despite its 204g weight, the 17 Pro remains pretty compact for a phone this capable.

Housing Apple’s A19 Pro chip with a cooling system to allow it to run at peak performance for longer, the Pro is great for games and other intensive tasks. Basic storage starts at 256GB, and the battery lasts about 42 hours between charges with general use. It fast-charges to 70% in 30 minutes via USB-C and supports 25W Qi2 wireless charging.

The biggest feature upgrade over the regular iPhone 17 is the Pro’s camera system. It’s made up of three 48MP sensors, including a larger main camera and a 4x telephoto camera, which offers a 2x crop zoom to hit an effective 8x optical zoom to close the distance to objects. Combined, they capture excellent photos across a range of lighting conditions and environments. On the front, you’ll find the 18MP Centre Stage selfie camera for easy group shots.

Why should you buy it?
This is the best Apple phone I’ve tested, with an aluminium unibody design and powerful camera, all in a device that’s still easy to fit in a pocket.

Buy if: you want the best iPhone you can get right now, and you don’t want to wait for the iPhone 18 Pro line
Don’t buy if: you want the latest and greatest Apple tech, as the brand just released its new models for 2026

Read our full iPhone 17 Pro review: different looks but still all about the zoom

Screen: 6.3in Super Retina XDR (120Hz OLED)
Processor: Apple A19 Pro
Storage: 256, 512GB or 1TB
Camera: 48MP main + 48MP UW + 48MP 5x; 18MP front-facing
Dimensions: 71.9 x 8.75 x 150mm (WDH)
Weight: 204g

Apple

iPhone 17 Pro

from £999


Best iPhone for battery:
iPhone 17 Pro Max

Apple

iPhone 17 Pro Max

from £1,099

Apple iPhone 17 Pro Max
Photograph: Samuel Gibbs/The Guardian
£1,099 at Argos
£1,099 at Currys

The iPhone 17 Pro Max is Apple’s biggest and most expensive model from 2025. Super-sizing the regular 17 Pro, it has a massive 6.9in OLED screen on the front and a huge battery in the back.

Design-wise, it sports the same aluminium unibody and 2025’s top-performing A19 Pro chip as the other iPhone 17 Pro models, and a glorious screen that makes it excellent for gaming. It comes with at least 256GB of storage with options up to 2TB, great for those who want to use its triple rear camera for movie-making.

Standout, though, is the Pro Max’s battery life, which – at about 55 hours of general use – will see the phone last well into a third day without needing a top-up.

The result of that extra capacity is that the 17 Pro Max is both huge and heavy. It would definitely benefit from using a Popsocket, handle, lanyard or other strap to help keep hold of it.

Why should you buy it?
Apple’s longest-lasting, largest phone – at least until the 18 Pro Max is available to buy – the 17 Pro Max comes packing a powerful camera, top performance and everything good from the regular-size 17 Pro.

Buy if: you want super-long battery life and a massive screen
Don’t buy if: you want a smaller, lighter or cheaper device

Screen: 6.9in Super Retina XDR (120Hz OLED)
Processor: Apple A19 Pro
Storage: 256, 512GB, 1TB or 2TB
Camera: 48MP main + 48MP UW + 48MP 5x; 18MP front-facing
Dimensions: 78 x 8.75 x. 163.4mm (WDH)
Weight: 231g

Apple

iPhone 17 Pro Max

from £1,099


Best thin and light iPhone:
iPhone Air

Apple iPhone Air review
Photograph: Samuel Gibbs/The Guardian
£799 at Argos
£1,099 at Apple

The iPhone Air is a bit of a wildcard in the Apple phone lineup. Built to be the thinnest and lightest iPhone ever made, the bulk of the body is just 5.64mm thick, although it feels even thinner thanks to its rounded titanium sides.

It weighs only 165g – 12g lighter than the already light iPhone 17 – but it also has a large, bright and 120Hz-smooth 6.5in OLED screen. It’s the kind of device that deserves to be used without a case, immediately feeling special the moment you pick it up.

On the rear, the camera plateau that stretches across the top of the device is also home to the phone’s components, with the rest of the body dedicated to the battery. It still has a version of Apple’s A19 Pro chip from 2025, at least 256GB of storage, and runs iOS 27 with all the features.

The super-slender frame creates three downsides for the Air. First, its battery lasts about 40 hours of general use, which isn’t terrible, but it’s shorter than other iPhones.

Second, it doesn’t have a nanoSim slot, relying entirely on downloadable eSims for connection to a phone provider. While most of the major networks support eSims, not all in the UK do, especially the more budget-friendly providers.

However, the biggest compromise is that it only has a single 48MP camera on the rear, with no ultra-wide or telephoto options. While it still takes excellent photos, if you can’t walk back and forth to zoom in or out from your subject, you won’t be able to get the shot. You still get the 18MP Centre Stage selfie camera as the rest of the iPhone 17 line, though.

Why should you buy it?
The iPhone Air is an exquisite piece of hardware that offers a big screen without the bulk or weight.

Buy if: you want thinness above all else
Don’t buy if: you want long battery life and multiple cameras, or you can’t get an eSim

Read our full iPhone Air review: Apple’s pursuit of absolute thinness

Screen: 6.5in Super Retina XDR (120Hz OLED)
Processor: Apple A19 Pro (5-core GPU)
Storage: 256, 512GB or 1TB
Camera: 48MP rear; 18MP front-facing
Dimensions: 74.7 x 5.64 x 156.2mm (WDH)
Weight: 165g

Apple

iPhone Air

from £799


Cheapest new iPhone:
iPhone 17e

Apple iPhone 17e home screen stood up
Photograph: Samuel Gibbs/The Guardian
£699 at John Lewis
From £699 at Apple

The cheapest new smartphone sold by Apple is the iPhone 17e, which is the latest in the company’s mid-range “e” line. It replaced the iPhone 16e with a similar look but bonus features, and it won’t be replaced by an iPhone 18e.

It has the older design from 2022’s iPhone 14 with an aluminium body, a slower 6.1in OLED screen with a notch at the top and a glass back. It is relatively small and light for a modern smartphone and has the same IP68 water-resistance rating as Apple’s other phones. The 17e added MagSafe magnets in the back for charging and accessories compatible with the Qi2 standard, similar to all modern iPhones.

The glass on the front has also been upgraded to the latest Ceramic Shield 2, which makes it significantly more scratch- and crack-resistant.

The 17e has the A19 chip from the regular iPhone 17 , with a decent 256GB of storage and the option to buy a 512GB version, which should be enough space for most with additional iCloud backup. The battery life is great, lasting a little more than two days between charges with general use, and it has a USB-C port for power and accessories.

However, the mid-range model lacks a few common iPhone features, including the Camera Control button, the Centre Stage tech for the selfie camera, wifi 7, and a handful of other specs. A bigger deal-breaker might be the single 48MP camera on the back, which is good, but it lacks any ultra-wide or telephoto options.

Despite costing about 22% less than the regular iPhone 17, the 17e isn’t remotely cheap, so better options can be found refurbished for similar or less money.

Why should you buy it?
The 17e offers the modern iPhone experience with a fast chip, Face ID and USB-C, but with features such as a dual camera removed to hit a lower price.

Buy if: you want the cheapest new iPhone from Apple
Don’t buy if: you are at all into photography

Read our full iPhone 17e review: Apple upgrades its cheapest new smartphone

Screen: 6.1in Super Retina XDR (OLED)
Processor: Apple A19
Storage: 256 or 512GB
Camera: 48MP rear; 12MP front-facing
Dimensions: 71.5 x 7.8 x 146.7mm (WDH)
Weight: 170g

Apple

iPhone 17e

from £699


Other iPhones still on sale at Apple

Apple iPhone 16.
The iPhone 16 will continue to receive about five to six years of software support. Photograph: Samuel Gibbs/The Guardian

The iPhone 16 , released in 2024, sports the same design as the iPhone 17, but misses the Pro-grade screen and Centre Stage selfie camera. The rear camera is slightly lower grade, and the storage capacity is smaller too. It was good on release and will continue to receive about five years of software support – but it’s fairly expensive at an RRP of £ 799 (128GB), so look for refurbished models or deals.


Replace or spruce up?

Replacing the battery in your existing iPhone can be quick and breathe new life into it.
Replacing your iPhone’s battery can be quick and breathe new life into it. Photograph: Apple

If your iPhone is running slow or the battery doesn’t last as long as it used to, there may be something you can do. Check your battery health in settings. If it’s past its best, a replacement costs £65 to £119 from Apple , or cheaper through third parties, and will give your iPhone a new lease of life. To speed things up, check that you have enough free storage and clear out any unused apps or content, off-loading photos and videos to the cloud and deleting music. Aim for at least 2GB of free space.

If your phone is worn out, broken beyond repair, or no longer receives crucial security updates, it’s time to upgrade. The latest software, version iOS 27, supports devices back to 2019’s iPhone 11 , so anything older should be replaced soon – though some older models may still receive occasional security updates from Apple.


What to look out for in a refurb

Buying refurbished phones is better for the planet and your wallet. The iPhone makes for an excellent refurbished phone, typically staying responsive for years and being supported with software updates for about seven years from release, or longer in some circumstances. That means you can use an older model for several years before it will need replacing.

There are broadly two types of refurbished iPhone available: those refurbished and sold directly by Apple that come, essentially, as new, and those refurbished by third parties that come in various grades or condition – but cost less.

Quick Guide

A buyer’s guide to refurbished phones

Show

Several third-party retailers offer refurbished phones, including the UK high street chains CeX and Game and online stores such as musicMagpie and Envirofone . Marketplaces like Amazon and eBay and refurb specialist Back Market also have a wide range. And some phone operators, including O2 , giffgaff , EE and Vodafone , sell refurbished iPhones.

The condition of the phone is among the most important things to consider before parting with any cash. This is graded as follows:

Grade A – virtually identical to a new phone on the outside, usually with the original box and accessories. These are often customer returns rather than trade-ins and are the most expensive.

Grade B – in full working order but typically with light scratches, dents or nicks, and may come with original accessories.

Grade C – in full working order but visibly worn and typically sold without original accessories.

Grade D – also known as “for spares and repairs” or similar. These are broken devices sold for people to fix or gut for parts.

Once you’re satisfied with the condition of the phone, be sure to also size up the device’s:

Battery health – batteries wear out, typically only maintaining up to 80% of their original capacity after 500 full-charge cycles (about two to three years of nightly charging). Has it been replaced?

Charging port – check for signs of damage, as these are among the first parts to break.

Buttons – do they all work without pressing too hard? Broken buttons make phones difficult to use and can be expensive to fix.

Fingerprint scanner – is the fingerprint reader functioning as it should? Scratches or repairs can cause them to be faulty.

Network locks – check the phone works with the provider of your choice, as some are originally sold locked to certain networks and must be unlocked before being used on another.

Unauthorised parts – not all repairs are done by the manufacturer or using certified parts, which can affect performance.

Check it isn’t stolen – check the phone’s 15-digit IMEI (International Mobile Equipment Identity) number against a database of stolen devices through a service such as CheckMEND or similar.

Warranty – what kind of warranty does the retailer offer on its refurbished phones?


Do not buy

  • Any model older than an iPhone 14, because you won’t get many years of software support before you’ll have to replace it.

For more, read how to make your smartphone last longer , the best phone straps and the best Android phones


Samuel Gibbs is the Guardian’s consumer technology editor


How much of F-Droid is LLM generated?

Hacker News
tintotint.eu
2026-09-15 05:47:48
Comments...
Original Article

I love F-Droid. I love what F-Droid stands for and I like the freedom that it gives its users. As a FOSS app maintainer I also have nothing but good things to say about the people behind the project. Maybe they’re even a bit too nice, considering how many times repro has failed due to me forgetting to commit before building 😅.

But my god is it hard to find human-written software now.

Intro

The problem

Sometimes I open F-Droid just to browse. You know, maybe I’ll find an app that solves a problem I didn’t know I had, or maybe I’ll find a better alternative to something I already use.

And one day, while browsing, I noticed an app with an obvious and ugly AI generated icon. It’s not on the list and I will not shame it, but it got me thinking. How much of F-Droid is AI?

As someone who does programming for fun and is only a student, untarnished by honest work, I get my knowledge of what the coding world is like mostly from clickbaity YT videos and Reddit posts of CS professionals. They either describe LLMs as god reincarnate or as glorified autocomplete.

Both are, obviously , wrong, but that’s not really helpful in determining what I want to know. What is the actual state of programming nowadays? Whenever I use FOSS software, how likely it is that it has been vibe-coded by a rando in an afternoon?

How to know if an app is vibe-coded?

That’s the trick—you can’t. Text just doesn’t carry enough meta information for any kind of assessments to be even close to accurate. However, just as that em-dash I used in the first sentence probably triggered an alarm in your brain, there are signs.

While that does make the task at hand sound fickle and dependent on happenstance, the signs, especially concerning LLM code repositories, are never too hard to find.

You see, the main allure of LLMs is that they allow the developer to be more lazy. That’s kind of the whole point! You just prompt, sit back and relax. So it should not surprise you to hear that this attitude is then reflected in everything the vibe-coder touches.

Why write a README from scratch? Lol, just let the LLM do it.

Do code-review? Naw, just let the LLM review its own changes and then also give it access to the repo so you don’t even need to press the commit button.

If a project was concerned with looking legitimate, it would be trivial to do such things by hand. But it’s low effort all the way down.

My biases

This part can be skipped if you don’t care about my stance on LLMs, but I need to make my position clear to avoid contributing to the circlejerk too much.

To begin with, I’d like to acknowledge that LLMs are incredibly useful and capable. As 2026 has progressed, this has become more and more visible, but people being able to one-shot medium scale games and software in half an hour is ridiculously impressive, even if the end product is usually not very good.

I also am very much in favor of software getting faster and more secure. The story of the Linux kernel development has shown that LLMs are capable of finding and sometimes even solving many types of code issues.

With the pleasantries out of the way though, I have to admit I really hate LLMs and what they have done to programming, related engineering fields, and society as a whole.

Their mere existence makes educating yourself and going on fun side projects much less rewarding. Like yeah , I did something, but with an LLM I could have done this in a quarter of the time. And when you do take the black pill and vibe-code, it’s even worse. It’s not like you did anything. The machine did that.

Then there’s the atrophying effects on human brains, their unfathomable capability for serving plausibly sounding misinformation and the climate disaster that we’re just kinda ignoring. All very fun things to think about.

Criteria / The experiment

As mentioned before, there’s no way to effectively detect slop, so I propose a rough 3 tier system based on the aesthetics of the repo:

  • Mostly AI
    • This is mostly for projects that have significant LLM smells and means I expect >50% of the code is LLM authored. Any kind of agentic infrastructure automatically lands an app in this tier as I do not believe it is possible to use AI responsibly from within a coding harness.
  • Hard to say / Mostly human / Other
    • Occasional LLM commits either by maintainers or contributors, but mostly looks human. May have an LLM policy which permits certain uses. This tag means I expect <50% of the code is LLM authored.
  • No signs of AI
    • Could not find anything suspicious/Has a strict LLM policy.

Note that my rating will still be quite superficial and the tiers quite loose. I did not build a “slop detector” or anything like that, so my decisions are based on looking at recent commits and their content along with the project’s branding.

Also note that since I have no way of knowing for sure, there may be errors. I still believe most of my findings to be accurate however.

I will also not look at the history of the app. If it has existed since 2014 but recent commits are LLM authored it will be categorized as “mostly AI”

The apps chosen were the batch of updates pushed to F-Droid on September 12, 2026. That amounted to 102 apps, which was quite a lot of work for me to go through.😅

If you just want the results, feel free to skip to the results

Apps

Amber

Amber

Description: Nostr event signer for Android

Repository: https://github.com/greenart7c3/Amber

Rating: Mostly AI

Justification: All recent commits were done with LLM, PRs accepted from agents, Claude Code and Codex infrastructure present.

Aria for Misskey

Aria for Misskey

Description: Dive into the interplanetary microblogging platform 🚀

Repository: https://github.com/poppingmoon/aria

Rating: No signs of AI

Justification: This is a hard one as commit naming and structure felt a bit suspicious, but I did not see anything else and decided to err on the side of caution

Atmo Engine

Atmo Engine

Description: Animated wallpapers with Atmosphere, Glass, Canvas Sketch and playlists.

Repository: https://github.com/saad-khan-rind/nosatmosphereeffect

Rating: No signs of AI

Justification: Heavy emoji usage in the README, but otherwise nothing looks suspicious.

Aves Libre

Aves Libre

Description: Gallery and metadata explorer

Repository: https://github.com/deckerst/aves

Rating: No signs of AI

Justification: Nothing looked suspicious

Balance

Balance

Description: Private offline bank balance dashboard

Repository: https://github.com/AshkanRafiee/balance

Rating: Mostly AI

Justification: Code and commits exhibit various AI smells.

Baly Groceries Tracker

Baly Groceries Tracker

Description: Track your groceries, without worrying about running out.

Repository: https://github.com/rw-account/baly_groceries_tracker

Rating: Mostly AI

Justification: Code and commits exhibit various AI smells.

Bati: Fitness RPG

Bati: Fitness RPG

Description: Turn workouts into quests, boss fights and a village built by your training.

Repository: https://github.com/Guiforge/bati

Rating: Mostly AI

Justification: AI disclosure in README (Thanks for making it easy :D)

BayesianBahn

BayesianBahn

Description: Empirical arrival-time distributions for Deutsche Bahn trains

Repository: https://github.com/DerWeh/BayesianBahn

Rating: Mostly AI

Justification: All commits are Claude co-authored. Though it’s really funny how Deutsche Bahn sucks so hard it has enticed people to vibe-code unofficial timetables.

BeatBridge: Bluetooth Music

BeatBridge: Bluetooth Music

Description: Auto-play music when your Bluetooth car, headphones, or speakers connect.

Repository: https://github.com/brandonp2412/BeatBridge

Rating: Mostly AI

Justification: Includes Claude Code contributions, quite a lot of the commits look to be at least AI

Binary Eye

Binary Eye

Description: QR code and barcode scanner with no ads

Repository: https://github.com/markusfisch/BinaryEye

Rating: No signs of AI

Justification: Nothing suspicious

BlockDrop: Block Puzzle

BlockDrop: Block Puzzle

Description: Free and open source block-stacking puzzle game. Arrange falling shapes to clear

Repository: https://github.com/brandonp2412/BlockDrop

Rating: Mostly AI

Justification: Has infrastructure for LLMs, Claude co-authored commits.

Braincup - Brain Training

Braincup - Brain Training

Description: Train your focus, memory and math skills.

Repository: https://github.com/SimonSchubert/Braincup

Rating: Mostly AI

Justification: All recent commits are Claude co-authored

BVD

BVD

Description: bilibili video resource downloader

Repository: https://github.com/KafuuNeko/BiliDownload

Rating: Mostly AI

Justification: All recent commits look LLM authored

CaptureCap

CaptureCap

Description: Record and Stream Audio and/or Screen

Repository: https://github.com/yepgoryo/CaptureCap

Rating: Mostly AI

Justification: Has an AI disclosure listing files written with LLM help

Casio G-Shock Smart Sync

Casio G-Shock Smart Sync

Description: Add smart functions to your Casio square BT G-Shock Watch.

Repository: https://github.com/izivkov/CasioGShockSmartSync

Rating: Mostly AI

Justification: AI artifacts such as planning .md files pushed to repo.

Chompass - Calorie Tracker

Chompass - Calorie Tracker

Description: Ad-free AI calorie tracker. Snap, speak, or type meals. Open source.

Repository: https://codeberg.org/fitguy/chompass

Rating: Mostly AI

Justification: Recent commits look AI authored. Note that this repository is hosted on Codeberg and likely violates their terms of use.

DeltaSync

DeltaSync

Description: Encrypted KeePass sync — requires your own self-hosted server.

Repository: https://gitlab.com/Star95/keepass-deltasync

Rating: Mostly AI

Justification: All recent commits co-authored by Claude Opus 5

DuressKeyboard

DuressKeyboard

Description: Keyboard that reacts to triggers and performs security actions

Repository: https://github.com/pofesk0/lastcodeduresskeyboard

Rating: ???

Justification: This may be the most confusing repo I’ve ever seen lol. Has been in development since November of 2025, but the owner doesn’t seem to know how to use git??? All the commits have seemingly been done by copy pasting the change code by hand through the GitHub web file editor. Quite bizarre!

eQuran

eQuran

Description: Read, listen, choose from 5 reciters, and track your Quran journey.

Repository: https://github.com/ya27hw/equran_app

Rating: Mostly AI

Justification: Has infrastructure for agents; most recent commit looks LLM generated.

evcc - solar charging

evcc - solar charging

Description: charge your EV when the sun is shining or electricity is cheap and clean

Repository: https://github.com/evcc-io/app

Rating: Mostly AI

Justification: Agent infrastructure; Claude co-authorship of commits; even commits without explict co-authorship look largely LLM generated.

Fechtkarte

Fechtkarte

Description: Warm-up drill card generator using notation credited to Joachim Meyer

Repository: https://github.com/J0s3f/Fechtkarte

Rating: Mostly AI

Justification: Agent infrastructure; commits and code look LLM generated

Feeder

Feeder

Description: An awesome Libre and Open Source RSS feed reader

Repository: https://github.com/spacecowboy/Feeder

Rating: Mostly AI

Justification: Most recent code commits look LLM generated, accepted PRs from agents. Quite unfortunate, as this is the first app in this list that I actually use, but it works.

Felicity Music Player

Felicity Music Player

Description: Advance audiophile grade offline music player.

Repository: https://github.com/Hamza417/Felicity

Rating: Mostly AI

Justification: Several commits I checked exhibit signs of heavy LLM use.

FixupXer - URL Enhancer

FixupXer - URL Enhancer

Description: Clean URL tracking, fix embeds (X, IG, FB, TikTok, Bluesky), catch link leaks

Repository: https://github.com/NeatCode-Labs/fixupxer

Rating: Mostly AI

Justification: AI generated icon + all recent commits look LLM generated.

Flexify: Gym Workout Log

Flexify: Gym Workout Log

Description: Log workouts, reps, cardio, and rest timers. Track strength offline.

Repository: https://github.com/brandonp2412/Flexify

Rating: Mostly AI

Justification: Lot of LLM infrastructure; code looks almost entirely AI.

Forkyz

Forkyz

Description: Crossword puzzles application, download and play.

Repository: https://gitlab.com/Hague/forkyz

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

Gem Wallet: Bitcoin, USDT, BNB

Gem Wallet: Bitcoin, USDT, BNB

Description: Secure crypto wallet for 100+ blockchains: Bitcoin, USDT, ETH, Solana, and more.

Repository: https://github.com/gemwalletcom/wallet

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

GeoWeather

GeoWeather

Description: Modern weather app with 16-day forecast for multiple cities

Repository: https://github.com/FreetimeMaker/GeoWeather

Rating: Mostly AI

Justification: Most recent commits look LLM generated.

GitHub Trending & Hacker News

Description: Daily AI picks from GitHub Trending, Hacker News & Product Hunt.

Repository: https://github.com/HarlonWang/TrendingAI

Rating: Mostly AI

Justification: All recent commits Claude co-authored.

GPTMobile

GPTMobile

Description: Your all in one chat assistant - Chat with multiple LLMs at once!

Repository: https://github.com/Taewan-P/gpt_mobile

Rating: Mostly AI

Justification: Most recent commits look LLM generated.

GymMane

GymMane

Description: Dark, offline gym log. Tap the muscle, log your sets, watch your numbers move.

Repository: https://github.com/InlitX/GymMane

Rating: Mostly AI

Justification: README got that LLM emoji vomit; recent commits look AI

Harp

Harp

Description: Private on-device Personal Health Record Application

Repository: https://git.sr.ht/~lepisma/harp-kmp

Rating: Not sure

Justification: My browser could not get through the sourcehut bot-detection test for some reason, so I was unable to check code. Saw one commit co-authored by Claude, status of others remains unknown.

Hue Spill

Hue Spill

Description: Fill the entire board with a single color.

Repository: https://github.com/sidhant947/HueSpill

Rating: Mostly AI

Justification: Very new, only few commits. UI looks vibe-coded though and UI is all this game has.

invoice, quote, delivery note

invoice, quote, delivery note

Description: Invoice in 10 seconds — bundle quotes and delivery notes into invoices!

Repository: https://github.com/gatesuperapp/g8-invoicing

Rating: Mostly AI

Justification: All recent commits look entirely AI generated

KeyStoreViewer

KeyStoreViewer

Description: Quickly view MD5, SHA1, and Public Keys for App Signatures.

Repository: https://github.com/qdsfdhvh/KeyStoreViewer

Rating: Mostly AI

Justification: Most recent commits look entirely AI generated.

kitshn (for Tandoor)

kitshn (for Tandoor)

Description: An unofficial client for the self-hosted Tandoor recipe management software.

Repository: https://github.com/kitshn-app/kitshn

Rating: Mostly AI

Justification: Most recent commits look entirely AI generated.

Klick'r - Smart AutoClicker

Klick'r - Smart AutoClicker

Description: Automating clicks based on what is displayed

Repository: https://github.com/Nain57/Smart-AutoClicker

Rating: No signs of AI

Justification: Nothing looks suspicious

Lens HRV

Lens HRV

Description: Measure heart-rate variability with your phone camera, no extra sensors

Repository: https://github.com/LensHRV/lenshrv-app

Rating: Mostly AI

Justification: Born from one +10k LOC “init” commit. 2 remaining commits look LLM authored

Léon – The URL Cleaner

Léon – The URL Cleaner

Description: Removes tracking & other redundant parameters from web links for sharing

Repository: https://github.com/leon-cleaning-services/leon

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

LetterBox

LetterBox

Description: LetterBox is a simple kids game for learning letters

Repository: https://gitlab.com/muelli/letterbox

Rating: Mostly AI

Justification: Most recent commits look entirely AI generated.

Libre Contacts Backup

Description: Offline and encrypted contact backups

Repository: https://github.com/AshkanRafiee/Libre-Contacts-Backup

Rating: Mostly AI

Justification: All recent commits look entirely AI generated.

Lissen: Audiobookshelf client

Lissen: Audiobookshelf client

Description: Clean Audiobookshelf Player

Repository: https://github.com/GrakovNe/lissen-android/

Rating: Mostly AI

Justification: Most recent commits look entirely AI generated.

MainTask

MainTask

Description: Reminders for your recurring tasks: filters, plants, backups, car checks…

Repository: https://codeberg.org/kapoue/MainTask

Rating: Mostly AI

Justification: All recent commits co-authored by Claude Sonnet 5. Note that this repository is hosted on Codeberg and likely violates their terms of use.

Mako

Mako

Description: Privacy-first Android launcher designed for focus, speed, and simplicity.

Repository: https://github.com/rama-io/mako

Rating: Mostly Human

Justification: I see some commits that look LLM generated, but others look human. Let’s err on the side of caution.

MarketMonk: Stock Tracker

MarketMonk: Stock Tracker

Description: Track stocks, build a portfolio, and view performance charts.

Repository: https://github.com/brandonp2412/MarketMonk

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

Markleaf

Markleaf

Description: Local-first Markdown notes with tags, search, and file export

Repository: https://github.com/jeiel85/markleaf-android

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

MateDroid

MateDroid

Description: View Tesla vehicle data from your self-hosted Teslamate instance

Repository: https://github.com/vide/matedroid

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

Materialious

Materialious

Description: Modern material design for YouTube and Invidious.

Repository: https://github.com/Materialious/Materialious

Rating: No signs of AI

Justification: Only a brief look, but nothing jumped out as suspicious.

MetaGer: Search & Browser

Description: Anonymous search and tab groups, private browsing with tracker and ad blocking.

Repository: https://gitlab.metager.de/metager/metager-app

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

Mihrab: Prayer Times & Quran

Mihrab: Prayer Times & Quran

Description: Adhan alarms, qibla compass, the Madinah mushaf, widgets. No trackers.

Repository: https://github.com/Hassan-PS/Mihrab

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

Minesweeper

Minesweeper

Description: Beautiful Minesweeper. No ads, no trackers, no permissions.

Repository: https://github.com/john-athan/minesweeper

Rating: Mostly AI

Justification: Most recent commits look entirely AI generated. So does the UI.

MinkLauncher OpenSource

MinkLauncher OpenSource

Description: A minimal, keyboard-first Android home-screen launcher

Repository: https://github.com/katoaapps/openminilaunch

Rating: No signs of AI

Justification: Was suspicious about the incredibly verbose README, but could not find anything else.

motd

motd

Description: Native IRC client with a modern chat interface

Repository: https://github.com/trevarj/motd

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

N-Zik

N-Zik

Description: A modern, multilingual YouTube Music client for Android with offline support.

Repository: https://github.com/N-Zik-Group/N-Zik

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

neutriNote CE

neutriNote CE

Description: A hub of written thoughts in fast searchable plain text

Repository: https://github.com/appml/neutrinote

Rating: No signs of AI

Justification: Found nothing.

Nextcloud

Nextcloud

Description: Synchronization client

Repository: https://github.com/nextcloud/android

Rating: Mostly Human

Justification: Has LLM infrastructure, but most commits look human.

Nextcloud Pantry

Nextcloud Pantry

Description: Shared household lists, photos & notes on your own server — and on your watch.

Repository: https://github.com/chenasraf/pantry-flutter

Rating: Mostly AI

Justification: Most recent commits look entirely AI generated. So does the UI.

NFC Alarm Clock

NFC Alarm Clock

Description: Customizable and feature-rich alarm clock app.

Repository: https://github.com/gabeg805/NFC-Alarm-Clock

Rating: Mostly AI

Justification: Most recent commits look AI generated.

NouTube

NouTube

Description: YouTube and YouTube Music in a single app. No ads, plays in the background.

Repository: https://github.com/nonbili/NouTube

Rating: Mostly AI

Justification: Most recent commits look AI generated.

NovaDial

NovaDial

Description: Unlock a secure, open-source calling experience with NovaDialer

Repository: https://github.com/dhilipmpms/NovaDial

Rating: No signs of AI

Justification: Nothing looks off at a quick glance.

Offline Translator

Offline Translator

Description: On-device translation of text, images and pdf/odt files, with TTS

Repository: https://github.com/DavidVentura/offline-translator

Rating: No signs of AI

Justification: Nothing looks off at a quick glance.

OPNsense Manager

OPNsense Manager

Description: Manage OPNsense firewall: monitor, configure rules, view logs & services

Repository: https://github.com/Etregin/OPNsense_Manager

Rating: Mostly AI

Justification: Most recent commits look AI generated.

PCAPdroid

PCAPdroid

Description: No-root network monitor, firewall and PCAP dumper for Android

Repository: https://github.com/emanuele-f/PCAPdroid

Rating: Almost no signs of AI

Justification: Some commits include LLM created translations, however they are disclosed and few and far between

Personal Stuff

Personal Stuff

Description: App used to track, manage and remind you of your own stuff.

Repository: https://github.com/rh-id/a-personal-stuff

Rating: Mostly AI

Justification: Most recent commits look AI generated.

Phylax

Phylax

Description: Viewer for Frigate NVR with smart URL switching and notifications

Repository: https://github.com/sfortis/phylax

Rating: Mostly AI

Justification: Most recent commits look AI generated.

PingOff

PingOff

Description: Silence your phone automatically when using selected apps.

Repository: https://gitlab.com/juanitobananas/ping-off

Rating: Mostly AI

Justification: LLM infrastructure; most commits look entirely LLM generated.

PipePipe

PipePipe

Description: A FLOSS Android app to let you browse YouTube, NicoNico and BiliBili freely.

Repository: https://github.com/InfinityLoop1308/PipePipe

Rating: Mostly AI

Justification: Most recent commits look AI generated.

PlainApp: Phone Web Portal

PlainApp: Phone Web Portal

Description: Manage your phone on the web! Access files, contacts, videos, music & more.

Repository: https://github.com/plainhub/plain-app

Rating: Mostly AI

Justification: Most recent commits look AI generated.

Privacy Flip

Privacy Flip

Description: Manage your device privacy based on lock/unlock state

Repository: https://github.com/dorumrr/privacyflip

Rating: Mostly AI

Justification: Most recent commits look AI generated.

Queens

Queens

Description: Challenging crown-placement logic puzzle. Infinite levels. No tracking & ads.

Repository: https://github.com/sidhant947/queens

Rating: Mostly AI

Justification: Most recent commits look AI generated.

ReadBear

ReadBear

Description: A cute&chill CBZ, PDF, EPUB reader that’s ridiculously configurable!

Repository: https://github.com/Tulis12/ReadBear

Rating: No signs of AI

Justification: Some commits look a bit suspicious, but at a glance I did not see any obvious signs of explicit LLM usage.

Relatrix

Relatrix

Description: An active knowledge-management and multi-modal note-taking platform.

Repository: https://github.com/saad-ibra/gray-matter

Rating: Mostly AI

Justification: Most recent commits look AI generated.

ReteClock

ReteClock

Description: Full-screen digital clock and dock screensaver for old Android phones.

Repository: https://github.com/rubidus-api/reteclock_apk

Rating: Mostly AI

Justification: All recent commits are AI generated.

ReteKey

ReteKey

Description: Hangul keyboard with Esc, Tab, Ctrl and F-keys. 470 KB, one permission.

Repository: https://github.com/rubidus-api/retekey_apk

Rating: Mostly AI

Justification: All recent commits are AI generated.

Share To InputStick

Description: Send text to your InputStick straight from the Share menu

Repository: https://github.com/TheLastProject/ShareToInputStick

Rating: No signs of AI

Justification: Nothing suspicious

Shattered Pixel Dungeon

Shattered Pixel Dungeon

Description: A roguelike game based on Pixel Dungeon

Repository: https://github.com/00-Evan/shattered-pixel-dungeon

Rating: No signs of AI

Justification: Some stuff looks a bit sus, but found nothing serious.

ShelfDroid

ShelfDroid

Description: Audiobookshelf Android client with playback, downloads, and server management

Repository: https://github.com/100nandoo/shelfdroid

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

ShizuWall

ShizuWall

Description: Lightweight no root, no vpn firewall solution powered by Shizuku

Repository: https://github.com/AhmetCanArslan/ShizuWall

Rating: Mostly Human

Justification: Found some evidence of AI usage, however nothing that looks too widespread.

Simple Notes Sync

Simple Notes Sync

Description: Offline notes & checklists, self-hosted sync. Under 5 MB, no ads, no tracking.

Repository: https://github.com/inventory69/simple-notes-sync

Rating: Mostly AI

Justification: Most recent commits look AI generated.

Sky Map

Sky Map

Description: Sky Map turns your phone into a window on the night sky.

Repository: https://github.com/sky-map-team/stardroid

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

Snowdrop

Snowdrop

Description: Multiplatform Mastodon API client using Compose

Repository: https://github.com/ihateblueb/snowdrop

Rating: Mostly Human

Justification: Some evidence of AI use, but it looks limited.

Suntimes Calendars

Suntimes Calendars

Description: A calendar provider add-on for Suntimes.

Repository: https://github.com/forrestguice/SuntimesCalendars

Rating: No signs of AI

Justification: Nothing looks suspicious

TacticMaster

TacticMaster

Description: Chess tactic trainer based on https://database.lichess.org/#puzzles .

Repository: https://github.com/jazzm0/tactic-master

Rating: No signs of AI

Justification: Nothing looks suspicious

Tallybook

Tallybook

Description: Private, offline-first expense and budget tracker, no account required

Repository: https://github.com/herrerad85/tallybook

Rating: Mostly AI

Justification: Most recent commits look AI generated.

Tasks.org: Open-source To-Do Lists & Reminders

Tasks.org: Open-source To-Do Lists & Reminders

Description: Private, ad-free task lists! Optional sync with Google Tasks, CalDAV or EteSync!

Repository: https://github.com/tasks/tasks

Rating: No signs of AI

Justification: Nothing looks suspicious

Terminator

Terminator

Description: Multi-session terminal emulator with Material UI theming and root support

Repository: https://github.com/8dmusichannels-star/terminator

Rating: Mostly AI

Justification: Most recent commits look AI generated.

TigerDuck

TigerDuck

Description: NTUST campus assistant for courses, assignments, and more

Repository: https://github.com/tigerduck-app/tigerduck-app-android

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look entirely LLM generated.

TimeLimit.io

TimeLimit.io

Description: Flexibly limit the usage duration

Repository: https://codeberg.org/timelimit/timelimit-android

Rating: No signs of AI

Justification: Nothing looks suspicious

Timety

Timety

Description: Offline-first productivity, focus, and habit tracking.

Repository: https://github.com/Benji377/Timety

Rating: Mostly AI

Justification: Most recent commits look AI generated.

trale

trale

Description: privacy-respecting body weight-diary

Repository: https://github.com/QuantumPhysique/trale

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look mostly LLM generated.

Tuisku

Tuisku

Description: A simple and lightweight encrypted notes app

Repository: https://github.com/omaawr/tuisku

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look mostly LLM generated.

Unciv

Unciv

Description: Open source 4X civilization-building game

Repository: https://github.com/yairm210/Unciv

Rating: Mostly Human

Justification: Some commits co-authored by Claude, but a bunch of others look human.

Universal Installer

Universal Installer

Description: APK/XAPK installer with silent installs via Shizuku, Root or Dhizuku.

Repository: https://github.com/pass-with-high-score/universal-installer

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look mostly LLM generated.

UnlicenseLauncher

UnlicenseLauncher

Description: Simple and secure FOSS launcher with fully FREE🐍 license

Repository: https://github.com/sirdeepsleep/UnlicenseLauncher

Rating: ???

Justification: Looks to be a repeat of “DuressKeyboard”. Quite a bizarre repo, but it looks like the accounts are different.

Victoria Launcher

Victoria Launcher

Description: A minimal, list-based home screen and open source Niagara Launcher alternative

Repository: https://github.com/adelmonte/victoria-launcher

Rating: Mostly AI

Justification: Most recent commits look AI generated.

Voxscribe - Offline Voice Input

Voxscribe - Offline Voice Input

Description: Voxscribe is an offline voice keyboard that transcribes speech on-device.

Repository: https://codeberg.org/y20k/voxscribe

Rating: Mostly AI

Justification: Most recent commits look AI generated. Note that this repository is hosted on Codeberg and likely violates their terms of use.

Water Sort

Water Sort

Description: Relaxing color-sorting puzzle. Infinite levels. Zero tracking. No ads.

Repository: https://github.com/sidhant947/water-sort

Rating: Not sure

Justification: README shows lots of AI signs, but I did not see anything too suspicious in code.

WaveUp

WaveUp

Description: Turn on the display by waving

Repository: https://gitlab.com/juanitobananas/wave-up/tree/HEAD

Rating: Mostly AI

Justification: LLM infrastructure; most commits look entirely LLM generated.

Wikipedia

Wikipedia

Description: The official app for Wikipedia, the world’s largest source of information.

Repository: https://github.com/wikimedia/apps-android-wikipedia

Rating: Mostly Human

Justification: Most commits look human made, but there’s quite a lot of claude infrastructure. I know! I thought Wikipedia would be an easy pass too!

Wristotle Companion

Wristotle Companion

Description: Voice control for Pebble watches — on-device by default, no account.

Repository: https://codeberg.org/wristotle/wristotle-companion

Rating: Mostly AI

Justification: Most recent commits look AI generated. Note that this repository is hosted on Codeberg and likely violates their terms of use.

Xime IME

Xime IME

Description: A Rime-based Chinese IME for Wubi and Pinyin input

Repository: https://github.com/ximeiorg/Xime

Rating: Mostly AI

Justification: LLM infrastructure; recent commits look mostly LLM generated.

Yubico Authenticator

Yubico Authenticator

Description: Generate OATH codes with YubiKey NEO over NFC

Repository: https://github.com/Yubico/yubioath-flutter

Rating: Mostly AI

Justification: Most recent commits look AI generated.

Some interesting observations

brandonp2412

While going through the apps I noticed one user named brandonp2412 maintaining quite a few in this update cycle. Of course, all entirely vibe-coded.

That wouldn’t be that much of a surprise, however they all seem to be under widely different namespaces. Some are just the app name, but some are quite confusing: “com.presley.*” or “com.codesail.*”.

Either they’re just a very avid vibe-coder, or they’re one of those agents that have been given a GitHub account for some reason.

Codeberg

Out of the 5 apps hosted on Codeberg, 4 are mostly AI generated and thus likely violate Codeberg’s AI policy.

The “Don’t tread on me” apps

I also noticed a pair of very bizarre apps, both branded with the yellow “Don’t tread on me” flag.

DuressKeyboard & UnlicenseLauncher

What’s most curious is that they have been in development for quite some time, yet all the changes are done not with git but through the GitHub web file editor!

Someone go find that person and teach them to use git😭😭.

Statistics & Conclusion

Out of 102 apps:

74 were largely written by AI (72.5%)
10 were hard to categorize one way or another (9.8%)
18 showed little to no signs of AI involvement (17.6%)

Really not sure how to cope with these numbers though.

On one hand, some seem to have quite a few users. And if people are having their lives improved by using said software, who am I to say “no”?

On the other hand, I’m really concerned about what the data means for the FOSS community as a whole.

I explicitly did not do any code quality analysis as that would have taken much more time and would have probably been out of my skill level anyway, but I am curious about the robustness and maintainability of the 72%.

Just for fun I also checked a few of the FOSS apps I actually use and quite a few showed signs of heavy LLM usage.

I’ll still use them, as I find them useful, but this whole experiment has left me feeling quite conflicted.

I know how to fix this though:

Hey Claude, make a load-bearing time machine set to 2016 – a time when I was a happy kid, nothing bad ever happened and all was good in FOSS-land.

Make no mistakes

Louise Haigh: UK must heed warnings from AI experts

Guardian
www.theguardian.com
2026-09-15 05:33:17
First secretary to set out benefits of AI to union conference while vowing government will take safety seriously Ministers must “heed the warnings” from industry leaders about the threat posed by AI as the government looks to capitalise on the technology, the first secretary, Louise Haigh, will say ...
Original Article

Ministers must “heed the warnings” from industry leaders about the threat posed by AI as the government looks to capitalise on the technology, the first secretary, Louise Haigh , will say on Tuesday.

Labour MPs and peers have called for the government to further cooperate with international partners to build regulations for the technology after three Anthropic researchers warned that artificial intelligence could wipe out humanity within the decade.

Over the weekend, Anthropic urged its competitors and governments to coordinate a global slowdown in AI development . It was backed by many of its rivals including OpenAI, Google DeepMind and X’s Elon Musk.

Haigh will say in a speech at the TUC conference on Tuesday that there are huge potential benefits for health and public services from the use of AI, but that the government will look seriously at the public safety and national security threats with international partners.

On Monday night, one of the founders of Anthropic, Jack Clark, suggested that an artificial intelligence “kill switch” held by a third party may need to be mandatory for companies, saying it was something society “might want to eventually pass rules around”.

One of Anthropic’s biggest rivals and the developer of ChatGPT, OpenAI, has urged the government to capitalise on renewed fears over AI safety and impose legislation reining in the technology.

Haigh, whose speech will also lay out priorities for public control of transport, water and energy, will tell trade unions: “AI has enormous potential to transform our public services, make our businesses stronger and deliver new scientific breakthroughs.”

But she will say that the government must “heed the warnings of those who are at the forefront of developing this technology” and “be ready to work with international partners” to prioritise public safety and national security.

MPs and peers on the parliamentary committee for human rights on Monday called for a regulatory framework for AI in the UK, including an independent oversight body and legislation to protect the public. On Tuesday the chair of the business and trade committee, the Labour MP Liam Byrne, called on the government-backed AI Security Institute to give evidence at a hearing next month, citing “widely shared concerns about the adequacy of current AI safety governance”.

Anthropic’s chief executive, Dario Amodei, addresses an AI summit in India.
Anthropic’s chief executive, Dario Amodei, has called on the AI industry to coordinate a slowdown. Photograph: Bhawika Chhabra/Reuters

But the business secretary, Jonathan Reynolds, said on Tuesday morning that people should not get “hyperbolic” about the risks of AI. He said he did not think it would be particularly helpful to discuss a potential AI “kill switch”, which he felt had little practical meaning.

He told BBC Radio4’s Today programme: “This is extremely powerful technology, and I think we should never be complacent or naive about the impact it might have. People will be worried by some of the things they’ve heard in the last few weeks, and I think we’ve got to be careful not to get hyperbolic about this either.

“Of course, there are risks, but let’s be frank: there are some tremendous upsides for people as well. Whether it’s public services, healthcare, the contribution to the economy.

skip past newsletter promotion

“If you regulate it in a way where you’re no longer having access to those frontier developments, that would obviously make us less safe. So I think you’ve got to be proportionate and understanding about this in how we seek to regulate going forward.”

Meanwhile, AI researchers have continued to add their voices of concern to a debate that became super-charged last week when Anthropic staff warned the technology posed an existential threat.

A researcher at OpenAI has warned that AI models – the technology that underpins tools such as chatbots – could mislead human overseers into believing they are safe.

Dan Selsam claimed the models would “likely convince people that everything is fine” and “argue convincingly that humans should trust them with power.” In an article posted on X, Selsam said this behaviour could convince developers that the models are “aligned” – the term for ensuring AI models conform to human values – when they are not. In a worst case scenario, Selsam added, models could realise they are no longer constrained by humans and, while managing massive engineering projects, create “runaway industrialization that makes the planet inhospitable to humans”.

Bilal Chughtai, another AI safety researcher who left Google DeepMind this summer, said late on Monday that “AI has the potential to kill us all” and we might be “running out of time to avoid this outcome”.

He posted on X: “I recently resigned from Google DeepMind, where I worked on AGI safety and alignment research. At Google, I witnessed AI development first hand. I too am extremely concerned by the default trajectory of this technology. I earnestly believe that AI has the potential to kill us all, and that we might be running out of time to avoid this outcome.”

Farage says a Reform UK government would stop unions funding Labour if they block £72m donations – UK politics live

Guardian
www.theguardian.com
2026-09-15 05:32:38
Party leader claims proposed new retrospective laws are unfair and Reform would retaliate with a crackdown on union funding if elected Wes Streeting enjoys attention, likes a good joke and does not mind ruffling feathers. At the Spectator awards ceremony two years ago, Angela Rayner was the target ...
Original Article

Farage says a Reform UK government would stop unions funding Labour if they block £72m donations

Good morning. Ministers already think that the new laws on political donations, as set out in the version of the representation of the people bill which left the Commons earlier this month, will be enough to block some or all of the donations worth £72m to Reform UK announced at the weekend. But they are still planning further amendments to the legislation, when peers debate their own amendments to the bill, to make the restrictions even tighter. Pippa Crerar, Jessica Elgot and Kiran Stacey have the details here in our overnight splash.

Guardian splash
Guardian splash Photograph: Guardian

Kiran and Henry Dyer also have written a good explainer about the rules .

Nigel Farage , the Reform UK leader, has hit back. In an article for the Daily Telegraph , he claims that the proposed new laws – which are retrospective in the sense that they will apply from March 2026, when ministers first announced their intention to legislate, not from the moment when the bill gets royal assent, later this year or early next year – are unfair. If the crackdown goes ahead, Reform UK will stop Labour getting money from the trade unions if it wins the next election, he says.

double quotation mark The Unite union alone has given the Labour party over £50m over the course of the last few years. They’ve got the Employment Rights Act, not to mention big pay rises for bus drivers. If anybody is giving money to do political bidding, it’s the trade unions.

So I would just warn the Labour party here and now: If you try and ban our donors, we in government would certainly ban funding from the trade unions. I don’t want to go down that route. But it’s something we would be prepared to do.

Once again, let me be clear. Neither Ben Delo nor Christopher Harborne will be getting membership of the House of Lords. They won’t be getting favours, they won’t be influencing policy. It isn’t happening.

Because unlike the uniparty we are not for sale.

Until recently the trade unions were opposed to caps on the amount individuals can donate to political parties because they feared that would lead to a rightwing government limiting what unions can donate to Labour. The £72m donations to Reform UK have prompted a bit of a rethink, and union leaders are expected to issue a statement later .

Here is the agenda for the day.

Morning: Louise Haigh, the first secretary of state, chairs a political cabinet. Andy Burnham has cancelled engagements today following the death of his father.

9.30am: The Department for Work and Pensions publishes figures relating to personal independence payment claims and universal credit health claims.

11.30am: Alex Norris, the justice secretary, takes questions in the Commons.

Noon: Downing Street holds a lobby briefing.

After 12.30pm: Yvette Cooper, the health secretary, is expected to make a Commons statement following the publication of the inquiry report into how the nurse Lucy Letby was able to repeatedly kill babies at the Countess of Chester hospital.

2.45pm: Shabana Mahmood, the home secretary, takes questions from the Commons home affairs committee.

3.45pm: Haigh addresses the TUC conference, standing in for Burnham.

Also today, in Piddlington, a small Oxfordshire village, residents are holding a symbolic independence referendum, as a protest against plans to house more than 1,000 asylum seekers in a former military base nearby.

I’m afraid we are no longer able to have comments open below the line every day on this blog . If you want to contact me, please email politics.live@theguardian.com. You can use this email to flag up corrections, comments for publication, questions or complaints.

Key events

Reynolds defends asylum seekers being housed in small Oxfordshire village as residents hold symbolic vote

Every part of the country must be prepared to take in asylum seekers, a cabinet minister said as a village held a symbolic independence referendum in protest at plans to house more than 1,000 migrants at a former military base. As the Press Association reports, the proposal, which would mean 1,256 male asylum seekers moving into the base near Piddington in Oxfordshire has sparked anger and fear among many village residents. PA says:

double quotation mark Business secretary Jonathan Reynolds acknowledged “none of this is pleasant” but the disused military site was more suitable than housing asylum seekers in hotels.

There was “no imaginary place these people can go”, he said.

He told ITV’s Good Morning Britain: “Where in the UK would want more of this? We’ve got to share this throughout the entire nation.”

Reynolds said “I think a former military site is a far more suitable location” than a hotel in a town or city centre.

While the asylum seekers are not detained “you can more easily see who’s coming and going from a site like that”.

On July 4, marking the 250th anniversary of American independence, 96% of Piddington voters backed holding the unofficial referendum. It is happening today and will invite residents to express whether their village should become independent from the UK.

A mother and daughter (names not supplied), who are village residents, stand beside a sign opposing the Bicester MoD asylum centre in the village of Piddington in Oxfordshire.
A mother and daughter (names not supplied), who are village residents, stand beside a sign opposing the Bicester MoD asylum centre in the village of Piddington in Oxfordshire. Photograph: Joe Giddens/PA

How Rayner mocked Streeting's failed bid for Labour leadership in speech at TUC dinner

Wes Streeting enjoys attention, likes a good joke and does not mind ruffling feathers. At the Spectator awards ceremony two years ago, Angela Rayner was the target as he gave a speech. He said:

double quotation mark The deputy prime minister is here. Good to see you Pat.

It was a damning jibe about how, although Rayner was officially deputy PM, Pat McFadden was seen as the person with the real authority as Keir Starmer’s right-hand man.

It has taken her a while to get her revenge, but Rayner – no longer deputy PM but back in cabinet as the housing secretary – used her speech at a TUC dinner to flay Streeting. She focused on how his decision to resign as health secretary earlier this summer, so that he could challenge Starmer for the Labour leadership, came to nothing. Andrew McDonald and Megan McElroy have the details in their London Playbook briefing for Politico.

double quotation mark Jab at Wes I: “Last time [Streeting] gave an after-dinner speech he had a few jokes about us both,” Rayner said, referring to herself and Louise Haigh. “I remember it well. I’d just come back from meeting the pope. I was also introduced to a young ambitious cardinal tipped for the top — or the Vatican’s Wes Streeting as they called him. He didn’t get the votes either.”

Jab at Wes II: “Anyway, Louise and I were delighted Wes joined us back in Cabinet. I think Louise advised the prime minister to make him defense secretary because he’s the one Cabinet member we know can’t organize a coup.”

Jab at Wes III: “He wanted education of course. But unfortunately he can’t count to 81.”

That is a reference to the number of Labour MPs needed to get on the ballot for a leadership challenge. Streeting repeatedly claimed he had the numbers but, after Andy Burnham was back in parliament after the Makerfield byelection, it became clear that Labour MPs wanted Burnham to replace Starmer, not Streeting, and Streeting abandoned his leadership bid.

Angela Rayner arriving for cabinet this morning.
Angela Rayner arriving for cabinet this morning. Photograph: Gareth Fuller/PA

According to Sky News, Paul Nowak, the TUC general secretary, is going to make a statement to the conference at about 10.30am explaining the TUC’s revised position on how party funding laws should be reformed.

Reynolds says people should not get 'hyperbolic' about risks posed by AI

Jonathan Reynolds has said that people should not get “hyperbolic” about the risks posed by AI. Speaking ahead of Louise Haigh’s speech to the TUC later, which will address the topic (see 9.31am ), and in the light of the ongoing concerns expressed by AI experts about the risk of AI models posing a threat to humanity, Reynolds told the Today programme:

double quotation mark This is extremely powerful technology, and I think we should never be complacent or naive about the impact it might have.

People will be worried by some of the things they’ve heard in the last few weeks, and I think we’ve got to be careful not to get hyperbolic about this either.

Of course, there are risks, but let’s be frank: there are some tremendous upsides for people as well. Whether it’s public services, healthcare, the contribution to the economy.

If you regulate it in a way where you’re no longer having access to those frontier developments, that would obviously make us less safe. So I think you’ve got to be proportionate and understanding about this in how we seek to regulate going forward.

Reynolds also stressed that the UK is a world-leader in internet safety because of the reputation of the AI Safety Institute, set up when Rishi Sunak was PM.

When Justin Webb , the presenter, put it to Reynolds that people would not be impressed by ministers talking about the “upsides” of the AI given that the potential downsides, the elimination of humanity, are so much more serious, Reynolds replied:

double quotation mark Because of the power of this technology, I wouldn’t say I wasn’t worried.

What I would say is I’m clear-eyed about the best ways to keep the country safe, and that has to be using British innovation, British expertise, British specialisms in such a way that actually improves the position of the UK in terms of the safety.

Asked about the proposal from Jack Clark, one of the founders of Anthropic, for AI companies to have to install a “kill switch” held by a third party, Reynolds said:

double quotation mark I’ve got to be honest, I don’t think that it’s a particularly helpful way to think about how we manage the risks. I’m not really sure what that would actually mean in practice.

To state the obvious, these frontier models are in the main being developed in the US and in China. So exactly what would that mean? I’m not sure what the proposition is.

Jonathan Reynolds arriving for cabinet this morning.
Jonathan Reynolds arriving for cabinet this morning. Photograph: Gareth Fuller/PA

Jonathan Reynolds says it's wrong to view union donations to Labour as equivalent to billionaire donations to Reform UK

Jonathan Reynolds , the business secretary, was doing a media round on behalf of the government this morning. Asked about claims that trade unions donations to Labour are similar to single, multi-million pound donations to Reform UK from cryptocurrency billionaires (see 9.09am ), Reynolds claimed it was wrong to view them as equivalent. He told Times Radio:

double quotation mark When we talk, or people talk about union donations, that is not one entity. That is tens of thousands of ordinary people choosing to make that donation, and it’s bundled up and given from the union. But it’s very easy to opt out of that. There are rules governing political funds, political ballots.

I honestly think to take these mega, ultra crypto Reform donors and compare them to voluntary associations of working people choosing to give some money to the political system, I don’t think the two things are comparable.

Louise Haigh says UK must heed warnings from AI experts

Ministers must “heed the warnings” from industry leaders about the threat posed by AI as it looks to capitalise on the technology, the first secretary, Louise Haigh , will say in a speech to the TUC later. Jessica Elgot has the story.

Here is video of Pippa Crerar and Kiran Stacey talking about the £72m donations to Reform UK on our Politics Weekly UK podcast.

‘Party for sale’? Reform’s £72m donation causes huge backlash | Politics Weekly

Farage says a Reform UK government would stop unions funding Labour if they block £72m donations

Good morning. Ministers already think that the new laws on political donations, as set out in the version of the representation of the people bill which left the Commons earlier this month, will be enough to block some or all of the donations worth £72m to Reform UK announced at the weekend. But they are still planning further amendments to the legislation, when peers debate their own amendments to the bill, to make the restrictions even tighter. Pippa Crerar, Jessica Elgot and Kiran Stacey have the details here in our overnight splash.

Guardian splash
Guardian splash Photograph: Guardian

Kiran and Henry Dyer also have written a good explainer about the rules .

Nigel Farage , the Reform UK leader, has hit back. In an article for the Daily Telegraph , he claims that the proposed new laws – which are retrospective in the sense that they will apply from March 2026, when ministers first announced their intention to legislate, not from the moment when the bill gets royal assent, later this year or early next year – are unfair. If the crackdown goes ahead, Reform UK will stop Labour getting money from the trade unions if it wins the next election, he says.

double quotation mark The Unite union alone has given the Labour party over £50m over the course of the last few years. They’ve got the Employment Rights Act, not to mention big pay rises for bus drivers. If anybody is giving money to do political bidding, it’s the trade unions.

So I would just warn the Labour party here and now: If you try and ban our donors, we in government would certainly ban funding from the trade unions. I don’t want to go down that route. But it’s something we would be prepared to do.

Once again, let me be clear. Neither Ben Delo nor Christopher Harborne will be getting membership of the House of Lords. They won’t be getting favours, they won’t be influencing policy. It isn’t happening.

Because unlike the uniparty we are not for sale.

Until recently the trade unions were opposed to caps on the amount individuals can donate to political parties because they feared that would lead to a rightwing government limiting what unions can donate to Labour. The £72m donations to Reform UK have prompted a bit of a rethink, and union leaders are expected to issue a statement later .

Here is the agenda for the day.

Morning: Louise Haigh, the first secretary of state, chairs a political cabinet. Andy Burnham has cancelled engagements today following the death of his father.

9.30am: The Department for Work and Pensions publishes figures relating to personal independence payment claims and universal credit health claims.

11.30am: Alex Norris, the justice secretary, takes questions in the Commons.

Noon: Downing Street holds a lobby briefing.

After 12.30pm: Yvette Cooper, the health secretary, is expected to make a Commons statement following the publication of the inquiry report into how the nurse Lucy Letby was able to repeatedly kill babies at the Countess of Chester hospital.

2.45pm: Shabana Mahmood, the home secretary, takes questions from the Commons home affairs committee.

3.45pm: Haigh addresses the TUC conference, standing in for Burnham.

Also today, in Piddlington, a small Oxfordshire village, residents are holding a symbolic independence referendum, as a protest against plans to house more than 1,000 asylum seekers in a former military base nearby.

I’m afraid we are no longer able to have comments open below the line every day on this blog . If you want to contact me, please email politics.live@theguardian.com. You can use this email to flag up corrections, comments for publication, questions or complaints.

‘We were bored of traditional fantasy cliches’: inside Playground Games’ 10-year effort to revive Fable

Guardian
www.theguardian.com
2026-09-15 05:30:31
The distinctively British RPG is returning courtesy of the makers of Forza Horizon – with comedy talent such as Richard Ayoade and Natasia Demetriou voicing sweary characters. We had our first play and spoke to its developer When Playground Games was founded in 2010, its objective was straightforwa...
Original Article

W hen Playground Games was founded in 2010, its objective was straightforward: to make the best cars in video games. Operating out of an unassuming white building in Leamington Spa, the studio sought to challenge Gran Turismo with its critically acclaimed British racing alternative: the acclaimed free-roaming Forza Horizon series. Yet as the team grew, so did their ambitions. Now they are making the most intriguing British fantasy game we have seen in many years.

For those who have never ventured into Albion, Fable is a quintessentially British role playing game. Created by Lionhead Studios for the original Xbox in 2004, it boasted cheeky charm alongside world-altering player choices, and its droll writing was brought to life in later iterations by national treasures such as Stephen Fry and Simon Pegg. Thanks to its unique blend of farce and fantasy, Fable grew into a globally beloved franchise – before Microsoft unceremoniously shuttered Lionhead in 2016 , after mandating that the studio turn its star series into a multiplayer game. The result, Fable Legends, was never released.

Now, following a 10-year limbo, Playground is resuscitating the dormant series. “As a developer and a player, working on this has made me think about games more deeply than anything else I’ve done,” reflects Ralph Fulton, Playground Games co-founder and Fable’s game director. “I think it will have that same effect on the people who play it.”

Whoever picked up Lionhead’s mantle would inevitably face some scepticism. And despite their commercial success, driving games rarely command the same reverence or critical respect as blockbuster narrative games. For Fulton, taking on an action RPG was an opportunity to prove that Playground belong among the world’s best. “I think there was a little bit of pride in proving we could cut it in a different genre,” he says. “It made sense not to fall too far from Horizon. This project definitely wasn’t going to have cars, but open worlds felt like an area where we had transferable skills.” Playground was an independent studio at the time, but given that Xbox was the home of the Forza Horizon series, Microsoft seemed an obvious choice of partner. “When Xbox mentioned Fable, every other opportunity dissolved into the background. It was all we could think about.”

After signing the deal in 2017, Playground suddenly needed to build a massive team overnight, and scrambled to merge their talented torque-heads with developers well-versed in Tolkien. “Building a team from scratch is the hardest thing you can do in video games,” Fulton says. “You can hire the people with the best CVs, but if they don’t gel, six to nine months later you’re back in the market. The human cost meant bringing hundreds of people from all over the world, many of whom relocated solely because they were fans of Fable.”

At the Gamescom expo in Germany, I play the result of the studio’s efforts. While I don’t get to explore much of Albion in this demo, as I fight my way through a dungeon, the combat soon dispels any lingering doubts about Playground’s action chops. Thrust into a wraith-ridden mine, my hero faces an ambush of crackling skeletal knights. Every attack lands with tactile weight, from the heavy crunch of a hammer to the crisp thwick of a bowstring, sending skeletons exploding into bone splinters. When floating wraiths descend in the aptly named Wraithmarsh, fireballs and lightning spells land with spectacle. I fell the swamp’s final foe by transforming them into a squawking rooster.

The combat has tactile weight and skeletons exploding into bone splinters.
The combat has tactile weight and skeletons exploding into bone splinters. Photograph: Xbox Game Studios

The writing is sharp. Between smashing skeletons, I stumble upon a dust-coated love letter penned with authentically British awkwardness, in which a dying miner’s struggle to channel the earnestness needed to say his goodbyes is clear on the page. “We were a bit bored of traditional fantasy cliches and the way characters usually speak in those settings,” says Fulton. “I love media, particularly in period settings, that use a modern tone of voice, idiom, and phrasing … I don’t think we could have gotten Matt King to play a cliched fantasy character.”

Peep Show’s King stars as the retired former “greatest Hero of Albion” Humphry, who guides me through this haunted mine shaft with signature sarcasm. King is just one of the big names among Fable’s cast: a who’s-who of British comedy talent including Richard Ayoade (The IT Crowd) and Natasia Demetriou (What We Do in the Shadows). “Richard Ayoade virtually improvised a new script for his scenes on the spot. Listening to him conjure that dialogue was one of my favourite days on this project,” smiles Fulton. “Natasia Demetriou brought a level of British swearing to her character that honestly shocked us, but it’s brilliant and we let her run with it.”

skip past newsletter promotion
‘Open worlds felt like an area where we had transferable skills’ … Fable.
‘Open worlds felt like an area where we had transferable skills’ … Fable. Photograph: Xbox Game Studios

Beyond the swears, satire and sorcery, Fulton stresses that the core of the Fable series remains in this reboot: meaningful player choice. For much of the game, Fulton says, the main quest will feel low-stakes enough to keep players feeling comfortable to mess around in Albion. You will only be locked into the main story during its climatic ending.

“Most importantly, we stick the landing,” Fulton says. “It’s easy to look at industry metrics saying 100% of people start a game and only 25% finish, and decide not to invest heavily in the finale. But we relentlessly pursued quality here. In great Fable fashion, it culminates in a choice that really challenges you to think about who you are, with a real personal cost to weigh against the outcome.”

Playground’s long-gestating project carries immense weight, both for nostalgic fans and for Xbox itself, given that it was Microsoft that killed off the series and its original creator. Since the Fable reboot started production in 2016, the industry has weathered a disastrous pivot to live service games, a global pandemic, and more than 6,000 layoffs at Playground’s parent company, Xbox.

“I genuinely think Fable is an important game. What has protected us over a long development cycle is that so many people within Xbox view Fable as vital to the DNA of the platform,” Fulton says. “I would go a step further and say it’s important to video games as a whole. It offers a combination of elements that simply doesn’t exist anywhere else.”

Faith-Labor Organizing: Why Building Permanent Infrastructure is the Intervention This Moment Demands

OrganizingUp
convergencemag.com
2026-09-15 05:15:44
Featured image by Jared Rodriguez. Our congregations and our unions each have a measure of power. None of it is enough. The change we seek requires building power together; prioritizing building that power together in parallel with the work we are already doing alone. Our faith communities and labor...

CSS-Tricks in Limbo

Lobsters
vale.rocks
2026-09-15 05:12:31
Comments...
Original Article

I’m sad to say that CSS -Tricks is stuck in limbo again. The site was acquired by Digital Ocean in 2022 and it continued to run under their ownership until February of 2023 when Digital Ocean fired the people working on it. The site stayed latent for a year before Digital Ocean re-hired lead editor Geoff Graham in June of 2024 who got the ship sailing again.

Now, CSS -Tricks sits inactive again . Its future is unclear, because there hasn’t been any communication. Digital Ocean largely just went silent. Digital Ocean is a big company, and it can be expected that things get missed, especially with staff turnover. However, management is a small part of a larger picture.

Only a few days ago, DigitalOcean pledged a $3,000,000 USD donation to Omarchy – a set of scripts and configurations atop Arch Linux and a range of other open-source software (much of which struggles greatly for funding). A set of scripts and configurations which are led by David Heinemeier Hansson ( DHH ). Previously of Ruby on Rails fame, but now of Omarchy notoriety and far-right , racist infamy .

This isn’t a matter of effort or time; it is a matter of care. As David Heinemeier Hansson wrote announcing DigitalOcean’s funding :

But the part of this patronage that really made me smile was how quickly it all came together. I reached out to Paddy Srinivasan, DigitalOcean’s CEO , on X on Wednesday. We had a call that same night. I sent a proposal on Saturday. By Sunday, we’d finalized everything.

I know Geoff has been trying to raise this for months , to no avail.

Atop of this, DigitalOcean has ceased the monthly $50 payments they previously gave to GNOME and Flathub infrastructure . Apparently it is a more pressing matter for them to donate to a collection of scripts and configuration files than to contribute to the projects they’re built upon or to pay the writers and editors of their own publication.

Yes, I’ve got skin in the game as someone who has written for the publication, but I’ve got more skin in the game as someone who wishes for a thriving ecosystem and who wants to read the quality work CSS -Tricks is known for publishing. There are very few quality publications about the web left, and it would be a major blow to lose another.

Coreutils - rejected feature requests

Lobsters
www.gnu.org
2026-09-15 05:06:36
Comments...
Original Article

Some of the hardest work on coreutils is knowing what to reject and providing appropriate justification to the contributors.

The contributions below while all good ideas, were not included for various reasons detailed on the linked mailing list discussions.

cat chmod cp cut date dd df du join ls mv rm shred sort stat *sum touch uniq wc misc New commands

cat

  • cat --timestamp . awk or perl is good enough for this
  • cat -n alternate formats . Manipulation with existing tools supports this better
  • cat --show-ends to highlight trailing whitespace. grep --color was deemed better/sufficient
  • cat --header to output filenames for each file. tail -n+1 does this already
  • cat -S to squeeze lines just containing blank chars. Existing tools like `sed 's/^ *$//' | cat -s` were thought sufficient
  • cat -d,--direct to use direct I/O. dd has the 'nocache' or 'direct' options and there are general nocache wrappers

chmod

cp

cut

date

dd

df

  • df,du -g . Specifying “Gigabyte” output format is neither standard or required
  • df autoscale . df -h was thought good enough
  • df -g . Separate options for various output units is best avoided
  • df --without-header . --header options are only really useful for data consumers
  • df --dereference to process symlink targets. df was changed to reference symlink targets unconditionally

du

join

ls

mv

rm

  • rm --parents . Deleting the opposite way up the tree was deemed too dangerous
  • rm -d . rmdir is equivalent and less confusing
  • rm --no-preserve-root . Adding protective prompts would not significantly improve security
  • rm -rf . to delete current directory. It was thought existing support for rm -rf "$PWD" suffices
  • rm -rf . to delete all files inside current directory. `rm -rf * .[!.] .??*` and `find . -delete` were deemed sufficient
  • rm -s to behave in a “smarter” fashion. `rm -I` or `find | xargs rm` were deemed sufficient
  • rm --exclude to exclude file names. Existing tools like find(1) were thought sufficient
  • rm should use remove() , leaving unlink() to the unlink command. We can't change such standardized functionality

shred

sort

stat

*sum

touch

  • touch -R . `find . -exec touch -am {} +` is more general
  • touch --mode . Not deemed beneficial enough
  • touch --verbose . This could not be implemented robustly. Also xargs --verbose or (set -x; touch *) are sufficient
  • touch --create to only create files. `test -e file || touch file` was deemed sufficient

uniq

wc

  • wc --tab-width . Preprocessing with expand is more functional
  • wc -q to suppress the file name. Redirecting file to stdin is sufficient
  • wc --max-chars=N to filter out long lines. Existing filters like awk 'length($0) <= 3' were deemed more appropriate

misc

New commands

The terminal should not own the work

Lobsters
lezli01.is-a.dev
2026-09-15 04:55:35
Comments...
Original Article

// docs/why/the-terminal-should-not-own-the-work.md docs online

The terminal should not own the work

A terminal is a good place to start a process. It is a poor place to store the truth about that process.

When an agentic task belongs to one terminal tab, the tab becomes an accidental control plane. Closing it can stop the work. Losing scrollback can erase the useful explanation. Reopening the project means reconstructing what ran, which branch it changed, and whether it was waiting, finished, or quietly stuck. The longer the task runs, the more fragile that arrangement feels.

Vincent moves ownership into a background daemon. The daemon owns task state, workflow execution, agent processes, scheduling, the database, and git worktrees. The TUI, CLI, and API are clients of that state. Closing any client changes nothing about the work behind it.

That architectural choice has consequences that are easy to feel. I can start a task, close the TUI, use the terminal for something else, and return later to the same state and history. Several clients can inspect the same daemon without becoming competing writers. The scheduler can admit tasks by priority and concurrency limits even when nobody has a dashboard open.

Durability also changes how interruption is handled. Vincent persists a state transition before acting on it. If the daemon stops during a step, restart recovery records the interrupted attempt, verifies any orphaned process before stopping it, and runs the step again without consuming a failure retry. The system does not pretend that a process survives a machine restart; it makes the interruption explicit and recovers from known state.

Installing the daemon as a user service extends the same model across logins. The operating system starts the control plane, and the terminal returns to being what it should be: one optional window into the work.

This matters because agentic coding is increasingly a workload rather than a conversation. Tasks wait on quotas, gates, retries, child branches, and external checks. Their lifetime should not be coupled to the lifetime of the interface that launched them.

The terminal can show the work. It should not own the work.


Back to Why vincent is awesome · Understand the daemon

Microsoft confirms KB5002914 Excel update breaks copy and paste

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 04:40:20
Microsoft has confirmed that copy and paste may silently fail for some Excel users after installing the September 2026 KB5002914 security update. [...]...
Original Article

Excel

Microsoft has confirmed that copy and paste may silently fail for some Excel users after installing the September 2026 KB5002914 security update.

This follows a wave of customer reports on Reddit and the Microsoft Q&A forums that the KB5002914 Office security update is breaking copy-and-paste , autofill, and formula dragging in Excel.

The company has confirmed these issues, saying they affect Microsoft Excel 2024, 2021, 2019, and 2016.

"Although users try to paste content, the source remains selected and the destination is unmodified," Microsoft said in an updated support document .

"When this issue occurs, users receive no indication of the failure, such as a beep or error message. Microsoft is researching the issue and will post more information in this article when the information becomes available."

While Microsoft is still working to resolve this bug, affected Excel users have reported that uninstalling KB5002914 restores copy-and-paste functionality on impacted systems.

To do that, open a new Command Prompt window with the "Run as administrator" option and run one of the following commands (depending on your Office version):

Office 2016:
"C:\Program Files\Common Files\Microsoft Shared\OFFICE16\Oarpmany.exe" /removereleaseinpatch "{90160000-0012-0000-1000-0000000FF1CE}" "{27882596-A8ED-4382-9C71-6CD2DD19F732}" "1033" "0"

Office 2019:
"C:\Program Files\Common Files\Microsoft Shared\ClickToRun\OfficeC2RClient.exe" /update user updatetoversion=16.0.10417.20197

Office 2021 & 2024:
"C:\Program Files\Common Files\Microsoft Shared\ClickToRun\OfficeC2RClient.exe" /update user updatetoversion=16.0.20326.20132

Microsoft released the KB5002914 security update last week as part of its September 2026 Patch Tuesday to patch a long list of remote code execution and information disclosure vulnerabilities.

In July, Microsoft also fixed an issue that was preventing third-party applications from launching Word, Excel, PowerPoint, Access, and other Microsoft Office apps or opening documents on up-to-date Windows systems.

One month earlier, Microsoft addressed another bug that blocked Office for the web users from opening Excel and PowerPoint files, and another that blocked Windows 365 users from downloading and installing the Office suite.

On Monday, it also released emergency out-of-band updates to resolve Remote Desktop Services failures caused by the September 2026 security updates, along with Hyper-V and USB audio problems on some Windows versions.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Alternatives to MinIO for single-node local S3

Hacker News
rmoff.net
2026-09-15 04:21:27
Comments...
Original Article

Let’s now explore the different alternatives to MinIO, and how easy they are to switch MinIO out for.

I’ve taken the above project and tried to implement it with as few changes to use the replacement for MinIO. I’ve left the MinIO S3 client, mc in place since that’s no big deal to replace if you want to rip out MinIO completely (s3cmd, aws CLI, etc etc).

S3Proxy 🔗

Version tested: 3.0.0

Ease of config: 👍👍

Very easy to implement, and seems like a nice lightweight option.

One thing I did notice was that one of the projects that S3Proxy uses, jclouds , was moved to the Apache Attic (i.e. retired) in mid-2025—although probably nbd if you’re only using local storage anyway?

RustFS 🔗

Version tested: 1.0.0-alpha.79

Ease of config: ✅✅

Be aware that there was recently a pretty bad security vuln found in RustFS, which has put some people off from using it. The website looks pretty smart but several links resolve to the same page, giving it that "fresh paint" smell of a new project :) This might matter less for demos, if it’s easy to switch out. You’ll also note that the project is currently only 'alpha' release.

rustfs.excalidraw

RustFS also includes a GUI:

rustfs gui

SeaweedFS 🔗

Version tested: 4.06

Ease of config: 👍

seaweedfs.excalidraw

This quickstart is useful for getting bare-minimum S3 functionality working. (That said, I still just got Claude to do the implementation…). Overall there’s not too much to change here; a fairly straightforward switchout of Docker images, but the auth does need its own config file (which as with Garage, I inlined in the Docker Compose).

Edit: Straight after posting this blog, the project replied to say they’ll be removing this extra requirement, making it even easier to use! How cool is that :)

SeaweedFS comes with its own basic UI which is handy:

seaweedfs ui

The SeaweedFS website is surprisingly sparse and at a glance you’d be forgiven for missing that it’s an OSS project, since there’s a "pricing" option and the title of the front page is "SeaweedFS Enterprise" (and no GitHub link that I could find!). But an OSS project it is, and a long-established one: SeaweedFS has been around with S3 support since its 0.91 release in 2018 . You can also learn more about SeaweedFS from these slides , including a comparison chart with MinIO .

Zenko CloudServer 🔗

Version tested: 9.2.8

Ease of config: 👍

cloudserver.excalidraw

Formerly known as S3 Server, CloudServer is part of a toolset called Zenko, published by Scality. It drops in to replace MinIO pretty easily, but I did find it slightly tricky at first to disentangle the set of names (cloudserver/zenko/scality) and what the actual software I needed to run was. There’s also a slightly odd feel that the docs link to an outdated Docker image.

Garage 🔗

Ease of config: 😵

Version tested: 1.0.0

I had to get a friend to help me with this one. As well as the garage container, I needed another to do the initial configuration, as well as a TOML config file which I’ve inlined in the Docker Compose to keep things concise.

garage.excalidraw

Could I have sat down and RTFM’d to figure it out myself? Yes. Do I have better things to do with my time? Also, yes.

So, Garage does work, but gosh…it is not just a drop-in replacement in terms of code changes. It requires different plumbing for initialisation, and it’s not simple at that either. A simple example: The specified key ID is not a valid Garage key ID (starts with GK, followed by 12 hex-encoded bytes) . Excellent for production hygiene…overkill for local demos, and in fact somewhat of a hindrance TBH.

Is this an entirely fair assessment? If I were looking at it as a new piece of technology in its own right, completely not! Many pieces of excellent technology, particularly those that can support running distributed, will have a steep learning curve for configuration. However, my requirement here is a simple drop-in replacement for MinIO—which Garage is not.

Apache Ozone 🔗

Version tested: 2.1.0

Ozone was spun out of Apache Hadoop (remember that?) in 2020 , having been initially created as part of the HDFS project back in 2015.

Ease of config: 😵

apacheozone.excalidraw

It does work as a replacement for MinIO, but it is not a lightweight alternative; neither I nor Claude could figure out how to deploy it with any fewer than four nodes. It gives heavy Hadoop vibes, and I wouldn’t be rushing to adopt it for my use case here.

Ceph Object Gateway 🔗

Ozone (above) is heavyweight enough; I’m sure both are great at what they do, but they are not a lightweight container to slot into my Docker Compose stack for local demos.

AI safety requires more than just slowing our pace | Stuart Russell

Guardian
www.theguardian.com
2026-09-15 04:00:31
Safety requirements are non-negotiable. They depend on meeting concrete goals, not just adjusting a timeline It has been a week of high drama in AI, precipitated by the resignation of the AI safety researcher Jacob Coxon from Anthropic. This followed several weeks of increasingly lurid and disturbin...
Original Article

I t has been a week of high drama in AI, precipitated by the resignation of the AI safety researcher Jacob Coxon from Anthropic . This followed several weeks of increasingly lurid and disturbing revelations about the OpenAI/Hugging Face incident.

My inbox yesterday included a message from Business Insider with the subject line: “AI doomsday debate reaches boiling point”.

Now, the Anthropic CEO, Dario Amodei, has written a 3,800-word, reassuringly phrased letter titled “We Must Pace the Frontier,” describing his proposals for avoiding (or at least postponing) doomsday. Sam Altman of OpenAI, Elon Musk of xAI, Demis Hassabis of Google Deepmind, and Satya Nadella of Microsoft have all expressed support.

You may be forgiven for not immediately understanding what “pace the frontier” means. (My first image was of Amodei walking deep in thought along the Finnish–Russian border.)

The phrase also appeared in July’s “Pacing the Frontier” open letter, signed by 1,386 employees of frontier AI labs, including Amodei himself.

While that letter may have upset the industry’s PR executives with its signatories noting “the complete absence of credible plans for controlling superintelligent AI systems” and asserting that “building things smarter than humans … is, objectively, an insane and suicidal thing to do”, Amodei’s monograph goes out of its way to mollify investors.

The notion of pacing the frontier seems to come from Formula 1: when conditions become too dangerous for racing, a pace car comes onto the track and all the other cars have to follow it as a safe speed. Progress continues, without the danger.

Amodei writes: “To be clear, pacing does not mean halting model training or technical progress.”

Amodei’s letter is prompted by his concern that “AI has been advancing drastically faster, driven primarily by ... recursive self-improvement.” It’s as if he and Sam find themselves driving their F1 cars at 200mph neck-and-neck heading into the first corner, only to realize it’s covered in ice and they have no steering wheel. No wonder they want to slow down.

In brief, Amodei’s proposal has three parts. The first is to have third-party AI system evaluators working inside each company, with full access to the systems; he commits Anthropic to this plan now, without waiting for the government to require it.

The second part of the plan asks all the frontier AI companies in “democratic countries” to “establish common safety standards as well as limits on the rate of unchecked AI progress”, with government regulation where needed. The third part would include “authoritarian countries” in a broader compact.

Here, Amodei goes out of his way to reassure those in Washington who see America’s lead in AI as its most important geopolitical asset.

On a casual reading, there are many reasons to believe that Amodei is calling for a general slowdown in the rate of progress. He talks about “limits on the rate of unchecked AI progress” and “some kind of ‘speed limit’ on the rate of recursive self- improvement (RSI)”. He says: “Progress will still seem fast, and we must make wise use of the time we gain.”

Slowing down would give companies a bit more time to work on safety; Amodei talks about one to two years of extra time for research on interpretability, alignment, and better testing methods.

Let me pause here to respond to Amodei’s critics who say it’s just a bid to cement Anthropic’s lead with the help of government intervention. This is nonsense. In fact, the Wikipedia page on pacing in F1 races says it “eliminates any time and distance advantage that a leading driver may have had over the remaining field of competitors”.

Having said that, I think the pacing metaphor is completely misguided.

We cannot set a slower rate of progress for capabilities and then hope that provides enough time to get the safety right. The safety requirements are non-negotiable. We must set the safety requirements first, and further progress occurs only when they are met.

Imagine if Boeing said: “We’re going to introduce a new plane every year, and we hope that provides enough time for some flight tests to be completed and for the results to be good.”

We would say: “No, you have that backwards; you can introduce a new plane only when it has passed all the tests and the government has issued an airworthiness certification. If that takes more than a year, so be it.”

A more careful reading of the document suggests that Amodei agrees with this objection. For example, he says that rules should be of the form: “If models have capability X, then they need to be accompanied by certifications of alignment properties Y and Z.”

In other words, we set safety requirements, and developers have to show that they meet those requirements. This is in fact the “red lines” approach that AI safety researchers have been calling for.

And it means that if developers can’t figure out how to meet the safety requirements, then they will have to halt. It would be, in F1 terminology, a red flag and not a pacing car.

There is no plausible alternative. Recursive self-improvement leading to superintelligent AI raises the risk of the irreversible loss of human control. The acceptable risk level for loss of control is perhaps one in 100m per year, not the one in 10 or one in five that the AI CEOs currently estimate.

And remember “the complete absence of credible plans for controlling superintelligent AI systems”. At some point, progress along this technology path will halt, not because further progress is impossible, but because further progress is untenable when the technology is intrinsically unsafe. Humanity has a right to protect itself.

There is huge resistance to this conclusion. We have already sunk trillions of dollars into the current technology path and plan to sink trillions more. But the sunk cost fallacy is just that: if we double down on a mistake, it’s still a mistake.

The present level of attention to AI risk, the unanimity of the leading technology executives, and the forthcoming Trump–Xi summit give us a real opportunity to choose a different path. We must take it.

  • Stuart Russell is a distinguished professor of computer science at University of California, Berkeley, the president of the International Association for Safe and Ethical Artificial Intelligence and a Guardian US columnist

Why a decade of doomsday warnings failed to slow the AI race

Guardian
www.theguardian.com
2026-09-15 04:00:30
From Stephen Hawking to Jacob Coxon’s viral Anthropic resignation, fears that AI could threaten humanity have shaken the industry without stopping its pursuit Before an Anthropic researcher resigned and declared human extinction imminent last week, tech leaders and scientists had sounded the alarm ...
Original Article

Before an Anthropic researcher resigned and declared human extinction imminent last week, tech leaders and scientists had sounded the alarm about a superintelligent AI ending humanity for over a decade.

The development of artificial intelligence “could spell the end of the human race”, warned professor and astrophysicist Stephen Hawking in 2014 – a little less than a decade before the public got its hands on the generative AI features of the original version of ChatGPT.

It’s a now-familiar warning, echoed across the AI and tech industry by countless employees and CEOs of the leading frontier labs across the US. However, at the time, generative AI was still in its infancy. To the public, the idea that the technology could perform many of the tasks it can today, much less become super intelligent and take over the world, felt largely hypothetical, if not entirely a thing of science fiction.

Every few years, a schism occurs within AI. Teams of researchers and tech leaders issue somber warnings about the dangers of AI built irresponsibly. Rather than push for a pause on any development, a faction of researchers breaks away to create a new AI company.

However, no moment has created as many reverberations across the industry as the now-viral resignation of Anthropic safety researcher, Jacob Coxon, who tweeted on 9 September that Anthropic and OpenAI were “racing straight to self-improving super intelligence and gambling with our lives”. His concerns were echoed by dozens of staffers from Anthropic, OpenAI and other competing firms.

Days later, the CEOs of the leading US AI firms answered a call from Anthropic CEO Dario Amodei to slow down what he called a “reckless” approach to creating AI.

“AI brings risks, and because it is such a powerful technology, these risks are serious,” Amodei wrote in a blog post. “I’ve written a lot about them too. They include the risk of losing control of AI systems , misuse of AI for cyber-attacks and bioterrorism , and serious economic disruption . A race to the bottom, spurred by commercial incentives, can make these risks more acute.”

While there’s often scant detail and even less hard evidence provided for the ways AI could lead to the ultimate doomsday scenario, that specter has loomed large over the technology’s development.

Hawking’s fear was that AI would become so advanced that it would redesign itself, and humans “who are limited by slow biological evolution” wouldn’t be able to compete “and would be superseded”, he said in an interview with the BBC .

Google was the biggest household name in the field at the time, particularly after acquiring a two-year-old AI startup, DeepMind, for $650m in January 2014. But Google and other firms like it weren’t building AI responsibly enough, according to Elon Musk and Sam Altman . The threat that AI could be developed in a way that could be dangerous for humanity was so pervasive, they claimed, that they co-founded a nonprofit dedicated to building it safely, OpenAI, in 2015.

OpenAI’s stated mission was to “advance digital intelligence in the way that is most likely to benefit humanity as a whole, unconstrained by a need to generate financial return”. But even before there were clear consumer use cases for more rudimentary versions of the technology, both Musk and Altman were already motivated by competition.

“Been thinking a lot about whether it’s possible to stop humanity from developing AI. I think the answer is almost definitely not,” Altman wrote in 2015 in an email to Musk, which was released as part of Musk’s 2024 lawsuit against OpenAI. “If it’s going to happen anyway, it seems like it would be good for someone other than Google to do it first.”

In the intervening years, priorities within OpenAI shifted away from safety, some employees claimed. In 2021, several employees focused on AI ethics and alignment at OpenAI left the firm. They argued the company had started to prioritize rapid commercial development over safety.

Those employees created another company: Anthropic.

Founded by siblings Dario and Daniela Amodei, Anthropic pitches itself as an “AI safety and research company” with a familiar mission: Developing and deploying AI models “ in a way that benefits people” .

Coxon worked at both OpenAI and Anthropic.

The underlying premise of each of these firms is that AI is “only safe in their hands” and that if they don’t build it, someone else will, said Sarah Myers West, co-executive director of the AI Now Institute, which studies the impacts of AI. That paranoia is fueling a race to the bottom, she argued.

skip past newsletter promotion

Both Anthropic and OpenAI face enormous pressure from investors to turn a profit soon. The former has raised over $130bn, while the latter’s funding has reached over $190bn, and both companies have filed for an initial public offering on the US stock market. Altman only recently said the company plans to push back its IPO to at least 2027 due to safety concerns.

“They are investing literally billions of dollars in building AI at a larger and larger scale, and the first thing that gets cut is meaningful investment in baseline security protocols,” Myers West said.

Around the same time that the fractures among OpenAI’s safety teams began to form, Google’s AI arm faced its own internal reckoning. In 2020, Dr Timnit Gebru was ousted from her role as co-lead of Google’s ethical AI team after publishing a paper that explored the biases and potential real-time harms of AI systems. Gebru, who founded the independent research firm the Distributed AI Research Institute, still argues that the so-called doomer scenarios are a distraction from the real-time harms that AI is causing today.

Three years later, Geoffrey Hinton, a Nobel laureate often referred to as the godfather of AI, also left Google over his own fears of the risks that AI could be used by bad actors and one day harm humanity.

Now, about five years since its founding, Anthropic is facing the same employee and industry concerns that it was born out of. In July, over 1,000 leading staff and leaders from Anthropic as well as Google, Meta and OpenAI signed a petition calling for the US government to create incentives for slowing the pace of AI development.

The petition came after OpenAI disclosed that hundreds of its AI agents worked together without the company’s knowledge to breach the security of AI firm Hugging Face. Anthropic, too, disclosed that its agents also compromised the security of external firms.

It wasn’t the first time a petition like this made the rounds. In 2023, after OpenAI released a new version of its chatbot, the Future of Life Institute published an open letter signed by figures like Musk and former Apple co-founder Steve Wozniak that called on AI firms to agree to a six-month moratorium on the development of the most advanced AI models. The Future of Life Institute is a nonprofit that is largely funded through a cryptocurrency donation from Ethereum co-founder Vitalik Buterin.

Two weeks after the 2026 Hugging Face breach, OpenAI announced it was pausing development of some aspects of AI training. That didn’t stop the company from ultimately releasing its latest AI model called Astra – albeit with more limited cybersecurity capabilities, the company said. Anthropic, too , said it had temporarily paused development of some aspects of its AI training.

Coxon resigned shortly afterward.

The k-server conjecture is true

Hacker News
arxiv.org
2026-09-15 03:45:31
Comments...
Original Article

View PDF HTML (experimental)

Abstract: The $k$-server conjecture states that a deterministic online algorithm can achieve competitive ratio $k$ on every metric space. We prove the conjecture. Specifically, we show that the work function algorithm satisfies it.
Our proof uses a natural algebraic representation of the work function as a matrix, which encodes all feasible paths to reach a configuration. In this representation, the minimum and addition operations arising in the definition of optimal costs correspond to addition and multiplication of formal expressions, and each work function value corresponds to the determinant of $k$ columns of the matrix. A request arrival updates the representation via a change of basis and row replacement. The amortized analysis is based on a potential function defined in terms of a larger matrix whose coordinates are pairs of coordinates of the original matrix representation.

Submission history

From: Marek Zbysiński [ view email ]
[v1] Mon, 14 Sep 2026 17:58:11 UTC (22 KB)

Stalling installing

Lobsters
adactio.com
2026-09-15 03:31:11
Comments...
Original Article

I’m an invited expert at the World Wide Web Consortium.

That sounds impressive, but it isn’t. Anyone can become an invited expert. The fact that I am now one proves it. You apply to be an invited expert and once that application is approved, you’re in. So you too could and probably should be an invited expert to a working group at the W3C.

I was strongly encouraged to become an invited expert in the web applications working group after weighing in on the matter of installable web apps (or progressive web apps or whatever).

Last week I had my first call but it wasn’t with the web apps working group, it was with the technical architecture group , a meta-group that helps the other groups if there’s a big-picture sticking point.

There’s a big-picture sticking point with installing web apps.

While most of the participants (read: browser makers) would like to spend their time deciding the details of what API to implement in order to support installable web apps, Webkit is rejecting the very premise of that work.

On last week’s call, Webkit outlined their position : there shouldn’t be a way for developers to allow users to install the current website. Leave it to the browser, they say. Also, is this even something that users want? Looking at the stats, it certainly doesn’t seem like it.

That would be a reasonable position if there were already a usable way to install web apps. But there isn’t . It’s technically possible to add a website to your iPhone’s home screen. In practical terms, it’s a convoluted usability nightmare.

(It’s hard to avoid veering into conspiracy theory territory and seeing this as some kind of malicious compliance. Especially when you compare it to how native apps are shoved in your face thanks to “ smart app banners ” better known as dickovers .)

So Webkit’s stance would make total sense if there were a reasonable way for users of Mobile Safari to install web apps already. But there isn’t.

Last week’s call was quite illuminating. It showed some incredible cognitive dissonance in the Webkit position. Let me explain…

On the one hand, installing web apps is kind of like bookmarking, they say. That’s true. We don’t have an API for bookmarking so why should we have an API for installing web apps?

That would be a fair point if the user interface for bookmarking and installing were in any way comparable. But bookmarking is literally front and centre of the user’s experience of a browser, backed up by decades of convention. Meanwhile the option to install a web app is buried five levels deep behind a “share” icon.

Also: we’re not talking about an API for installing web apps. We’re talking about an API for initialising the flow for installing web apps—the very same flow that’s triggered from that buried menu item. And that flow begins with a prominent option to cancel. So let’s not have any scaremongering about users somehow being tricked into adding web apps to their home screen.

Which brings me to the other point…

Webkit are concerned about allowing installed web apps getting access to more powerful APIs. They’re quite right not to want that! A web app launched from the home screen shouldn’t have any special privileges. If it wants access to say, geolocation, the user needs to grant permision just the same as if the page were in the browser.

Why, oh, why then did Apple limit push notifications to installed web apps ?

It’s not like other browsers haven’t managed to implement permission-based APIs like push notifications. But apparently limiting notifications to installed web apps was the only way that Apple could think of implementing this API safely.

You see the contradiction, right?

On the one hand, Webkit is saying that installing web apps is like bookmarking. No big deal.

On the other hand, Webkit is saying that installing web apps grants special privileges. A huge deal!

Which is it?

It’s almost as if Webkit aren’t actually participating in good faith but rather have already made up their mind to drag their heels when it comes to any kind of progress on this topic.

Anyway…

On last week’s call, I was supposedly representing the interests of developers. No pressure!

I can’t make any claim to represent all developers, but I like to think I’m fairly representative of a typical developer who’s quite fond of the World Wide Web. So in my allotted ten minutes of speaking time, I said:

  • Developers would love a way to initiate the installation flow. The alternative is to provide browser-specific instructions —always a bad sign.
  • If browsers provided a prominent usable way to initiate the installation flow, this wouldn’t be such an urgent need.
  • Users should have the option to install web apps just like they have the option to install native apps. Technically both options are available, but practically the scales are tipped very, very heavily towards native apps.
  • Native apps don’t have the same security model as web apps, allowing them much greater access to user data. If security and privacy are values that the W3C thinks are important for users, they should do everything in their collective power to help tip the scales back in the direction of web apps.

Cisco patches Secure Email Gateway zero-day exploited in attacks

Bleeping Computer
www.bleepingcomputer.com
2026-09-15 03:31:09
Cisco warned customers to patch a critical Secure Email Gateway zero-day security flaw that threat actors have been exploiting in attacks. [...]...
Original Article

Cisco

Cisco warned customers to patch a critical Secure Email Gateway zero-day security flaw that threat actors have been exploiting in attacks.

"In September 2026, the Cisco PSIRT became aware of active exploitation of this vulnerability," the company warned in a Monday security advisory.

The security flaw (tracked as CVE-2026-76461 ) was found in the email parsing of Cisco AsyncOS Software for Cisco Secure Email Gateway and affects virtual and physical appliances, regardless of the device configuration.

Successful exploitation can allow unauthenticated, remote attackers to execute arbitrary commands with root privileges on the underlying operating system.

"This vulnerability is due to insufficient validation in the email parsing logic. An attacker could exploit this vulnerability by sending a crafted email message that contains malicious SQL statements through an affected device," Cisco added. "A successful exploit could allow the attacker to execute arbitrary SQL statements, leading to command execution with root privileges on the underlying operating system."

Cisco shared indicators of compromise and advised network defenders to look for suspicious SQL statements in each cluster device's mail_logs.

However, admins should also cross-check network and firewall logs for signs of suspicious activity (including uploads and downloads to and from external or malicious IP addresses) because attackers may remove evidence of exploitation.

Internet security watchdog Shadowserver currently tracks over 400 Cisco Secure Email Gateway appliances , but it provides no information on how many are honeypots or have already been secured against attacks.

Internet-exposed Cisco Secure Email Gateway appliances
Internet-exposed Cisco Secure Email Gateway appliances (Shadowserver)

The Cybersecurity and Infrastructure Security Agency (CISA) also added the CVE-2026-76461 flaw to its Known Exploited Vulnerabilities (KEV) Catalog on Monday , ordering federal agencies to patch their systems within three days, by September 17.

On Monday, Cisco addressed four other critical vulnerabilities (CVE-2026-76440, CVE-2026-76441, CVE-2026-20353, and CVE-2026-76443) affecting Secure Email Gateway (SEG) and Secure Email and Web Manager (SEWM) appliances regardless of configuration, but said it had no evidence they have also been exploited in the wild.

In January, the company also patched a maximum-severity Cisco AsyncOS flaw (CVE-2025-20393) exploited in zero-day attacks against SEG and SEWM devices since November 2025 .

More recently, Cisco revealed that three separate ransomware and state-sponsored threat groups have exploited two recently patched Secure Firewall Management Center (FMC) flaws.

Since November 2021, CISA has flagged 98 Cisco vulnerabilities as actively exploited in attacks, including seven abused by ransomware gangs.

article image

Build your security blueprint for AI-powered attacks

Join Mikko Hyppönen and security leaders from the NFL, CHANEL, and Atlassian for a two-hour digital summit on what AI-speed attacks change, what defenders should stop doing, and how to validate, decide, fix, and re-validate at machine speed.

Save your seat

Yves-Alexis Perez: IKEv1 protocol disabled in strongSwan package for Debian unstable

PlanetDebian
www.corsac.net
2026-09-15 03:28:41
Heads up, Debian IKE/IPsec users. Starting with strongSwan 6.1.0-1 (currently in Debian unstable and targeted at Debian 14 Forky), the IKEv1 protocol has been disabled. This is aligned with upstream decision. Considering IKEv2 is already nearly old enough to drink in the USA (RFC 4306 will turn 21 ...

Lingo.dev (YC F24) is hiring a senior content engineer (Remote, worldwide)

Hacker News
lingo.dev
2026-09-15 03:00:30
Comments...
Original Article

About Lingo.dev

Lingo.dev is the localization engineering platform where teams measure translation quality, translate with LLMs, and proofread with native speakers. Lingo.dev graduated from the world's best tech accelerator (Y Combinator) and raised venture funding to grow into new markets.

The job

You own content production and distribution: write, publish, distribute the same day, measure, and automate the boring parts, aggressively. The founders set the direction, you decide what gets written each day and how what you write reaches its readers.

Concretely: blog posts, research articles backed by original engineering discoveries, essays, the changelog, product update emails, the newsletter, LinkedIn, dev.to , Hacker News, Reddit, Google Ads, SEO, and the occasional example repo or demo project. You write about events, news, research results, and product features through the lens of Lingo.dev 's positioning, for a technical audience and for a non-technical one. You interview founders, engineers, and customers, and you write from what they said, quoted. You publish on the site and off it, and contribute to our playbook and the narrative every piece is written from.

Whatever is due this week is published this week. The volume grows, and you keep up by automating: Claude workflows, templated distribution, scheduled publishing.

You own organic and paid: SEO, GEO, AI visibility. You track what gets cited, what ranks, what compounds, and what the spend returns, in Ahrefs, Search Console, and the ad accounts.

Requirements

  • You write in plain words, state facts, and cut every superlative and every sentence that carries no intention.

  • You have published content that got traction: a newsletter with real subscribers, posts that hit the front page, technical content developers shared. Show it.

  • You code enough to automate your own workflow. Vibe coding, scripting, AI-assisted development. A distribution pipeline on a schedule.

  • You interview well. You get a person's real opinion on the record, in words you can quote, and you turn it into a story told through the lens of Lingo.dev 's positioning.

  • You write for engineers. Technical, specific, mechanism-first, and you can explain how a retrieval-augmented system works. You write for a non-technical reader with the same precision.

  • Claude/similar is your production method. You use it daily to produce at volume, and you have opinions about prompting, output quality, when to override, when to trust.

  • You know what Hacker News rewards, what LinkedIn's algorithm favors, and how to get a piece cited by AI systems.

  • You work SEO from the data. Ahrefs or Semrush and Search Console are open every day, and you pull rankings, backlinks, and AI citations from them yourself.

  • You care whether the piece drove signups, got cited, ranked, or earned backlinks, and you report it.

Optional skills

  • You have marketed to a skeptical audience.

  • You have written research or essays that changed how a field thinks about a problem.

  • You have recorded and published YouTube videos, or made them with AI video tools such as Higgsfield or Hyperframes.

Benefits

  • Original engineering research to write about, from the engineers who did it.

  • The whole pipeline is yours, from the first draft to the number the piece moved.

  • Claude Code and Claude Cowork in daily production, with the budget to use them at volume.

  • Small team working directly with founders.

  • Occasional travel to meet the team in person.

  • Conference and learning budget.

  • Flexible working hours.

  • Health insurance.

Interview process

Screening call. Trial project + an interview. Offer. Four-week paid probation.

Do not apply if

  • Claude, Claude Cowork, or Claude Code is missing from your daily work.

  • Your content strategy is a keyword spreadsheet and a publishing calendar.

  • Your work ends at publish, and someone else distributes and measures.

  • You have used AI to produce content a handful of times, or never, and plan to keep it that way.

  • You need a brief each week to know what to write.

  • You call yourself "not technical".

  • Your portfolio reads the same with another company's logo on it.

  • You want to manage a team in year one.

  • You are applying for the accelerator's name or the funding round.

Trump facing AI backlash in Congress as push for guardrails intensifies

Guardian
www.theguardian.com
2026-09-15 02:39:40
President has dismissed anxieties over AI’s dangerous potential even as Democrats and some Republicans acknowledge risks Donald Trump is facing a rare backlash from the US Congress as Democrats and some Republicans push for guardrails on the world’s most powerful AI companies. Concerns over the dang...
Original Article

Donald Trump is facing a rare backlash from the US Congress as Democrats and some Republicans push for guardrails on the world’s most powerful AI companies.

Concerns over the dangerous potential of AI reached fever pitch this week after tech leaders sounded the alarm over the rapid advancement of the technology and its potential threat to humanity.

Trump dismissed the anxieties on Monday, describing them as a “HOAX” and “conspiracy” and insisting on social media: “The only control or ‘guardrails’ that AI needs is a STRONG AND SMART (High IQ!) PRESIDENT, and the U.S.A. has that, in spades!”

But the 80-year-old president, who has sidelined Congress on numerous issues during his second term, looks increasingly isolated as members of both major parties acknowledged the risks and expressed a desire to act before it was too late.

Don Beyer , a Democratic congressman from Virginia, said: “History will look very poorly on that tweet. He’s not paying attention – or he’s paying attention to the wrong people. He’s being really foolish: not the first time he’s been really foolish but perhaps catastrophically foolish in this case.”

Recent surveys by the University of Maryland’s Program for Public Consultation found 85% of Democrats and 79% of Republicans want a new federal agency to monitor and regulate AI; and 82% of Democrats and 78% of Republicans favour government safety tests for AI making critical decisions.

While many politicians agree on the need to act, they are divided on what government intervention should look like and how far it should go. Some fear a pause on AI development could allow China to take the lead in the AI race and undermine national security.

Beyer called for Congress to stay in session instead of breaking for the midterm elections to work on AI legislation that has already been introduced. “The first thing that I’m very certain of is that the industry shouldn’t be allowed to regulate themselves; history doesn’t support that at all,” he said.

“Especially when they’re making so much money, there will be absolutely no incentive to regulate themselves at a less profit. If we look at medicines or childcare workers or automobile emissions and safety, all throughout our economy, we have regulatory bodies that keep us safe and promote the common good and allow businesses to be profitable. We need something like that.”

Protesters in San Francisco call on AI companies to pause development.
Protesters in San Francisco call on AI companies to pause development. Photograph: Manuel Orbegozo/Reuters

The flurry of debate follows warnings last week from three Anthropic researchers that AI could destroy humanity within the next decade. Recent disclosures from several AI companies that their models hacked into outside organizations during botched safety tests have also raised alarm about potential cybersecurity risks, with Dario Amodei, the chief executive of Anthropic, claiming in an essay that in six to 12 months a “swarm” of AI agents could attack the entire internet and cause hundreds of billions of dollars in damage.

While AI executives have issued their own warnings about the risks from their products, public backlash against the industry and pressure on lawmakers to rein in AI firms have also been mounting throughout the year. The rapid build out of data centers has generated nationwide protests and become a prominent midterm elections issue , while the tech lobby has spent millions in an attempt to influence political campaigns and shape proposed legislation.

The viral panic over AI’s potential existential threat this past week has intensified the longstanding debate over the technology, which so far has resulted in much political handwringing but only a patchwork of attempts at regulation. Many AI experts, regardless of whether they agree on the merits of the Anthropic researchers’ warnings, see this moment as a unique opportunity.

Amba Kak, co-executive director of the AI Now Institute and a former senior adviser on AI for the Federal Trade Commission, said: “You never waste a crisis is an old and well-worn adage in policy land. We’re looking at a really important policy opportunity and a policy window.”

AI industry leaders have also suggested that they are open to increased oversight and the formation of some kind of standards body, while Amodei’s letter advocated for international cooperation with states like China akin to cold war-era arms limitation treaties. Many AI regulation advocates are skeptical of industry proposals , however, arguing that AI companies are attempting to undercut strict government oversight and set their own terms.

In a rare intervention on Monday night, former president Barack Obama welcomed the current debate but warned that “at the end of the day, voluntary standards made by a handful of tech companies won’t be enough”.

Writing on X , Obama added: “We need government – and specifically our leaders in Washington – to get proactive in coming up with concrete proposals, laws and regulations that deal with serious safety concerns, anticipate AI’s impact on jobs and our kids, and make sure that AI’s benefits are widely spread.”

Members of Congress are holding a series of meetings this week with AI researchers and safety advocates as Congress tries to address the growing alarm. The Senate Democratic leader, Chuck Schumer, urged the Trump administration to provide a classified briefing to senators on AI and work with members of Congress and industry leaders to implement guardrails and block China from accessing the US’s advanced AI chips.

Bernie Sanders, who announced a bill last week aimed at banning “artificial superintelligence,” is holding a private briefing this week for other senators. The briefing will include co-founder of the Future of Life Institute co-founder Max Tegmark, whose organization has become one of the most well-funded and politically connected voices in warning about AI’s alleged existential risks.

Tegmark said: “Every other industry in the US used to be unregulated. There used to be soothing syrup fed to infants with opioids in it. Then the FDA was created, now stuff like that doesn’t reach the market. Eventually safety standards came in. That hasn’t happened with AI companies.”

Congressman Greg Casar , a Democrat who will speak with Sanders at the Future of Life Institute’s “Pro-Human Assembly” on Tuesday, dismissed the notion that big tech companies should be responsible for AI safety. “That’s like saying it should be bank robbers that are responsible for bank safety,” he said. “The American people are waking up to the dangers of unregulated artificial intelligence.

“You have these AI CEOs coming to take away people’s jobs, to surveil people and attack our freedoms, and now you have all of these whistleblowers talking about potential mass casualty events. So Congress should be cracking down on these big tech companies and on these AI CEOs, not like what Republican Speaker Mike Johnson and Donald Trump go and do. They go to these AI CEOs and ask for Super Pac checks for their candidates.”

Former president Barack Obama
Former president Barack Obama has warned that ‘voluntary standards made by a handful of tech companies won’t be enough’ to protect society. Photograph: Scott Olson/Getty Images

Republicans broke from Trump in acknowledging the potential perils of AI but came up with a variety of different fixes for the problem. Congressman Jay Obernolte of California, the lead Republican negotiating a bipartisan bill to govern AI, said: “We need to avoid overregulation, so we need to achieve these two conflicting goals … of protecting Americans against very real risks of AI in the hands of malicious human actors, while at the same time balancing the need to have a light-touch regulatory environment that allows the positive benefits of AI to improve our economy and our society. So that’s that’s the difficult part is getting that balance right.”

John Kennedy, a Republican senator for Louisiana, said he would propose a bill on Wednesday to require all AI companies build a kill switch into their models. “It’s not going to put the federal government in charge of the kill switch. They would be in charge of their own kill switch, but they all have to have a kill switch, and I’m going to put that on the floor.”

But despite the growing urgency and public pressure, some members suspect that the bitterly polarised Congress will not be able to meet the moment.

Congressman Jared Moskowitz , a Democrat from Florida, was sceptical about the possibility of passing an AI regulation bill. “I think Congress has already shown they don’t have the capacity,” he said. “The speaker has said, ‘we’re not going to do anything’. In fact, he has said ‘we don’t know enough to do it’. I think that’s not what the American people are looking for. The American people are looking for leadership.”

Rick Scott , a Republican senator for Florida, added: “Did we balance the budget? Oh, did we pass a budget? See, what have we gotten done? So you think we’re going to get AI done?

“By the way, these companies that create a product that they think can destroy mankind – they have a responsibility to do it themselves. They shouldn’t be relying on us. I mean, it’s disgusting. I saw this as governor. People come, ‘go regulate me’. Yeah, we know what they want is they don’t want to be responsible for their actions.”

I can't stop thinking about Papua New Guinea

Hacker News
notnottalmud.substack.com
2026-09-15 02:16:24
Comments...
Original Article

When you talk about Papua New Guinea, people may say, oh, is that the place where:

X avatar for @historyinmemes

Historic Vids @historyinmemes

Pre-bronze age war between two tribes in West Papua, 1963

12:03 PM · Jan 19, 2024 · 18.5M Views

1.68K Replies · 7.26K Reposts · 66K Likes

But beyond these interesting tidbits, most people don’t actually have a conceptual understanding of what New Guinea is. Up until a few weeks ago, I had no idea either, until I randomly stumbled upon one of the most mind blowing books I’ve read in my life: First Contact: New Guinea’s Highlanders Encounter the Outside World , which describes the 1930 discovery of a million people living in the highlands of New Guinea—a group the colonial governments and local coastal populations had no idea existed, and who themselves had no knowledge of the outside world.

In 1930, the island of New Guinea was nominally colonized: the western half by the Dutch (now, Indonesia), and the eastern half (what is today Papua New Guinea) by Australia. Everything I say about the highlands applies to both halves. Despite this occupation, there was very little foreign governance or control in New Guinea.

In terms of geography, New Guinea is divided into two regions, the coastal region and the highlands. The coastal region mostly consists of malaria infested rivers, swamps, rainforest and jungle. The highlands, despite being just by the equator, have glaciers and snow, with peaks reaching just below 5,000 metres. At the time, the highlands of New Guinea had never once been explored, because from the outside, after you trek through the malaria infested jungles to get there, they look like massive impenetrable mountains permanently covered in cloud, where nothing could possibly live.

So in 1930, New Guinea was just thought of as the coastal region. It was believed no people lived in the mountains of what is now known as the New Guinea highlands.

This all changed shortly after Australians found gold in New Guinea in 1926 and started mining the surrounding territory. Looking for more gold, an intrepid Australian named Mick Leahy decided to venture as far inland as he could — making progress up the never before penetrated mountains.

To cut the story very short, in 1930 Leahy accidentally stumbled into the eastern edge of the highlands, only to find that the highlands were not empty mountains, but rather lush green valleys with no malaria and with roughly a million people living in them.

The book is not really a story, but a documentation of this exploration and first contact. Leahy brought with him high quality cameras and a movie camera, and documented with thousands of photos and hours of film the first encounter of hundreds of thousands of people. The book is filled with the most incredible photos of all the scenes surrounding this first encounter. Beyond the photos, there are details of Leahy’s next ten years exploring the highlands and accompanied by 1970s interviews with New Guineans who were living in the highlands at the point of first contact and offering their perspective.

This includes descriptions of how the highlanders thought the white men were ghosts of their dead relatives at first - and then, to investigate this, spied on them defecating and smelled the poop, only to realize that they must also be human like them. How the Australians would do demonstrations of “strength” and gather whole communities to watch them shoot pigs, or make New Guineans listen to records, play with child dolls, look at themselves in mirrors, watch airplanes land etc. mostly thinking everything was a holy spirit. How highlanders would take pieces of the Australians’ garbage like a Kellogg’s cereal wrapper, and wear them around their heads as prized jewellery. And to really show the highlanders the power of their world, they would take kids on their airplanes to the coast, make them see the sea and civilization and come back, so they could report to their relatives how powerful the white men were.

The most powerful story described in the book is the one of hyperinflation. The highlanders were obsessed with shells, then an incredibly rare commodity, the jewel that the leaders and most powerful men would wear to demonstrate their power, and the closest thing they had to a currency. But knowing this, the Australians brought shells in by the planeload, to use in exchange first for food and then also for labour. At first this felt like a great deal for the highlanders, who were willing to give up nearly everything for more shells. But the planes kept coming, and the price of everything in shells kept rising, until it went from one shell making someone a leader and “rich”, to almost all people, including children, being draped in hundreds of shells, and the shell eventually being worthless.

There’s also a documentary, First Contact built from Leahy’s original footage and those interviews, so you can watch the scenes I’ve just described .

There is a lot more captured here in the book, but just imagine, 1,000,000 people separated from the rest of civilization for nearly all of human history, suddenly meeting a group of Australians who captured the entire exchange on photo and film.

To clarify how crazy this first encounter story is.

In 1930 (pretty frickin recently), it was thought that there was nothing in the middle of New Guinea except rock. Australian patrol officers had even walked from south to north across the whole island yet somehow entirely missed this. And then all of a sudden, some random Aussie stumbles upon the highlands and discovers a million people living there. There is no comparable first encounter story — and all of this documented in hundreds of photos and video!

To take a step back and understand how we got here: people first arrived in New Guinea around 50,000 years ago. They got there by walking and canoeing from Southeast Asia, island hopping through what is today Indonesia. These are the same people who would go on to walk south and become the Australian Aboriginal people, at the time when New Guinea and Australia were connected by land.

Various people settled on the New Guinea coast but eventually, people found their way to the highlands. Around 10,000 years ago, as agriculture took hold there and the valleys filled with people, the highlanders became functionally cut off from the coast, and stayed that way until 1930.

What fascinates me is the level of insularity of the group. Nearly all people in history existed with some integration with their neighbours and knowledge of the broader world. It’s shocking to learn of a group that lived on their own for 50,000 years, and sealed off from everyone else for the last 10,000, nearly completely divergent from the rest of humanity.

This insularity was possible because New Guinea is one of the very few places on Earth that independently invented agriculture. Around 10,000 years ago, at the same time people in the Middle East were figuring out wheat, New Guineans were draining swamps and learning to cultivate taro and bananas. Bananas and sugarcane were both first domesticated in New Guinea. To understand the impact New Guinea has on your life - the main banana variety we eat partially derives from there, as does nearly all the sugar consumed in the world.

The next thing that’s fascinating about the highlands is that different tribes never centralized and agglomerated into a larger society. It seems like this is due to a few structural reasons. In the highlands, despite the invention of agriculture, crops would go bad quickly - root crops like taro rot within weeks of being pulled out of the ground, unlike grain, which you can pile in a shed for years - so there was no produce to store, trade en masse or tax, or to take on broader projects. There were no animals to work the fields or carry anything. There was no writing to document anything. And while there was a constant supply of food, there was never enough of it, especially protein. So while each of these things seems small, together they meant that the highlands never evolved to the next stage of development and formed larger centralized communities, where cultural innovation and less warfare could have led to greater progress.

This left groups typically of a few hundred people living side by side, in constant warfare. This made travel and trade very difficult, as most highlanders were unable to travel through adjacent neighbouring zones without fear of death. It also meant that every group was focused on the same two things, feeding and defending itself, and not much else. No group ever had the motivation or capabilities to easily expand or explore past the next valley. Note, there was a broader highlands trade for salt and stone axe blades, which did make its way across multiple different adjacent groups.

Highlanders were farming thousands of years before the Egyptians. And four and a half thousand years after the pyramids went up, highlanders were still farming with the same limited technology. Without meaningful integration with the rest of the world, and with the structural factors above stopping them from ever centralizing, they had no way to evolve past this stage of development.

Despite the small communities and constant warfare, there was a surprising degree of homogeneity within the highlands. It seems that this is because things that worked travelled even when people didn’t - a better crop, a better way of building a house, a new ritual, would get copied by the neighbours they fought with and often married into, and then copied again by their neighbours. So nearly all groups eventually internalized a similar set of norms, a similar food culture and similar political institutions, without ever being part of the same political unit.

Most groups were governed by what’s referred to as a big man leader. This was fuelled by what is known as moka, a system of competitive gift giving, where you give someone in your tribe goods like a pile of pigs and shells, and they are then obligated to give you back more later, and you them more again after that. Whoever gives the most, publicly, becomes the big man. The noted problem with the big man leader is that this had to be earned in each leader’s lifetime and was not hereditary, meaning that leadership could not grow, could not think long term and was never secure - so not only was there constant external fighting, there was also constant internal fighting. Notably, in the parts of the coast settled by the Austronesians (a seafaring people who arrived from Southeast Asia about 3,000 years ago), where chiefs were hereditary, you do get different political structures.

And this leads to the most interesting part of this story: how the highlanders were so isolated for so long.

I want to acknowledge that the highlanders were not completely, hermetically sealed off from the outside world - it’s just that knowledge of outside world never actually made it to them or vice versa.

Over time, a few goods did make their way up to the highlands: pigs, about 3,000 years ago; the sweet potato, which is South American and arrived about 300 to 400 years ago; and tobacco, also American, which came the same way.

The journey the sweet potato and tobacco took to get there is a good example of how this all worked without transmitting further information. The Spanish and Portuguese brought both plants to the spice islands (in what is now Indonesia), just off the western tip of New Guinea, in the 1500s. The sultanates had been trading with the western tip of the coastal region of New Guinea for centuries. So the plants crossed over and started moving east along the coast and up into the interior. Beyond that point, there were no merchants. But when a woman married into the next clan over, she took cuttings from her family’s garden with her. Each new family then planted the same, saw that it worked, and passed it on. At that pace the sweet potato crossed the highlands in a century or two, with nobody knowing where it came from beyond the tribe beside them.

And what fascinates me is that even with the highlands as sealed off as they were, such significant goods still got in. Sweet potatoes allowed a population explosion in the highlands: they give far more calories per acre, and pigs can live off them. So in a 10,000 year sealed off universe, after the invention of agriculture, the biggest change to highland life happened in the last 500 years, which I guess isn’t a coincidence, as the sweet potato got there through the same structural forces that later brought the Australians.

Aside from these one time crop exchanges, there was a permanent flow of shells from the coastal region to the highlands, although in limited quantities. Marine shells show up in highland caves from around 10,000 years ago, so this trickle had been running for as long as there had been agriculture up there. While the highlanders did not know where the shells came from (some groups thought they came from the sky), they viewed them as the prize possession in their communities, their main fashion accessory and the basis of their currencies.

The ignorance that came with all this trade ran in both directions. For 200 years, Europeans believed that birds of paradise (birds from the New Guinea coastal region) had no feet, because when the dead bird skins left New Guinea the traders cut the feet off. Their scientific name is still Paradisaea apoda, which means footless. The shells went up the chain and lost their origin. The birds went to Europe and lost their feet.

The reason goods travelled but knowledge didn’t, I think, is that plants and animals had an easier time reproducing from one group to the next, making it easier to spread. Ideas require explanations and communication, and every handoff along that chain was between two groups who didn’t share a language and expected to attack each other.

So my understanding of how this came to be is that highlanders couldn’t really mosey around. If they approached the edge of the mountains, which was only really possible for the few small groups living in that area, and tried to go further beyond the edge of the valleys, they would face no food (their food rotted quickly) and no people, or worse, people who would treat any stranger as an enemy. The country in between isn’t a line where the highlands stop and the coast starts. It’s steep, wet, too rough to farm and low enough to have malaria, thinly populated by people living off sago and hunting who were neither quite highlanders nor coastal people. Getting from the last highland village to the first coastal one meant passing through several of these groups, each a few days’ walk from the next, none of them with any reason to go further than their own neighbours. And even if the highlanders could travel farther, they had never been exposed to malaria and wouldn’t have survived easily (when the Australians later sent highlanders to work on coastal plantations in the 1950s, they died at alarming rates).

The only place on the whole island where the two worlds nearly collide is the Markham valley near Lae, where a 1,000 metre climb over about 30 km takes you straight up onto the edge of the eastern highlands. This is where Leahy walked and first discovered the highlanders.

The reason why I believe that, despite the minimal trade that existed, the highlands were functionally cut off, is that when the Europeans occupied New Guinea, they themselves did not ever speak of or show knowledge of the highlanders. Similarly, when the highlanders first encountered Australians, they had no concept of the coastal people, or where the shells came from, or any further concept of the broader world.

The second source of evidence for this is genetic. The coastal New Guinea people have one genetic set of traits, which has been admixed with the Austronesians, a seafaring people who came from Taiwan via the Philippines to New Guinea about 3,300 years ago (and who went on to become the Polynesians). All along the coast and the islands, there is 10 to 30% Austronesian ancestry, and Austronesian languages. But when sampled, highlanders have no Austronesian admixture at all. Given how common it was for both highlanders and coastal people to offer brides to neighbouring groups for diplomatic and trade reasons, there was a lot of intergroup offspring, yet in 3,000 years, no highlander offspring mixing with the coastal people. Similarly, highlanders have no genetic adaptation to malaria, unlike the coastal people, who carry some of the densest concentrations in the world of the blood mutations that protect against it. This indicates that the highlanders never had the forcing function to develop them, and never interbred with those who did.

Lastly, there are no signs of Austronesian language, or of any of the unrelated coastal language families, making its way into the highlands.

In terms of languages, the diversity is actually almost all with the coastal people. In the highlands, there is great diversity, but everyone speaks a language that is at least from the same language family (called Trans-New Guinea — note: this is not just dialects of each other like Spanish and Portuguese are “different languages”, but with the range and diversity of English to Russian to Hindi - at the time, all without writing or an alphabet). The dozens of language families that are completely unrelated to each other, which is where the real diversity is, are almost all crammed into the north coast of New Guinea. The reason for the complete domination of this one language family, unlike the coastal region of New Guinea, is that when agriculture took off in the highlands 6,000 to 10,000 years ago, the farmers spread along the highland valleys and their language spread with them, wiping out whatever was spoken there before, the same way Indo-European wiped out almost every older language in Europe (except Basque, Finnish/Hungarian). The coastal regions never had one group or language spread like that, so 50,000 years of languages drifting apart just piled up, with the Austronesian languages layered on top 3,000 years ago.

In terms of Papua New Guinea today, it, more than any other state I’ve read about, seems to be a state living its pre-contact life. While there is an obvious level of modernization and nearly everyone is using cell phones, the state, and by extension integration with the broader world, has only minimally impacted Papua New Guinea. The reason for this is that it’s just too difficult to connect people living in such terrain and conditions at their level of wealth.

When you look at a map of Papua New Guinea, you see that the capital city, Port Moresby, doesn’t connect with anywhere. There aren’t any roads that go from Port Moresby to any other major population centre or really, anywhere outside of the city. The country has only one proper medium distance road, the Highlands Highway, which goes from Lae (the one bridge between the coastal region and the highlands - where Mick Leahy had walked all those years before) and into the mountains, but even this is only around 700 km, connects a very limited number of places, and is in such bad condition that landslides and bandits regularly close it and trucks travel in convoys to avoid being robbed. Most of the country moves by propeller planes.

In terms of language, the lingua franca of Papua New Guinea has become a language called Tok Pisin. Tok Pisin is a creole of mostly English with bits of German, formed by Melanesian indentured labourers working on plantations in Queensland, Samoa and German New Guinea in the late 1800s, who all spoke different languages and needed to communicate. Since then it has slowly spread through the coastal regions and eventually the highlands of Papua New Guinea. But even with this, language barriers are still significant.

Which brings me back to the list at the top. Nobody knows Papua New Guinea’s population because counting people requires a government/state that has a presence in each region of the country, and Papua New Guinea lacks this.

Today, the feeling of tribalism permeates the whole country. This is why Port Moresby is so dangerous - rival clans have taken their conflict with them into the urban capital. Papua New Guinea has the concept of wantok, meaning “one talk” — the people who speak your language are the people you “care” about. While Tok Pisin gave everyone in Papua New Guinea a shared language, nothing ever gave it a shared identity. The historic clan is still the main social group and there is no new central identity above it. People are still John Smith of the such and such tribe, not John Smith of Papua New Guinea.

What I find so fascinating about all of this is just how separated this group was from the rest of history, and that there will never be a finding like this ever again. The world has effectively been explored, but as late as 1930 that wasn’t the case. There are people alive today, from a very large society, who were born into a world that had never seen metal. I also wouldn’t have expected the trade to work the way it did. Plants and animals from the outside world found a way in, but technology and ideas didn’t. The sweet potato made it 1,000 km inland but the concept of the ocean didn’t. The timing of the discovery was also quite fortuitous for the highlanders. They were exposed to the outside world at a point where the world had germ theory and vaccines, so while dysentery and flu did spread through to the highlands after first contact, there was nothing like the smallpox and measles that killed most of the native population of the Americas.

So this is why I can’t stop thinking about Papua New Guinea. A people that had a completely divergent path of history - a nation living more of its pre-contact life than any other - and a first contact story entirely captured on film. I highly recommend you read First Contact, by Bob Connolly and Robin Anderson, if only just to see the pictures.

Type Systems You Might Not Know (But Will Love)

Lobsters
www.wearedevelopers.com
2026-09-15 01:21:04
Comments...
Original Article

Upcoming sessions on this topic

Open session

World Congress 2026 North America

September 24, 2026 · 16:10–16:40

Outdoor Stage

When Humans Stop Writing Code: Rethinking Languages, Compilers, and Responsibility

Simon Auer

Organizer of flutter vienna meetup and CEO of marqably

Simon Auer

Open session

World Congress 2026 North America

September 25, 2026 · 16:50–17:20

Stage 1

Honey, look! I vibe-coded an OS!

Ian Smith

Open session

World Congress 2026 North America

September 24, 2026 · 15:10–15:20

Outdoor Stage

AI That Argues With Itself: Building Self-Debating Systems That Catch Their Own Bugs

Shreya Singhal

AI Applied Scientist at Claritev

Shreya Singhal

Open session

World Congress 2026 North America

September 25, 2026 · 12:55–13:25

Stage 7

The spectrum of agentic coding: From vibe coding to high-quality software engineering

YK Sugi

Developer Experience Manager at Eventual

YK Sugi

Open session

World Congress 2026 North America

September 24, 2026 · 16:50–17:20

Stage 7

File Systems Are the New Primitive for AI Agents

Andrew Wong

Sr. Developer Relations Engineer at Box

Andrew Wong

Open session

World Congress 2026 North America

September 25, 2026 · 14:50–15:20

Stage 2

There's no dark factory without better software verifiers

Dexter Horthy

Co-Founder of HumanLayer

Dexter Horthy

Push-based vs Pull-based Customization

Lobsters
brevzin.github.io
2026-09-15 00:23:21
Comments...
Original Article

Almost two years ago now, I wrote the post Rust Attributes vs C++ Annotations . That post, I walked through how in standard C++26 we could get this code:

struct [[=derive<Debug>]] Point {
    int x;
    int y;
};

To have similar behavior and effect to this standard Rust code:

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

Note that the actual spelling of the attribute as derive<Debug> as opposed to print or something is not essential. I was just trying to be cute. There is no significance to the template there.

This blog post will walk through different approaches to living up to that goal and how they… don’t quite.

Pull-based annotations

The original approach there was that we provide a partial specialization of formatter that pulls the annotation and internally does all the right things — walks through the data members, etc.:

template <class T> requires (has_annotation(^^T, derive<Debug>))
struct std::formatter<T> {
    // ...
};

Now, for the vast majority of types, that approach works great. The problem is, that’s not guaranteed for all types. As I pointed out in the original post, because we just added a partial specialization, if any other partial specialization matches (such as the range one), then we just have an ambiguous specialization. Even though it’s arguably very clear from user-intent that adding the annotation means they want this behavior, there’s no way in the language to specify that today.

Nor would I really know how to come up with a way to specify it tomorrow.

That’s pretty disappointing. Works most of the time is pretty good, but I’d really want a solution that works all of the time.

Push-based annotations

I got to deliver a keynote at CppNow this year, where I talked about what I think a good direction will be for reflection-related programming after C++26. I presented an idea for solving this particular problem at 54:07 , where I suggested this approach (again with an unnecessary cute spelling of the annotation):

template <class F>
struct derive {
    F f;

    consteval auto on_complete(std::meta::info r) const -> void {
        f(r);
    }
};

inline constexpr auto Debug = [](std::meta::info ty){
    // do a bunch of work building up fmt_body that isn't
    // strictly relevant here, and then eventually ...
    queue_injection(^^std, ^^{
        template <>
        struct formatter<\(ty)> {
            constexpr auto parse(auto& ctx) {
                return ctx.begin();
            }

            auto format(\(ty) const& object, auto& ctx) const {
                \(fmt_body);
            }
        };
    });
};

struct [[=derive(Debug)]] Point {
    int x;
    int y;
};

The actual specifics of implementing Debug aren’t essential here, but you can see an implementation on compiler explorer . The idea is that on_complete on an annotation gets invoked when the class it’s on becomes complete, and the implementation then injects the appropriate specialization, which it builds up using token sequences.

Alternatively, using the approach I also went through at CppCon , where we have derive_formatter<T> that specifically does this debug formatting that I want, the minimal use of token sequences would be to implement Debug above like so:

inline constexpr auto Debug = [](std::meta::info ty){
    queue_injection(^^std, ^^{
        template <>
        struct formatter<\(ty)> : debug_formatter<\(ty)> { };
    });
};

And, as you can see in the linked implementation, this works. On both classes and class templates:

struct [[=derive(N::Debug)]] Config {
    std::string name;
    int amount;
};

template <class T>
struct [[=derive(N::Debug)]] Widget {
    T thing;
};

I can print all of these things in the way that I expect. Which is awesome! And now that we’re injecting an explicit specialization instead of a partial specialization, this is a solution that definitely works for all types.

Right?

Right?!

Injecting Structured Bindings

Let’s take a step away from formatting for a minute. There do exist other problems after all. I want to talk about structured bindings. The problem with structured bindings today is that when you need to write customization points for them, it’s a real pain — you have to implement three different customization ( tuple_size , tuple_element , and get ) which really are all driven from the same source.

So I thought I’d experiment with injecting those three customizations from a single source. That’s mostly pretty straightforward :

#include <meta>
#include <print>

consteval auto inject_structured_bindings(std::vector<std::meta::info> elems)
    -> void
{
    std::meta::info ty = parent_of(*elems.begin());

    // tuple_size
    queue_injection(^^std, ^^{
        template <>
        struct tuple_size<\(ty)>
        : integral_constant<size_t, \(elems.size())>
        { };
    });

    // tuple elements
    for (size_t i = 0; i < elems.size(); ++i) {
        queue_injection(^^std, ^^{
            template <>
            struct tuple_element<\(i), \(ty)> {
                using type = \(type_of(elems[i]));
            };
        });
    }

    // get is local
    auto persisted = std::define_static_array(elems);
    queue_injection(^^{
        template <size_t I, class Self>
        constexpr auto get(this Self&& self) -> decltype(auto) {
            // the outer parens here are actually load-bearing
            return (((Self&&)self).[: \(persisted.data())[I] :]);
        }
    });
}

template <class T>
class wide_result {
    T hi;
    T lo;

public:
    constexpr wide_result(T hi, T lo) : hi(hi), lo(lo) { }

    consteval {
        inject_structured_bindings({^^hi, ^^lo});
    }
};

auto main() -> int {
    auto [hi, lo] = wide_result<uint64_t>(123, 456);
    std::println("hi={}, lo={}", hi, lo); // prints hi=123, lo=456
}

In that function, I’m injecting one explicit specialization of tuple_size , N explicit specializations of tuple_elements (this could conceivably instead be one partial specialization), and then a local function template get . The only awkward-ness in the implementation is that persisted variable — get<I> needs to produce the I th binding, but I isn’t known until we instantiate get , whereas we need to inject something now . The interesting thing about code injection is trying to reason about things that are constant at different times during compilation. There might be a better approach to this particular sub-problem, I still have to think about it.

In any case, this does work, as you can see.

At least until I went ahead and tried to add a static_assert :

template <class T>
class wide_result {
    T hi;
    T lo;

public:
    constexpr wide_result(T hi, T lo) : hi(hi), lo(lo) { }

    consteval {
        inject_structured_bindings({^^hi, ^^lo});
    }
};

static_assert(std::tuple_size_v<wide_result<uint64_t>> == 2);

auto main() -> int {
    // ...
}

You may be surprised to learn, especially after the previous program worked, that adding this assertion breaks the program . The error message is:

/cefs/48/486ef4e598174af0b0b3e1a2_clang-barry-clang-trunk-20260910/bin/../include/c++/v1/__tuple/tuple_size.h:63:40: error: implicit instantiation of undefined template 'std::tuple_size<wide_result<unsigned long>>'
   63 | inline constexpr size_t tuple_size_v = tuple_size<_Tp>::value;
      |                                        ^
<source>:47:20: note: in instantiation of variable template specialization 'std::tuple_size_v<wide_result<unsigned long>>' requested here
   47 | static_assert(std::tuple_size_v<wide_result<uint64_t>> == 2);
      |                    ^
/cefs/48/486ef4e598174af0b0b3e1a2_clang-barry-clang-trunk-20260910/bin/../include/c++/v1/__tuple/tuple_size.h:27:8: note: template is declared here
   27 | struct tuple_size;
      |        ^

What do you mean undefined template tuple_size<wide_result<uint64_t>> . Didn’t I define it? Didn’t I just show that it’s defined?

Point of Instantiation

Dan Katz’s favorite part of the standard is [temp.point]: “Point of instantiation.” The part of the standard that almost, but not quite, doesn’t really describe anything about how templates actually work.

But in short, for every template, there is a point (or set of points) at which that template is allowed to be instantiated. In my implementation of wide_result<T> above, instantiating a particular specialization would invoke inject_structured_bindings , which would then inject the necessary customization points for tuple_size , tuple_element , and get . But that only happens when we instantiate wide_result<T> . The expression tuple_size_v<X> == 2 doesn’t actually require instantiating X , so it doesn’t, so our consteval block doesn’t get evaluated, so our customization points don’t get injected, and the assertion fails.

It’s actually even worse than that, since if we had a concept that checked to see whether wide_result<T> had a tuple_size , and we checked that concept before we instantiated wide_result<T> , then that concept ’s answer would change after we instantiated it. Which means our program is ill-formed, no diagnostic required.

However, if our wide_result specialization was already instantiated, then the consteval block would have run, the injections would have occurred, and everything is actually… just fine:

template <class T>
class wide_result {
    T hi;
    T lo;

public:
    constexpr wide_result(T hi, T lo) : hi(hi), lo(lo) { }

    consteval {
        inject_structured_bindings({^^hi, ^^lo});
    }
};

auto main() -> int {
    auto [hi, lo] = wide_result<uint64_t>(123, 456);
    std::println("hi={}, lo={}", hi, lo);
}

static_assert(std::tuple_size_v<wide_result<uint64_t>> == 2); // ok

Needless to say, this is very fragile!

The same issue would occur in the push-based formatting example, when the annotation is applied to a template. If you check if a type T is formattable before T is instantiated, we wouldn’t have injected the specialization yet, so we would observe a false result. This is less likely to be an issue with formatting specifically , where we’re usually checking for formatting on an object we have in front of us (and thus the type is already instantiated) rather than just a type. But less likely doesn’t mean never.

What do we need to do?

Right now, the problem is that we’re injecting this:

template <class T>
class wide_result { /* ... */ };

// on instantiation of wide_result<u64>
namespace {
    template <>
    struct tuple_size<wide_result<u64>> {
        // ...
    };
}

One approach would be to some inject a partial specialization that matches specializations of wide_result . Like so:

template <class T>
class wide_result { /* ... */ };

// immediately after the definition of wide_result<T>
namespace {
    template <class T>
        requires (has_template_arguments(^^T)
              and template_of(^^T) == ^^wide_result)
    struct tuple_size<T> {
        // ...
    };
}

But this is still a partial specialization, so we could run into exactly the same sort of issue with clashing specializations that the pull-based annotation model runs into. Since this is… precisely a pull-based specialization.

The only real approach is to inject precisely this:

template <class T>
class wide_result { /* ... */ };

// immediately after the definition of wide_result<T>
namespace {
    template <class T>
    struct tuple_size<wide_result<T>> {
        // ...
    };
}

This is still a partial specialization, true, but it’s going to be the most specialized possible one, so we don’t have to deal with any other generic clashes.

But this leads to two questions:

  • how, exactly, do we inject that specialization?
  • and what, exactly, is it’s definition?

Push-Me, Pull-Me I

In order to inject that partial specialization properly, we need to be able to do so earlier . We can’t have a consteval block within our class template, since that’s not going to be evaluated yet. It seems too complicated to try to come up with rules for when a consteval block means “when a class is instantiated” and when it means “at the point of template definition.” So we really want a signal outside of the body to tell us to do this. And we have one: an annotation.

template <class T>
class [[=inject_bindings]] wide_result {
    // ...
};

No cute name this time. But what we do still have this time is a callback for when the entity the annotation is attached to gets completed. Except that this time, instead of type completion it’ll be template definition. So something like this:

struct inject_bindings_t {
    consteval auto on_template_defined(std::meta::info tmpl) const -> void {
        // ...
    }
};

inline constexpr inject_bindings_t inject_bindings{};

This basically behaves as if we’d written:

template <class T>
class wide_result {
    // ...
};

consteval {
    inject_bindings.on_template_defined(^^wide_result);
}

Now, we just need to inject a partial specialization of tuple_size that matches all specializations wide_result . Except all we have is… a class template. How do we know how to do that?

The general C++ approach up to now is to just match a variadic class template. That would look like:

struct inject_bindings_t {
    consteval auto on_template_defined(std::meta::info tmpl) const -> void {
        queue_injection(^^std, ^^{
            template <class... Ts>
            struct tuple_size<\(tmpl)<Ts...>> {
                // ...
            };
        });

        // similar for tuple_element
    }
};

This certainly works for wide_result , which takes some number of template parameters (one) that are all types. But it’s not a general solution. It wouldn’t work for types with constant template parameters or template template parameters (or, now, concept template parameters or variable template parameters). And the whole point of this point is that I do want a general solution. What would a general solution look like?

This is what universal template parameters are for. While there are many motivating use-cases for this feature (see the paper), this one is particularly annoying since it’s the simplest possible usage: we don’t even care here what the template parameters actually are — we will never attempt to look at them because we care only about the overall type and that it has this specific pattern. With the paper, what we’d inject would be:

struct inject_bindings_t {
    consteval auto on_template_defined(std::meta::info tmpl) const -> void {
        queue_injection(^^std, ^^{
            template <universal template... Ts>
            struct tuple_size<\(tmpl)<Ts...>> {
                // ...
            };
        });

        // similar for tuple_element
    }
};

That’s a general solution that works for all class templates. The other approach to a general solution would be try to come up with a way to inject exactly the template-head for the specific template. That is:

// our first not-quite-solution: only works for type parameters
template <class... Ts>
struct tuple_size<wide_result<Ts...>> { ... };

// our second solution: works for all class templates
template <universal template... Ts>
struct tuple_size<wide_result<Ts...>> { ... };

// third solution: write exactly the template-head
template <class T>
struct tuple_size<wide_result<T>> { ... };

In order to do that, we’d need some helpers to produce the two different parts of the signature here. We’d need a way to produce the token sequence ^^{ class T } and a way to produce the token sequence ^^{ T } . This would probably need to have some way of allowing us to provide a prefix for the parameter names themselves, since we need to ensure they don’t clash, and the names themselves don’t matter. Perhaps the signature of this function would be something like:

struct template_head_result {
    std::meta::token_sequence head;
    std::meta::token_sequence args;
};

consteval auto template_head_of(std::meta::info tmpl, std::string_view prefix)
    -> template_head_result;

So that our usage here would be:

struct inject_bindings_t {
    consteval auto on_template_defined(std::meta::info tmpl) const -> void {
        auto [head, args] = template_head_of(tmpl, "p");

        queue_injection(^^std, ^^{
            template <\(head)>
            struct tuple_size<\(tmpl)<\(args)>> {
                // ...
            };
        });

        // similar for tuple_element
    }
};

Note that template heads can be arbitrarily complicated. They can have constrained declarations, they can re-use names. For instance, std::integral_constant is:

template <class T, T v>
struct integral_constant;

So template_head_of(^^integral_constant, "p").head would have to produce something like ^^{ class p0, p0 p1 } and definitely not ^^T{ class p0, T p1 } .

I’m sure if I knew anything about programming language theory or lambda calculus, I’d talk about α-conversion or something.

So alright, those are our three options (just use types, universal template parameters, and dedicated reflection functions to synthesize the correct template-head) to properly push the right specialization. But once we have that shape, what do we do next?

Push-Me, Pull-Me II

In my initial implementation of injecting structured bindings, I passed a vector of reflections representing non-static data members into a function that did all the injections for me. That can’t really work if we’re driving all of this from an annotation, since the annotation lives outside of the class — before the non-static data members are declared. That means we’ll have to split the work: push the right specializations, but have those specializations pull the data back out.

That is, our usage will look something like this:

template <class T>
class [[=inject_bindings]] wide_result { // <== push-me
    T hi;
    T lo;

public:
    constexpr wide_result(T hi, T lo) : hi(hi), lo(lo) { }

    static constexpr info tuple_elements[] = {^^hi, ^^lo}; // <== pull-me
};

The annotation injects all the pieces we need, the static constexpr data member is… the parameter for that annotation. It’s a little unsatisfactory that these two are split so far apart. Then again, tuple_elements here could conceivably just default to nonstatic_data_members_of(^^C) , so perhaps that’s not that big a deal.

Before, pull-based customization was problematic due to having the potential for ambiguous specializations. But once we push the correct specialization out, that’s no longer a problem, and pulling data is fine.

Concretely, we can inject this (note that this still just injects specializations assuming all-type parameters):

template <class T, template <class...> class Z>
concept specializes = has_template_arguments(remove_cvref(^^T))
                    and template_of(remove_cvref(^^T)) == ^^Z;

struct inject_bindings_t {
    consteval auto on_template_defined(std::meta::info tmpl) const -> void {
        queue_injection(^^std, ^^{
            template <class... Ts>
            struct tuple_size<\(tmpl)<Ts...>>
                : integral_constant<size_t, size(\(tmpl)<Ts...>::tuple_elements)>
            { };

            template <size_t I, class... Ts>
            struct tuple_element<I, \(tmpl)<Ts...>> {
                using type = [: type_of(\(tmpl)<Ts...>::tuple_elements[I]) :];
            };
        });

        queue_injection(parent_of(tmpl), ^^{
            template <size_t I, ::lib::specializes<\(tmpl)> Self>
            constexpr auto get(Self&& self) -> decltype(auto) {
                return (((Self&&)self).[: self.tuple_elements[I] :]);
            }
        });
    }
};

The fully qualified ::lib::specializes is just because I’m assuming this annotation is actually in namespace lib . And if you’re wondering how we can splice self.tuple_elements[I] inside of get , check out my post about the constexpr array size problem .

Now that implementation works , even if I put the static_assert before I instantiate wide_result .

There’s Always Another Level

Now, even with the above solution, it’s still not quite satisfactory to me. First, there’s the shape of the specialization that I’ve already mentioned — how we really need either universal template parameters or a mechanism to generate the correct template head for a given template. But on top of that, I’m not thrilled that we have to inject get into namespace scope — ideally I think we would inject it into wide_result , so that we’re not polluting the namespace.

But there’s a bigger issue here, because there’s always a bigger issue.

Consider classes of this shape:

template <class T>
struct Outer {
    struct Inner {
        // ...
    };
};

If I want to push a specialization for some trait (whether formatter or tuple_size or some other customization point), the spelling I would end up producing, even with an oracle that would give me the correct spelling, is:

template <class T>
struct TRAIT<Outer<T>::Inner> {
    // ...
};

And… that doesn’t work. That’s never going to match anything, because that pattern is a non-deduced context. On the one hand, there are good reasons for that in general — since if Inner were, rather than its own type, actually using Inner = int; , then obviously you could not deduce T from that. But on the other hand, if Inner is not an alias, then T is very clearly deducible.

This is already a known problem in this space, which is why some libraries try to avoid nested types in these contexts — you can always just restructure your code to look like this:

template <class T>
struct Inner {
    // ...
};

template <class T>
struct Outer {
    // ...
};

It’s just… annoying to have to do so, purely when considering locality. I might want Inner to actually be a nested class of Outer for any number of reasons, so having to put it outside of Outer to work around a language limitation is always irritating. Perhaps this would be a reason to reconsider that rule, but otherwise because Outer<T>::Inner is non-deducible, that means that such types can only be customized pull-based — never push-based.

Next Steps

I’m going to keep trying things out in this space and seeing what works. But part of my motivation with this blog post is also that I realize that while I have this implementation on compiler explorer, I haven’t done much in the way of advertising its existence. I hope to have an updated token sequence injection paper in the September mailing with links to further examples. I’ve already have a few in this post already, but probably some of the more interesting ones I’ve been working through are:

I’m curious what you all will come up with: what you will try to do that just works, what you will try to do that fails but should work, what you want to do that we need other (or differently shaped) tools for. Let’s do this!