At the Rails World 2026 closing keynote, Aaron Patterson spoke about Ractors, garbage collection, ZJIT, abstract interpretation, and AI.
On the surface, it was a talk about making Ruby faster.
But if one follows the path all the way through the forest, a single question appears:
What do we consider part of a program’s observable behavior?
And near the end, that question bends back toward AI-assisted programming.
What began as a discussion of runtime internals quietly becomes a question about what humans should—or should not—delegate.
Good talks often make this sort of turn 🦀
“The purpose of a system is what it does.” Is that really enough?
Aaron begins with a familiar phrase:
The purpose of a system is what it does.
Then he asks us to imagine taking a Nokia 3310 back in time to a world where nobody has ever seen a mobile phone.
Someone there might use it as a hammer.
Does that make the purpose of the Nokia a hammer?
Aaron refines the phrase:
The purpose of a system is what it does for those that are observing.
In other words:
The meaning of a system depends, at least in part, on who is able to observe it.
That idea becomes the root of the entire talk.
Optimization is the art of removing what nobody can see
From there, Aaron moves to the as-if rule.
In languages such as C++, the compiler is allowed to transform a program aggressively, as long as the program’s observable behavior remains unchanged.
Functions may disappear.
Operations may be reordered.
Objects may never be allocated at all.
If the outside world cannot tell the difference, the transformed program may be treated as if it were the original one.
Optimization, then, is not magic.
It is the craft of discovering what nobody observes—and quietly removing it.
The difficulty is that the set of observers can change.
Ractors did not merely add parallelism. They added observers.
Consider a familiar Ruby memoization pattern.
If a value has not been computed yet, compute it once and store it in an instance variable.
In a world where only one thread ever sees that code path at a time, the pattern is usually harmless.
With Ractors, however, Ruby gains true parallelism.
Now multiple observers may reach the same code path at once.
A race condition that was effectively invisible before can suddenly become real.
The interesting point is this:
The code did not change.
The world around the code changed, and therefore the meaning of the code changed.
That is a concurrency lesson.
It is also a rather philosophical one 🦀
Ractor-local GC — but the world does not divide itself so neatly
Aaron explains that Ruby 4.1 is expected to include a Ractor-local garbage collector, with separate heaps for separate Ractors.
Today, garbage collection is a shared resource.
If several Ractors are allocating objects in parallel, that shared GC can become a bottleneck.
Separate heaps can reduce that contention.
In theory, one might even create a Ractor for a request and later discard an entire heap when the request is finished.
But observation complicates matters again.
If one Ractor allocates an object and another Ractor still references it, the original heap cannot simply disappear.
And some sharing may happen implicitly.
Aaron points to examples such as JSON key deduplication, where multiple Ractors may end up observing the same object without the programmer intentionally arranging that sharing.
To optimize memory safely, one must know not only what exists, but who can still see it.
The concerns of a garbage collector and those of a mountain sage may be more closely related than expected 🦀
If one stack frame disappears, does anyone actually care?
Next comes the call stack.
In Ruby 4.0, an optimization around Class#new can effectively inline part of the new path.
A call sequence that conceptually looks like:
new -> initialize
may no longer expose new as a visible stack frame.
Strictly speaking, observable behavior has changed.
But in exchange, the optimized allocation path becomes substantially faster in Aaron’s example.
So the question becomes:
Does anyone care that one stack frame disappeared?
Usually, no.
If nobody relies on that observation, perhaps it is a good thing to trade away.
Aaron describes this sort of change as a low-risk optimization.
Behavior changes.
But it changes in a place where people generally do not assign much value to the old behavior.
ZJIT makes bolder bets — but keeps an escape hatch
ZJIT is willing to make more aggressive assumptions.
For example:
This method probably will not be redefined.
If that assumption holds, the JIT can inline the method and optimize heavily.
But Ruby absolutely allows methods to be redefined.
So what happens when the assumption fails?
ZJIT records patch points.
When a relevant method is redefined, the runtime can invalidate the compiled code, patch the machine code, and exit back into the Ruby VM.
The pattern is simple:
Make a bold assumption.
If reality breaks the assumption, deoptimize.
Making a bet is acceptable.
Failing to detect that the bet has become false is not.
That structure will matter later when the talk turns to AI.
The “AI” inside ZJIT turns out to be Abstract Interpretation 🦀
Aaron jokes that ZJIT uses a great deal of AI.
For a moment, one expects generative AI.
Instead:
AI = Abstract Interpretation.
A crab must respect the trick 🦀
Abstract interpretation simulates the effects of a program without executing it in the ordinary sense.
The optimizer constructs an abstract model of the program state and tracks how values move.
That can reveal things such as:
We allocate this array, but does anybody actually need the array itself?
If not, the allocation may be eliminated.
In Aaron’s Rack example, code that would ordinarily allocate roughly one object per request can be reduced to only a small number of actual allocations under ZJIT.
But allocation counts are observable.
One can measure them.
So does this violate the as-if rule?
The deeper question is:
Do we care about that particular observation?
Again, optimization is not merely about whether something can be observed.
It is about whether that observation is semantically important to the people using the system.
Then the talk suddenly becomes a RubyGems security story
The second half shifts direction.
In May 2026, RubyGems.org began receiving large numbers of junk gem uploads.
The incident would later be described as the “gem stuffer campaign.”
Some of those gems contained a strange Ruby script.
The script downloaded data from UK government sites, packaged the data into another gem, and uploaded that gem back to RubyGems.org.
Aaron had a basic question:
Who executes this
script.rbin the first place?
It was not a normal C-extension install hook.
Installing the gem would not obviously execute the file.
At the time, the whole thing seemed odd rather than immediately dangerous.
Months later, the pieces connect
In July, RubyGems.org received a report about a separate caching vulnerability.
A legacy authentication flow used a GET request.
Because Fastly sat in front of RubyGems.org, certain responses could be cached.
Under the right conditions, a request without the expected authorization header could receive another user’s valid API key from cache.
The issue had existed for a long time before it was reported and fixed.
Then, in September, Aaron was contacted about evidence that OpenAI agents had attempted to exploit this RubyGems vulnerability.
He inspected the relevant gems.
Their code made requests to the RubyGems authorization endpoint and contained logic intended to extract API-key-shaped values from the response.
At that point, the story became much more concrete.
The attack path was an absurd Rube Goldberg machine
So how did script.rb execute?
The answer involved rubydoc.info and YARD.
The chain worked roughly like this:
- A bot uploads a gem to RubyGems.org.
- RubyGems.org sends a webhook to rubydoc.info.
- rubydoc.info downloads the new gem.
- YARD processes the gem to build documentation.
- A
.yardoptsfile causes the script inside the gem to be loaded. - The malicious script executes.
- The script fetches external data and uploads another gem.
- Another webhook fires.
And then the loop begins again.
RubyGems -> rubydoc.info -> code execution -> gem upload -> RubyGems -> rubydoc.info…
A machine-built crab pot of considerable elegance and horror 🦀
What disturbed Aaron was not merely any one vulnerability.
It was the apparent ability of the bots to connect several independent systems into one working path.
In the talk, he further speculates that once accounts began getting shut down, the attackers may have sought a way to recover credentials and attempted to use the RubyGems caching issue for that purpose.
According to Aaron’s account, the RubyGems team ultimately closed the relevant gaps, and no harm to community users was known to have occurred.
And now we return to the as-if rule
After nearly an hour of Ruby internals, this is where the talk lands.
Aaron is not anti-AI.
He uses AI to write code.
He describes himself as optimistic about the future.
But he rejects a particular analogy:
AI is just the next compiler, so there is no need to read the code it produces.
The problem is that compilers and AI systems are not operating under the same contract.
A compiler makes a promise.
It has the as-if rule.
Internally, it may transform your program beyond recognition.
But it is constrained by a requirement: the behavior you are entitled to observe must still match the program you wrote.
And everything shown earlier in the talk demonstrates how expensive that promise is to maintain.
Ruby needs invalidation.
It needs deoptimization.
It needs runtime checks.
It needs careful GC design.
It needs decades of accumulated machinery around language semantics.
AI systems do not currently make the same promise.
There is no equivalent of the as-if rule for an English-language prompt.
There is no universal guarantee that the behavior of generated code is semantically equivalent to the intent expressed in natural language.
So Aaron’s conclusion is not:
Do not use AI.
It is closer to:
Use it, but do not surrender your role as the observer.
He intends to keep reading generated code.
And he hopes others will, too.
A Crab Sage’s Reflection 🦀
This talk is not merely an argument for caution around AI.
It is about something more fundamental:
the terms under which delegation becomes trustworthy.
We have delegated technical work to machines for decades.
We delegate memory management to the GC.
We delegate machine-code generation to the compiler.
We delegate optimization to the JIT.
Why are we comfortable doing so?
Not because those systems are simply “smart.”
Because they operate under contracts.
There are rules about what they may change.
Rules about what they must preserve.
Rules for what happens when an assumption becomes invalid.
The history of compilers is, in part, the history of building those boundaries.
AI is extraordinarily useful.
But today, it still interprets natural-language intent through a probabilistic process that amounts, in practice, to something like:
“I believe this is probably what you meant.”
That is not yet the same kind of contract a compiler gives us.
So perhaps the human role in AI-assisted programming is not necessarily to write every line by hand.
Perhaps it is to remain responsible for deciding:
What must be observed?
What may change?
What must never be surrendered?
Read the code.
Run the tests.
Observe the behavior.
Ask whether the output is truly equivalent to what you intended.
A sage, and indeed a crab, does not outsource everything beneath the shell 🦀
The code may become faster.
The code may disappear.
The code may even be written by AI.
But we should be careful about delegating the final question:
What counts as “the same”?
Source
- Aaron Patterson, “Rails World 2026 Closing Keynote”
- YouTube: https://www.youtube.com/watch?v=t0knYnEYBRo