Omarchy Core, and the vocabulary open source borrowed from employment
Open source spent thirty years borrowing the vocabulary of employment for work nobody is paid for, and this week it cost two people a day of being shouted at. On 2026-09-26 the Omarchy project published a post headlined "ThePrimeagen joins Omarchy Core to lead Agentic QA". Within hours ThePrimeagen was posting on X that he had been paid nothing, because enough readers had concluded from the headline that he had.
I opened the announcement myself and read it end to end. Everything I say about that page below is from the page. The posts on X I am quoting as posts, from the text as it was captured, and nothing in this article rests on them being more than that.
What the announcement page actually says
The post at omarchy.org, by DHH, dated September 26, 2026, describes a role and a piece of software. The role is Agentic QA. The software is Oligarchy, which the page calls "a custom agent harness that boots Omarchy in QEMU virtual machines and lets agents drive them just like a person would. They send keystrokes, move and click the mouse, take screenshots, and record whether things did what they were supposed to." Each test is described there as a ticket booting from a freshly minted disk, with a fleet of automation clients picking up work and reporting back.
The page gives a reason for the machine. Omarchy is shipping across x86, Apple hardware, Snapdragon, Nvidia, Pi and more, and the post's argument is that no human QA team can click through that surface for every release. That is a normal engineering argument and it is the substance of the announcement.
Here is the part that produced the week. The page contains no occurrence of pay, paid, payment, salary, compensation, employment, hire, hired, volunteer, unpaid or contract. Not one of those words, in either direction. The post does not say the role is a job and it does not say the role is voluntary. It names DigitalOcean Droplets as what the agents run on and says nothing at all about what the person running them receives.
The two posts, and the one that is being read wrong
ThePrimeagen's post on X, roughly eleven hours after the announcement: "For whatever reason a lot of people are saying that I have taken money. I have received a grand total of $0.00 dollars from omarchy and/or dhh. My time at Netflix was spent building tools for developers. I don't just want to make YouTube videos, I still want to try to make deadlines and build cool things. Just trying to build tools for developers. Sorry for the convenience."
DHH's reply, same window: "How DARE you spend your FREE TIME working on tools and projects YOU LIKE. This is VERY DISAPPOINTING, Prime", with a laughing emoji, followed by "These clowns are projection all their insecurities and character flaws."
Read straight, that first line is an employer scolding a contributor, and half the quote-tweets read it straight. It is sarcasm, and it is aimed at the people criticising ThePrimeagen, not at ThePrimeagen. The emoji is in the original post and the second sentence names who the target is. Anyone forwarding the first line without the second is manufacturing a fight that the two people in it are not having.
Why "joins" made people assume a salary
"Joins" is what a press release says when someone starts a job. "Lead" is a title. "Core" is an org chart. All three words arrived in open source from companies, and open source kept them because there was nothing else to call the thing: a person taking responsibility for an area of a project, indefinitely, with the authority to say no. That is a real role. It just has no employment behind it, and the language does not carry that distinction.
The result is a default assumption, and the default runs the wrong way. A reader outside the project reads "joins X to lead Y", maps it onto every other sentence shaped like that they have ever read, and concludes there is money. When it turns out there is not, some of them feel misled, which is how you get a maintainer publishing his own compensation figure to a public timeline to end an argument he did not start.
I do not think the announcement did anything wrong by omitting it. A project is not obliged to publish each contributor's financial arrangements, and a line reading "this role is unpaid" carries its own problems, starting with the implication that the paid roles are listed somewhere. But the omission is not neutral either, because the surrounding words are doing the claiming. That is the cost of describing unpaid open source work in employment vocabulary, and it lands on the contributor rather than on the project.
Count it in your own Gemfile before deciding it is somebody else's problem
A Rails application is the same arrangement, at scale, and it is sitting on your disk right now.
This site's Gemfile has 55 direct entries. Resolving them produces 192 gems besides Bundler, two
of which are local path engines in this repository, leaving 190 that exist on RubyGems. I asked
RubyGems how many accounts hold push rights on each one, on 2026-09-27:
bundle list | grep -E "^ \* " | sed -E 's/^ \* ([^ ]+) .*/\1/' > gems.txt
while read -r g; do
n=$(curl -s "https://rubygems.org/api/v1/gems/$g/owners.json" | ruby -rjson -e 'puts JSON.parse($stdin.read).size')
printf "%s\t%s\n" "$g" "$n"
sleep 0.35
done < gems.txt
64 of the 190 came back with exactly one owner. The distribution of the rest: 36 gems with two
owners, 32 with three, 16 with four, and a long thin tail up to 14 gems with ten owners, which is
the Rails family (rails itself lists fxn, tenderlove, cantoniodasilva, guilleiguaran,
rafaelfranca, matthewd, eileencodes, byroot, jhawthorn and kamipo).
17 of the 55 gems I chose deliberately are single-owner: stripe, rspec-rails, stimulus-rails,
brakeman, bundler-audit, prosopite, ruby_llm, ahoy_matey, annotaterb,
letter_opener_web, rubocop-rails-omakase, and six omniauth-* provider gems. The ones I did not
choose are worse, because nobody audited them: zeitwerk is fxn, minitest is zenspider, timeout
and uri are both hsbt, diff-lcs is austin, crass is rgrove. Autoloading, the test framework
and URL parsing in this application each pass through a gem where one person can push.
Then I asked when each was last released, and crossed that with the owner count. 14 gems in this bundle have a single owner and no release in over two years:
bindex 0.8.1 2019-07-10
websocket-extensions 0.1.5 2020-06-02
aes_key_wrap 1.1.0 2020-07-11
omniauth-gitlab 4.1.0 2022-09-16
net-pop 0.1.2 2022-09-28
tzinfo 2.0.6 2023-01-28
ruby-progressbar 1.13.0 2023-03-04
event_stream_parser 1.0.0 2023-11-13
omniauth-discord 1.2.0 2024-03-02
multipart-post 2.4.1 2024-05-07
letter_opener_web 3.0.0 2024-05-14
websocket 1.2.11 2024-07-08
stimulus-rails 1.3.4 2024-08-16
omniauth-linkedin-openid 1.0.2 2024-09-19
tzinfo on that list is the one that should stop you. Every Time.zone call in this application
goes through it, the time zone article is built almost entirely on its
behaviour, and its last release was over three and a half years ago from one account. A stale
release is not by itself a problem, and a small library that is finished should stop shipping
versions. The problem is that "finished" and "abandoned" produce an identical row in that table, and
the only way to tell them apart is to go and look at the repository, which nobody does for 190 gems.
bundle fund under-reports, and I believed it for twenty minutes
Bundler ships a command for this, so I ran it first. bundle fund printed two gems:
* ruby_llm (1.16.0)
Funding: https://github.com/sponsors/crmne
* selenium-webdriver (4.49.0)
Funding: https://github.com/sponsors/SeleniumHQ
Two out of 192 is a believable number if you already think nobody bothers declaring funding metadata, which is what I thought, so I nearly wrote that down. Reading the metadata directly gives a different answer:
require "bundler"
specs = Bundler.load.specs.to_a.uniq(&:name).reject { |s| s.name == "bundler" }
funded = specs.select { |s| s.metadata.to_h["funding_uri"].to_s != "" }
puts funded.size
- The twelve
bundle funddid not print areloofah,nio4r,public_suffix,version_gem,anonymous_loader,auth-sanitizer,dotenv,multi_xml,oauth-tty,snaky_hash,oauthandoauth2, and every one of them is a transitive dependency.bundle fundwalks the Gemfile's direct dependencies only. That is defensible as a design, since it answers "who did I choose to depend on", but it is the wrong question for funding: the gems nobody chose are exactly the ones nobody thinks about.
Look at those twelve names again. oauth, oauth2, oauth-tty, snaky_hash, version_gem,
anonymous_loader and auth-sanitizer all carry the same funding link, github.com/sponsors/pboling.
Seven of the fourteen sponsorship links in this entire bundle point at one person. The sign-in flow
on this site rests on a stack that one individual is asking for money to maintain, and Bundler's own
funding command does not mention him.
What the owner count is not
A RubyGems owners list records push permission, not labour. An account on that list may have written every line or may have been added in 2019 and never pushed. The 64 is a blast radius, not a headcount, and it answers "how many people could ship a release of this tomorrow", which is the security question rather than the sustainability one. Counting gem maintainers properly means reading a commit history per repository, which is a week of work across 190 of them, and is why nobody has that number either.
The gemspec has a field that looks like the sustainability answer and is not. Array(spec.authors)
returns one element for 116 of the 192 specs here, which looks like a devastating finding until you
check it against a gem you know: rails lists exactly one author, "David Heinemeier Hansson", with
email set to david@loudthinking.com, while ten accounts hold push rights. activerecord is
identical. The authors field is closer to a copyright line than to a contributor list, it is
almost never updated after the first release, and any count built on it is measuring how long ago
the gemspec was written.
Where I stand, and what would move me
Unpaid core roles are fine and the vocabulary around them is not. A person can take responsibility for a large area of a large project for nothing, entirely happily, and ThePrimeagen's own post is a clear statement that this is what is happening. The fix is not to pay everyone, which nobody can afford, and it is not to stop using the word "joins", which is the only word that fits. The fix is one sentence in the announcement saying which arrangement this is, because the reader will assume an answer whether or not you supply one, and they will assume the flattering one.
What would change my mind: evidence that projects which state compensation explicitly get fewer contributors, or get worse ones. That is measurable and I have not measured it, and I can construct the argument for it easily enough. Publishing "unpaid" next to a name turns a role into a status, and a status is easier to resent than an arrangement nobody described. If someone has run that experiment on a project of any size, the position above is the one I would give up.
What would not change my mind is the volume of the argument on X this week, in either direction.
What this page does not cover
Whether Oligarchy is any good. I have not run it, the announcement is the only thing I have read about it, and a harness that drives QEMU virtual machines with agents is worth its own article by someone who has booted it.
Nor does it cover what either person thinks. Both are real people, both posted their own words this
week, those words are quoted above in full, and I am not going to characterise anybody's motives
from a timeline. And the gem numbers are for this repository on 2026-09-27 only. Run the two scripts
against your own Gemfile.lock before quoting any of them, because yours will differ and the count
that matters to you is yours.
Comments
No comments yet. Be the first.