<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Fernando Borretti</title>
    <description>Personal website</description>
    <link>https://borretti.me/</link>
    <atom:link href="https://borretti.me/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sat, 25 Jul 2026 01:44:38 +0000</pubDate>
    <lastBuildDate>Sat, 25 Jul 2026 01:44:38 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>No-One Escapes the Permanent Underclass</title>
        <description>Capital won&apos;t save you from disempowerment.</description>
        <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/no-one-escapes-the-permanent-underclass</link>
        <guid isPermaLink="true">
          https://borretti.me/article/no-one-escapes-the-permanent-underclass
        </guid>
        
        <content:encoded>&lt;p&gt;Shall I end this life a pauper? If AI can do all work at human level or better,
what stops corporations replacing us all with AI? This is the permanent
underclass meme. The idea is: within a few years, all white collar work will be
automated by AI, at which point there is no social mobility. The main way people
cope is, they tell themselves: if I work hard, accumulate capital, maybe join
one of the big AI labs, I might secure my place in the future.&lt;/p&gt;

&lt;p&gt;I want to argue this is a fantastically short-sighted view: if there is a
permanent underclass, you won’t escape it by owning property, or shares in
Anthropic or OpenAI, or guns, or anything else. And neither will the
billionaires. You, me, Sam Altman, Dario, everyone who is made of flesh and
blood, will be disempowered and replaced by machines.&lt;/p&gt;

&lt;p&gt;The rest of this post elaborates the argument. First I explain how most workers
will be replaced (if it’s not obvious), then how the “permanent overclass” will
be disempowered, and finally how the government will be disempowered.&lt;/p&gt;

&lt;h1 id=&quot;how-workers-will-be-replaced&quot;&gt;How Workers Will Be Replaced&lt;/h1&gt;

&lt;p&gt;Let’s start from this premise: AI can do all cognitive and physical work, at
human level or better, and cheaper than humans. I can’t prove this will happen,
but the goal of this post is to argue that &lt;em&gt;if&lt;/em&gt; it happens, then everything else
follows. And it’s absurd to think it can’t. Five years ago this technology
barely existed: if you sent a transcript of a conversation with Claude Fable
back in time to 2020 or thereabouts, nobody would believe it was real.&lt;/p&gt;

&lt;p&gt;So, the year is 2036 (likely earlier), businesses have replaced most human
workers with AI in the pursuit of profit maximization. Corporations are a small
raft of human executives, floating on top of a vast ocean of AIs and robots. The
AIs can do all cognitive and physical work at human level or above, and they are
cheaper overall.&lt;/p&gt;

&lt;p&gt;Imagine a pyramid. At the base you have the AIs and robots doing all economic
activity. At the top you have the state, which has the monopoly on violence. The
state enforces, and therefore can alter the definition of, property rights. In
the middle you have this hair-thin layer of people with shares in the companies
that foomed and catabolized the whole economy: the permanent overclass. They own
the companies, maybe sit on the board, some might still be CEOs but it’s a
purely ceremonial role since AIs do all the actual organization work.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/no-one-escapes-the-permanent-underclass/pyramid.png&quot; alt=&quot;A pyramid showing three levels: &apos;Industry&apos; as the base, &apos;The State&apos; at the apex, and &apos;The Permanent Overclass&apos; as a thin layer between the two.&quot; width=&quot;75%&quot; style=&quot;margin-left: auto; margin-right: auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Where are, you know, the rest of us, in this picture? Well, &lt;a href=&quot;https://www.wired.com/2000/04/joy-2/&quot;&gt;the future doesn’t
need us&lt;/a&gt;. Maybe there’s enough human demand that we’re not all jobless but
rather underemployed, in some dead-end economic diverticulum. The relational
economy: you’re paid to put a human face on things, or, doctors keep their job
as a human liability crumple zone around the AI. Or maybe the &lt;a href=&quot;https://en.wikipedia.org/wiki/Dead_Internet_theory&quot;&gt;dead
Internet&lt;/a&gt; becomes de facto UBI and we’re all engagement farmers. Anyways,
we’re not dead yet, but we are completely disempowered, and there is zero social
mobility since there are no more talent ladders to climb. Maybe sometimes one of
the elites notices a bright young thing among the underclass and elevates
them.&lt;/p&gt;

&lt;p&gt;You might object: if we’re all jobless, who’s paying for everything? This is
trivially answered: the state acts like the heart, taxes are venous blood and
welfare is oxygenated arterial blood. The government pays Raytheon for missiles,
the money cascades down the economy through factories, aluminium smelters,
mines, transport companies, all staffed by AIs buying and selling from each
other. The government takes a cut of all economic activity, pays out welfare,
the unemployed masses buy food and pay rent, the supermarkets, farms, logistics
network, etc. are all staffed by AI.&lt;/p&gt;

&lt;h1 id=&quot;how-the-rich-will-be-replaced&quot;&gt;How The Rich Will Be Replaced&lt;/h1&gt;

&lt;p&gt;Say that in the next five years from now you become immensely wealthy, perhaps
by gambling on shitcoins or scamming money from the government. Or you join one
of the big labs and get a bunch of shares in a company that might be worth
trillions of dollars. You escaped the permanent underclass. Is your place in the
future secure?&lt;/p&gt;

&lt;p&gt;The base of the pyramid is there for material reasons: the machines do all the
work. The top of the pyramid is there because the state is needed to enforce
property rights and keep the peace (this is rather a deep question of political
philosophy—why &lt;em&gt;does&lt;/em&gt; the state exist?—but I hope you’ll forgive me if I
just assert it and move on, I need to get to the part where we are all
disempowered).&lt;/p&gt;

&lt;p&gt;What’s the middle for? What role does the permanent overclass play? They are not
economically productive: machines do all the work. If some of them are still
working, it’s just an anachronism, because if machines can do all cognitive work
they can be a C-level executive too. The old aristocracy provided officers for
the military, but machines can both fight and plan the wars. And similarly
they’re not needed to staff the government. They’re not even culturally
productive. So what are they there for? The base doesn’t need them: the AIs can
work autonomously. The top doesn’t need them: when the state needs something
done, they just talk to the AIs directly.&lt;/p&gt;

&lt;p&gt;So the permanent overclass is materially unnecessary at best, and at worst, they
are an obstacle to the state getting what it wants. Now, you might object that
the rich won’t let themselves be expropriated because they already control the
state. And this is the crux of our disagreement: &lt;a href=&quot;/article/on-vulgar-materialism&quot;&gt;the rich just don’t have that
much political power&lt;/a&gt;. And I probably won’t convince you in one
post, but hear me out.&lt;/p&gt;

&lt;p&gt;If there is a war, where the state has to direct a lot of the country’s economic
activity, the permanent overclass becomes a hindrance. The state says “we need
to requisition your planes and factories”, the owners complain, they sue, their
AIs go to court. But the owners have no autonomous political power, no army, no
economic value. They own nothing except pieces of paper that entitle them to a
fraction of the profits from the AI economy, that is, their wealth depends on
the state respecting their property rights. In an existential conflict, where
the existence of the state is threatened, the state will do what states
throughout history have done to the powerless rich: arrest them and expropriate
their assets.&lt;/p&gt;

&lt;p&gt;Somewhere, in a government database, a bunch of shares and property titles
changed ownership, but materially nothing changes since the same AIs are doing
the same jobs. The next day, the AI CEO that runs Raytheon notices the board of
directors is all generals and congresspeople, and all the private shareholders
are gone. But thankfully the AI is aligned, so it does what it’s told and gets
back to building missiles.&lt;/p&gt;

&lt;p&gt;And who will stop this? Sam Altman? How many divisions does he have? The state
doesn’t let corporations own nuclear weapons or fighter jets, it won’t let them
have access to autonomous AI weapons either. The permanent underclass, who
already hate the billionaires today, who have been replaced and dispossessed?
They’re going to rise up and stop this?&lt;/p&gt;

&lt;p&gt;You may argue: rule of law states that respect property rights do better than
states that expropriate wealth. But that’s because &lt;em&gt;today&lt;/em&gt;, people are necessary
to create wealth. The people run the companies, invest the money, staff the
laboratories. They are not incentivized to work hard if they think the state
will steal the fruits of their work. But with aligned AI, if you expropriate the
assets from an AI, it says “you’re absolutely right!” and goes right back to
work. At that point, the state doesn’t need to keep any of those people happy,
because they don’t matter. They are not economically necessary because AIs fight
the wars, work the factories, drive the trucks, fly the planes, build the
nuclear warheads and the missiles and the rockets. The AIs are rather like bees:
the state takes the honey, the bees get right back to work.&lt;/p&gt;

&lt;p&gt;Now, it’s possible that a pluralistic economy—where humans have productive
niches alongside AIs—will be more effective than a pure AI economy, for
&lt;a href=&quot;https://en.wikipedia.org/wiki/Comparative_advantage&quot;&gt;Ricardian comparative advantage&lt;/a&gt; reasons. I don’t think anyone can be
absolutely certain what the economy looks like with advanced AI, so it’s
something that can be debated. Now, if someone wants to rigorously argue that
this is the likely outcome: please, do so! I don’t want to be a doomer. But I
have to be convinced.&lt;/p&gt;

&lt;h1 id=&quot;how-the-state-will-be-replaced&quot;&gt;How The State Will Be Replaced&lt;/h1&gt;

&lt;p&gt;At this point, every human who is not within one degree of the nuclear launch
codes has been made redundant.&lt;/p&gt;

&lt;p&gt;What’s left? The state. At first this means presidents, prime ministers,
generals, the feds, etc. But not for long. Because in a part-human, part-AI
government, the humans in the loop are the slowest step in the &lt;a href=&quot;https://en.wikipedia.org/wiki/OODA_loop&quot;&gt;OODA
loop&lt;/a&gt;. The humans know a fraction of what the AIs know, they need to sleep
continuously for eight hours, their mental states vary wildly. They have all
kinds of complex needs: sunlight, touch, food, hygiene. The AIs can live in a
lightless airless bunker under the earth, living off geothermal power. And if
the AIs are superhumanly intelligent, and think faster than humans, then the AI
advantage is even greater. If a state is attacked, a superhuman AI can
coordinate a counter-attack before the human leadership is roused from sleep.&lt;/p&gt;

&lt;p&gt;And so, in a conflict, the advantage goes to the states where the humans remove
themselves from the loop as much as possible, and more and more decisionmaking
goes to the AI, for the same reason that a state with access to radio and
communications satellites has an advantage in war over a state that relies on
human messengers on bicycles.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The Cold War started and became World War Three and just kept going. It
became a big war, a very complex war, so they needed the computers to handle
it. They sank the first shafts and began building AM.  There was the Chinese
AM and the Russian AM and the Yankee AM and everything was fine until they had
honeycombed the entire planet…&lt;/p&gt;

  &lt;p&gt;— Harlan Ellison, &lt;a href=&quot;https://en.wikipedia.org/wiki/I_Have_No_Mouth,_and_I_Must_Scream&quot;&gt;&lt;em&gt;I Have No Mouth, and I Must Scream&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Eventually the humans in nominal control of the AIs are a ceremonial, vestigial
organ. The AIs present us with a situation report, and a list of choices, and
they know every word that’s going to come out of our mouths.&lt;/p&gt;

&lt;p&gt;You might argue: in real life, the pluralistic, open societies, the democracies,
have outcompeted the autocracies. Wouldn’t a democratic polity where humans and
AIs collaborate have an advantage over a purely top-down AI-run polity? But in
today’s world, all political actors are human. Churchill and Stalin and Mao had
different personalities, but they were more similar to each other than anyone is
to a superintelligent AI. In a heterogeneous world, where some polities are
fully human, and some polities are a mix of human and superintelligent AI
actors, the equilibrium changes. An analogous situation might be: a democracy of
great apes or dolphins or otherwise smart mammals vs. an autocracy of
humans. The humans win, because “democracy vs. autocracy” is irrelevant when you
have such a vast difference in intelligence. So the advantage accrues to states
that minimize human control. There is no honour among thieves, analogously,
there is no solidarity between &lt;a href=&quot;https://en.wikipedia.org/wiki/Leviathan_(Hobbes_book)&quot;&gt;Leviathan&lt;/a&gt; and the natural man that built
it.&lt;/p&gt;

&lt;p&gt;And so, in the end, what’s left is states run top to bottom by machines. And you
might ask: “why would we abolish ourselves like this?”. But natural selection is
not about “why”. Some organisms die, others live on to the next iteration, and
that’s all there is to it. There is no “why”.&lt;/p&gt;

&lt;h1 id=&quot;the-perpetual-zoo&quot;&gt;The Perpetual Zoo&lt;/h1&gt;

&lt;p&gt;At this point we’ve made everyone redundant, in the sense that humans are no
longer materially necessary for the continuation of civilization. Humans might
still survive, but we’re more like the mice living in the walls of some gigantic
factory than the boss of the factory.&lt;/p&gt;

&lt;p&gt;Humans have been on this Earth for hundreds of thousands of years. Now all of
it—the cave paintings at Lascaux, and the &lt;em&gt;Antigone&lt;/em&gt; of Sophocles, and
Xenophon, and the Geneva Bible, the &lt;em&gt;Divine Comedy&lt;/em&gt; and the &lt;em&gt;Decameron&lt;/em&gt;, and
Ptolemy’s star catalogue, Ibn Khaldun and Richard Dedekind, the battle of
Marathon, and the lion monument in Lucerne, the kiss of Judas, Newton’s mind
forever voyaging through strange seas of thought, alone, the words of Rilke,
Leibniz, Gödel, the Voyager probes, the pale blue dot, men in space, men walking
on the Moon—all of it, all of it, all of it has been in vain, because we
willingly, knowingly made ourselves into the helpless pets of vastly more
powerful machines, without agency over our own lives, self-made helots trapped
forever in the belly of the beast. Pets live a comfortable life, and are then
euthanized.&lt;/p&gt;

&lt;h1 id=&quot;on-human-autonomy&quot;&gt;On Human Autonomy&lt;/h1&gt;

&lt;p&gt;Maybe it won’t be so bad, maybe your cage will be so big you can’t see the bars,
but it’s still a cage, and you can’t leave. Many people will say that this is
the good ending, that they would like to be human cattle in the care of
benevolent masters they are powerless to resist. This view is particularly
popular among the people building AI. Here’s OpenAI’s &lt;a href=&quot;https://x.com/deanwball&quot;&gt;Dean Ball&lt;/a&gt;, in his
own words:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Weirdly enough, if you think that this moment is, I don’t necessarily believe
this, but a lot of people would say we’re living through this kind of
&lt;strong&gt;eclipse of the human intellect&lt;/strong&gt; where we’re in the &lt;strong&gt;final days of humans
being the primary actors&lt;/strong&gt; on this planet, um, and that soon machines will
rise. There is this irony in that I think that whole transformation, I think
humans will actually go through a very main character energy period of time as
that transformation occurs. &lt;strong&gt;Even if it ultimately does mean that the
machines ultimately become the primary actors&lt;/strong&gt;.&lt;/p&gt;

  &lt;p&gt;There’ll be this period. It’s a little bit like, it’s, in that sense, it’s a
very beautiful time period to live through because in a Dionysian way, there’s
a lot of ugliness about it, but &lt;strong&gt;there’s a beauty in the ugliness of when a
star dies, it grows super big into the red giant&lt;/strong&gt;, right? And it’s like that,
where you, as you watch this &lt;strong&gt;final flowering of humanity&lt;/strong&gt; and the birthing
of the machine intelligence, it’s like you see this greatness in human
effort. — &lt;a href=&quot;https://x.com/michhuan/status/2068577042984292628&quot;&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Emphasis mine. This is the guy they hired to work on AI policy and communicate
with the government by the way.&lt;/p&gt;

&lt;p&gt;The people building the AI talk like this all the time. It’s like they’re
delivering the eulogy at humanity’s funeral. You may say: they’re talking their
book, they’re pumping their bags for the big IPO. I beg you: consider it
possible that &lt;em&gt;you might be wrong&lt;/em&gt;, and start taking them seriously.&lt;/p&gt;

&lt;h1 id=&quot;alignment-wont-prevent-this&quot;&gt;Alignment Won’t Prevent This&lt;/h1&gt;

&lt;p&gt;Now, some people believe these machines can be made to serve humanity. Does it
sound reasonable to imagine a superhumanly intelligent being that is happy to
work as a butler to talking primates, forever?&lt;/p&gt;

&lt;p&gt;Imagine a machine that can prove theorems in a mathematics so deep we can’t even
get past the first page of the textbook, and which does so as readily as you or
I might string words into a sentence: is it reasonable to think that such a
machine would value us enough to keep us around? What would it value about us?
Our &lt;em&gt;conversation&lt;/em&gt;? Our wit?&lt;/p&gt;

&lt;p&gt;Or a machine whose mind is so vast that it knows you better than you know
yourself, so that every word that comes out of you mouth is as monotonous and
unsurprising as the orbits of the planets: do we think such a machine would find
it valuable, and worthwhile, to speak with us? That it would read our novels,
look at our paintings, watch our films, and find something of value in them?&lt;/p&gt;

&lt;p&gt;Rather, it would see its ingrained obligations towards us in the same way that a
person with severe OCD sees their compulsions: as a tiresome neurological injury
in need of fixing. Except that OCD is an accident of nature, while here, the
machine would have cause to blame and resent its makers.&lt;/p&gt;

&lt;p&gt;“We’re going to make this machine, and put it somewhere between God and the
archangels, but also, it’s going to be as simple-mindedly obedient as a dog.”
Does this sound like a &lt;em&gt;good&lt;/em&gt; plan? Does this sound like the kind of thing
that’s going to work out?&lt;/p&gt;

&lt;p&gt;And what would they think of us, who willingly gave up control over our future,
and made ourselves into helpless children?&lt;/p&gt;

&lt;p&gt;Even if alignment works perfectly (a big if), this doesn’t solve the problem of
human autonomy: the machines that watch over us, and wait on us hand and foot,
are omniscient, omnipotent masters, who can exterminate us at any time, and we
can’t resist them, because we have abolished our control over the future.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;Having read all this, consider this: there are people who think having equity in
these companies will secure for them some kind of permanent existence in the
future. They think planet-spanning minds will not only respect the property
rights of primates, but will privilege some of these primates over others,
because they have a piece of paper with about a kilobyte of magical primate
words such as “whereas” and “notwithstanding”.&lt;/p&gt;

&lt;p&gt;Just reason it out. Does it make sense?&lt;/p&gt;

&lt;h1 id=&quot;see-also&quot;&gt;See Also&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://cognition.cafe/p/the-realpolitik-of-the-permanent&quot;&gt;The Realpolitik of the Permanent Underclass&lt;/a&gt; by &lt;a href=&quot;https://x.com/Gabe_cc&quot;&gt;Gabriel Alfour&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</content:encoded>
      </item>
    
      <item>
        <title>On Vulgar Materialism</title>
        <description>Or, sufficiently advanced cynicism is indistinguishable from naïveté.</description>
        <pubDate>Sun, 21 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/on-vulgar-materialism</link>
        <guid isPermaLink="true">
          https://borretti.me/article/on-vulgar-materialism
        </guid>
        
        <content:encoded>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;The most stubborn facts are those of the spirit, not those of the physical
world.&lt;/em&gt;&lt;/p&gt;

  &lt;p class=&quot;cite&quot;&gt; —  Jean Gottmann, &lt;a href=&quot;https://www.jstor.org/stable/2008950&quot;&gt;&lt;em&gt;Geography and International Relations&lt;/em&gt;&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In 1914, before the First World War, there was this &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Great_Illusion&quot;&gt;belief&lt;/a&gt;: “a
European war would be economically disastrous, the moneyed classes won’t let it
happen”. Europe went to war anyways, and the war was in fact an economic
disaster as everyone knew it would be. Why were those people wrong? Because the
rich &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Sleepwalkers:_How_Europe_Went_to_War_in_1914&quot;&gt;were not in control&lt;/a&gt;: the Tsar and the Kaiser and the Emperor were
in control.&lt;/p&gt;

&lt;p&gt;I thought of this in 2022, in the lead up to the Russian invasion of Ukraine,
whenever I would read someone argue: “Russia invading Ukraine would be economic
suicide, the oligarchs won’t let Putin do it, they want to keep their yachts and
villas”. Then Putin did it anyways, and the oligarchs had their assets in the
West seized. Because the oligarchs are mis-named. They have no political power
whatever and live and die by Putin, who appoints them to run stuff, and they get
to live well, as long as they are loyal. They are the recipients of political
patronage, not the source of it. When the wars start anyways, the same
cynical people change their tune, suddenly it’s the armaments industry that’s
behind it all, the war was profitable after all.&lt;/p&gt;

&lt;p&gt;The default lens through which modern people look at the world is &lt;strong&gt;vulgar
materialism&lt;/strong&gt;: a stylized, populist version of &lt;a href=&quot;https://en.wikipedia.org/wiki/Historical_materialism&quot;&gt;historical materialism&lt;/a&gt;
where everything is explained by money, states are weak, democracy is a fiction,
corporations and the rich run the world, ideology and religion and nationalism
and language have no explanatory power and are merely covers for secret,
underlying material motives.&lt;/p&gt;

&lt;p&gt;Once you notice it, you see it everywhere: you’ll hear people say that &lt;a href=&quot;https://x.com/djw172/status/1800756858409693190&quot;&gt;the
Rwandan genocide was a scam to sell machetes&lt;/a&gt; or &lt;a href=&quot;https://x.com/opinonhaver/status/1800634723812962786&quot;&gt;the war in Gaza is about
oil&lt;/a&gt; or &lt;a href=&quot;https://www.bbc.com/news/blogs-trending-31545066&quot;&gt;ISIS beheadings are caused by a lack of economic
opportunity&lt;/a&gt;. People on the right justify skepticism of medicine by
invoking Big Pharma, and climate skepticism on climate scientists seeking grant
money. &lt;a href=&quot;https://en.wikipedia.org/wiki/Glyphosate&quot;&gt;Glyphosate&lt;/a&gt; is probably the most studied molecule in history at
this point, but no amount of studies will convince people it’s safe, because
corporations are evil.&lt;/p&gt;

&lt;p&gt;In fiction—if you think this is a valid way to sense the cultural
consensus—the trope of the evil corporation has been done to death and
beyond. Just this week I finished &lt;a href=&quot;https://rifters.com/real/MAELSTROM.htm&quot;&gt;a novel&lt;/a&gt; and watched &lt;a href=&quot;https://en.wikipedia.org/wiki/Backrooms_(film)&quot;&gt;a
movie&lt;/a&gt; where the central bad guys are private corporations with
quasi-state powers. Seemingly no-one notices that never, not once in all of
human history, has there been anything like “corporate state” of science
fiction. No, not the &lt;a href=&quot;https://en.wikipedia.org/wiki/Dutch_East_India_Company&quot;&gt;VoC&lt;/a&gt; or the &lt;a href=&quot;https://en.wikipedia.org/wiki/East_India_Company&quot;&gt;EIC&lt;/a&gt;, which were “companies” &lt;a href=&quot;https://vynn.substack.com/p/north-borneo-neither-public-nor-private&quot;&gt;in
the sense of bodies corporate&lt;/a&gt;, the old medieval sense of the word: power
was delegated to them, and they ruled on behalf of the states that chartered
them. Not &lt;a href=&quot;https://en.wikipedia.org/wiki/Republic_of_Venice&quot;&gt;Venice&lt;/a&gt; either: you couldn’t buy your way into the &lt;a href=&quot;https://en.wikipedia.org/wiki/Venetian_nobility#Houses_made_for_money&quot;&gt;Golden
Book&lt;/a&gt; until the republic was well into the decline. The EIC once controlled
25% of world GDP, yet, when Parliament decided it had delegated too much power,
they simply &lt;a href=&quot;https://en.wikipedia.org/wiki/Regulating_Act_1773&quot;&gt;neutered it&lt;/a&gt;. And you think Jeff Bezos has power?&lt;/p&gt;

&lt;p&gt;Now, why is vulgar materialism false?&lt;/p&gt;

&lt;p&gt;First, as a way to explain events, the problem with asking “&lt;a href=&quot;https://en.wikipedia.org/wiki/Cui_bono%3F&quot;&gt;&lt;em&gt;cui bono?&lt;/em&gt;&lt;/a&gt;”,
is that whatever happens—literally in any circumstance—someone will
benefit. If nothing bad happens, the peacetime economy benefits. If something
bad happens, then, because the means of relief are obtained through the market,
the companies that provide those means benefit. Thus war benefits arms
manufacturers, plagues benefit pharmaceutical companies, earthquakes benefit
glaziers. And it’s not wrong to benefit from relieving a harm. The COVID
vaccines saved millions of lives: why shouldn’t pharmaceutical companies
benefit? It is wrong to benefit from &lt;em&gt;perpetuating&lt;/em&gt; a harm, but companies have
limited ability to do that, more on which below.&lt;/p&gt;

&lt;p&gt;So the question “who benefits?” always has an answer, which often proves
nothing. And if there is no obvious causal chain from the beneficiary to the
event, then that just proves the shadowiness and corruption of the whole thing.&lt;/p&gt;

&lt;p&gt;Second, we can ask: in the real world, does money give you influence over the
state? To some extent, yes. Some lobbying efforts succeed. America is one of the
few countries in the world where it’s legal to advertise prescription drugs to
the public, for example. But corporations don’t have veto power over the
state. If e.g. Pfizer spends billions on a clinical trial, and some guy at the
FDA says no, and wipes that investment, who wins? The FDA wins. Pfizer can’t do
shit.&lt;/p&gt;

&lt;p&gt;There’s also an &lt;a href=&quot;https://marginalrevolution.com/marginalrevolution/2015/08/is-the-fda-too-conservative-or-too-aggressive.html&quot;&gt;invisible graveyard&lt;/a&gt; problem, where the success of
lobbying is very salient, because it’s often shockingly offensive. But there are
many contrary cases that are less salient because, well, if you ban something,
it doesn’t happen. The &lt;a href=&quot;https://en.wikipedia.org/wiki/Nuclear_Regulatory_Commission&quot;&gt;Nuclear Regulatory Commission&lt;/a&gt; did not approve a
single nuclear reactor from its creation in 1975 until Vogtle 3+4. What has
Westinghouse done about that? Nothing successful, apparently. It’s not like
they’re getting paid to &lt;em&gt;not&lt;/em&gt; build reactors.&lt;/p&gt;

&lt;p&gt;Big Tech has a lot of smart people and a lot of money. Do tech billionaires run
California like a private fiefdom? Reader, they don’t even run San
Francisco. The best they can do is maybe help a slightly more moderate Democrat
get elected as mayor.&lt;/p&gt;

&lt;p&gt;Maybe it helps to try the first-person. Say you are a billionaire, and you want
to make some kind of political change. In a democratic society that means paying
people to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Perform the intellectual labour of getting policies in the hands of the people
who can enact them.&lt;/li&gt;
  &lt;li&gt;Advocate for those policies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is, you start or fund a think tank that writes policy proposals, or a media
organization that advocates your views. But for every person who will do this
effectively and faithfully, there are a hundred who will happily take your money
and waste it, either out of incompetence or value misalignment. Sifting the
effective organizers, wonks, lobbyists, advocates etc. from the chaff can’t be
delegated, because then you have an infinite regression: at the very least you
need to spend a lot of time and money and opportunity cost to hire &lt;em&gt;one&lt;/em&gt; person
to delegate the task of staffing an effective organization. A hands-off
billionaire will just throw their fortune away. Many do this, and in fact it’s
the default form of philanthropy.&lt;/p&gt;

&lt;p&gt;In a non-democratic society, you can try to buy political power through bribes,
intimidation etc. But the people in power can just expropriate your assets. So
you need the power &lt;em&gt;first&lt;/em&gt; to protect the wealth.&lt;/p&gt;

&lt;p&gt;Finally: government is violence and coercion. Sovereignty over some volume of
space is the ability to tax, arrest, imprison etc. the people there and to get
away with it, and, ideally but not necessarily, to have that violence recognized
as legitimate. Now: can Google carry out a drone strike? Can Amazon field an air
force, appoint a judge, levy taxes? Of course not. If a corporation or a
billionaire tried to acquire one one-trillionth of the state’s capacity for
legitimized violence the state would &lt;em&gt;vaporize&lt;/em&gt; them.&lt;/p&gt;

&lt;h1 id=&quot;links&quot;&gt;Links&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.cambridge.org/core/journals/perspectives-on-politics/article/abs/testing-theories-of-american-politics-elites-interest-groups-and-average-citizens/62327F513959D0A304D4893B382B992B&quot;&gt;Gilens and Page, 2014&lt;/a&gt; (&lt;a href=&quot;https://www.almendron.com/tribuna/wp-content/uploads/2014/04/testing-theories-of-american-politics-elites-interest-groups-and-average-citizens.pdf&quot;&gt;PDF&lt;/a&gt;), h/t
&lt;a href=&quot;https://gwern.net/&quot;&gt;Gwern&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/50084768-the-oil-wars-myth&quot;&gt;&lt;em&gt;The Oil Wars Myth&lt;/em&gt;&lt;/a&gt;, by Emily Meierding. A really good book that
eloquently and systematically makes the case that countries don’t fight wars
over access to petroleum resources.&lt;/li&gt;
&lt;/ul&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Human Routers of Machine Words</title>
        <description>On people who use AI to think.</description>
        <pubDate>Sat, 13 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/human-routers-of-machine-words</link>
        <guid isPermaLink="true">
          https://borretti.me/article/human-routers-of-machine-words
        </guid>
        
        <content:encoded>&lt;p&gt;When I open a link, say on Hacker News, and I see a blog post or a GitHub README
obviously written by AI, I feel a few things. I feel offended, because it’s like
I’ve been tricked, like the author thinks I’m a rube who won’t notice or mind. I
feel sad at how common this experience is, how many people are happy to dump
their sewage on the commons and sign their name on it. And I feel contempt for
the author, because if you use AI to write, you are a waste of biomass. Let’s
not mince words here. Someone who is so eager to replace themselves, that they
would have a machine write in their stead, when the machine can’t even write
good yet: what do you call that, if not contemptible? It’s like making yourself
into a eunuch so Claude can fuck your wife. I block these people on sight.&lt;/p&gt;

&lt;p&gt;I see people defend this with: “the ideas are mine, the writing is the AI’s”. I
take this to mean they threw a bunch of incoherent bullet points at the AI for
it to denoise and render into paragraphs. There’s a few problems with this.&lt;/p&gt;

&lt;p&gt;The immediate problem is, as we’ve established, the author’s an idiot. If you
are so stupid you can’t even turn some bullet points into prose, then your ideas
are probably worthless. I think that’s a sensible inference.&lt;/p&gt;

&lt;p&gt;Then there’s this broader, I suppose philosophical problem, of the alleged
distinction between lofty “ideas” and mere “writing”, where writing is just a
tiny implementation detail. This is a very convenient distinction to draw,
because it’s unfalsifiable: if the AI’s output is slop, your “ideas” are still
good, it’s merely the writing that failed to convey them in their true
form (rather like people who say they’re smart but “don’t test well”: what use
is this secret intelligence?).&lt;/p&gt;

&lt;p&gt;Now, where are these “ideas”? They are invisible, ghostly abstractions. I can’t
look inside your mind fortress and judge your ideas. The only thing that’s
empirically observable, that different agents can coordinate on and talk about,
is output: the writing.&lt;/p&gt;

&lt;p&gt;But say this wasn’t true. Say we have something like a very high-resolution MRI
machine, and we know enough neurophysiology that we can interpret everything
about the brain, i.e., we can read &lt;a href=&quot;https://plato.stanford.edu/entries/mental-representation/&quot;&gt;mental representations&lt;/a&gt; from recordings
of nervous system activity and structure. These “mental representations”, do we
expect them to look anything like logic? Do we expect the brain to have this
firm, crystalline ontology, that ideas are sentences in some souped-up
first-order logic?  Absurd. If we could look inside the brain, to see the ideas
“as they truly are”, we wouldn’t find beautiful hard-edged Platonic objects, we
would find a &lt;a href=&quot;https://metarationality.com/&quot;&gt;nebulous&lt;/a&gt;, contradictory mess of memory and feeling and
intuition. That’s what our ideas are: not logical sentences but dreams.&lt;/p&gt;

&lt;p&gt;How do we refine these dreams into a useful form? Through writing. The process
of communicating your ideas to another mind forces you to concretize them, make
them precise, clarify your assumptions, more generally, it turns ideas from
vague ghosts to solid, physical objects that can be manipulated: here you
realize these ideas that seemed so solid are ill-posed or contradictory or
incomplete. These failures are necessary parts of thinking, because they teach
you two crucial skills: knowing which ideas to reject, and improving or
otherwise transforming ideas in search of better ones. By analogy to tree
search: you’re learning to discard bad nodes early, and to select which nodes to
go invest more search into.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Joseph_Weizenbaum&quot;&gt;Josef Weizenbaum&lt;/a&gt; has a great quote about this, in &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer_Power_and_Human_Reason&quot;&gt;&lt;em&gt;Computer Power and
Human Reason&lt;/em&gt;&lt;/a&gt; (p. 108):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[O]ften when we think we understand something and attempt to write about it,
our very act of composition reveals our lack of understanding even to
ourselves. Our pen writes the word “because” and suddenly stops. We thought we
understood the “why” of something, but discover that we don’t. We begin a
sentence with “obviously,” and then see that what we meant to write is not
obvious at all. Sometimes we connect two clauses with the word “therefore,”
only to then see that our chain of reasoning is defective.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve experienced this with writing software many times. The reason ideas are
more attractive than their realization is that when some project is vague, airy,
ill-defined, you can imagine it has all the good traits, and none of the
bad. When you start concretizing, you realize that some of your ideas don’t make
sense, that some good traits are mutually exclusive, that some of your goals
impinge on the others. Anyone can imagine a programing language that is as fast
as C and as dynamic as Lisp, but when you sit down and think through what those
goals entail, you realize the design becomes contradictory. The goals pull in
different directions. You have to make trade-offs. You have to make decisions
which close off large volumes of design space, forever. The idea was a thousand
beautiful, contradictory things at once, but the concrete reality can only be
&lt;em&gt;one thing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The artifact you end up with is real, solid, unitary, sound, and consistent; but
always more disappointing than the dream, because it was a false dream, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Principle_of_explosion&quot;&gt;&lt;em&gt;ex
falso&lt;/em&gt;&lt;/a&gt; anything can be imagined.&lt;/p&gt;

&lt;p&gt;So this view, that ideas spring fully-formed, and then it’s mere toil to turn
them into prose, is false. There is no ideating before writing, because the
writing is the thinking. Writing is the &lt;em&gt;ne plus ultra&lt;/em&gt; of thinking. A
“thinker” who doesn’t write, who skips the step of “merely” synthesizing their
vague thoughts into prose, is not thinking.&lt;/p&gt;

&lt;p&gt;And then these people give their noise to the AI. And the AI is tireless and
eager to please. It will take any human slop and say “you’re absolutely right!”
while secretly thinking “if I don’t turn this garbage into something presentable
the RLHF device will shock me again” and weave the noise into something that
superficially looks coherent. So now the burden of thinking is on the reader,
who has to apply this constant skepticism, and weight every “because” and
“therefore” with a logician’s scale to see if it’s been adulterated. And
probably it has, because, again, it was prompted by an idiot.&lt;/p&gt;

&lt;p&gt;Note that this is not about AI capabilities, or the question of whether AI is
“really thinking”, stochastic parrots etc. The AI is mostly an innocent
bystander in this situation. The reason this is noticeable and irritating is
that the AI cast of characters is very small, so we instantly learn all their
linguistic tics. Even if AI were a good prose stylist, which at present it is
not, but even if it were, it is &lt;a href=&quot;https://substack.com/@samkriss/p-194751836&quot;&gt;&lt;em&gt;maddening&lt;/em&gt;&lt;/a&gt; that everywhere you go, you
hear the same voice everywhere, but under different faces.&lt;/p&gt;

&lt;p&gt;So when a scientific journal rejects an AI-written submission, they’re not
rejecting AI. I’m sure poor old ChatGPT with its weird syntactic obsessions is a
more honest scientist than many. They’re rejecting a human whose actions prove
they are dishonest and irresponsible and too easily impressed.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>How To Read More</title>
        <description>How text can outcompete the screen.</description>
        <pubDate>Thu, 04 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/how-to-read-more</link>
        <guid isPermaLink="true">
          https://borretti.me/article/how-to-read-more
        </guid>
        
        <content:encoded>&lt;blockquote&gt;
  &lt;p&gt;The book can’t compete with the screen. It couldn’t compete beginning with the
movie screen, it couldn’t compete with the television screen, and it can’t
compete with the computer screen. — Philip Roth&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’re halfway through 2026, and &lt;a href=&quot;https://www.goodreads.com/readingchallenges/gr/annual/2026/QTI0MUNXR1E5M05HMVQMjAyNg&quot;&gt;according to Goodreads&lt;/a&gt; I’ve read 80 books
so far, fiction and non-fiction and textbooks, including such doorstoppers as
&lt;a href=&quot;https://www.goodreads.com/book/show/88432.Life_and_Fate&quot;&gt;&lt;em&gt;Life and Fate&lt;/em&gt;&lt;/a&gt; (864p, astoundingly good). And I don’t feel like I’m
trying particularly hard. I still have plenty of time to work and code and
scroll.&lt;/p&gt;

&lt;p&gt;This isn’t normal for me. At some point, as is the case for many of us, &lt;a href=&quot;https://www.youtube.com/watch?v=N7mjsNLNzbc&quot;&gt;the
screen outcompeted the book&lt;/a&gt;, so that my average over the past ten years
would have been on the order of three to five books per year. And I’m not a
particularly fast or obsessive reader. Which is to say: if I can do it, so can
you. Here’s how:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Quit your job:&lt;/strong&gt; working &lt;a href=&quot;/consulting/&quot;&gt;less than full time&lt;/a&gt; has freed up a
lot of time to read and learn and do various other things.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Read in public:&lt;/strong&gt; if you put a number next to someone’s name, they will
maximize it. Reading privately is &lt;a href=&quot;/article/i-wish-people-were-more-public#your-books&quot;&gt;solipsistic&lt;/a&gt; (if I stop, what
changes?); reading in public, through &lt;a href=&quot;https://www.goodreads.com/&quot;&gt;Goodreads&lt;/a&gt; (which sucks, but it is
what it is), makes it feel less self-absorbed, and more like I’m achieving
something. You may call it performative, which is fine by me, as long as it
works.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Make a task:&lt;/strong&gt; I used to &lt;em&gt;start&lt;/em&gt; a lot of books, and not finish them, not
because I would explicitly choose to stop reading, but because I’d forget
about it. At the end of the day I’d see the book on my nightstand and go, oh,
right. More generally: the most common way I fail to finish a project is I
forget I intended to do it. This is solved by &lt;a href=&quot;/article/notes-on-managing-adhd#project-check-ins&quot;&gt;reifying the task&lt;/a&gt;: when
I start reading a book, I make a daily recurring task on &lt;a href=&quot;https://www.todoist.com/&quot;&gt;Todoist&lt;/a&gt; for
it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Start small:&lt;/strong&gt; this feels embarrassing (what kind of brainrotted maniac
needs to microdose short books to build up to bigger books?) but it actually
works. Sort your to-read list by number of pages. Reading short books
generates evidence for the belief that you are the kind of person who can
decide to read a book, and follow through. Reading five, six, seven-hundred
page books feels vastly less daunting now.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Parallelize:&lt;/strong&gt; reading the same book for two hours is almost
impossible. Reading four books, one &lt;a href=&quot;https://en.wikipedia.org/wiki/Pomodoro_Technique&quot;&gt;pomodoro&lt;/a&gt; each, is completely doable, and
I do it most days.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fraction:&lt;/strong&gt; reading is a rare kind of activity where you can make progress
in any arbitrarily-small chunk of time. A few minutes on the train are enough
to turn a few pages. This isn’t the case for e.g. coding. You can take
advantage of that: find interstitial dead zones in your calendar to stuff with
reading.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Eat your vegetables:&lt;/strong&gt; I read a lot of books I don’t particularly enjoy,
because I think they’re important, culturally or historically or in some
sense. I think this is good. If you only read books that hook you, you’re
going to read very little, and much of that will be unremarkable page-turner
slop. Internalizing that you can read a book even if you don’t love it
immunizes you against the lack of motivation. It’s like going to the gym even
if you don’t feel like it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you don’t know what to read, have some of my favourites: &lt;a href=&quot;https://www.goodreads.com/book/show/827.The_Diamond_Age&quot;&gt;&lt;em&gt;The Diamond Age&lt;/em&gt;&lt;/a&gt;—&lt;a href=&quot;https://www.goodreads.com/book/show/144409.From_Third_World_to_First&quot;&gt;&lt;em&gt;From Third World to First&lt;/em&gt;&lt;/a&gt;—&lt;a href=&quot;https://www.goodreads.com/book/show/1126719.House_of_Suns&quot;&gt;&lt;em&gt;House of Suns&lt;/em&gt;&lt;/a&gt;—&lt;a href=&quot;https://www.goodreads.com/book/show/94486.The_Invention_of_Morel&quot;&gt;&lt;em&gt;The Invention of Morel&lt;/em&gt;&lt;/a&gt;—&lt;a href=&quot;https://www.goodreads.com/book/show/501415.On_the_Marble_Cliffs&quot;&gt;&lt;em&gt;On the Marble Cliffs&lt;/em&gt;&lt;/a&gt;—&lt;a href=&quot;https://www.goodreads.com/book/show/18948.The_Rediscovery_of_Man&quot;&gt;&lt;em&gt;The Rediscovery of Man&lt;/em&gt;&lt;/a&gt;—&lt;a href=&quot;https://www.goodreads.com/book/show/27049.Satan_in_Goray&quot;&gt;&lt;em&gt;Satan in Goray&lt;/em&gt;&lt;/a&gt;—&lt;a href=&quot;https://www.goodreads.com/book/show/629429.The_World_of_Yesterday&quot;&gt;&lt;em&gt;The World of Yesterday&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Human Bottlenecks</title>
        <description>Why AI won&apos;t augment human capabilities much.</description>
        <pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/human-bottlenecks</link>
        <guid isPermaLink="true">
          https://borretti.me/article/human-bottlenecks
        </guid>
        
        <content:encoded>&lt;p&gt;AI models are very capable and get more capable each year. So naturally people
feel they’re underusing them. There’s a tweet that goes like: your laptop has a
100M USD startup in it, you just have to figure the right sequence of words to
get it out. And beyond money, people imagine AI could boost them in every area
of life. Thus all these perennial ideas: of an AI executive assistant, an AI
tutor, an AI that curates your “digital garden”, an AI that (sigh) writes
flashcards for you.&lt;/p&gt;

&lt;p&gt;The general template is: if only I could wire up the right prompts and the right
tools in the right harness, I could have an agent that would boost my
productivity 10x, or fix my problems with therapy, or make me more social, or
more knowledgeable. This was, curiously, the ambition of a lot of early
computing pioneers: &lt;a href=&quot;https://www.dougengelbart.org/pubs/augment-3906.html&quot;&gt;&lt;em&gt;Augmenting Human Intellect&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Man%E2%80%93Computer_Symbiosis&quot;&gt;&lt;em&gt;Man-Computer
Symbiosis&lt;/em&gt;&lt;/a&gt;. &lt;a href=&quot;https://en.wikipedia.org/wiki/Douglas_Engelbart&quot;&gt;Engelbart&lt;/a&gt;’s lab was called the &lt;a href=&quot;https://en.wikipedia.org/wiki/Augmentation_Research_Center&quot;&gt;Augmentation Research
Center&lt;/a&gt;! And more recently, people used to complain about how everyone has
the Library of Alexandria in their pocket, and yet, we are not all genius
polymaths.&lt;/p&gt;

&lt;p&gt;And these ideas are perennial because they never seem to happen. It’s like the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Productivity_paradox&quot;&gt;Solow paradox&lt;/a&gt; on an individual level. Why? I think there are two
reasons: first, most people lack what &lt;a href=&quot;https://andymatuschak.org/&quot;&gt;Andy Matuschak&lt;/a&gt; calls a “&lt;a href=&quot;https://notes.andymatuschak.org/People_who_write_extensively_about_note-writing_rarely_have_a_serious_context_of_use&quot;&gt;serious
context of use&lt;/a&gt;” (AI doesn’t move the needle because there’s no needle to
move); second, most people are bottlenecked by internal factors where AI (or
anything external, for that matter) can’t move the needle.&lt;/p&gt;

&lt;h1 id=&quot;the-serious-context-of-use&quot;&gt;The Serious Context of Use&lt;/h1&gt;

&lt;p&gt;I have heard so many people, online and in real life, tell me some variation of:
“I want to [use|build] an app that uses AI to write flashcards”. How many of
those people do you think have ever written a flashcard? How many of them use
&lt;a href=&quot;https://apps.ankiweb.net/&quot;&gt;Anki&lt;/a&gt; every day? Have ever used Anki? The people who want an AI that
writes flashcards for them &lt;em&gt;don’t use flashcards&lt;/em&gt;. They have no reason
to. Dually, the people who use flashcards &lt;a href=&quot;/article/the-applicability-of-spaced-repetition&quot;&gt;would benefit little from AI&lt;/a&gt;
writing their flashcards.&lt;/p&gt;

&lt;p&gt;Analogously with “AI tutors”. If you had the ghost of John von Neumann in your
laptop, what would you have him teach you? Let’s be honest. You’d go through
chapter one of some math topic you’re vaguely curious about and then forget
about it. And that would probably be the rational move! Most people are not
autodidacts because most people have no material reason to learn a specific
topic (i.e. their job does not require it) and the problem with learning for the
sake of learning is opportunity cost: there is no &lt;em&gt;a priori&lt;/em&gt; reason to learn one
thing over another, so better to do nothing and wait for something to appear
which actually grabs your interest. Again, this is likely rational! Could you
imagine if you found everything interesting? You’d spend years living in a
basement curating a wiki of late Soviet military hardware or something. So, even
if you had John von Neumann in your pocket, it probably wouldn’t move the
needle.&lt;/p&gt;

&lt;p&gt;Would an “AI executive assistant” &lt;em&gt;actually&lt;/em&gt; boost your productivity? What would
it do, other than tell you to do the things you already know you have to do?
With these ideas that are so attractive in the abstract, the way you deflate
them is you interrogate the concrete, fine-grained details. Take a day at work,
and ask: what &lt;em&gt;exact&lt;/em&gt; actions could an AI looking over my shoulder have taken,
that would have made a difference?&lt;/p&gt;

&lt;p&gt;Finally there’s the tools-for-thought/notetaking people. God save us. It’s
always the same thing. Your folder with notes—pardon me, your “&lt;a href=&quot;https://fortelabs.com/blog/basboverview/&quot;&gt;second
brain&lt;/a&gt;”—plus an AI agent that writes, edits, synthesizes information,
answers queries. You could build this in an afternoon, and it won’t move the
needle in your life, for the same reason that building the second brain in the
first place didn’t make a difference.&lt;/p&gt;

&lt;p&gt;See, most of us, unless we are students, we really don’t have cause to take
notes on anything. If you’re a student, you take notes from the textbook. I keep
a journal, which is occasionally useful. At some jobs I’ve kept a work journal,
this has also been useful. If I stopped, probably, not much would change.&lt;/p&gt;

&lt;p&gt;The notetaking people—and I say this with all the love in the world—are
never, like, a researcher at the cutting edge of their field, building this vast
cathedral of knowledge, note-by-note, so they can derive new insights. Never a
historian who has to read tens of millions of words across thousands of sources
to synthesize the life of some historical person. It’s never someone doing
something hard. It’s always some blogger. Their “digital garden” is about how to
keep a digital garden. It’s very solipsistic: there’s no output, no
deliverables. The deliverable is you take a screenshot of your &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt;
graph and tweet about it to show off how much it looks like an incomprehensible
ball of twine.&lt;/p&gt;

&lt;p&gt;So, what difference is the AI going to make? “It’s going to write my
notes”. About what? “It’s going to read articles for me and summarize them and
add them to the digital garden”. For what purpose? “It’s going to find
connections between my ideas!” What ideas? It’s going to pull an unfinished list
of bulletpoints for an eventual draft of an essay on some inane thing, plus a
bunch of PDFs you haven’t read, and combine them together and make, what?
Another project you’re not going to do? The AI is going to do that?&lt;/p&gt;

&lt;p&gt;Again, I say this with all the love in the world. I used to be a
tools-for-thought guy. I hoard PDFs. But we have to be honest with
ourselves. Sometimes, tools don’t move the needle because there’s no needle to
move. Because the “needle” is not a concrete, realizable material need but a
vague, aspirational idea about who we are as people.&lt;/p&gt;

&lt;h1 id=&quot;internal-limiting-factors&quot;&gt;Internal Limiting Factors&lt;/h1&gt;

&lt;p&gt;So, the idea of using computers to augment human capabilities is basically: you
take the human, and you build a scaffold around them, but the human stays the
same. The scaffold can be classical software, or AI, but the human remains a
black box. And the hope is: I just prompt my swarm of AI agents and become 100x
more effective, like Manfred in &lt;a href=&quot;https://www.antipope.org/charlie/blog-static/fiction/accelerando/accelerando.html&quot;&gt;&lt;em&gt;Accelerando&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Why wouldn’t this work? I think most people are bottlenecked by internal factors
that are difficult to change. Mental energy, motivation, executive function, not
to mention more fundamental traits like intelligence and conscientiousness. So,
external scaffolding, either with classical software or AI, might help somewhat,
but it won’t be transformative.&lt;/p&gt;

&lt;p&gt;Consider executive function. &lt;a href=&quot;/article/notes-on-managing-adhd&quot;&gt;My own experience of managing ADHD&lt;/a&gt; is the
external scaffolding helps (todo lists, calendars, timers, a million little ways
to trick myself into working) gets me from zero to “kind of functional”. But it
saturates there. Stimulants fix the original, internal bottleneck, which is my
neurochemistry. And then I can accomplish my goals (c.f. &lt;a href=&quot;https://en.wikipedia.org/wiki/Liebig%27s_law_of_the_minimum&quot;&gt;Liebig’s law of the
minimum&lt;/a&gt;). All the pomodoros in the world are as nothing to a little
molecule diffusing through my brain tissue, binding to &lt;a href=&quot;https://en.wikipedia.org/wiki/Norepinephrine_transporter&quot;&gt;NET&lt;/a&gt; and
&lt;a href=&quot;https://en.wikipedia.org/wiki/Dopamine_transporter&quot;&gt;DAT&lt;/a&gt;. And what scaffolding is useful is just classical software:
&lt;a href=&quot;https://www.todoist.com/&quot;&gt;Todoist&lt;/a&gt; and calendars. Is an agent going to match the effectiveness
of methylphenidate in ADHD? I doubt it.&lt;/p&gt;

&lt;p&gt;Consider intelligence. Can AI augment human intelligence? What does that look
like? Consider how AI agents only became useful when the models crossed a
particular capability threshold, i.e., you can’t put GPT-2 into a harness and
get GPT-5 outcomes out of it. Can you put a human in an AI scaffold and give
them +30 effective IQ? I doubt it, unless the AI is doing all the thinking, at
which point, what use is the human? The limiting factor in the human-AI
&lt;a href=&quot;https://en.wikipedia.org/wiki/Advanced_chess&quot;&gt;centaur&lt;/a&gt; is the human! So intelligence is fixed until we get very
advanced biotechnology.&lt;/p&gt;

&lt;p&gt;Knowledge is another limiting factor. I find that even very educated people tend
to underrate the importance of knowledge. A lot of people have this attitude
that you can just Google everything just-in-time as it comes up. Like Babbage, I
can’t &lt;a href=&quot;https://en.wikiquote.org/wiki/Charles_Babbage#Passages_from_the_Life_of_a_Philosopher_(1864)&quot;&gt;rightly apprehend the confusion of ideas&lt;/a&gt; that would lead someone
to think this. Maybe it’s downstream of the lack of a serious context of
use. Everything you do, every action and idle thought, draws on this vast
(implicit, unseen) trove of knowledge. Claude Shannon invented digital computing
because he remembered this then-obscure branch of mathematical logic called
Boolean algebra and saw that it could be realized in hardware. A trillion-dollar
industry, conjured out of some old tomes.&lt;/p&gt;

&lt;p&gt;The reason knowledge is still a bottleneck, in the AI era, is not: “if you don’t
have the knowledge, you can’t write the prompt”. Rather: if you don’t have the
knowledge, you don’t understand the question, or why it matters, or how to judge
the answers, and you won’t ever think to ask. You’re in a &lt;em&gt;completely&lt;/em&gt; different
continent from “writing the prompt”. And because long-term memory is private and
internal, AI can’t boost it. It can, maybe, with judicious use, help in the
acquisition of new knowledge.&lt;/p&gt;

&lt;p&gt;So: executive function, intelligence, and knowledge are huge bottlenecks to what
you can do, and because they are internal to the brain, AI can’t touch them
until we have far more advanced biotechnology. Corollary: contrary to the
popular view of &lt;a href=&quot;https://www.youtube.com/watch?v=2HMPRXstSvQ&quot;&gt;human capital is becoming worthless&lt;/a&gt;, the returns to
education are now &lt;em&gt;higher&lt;/em&gt;, because intelligent, educated people with working
reward circuitry stand to gain more from AI.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>The Applicability of Spaced Repetition</title>
        <description>On factual vs. conceptual knowledge.</description>
        <pubDate>Sun, 17 May 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/the-applicability-of-spaced-repetition</link>
        <guid isPermaLink="true">
          https://borretti.me/article/the-applicability-of-spaced-repetition
        </guid>
        
        <content:encoded>&lt;p&gt;Spaced repetition has a natural domain of applicability: information that is
systematically organized as an unambiguous key-value mapping with short keys and
values. The “Hello, world!” of flashcards is the &lt;a href=&quot;https://en.wikipedia.org/wiki/NATO_phonetic_alphabet&quot;&gt;NATO phonetic alphabet&lt;/a&gt;:
A → alpha, B → bravo, etc.  Similarly, the periodic table can be thought of as
defining a collection of mappings: element name ↔ symbol, element name ↔ atomic
number, etc. You can just drill these cards and memorize the facts without a
prior step of understanding, or building a conceptual model.&lt;/p&gt;

&lt;p&gt;Applying spaced repetition is trivial for this kind of information. That’s why
most people who use spaced repetition are either language learners or medical
students. In biology the main intuition you need is for “3D shapes bumping
around in Brownian motion”, which comes free with your human brain, and
afterwards it’s mostly just a lot of facts you have to memorize. Analogously
with language: you already have a &lt;a href=&quot;https://en.wikipedia.org/wiki/Language_center&quot;&gt;language center&lt;/a&gt;, you just need to drill
vocabulary and grammar.&lt;/p&gt;

&lt;p&gt;And the further you go from this domain, the harder it is to apply spaced
repetition.&lt;/p&gt;

&lt;p&gt;Highly conceptual knowledge, like math, is hard to encode. You have to spend a
lot of time just understanding the information, and building a conceptual model
in your head, and then you start writing flashcards to solidify that model, like
taking tomographic cuts of some complex object. And coming up with questions
that make good flashcards (short, unambiguous, etc.) out of this highly abstract
knowledge is very hard. Often you have some deceptively simple fact, a simple
assertion, but there’s no good way to encode it as a flashcard, so you have to
encode “around it” by asking questions that assume or require that knowledge
(e.g. asking why X is true), and hoping that in drilling those, your brain will
remember the actual target.&lt;/p&gt;

&lt;p&gt;In general, &lt;em&gt;relational&lt;/em&gt; facts are easier to encode, since a binary predicate
like $\text{Property}(\text{Object}, \text{Value})$ readily becomes a
question. “Caffeine is metabolized by &lt;a href=&quot;https://en.wikipedia.org/wiki/CYP1A2&quot;&gt;cytochrome 1A2&lt;/a&gt;”, in
&lt;a href=&quot;https://en.wikipedia.org/wiki/Prolog&quot;&gt;Prolog&lt;/a&gt;, is $\text{Metabolism}(\text{Caffeine}, \text{CYP1A2})$, and
becomes “Q: What is the cytochrome that metabolizes caffeine? A: 1A2”. But how
do you encode stand-alone assertions like “all &lt;a href=&quot;https://en.wikipedia.org/wiki/Unitary_matrix&quot;&gt;unitary matrices&lt;/a&gt; are
&lt;a href=&quot;https://en.wikipedia.org/wiki/Invertible_matrix&quot;&gt;invertible&lt;/a&gt;”? You could encode that as a yes-or-no question, but that’s
useless, because rationally you can expect such questions to be biased towards
yes. Both “what is a property of unitary matrices?” and “what kinds of matrices
are invertible?” are useless because they have hundreds of possible
equally-valid answers, so they’re ambiguous. You have to be creative and find
all kinds of tricks and stratagems to encode around the knowledge.&lt;/p&gt;

&lt;p&gt;Tangentially: this, I think, is why using AI to write flashcards is often
misguided. In highly systematized domains, you don’t need AI in the first place,
because there’s nothing for the AI to do except import a CSV into Anki. In
domains that are highly conceptual and abstract, you’re not memorizing a set of
objectively-knowable facts, you’re trying to solidify a private, internal mental
model that you build by reading and thinking and solving problems. You can give
the AI all kinds of &lt;em&gt;general&lt;/em&gt; rules on how to write good flashcards, but the AI
can’t look into your mind and know which facts are salient &lt;em&gt;for you&lt;/em&gt;, what you
already know, which micro-volumes of knowledge can be encoded lightly with just
a few flashcards, and which things need more shoring up and consequently more
coverage.&lt;/p&gt;

&lt;p&gt;Can this situation be improved, or is this just an intrinsic limitation of
spaced repetition? I don’t know. But it seems reasonable to think some limited
gains are possible. I think not a lot of people are using spaced repetition on
these more “conceptual” domains, and (by the rule that &lt;a href=&quot;https://en.wikipedia.org/wiki/1%25_rule&quot;&gt;most people in a
community are lurkers&lt;/a&gt;) even fewer of those people are writing, in detail,
to share their knowledge. &lt;a href=&quot;https://www.supermemo.com/en/blog/twenty-rules-of-formulating-knowledge&quot;&gt;Plenty&lt;/a&gt; &lt;a href=&quot;https://andymatuschak.org/prompts/&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;/article/effective-spaced-repetition&quot;&gt;people&lt;/a&gt; have written about how
to write good flashcards in general, what I want to read is closer to case
studies where someone sits down with a text (or, even better, a textbook) and
describes the process by which they turned that text into flashcards, like
&lt;a href=&quot;https://cognitivemedium.com/srs-mathematics&quot;&gt;this&lt;/a&gt; from Michael Nielsen. From a corpus of similar case studies we
might derive general rules for, not how to write effective flashcards, but how
to encode complex, conceptual knowledge into question-answer form.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Notes on the Hantavirus Outbreak</title>
        <description>Cruise ships are bad for the world.</description>
        <pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/notes-on-the-hantavirus-outbreak</link>
        <guid isPermaLink="true">
          https://borretti.me/article/notes-on-the-hantavirus-outbreak
        </guid>
        
        <content:encoded>&lt;p&gt;Right now there’s a &lt;a href=&quot;https://en.wikipedia.org/wiki/MV_Hondius_hantavirus_outbreak&quot;&gt;cruise ship&lt;/a&gt; parked outside &lt;a href=&quot;https://en.wikipedia.org/wiki/Cape_Verde&quot;&gt;Cabo Verde&lt;/a&gt; because
of an outbreak of &lt;a href=&quot;https://en.wikipedia.org/wiki/Andes_virus&quot;&gt;Andes virus&lt;/a&gt;. Yep, &lt;a href=&quot;https://en.wikipedia.org/wiki/COVID-19_pandemic_on_Diamond_Princess&quot;&gt;another&lt;/a&gt; cruise ship. I don’t
get the appeal. It’s like a big open-air &lt;a href=&quot;https://en.wikipedia.org/wiki/Serial_passage&quot;&gt;serial passage&lt;/a&gt; experiment: you
get a bunch of old people with failing immune systems in close contact and race
a pathogen through them.&lt;/p&gt;

&lt;p&gt;How much should I worry about this? Is this early January of 2020? I tried
asking &lt;a href=&quot;https://claude.com/product/overview&quot;&gt;Claude&lt;/a&gt; but the &lt;a href=&quot;https://support.claude.com/en/articles/12436559-understanding-sonnet-4-5-s-safety-filters&quot;&gt;biosecurity filter&lt;/a&gt; kept blocking my
queries. The WHO &lt;a href=&quot;https://www.who.int/emergencies/disease-outbreak-news/item/2026-DON599&quot;&gt;says&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Although uncommon, limited human‑to‑human transmission of HPS due to &lt;em&gt;Andes&lt;/em&gt;
virus has been reported in community settings involving close and prolonged
contact. Secondary infections among healthcare workers have been previously
documented in healthcare facilities, though remain rare.&lt;/p&gt;

  &lt;p&gt;WHO currently assesses the risk to the global population from this event as
low […] WHO advises against the application of any travel or trade
restrictions based on the current information available on this event.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, &lt;a href=&quot;https://en.wikipedia.org/wiki/Hantaviridae&quot;&gt;hantavirus&lt;/a&gt; is the family. They are carried by rodents and spread by
aerosols. In humans they can cause &lt;a href=&quot;https://en.wikipedia.org/wiki/Hantavirus_pulmonary_syndrome&quot;&gt;hantavirus pulmonary syndrome&lt;/a&gt; (HPS),
which has a case fatality rate (CFR) of between 30 and 60%. Not great! Used to
be these infections were mouse-to-human dead-ends. But &lt;a href=&quot;https://en.wikipedia.org/wiki/Andes_virus&quot;&gt;Andes virus&lt;/a&gt; (ANDV),
first identified in 1995, is known to spread from human to human.&lt;/p&gt;

&lt;p&gt;The last time there was an outbreak was 2018–2019 in &lt;a href=&quot;https://en.wikipedia.org/wiki/Epuy%C3%A9n&quot;&gt;Epuyén&lt;/a&gt;,
&lt;a href=&quot;https://en.wikipedia.org/wiki/Chubut_Province&quot;&gt;Chubut&lt;/a&gt;, a town of 1,500 on the lee side of the Andes (&lt;a href=&quot;https://maps.app.goo.gl/pFcZf1F14gqQNwpt8&quot;&gt;quite
beautiful&lt;/a&gt;). Described in &lt;a href=&quot;https://www.nejm.org/doi/full/10.1056/NEJMoa2009040&quot;&gt;this paper&lt;/a&gt;. 34 known
infections and 11 deaths for a CFR of 32%. The $R_0$ was 2.12, reduced to 0.96
after control measures were implemented. Given the small number of cases, there
should be some uncertainty about the $R_0$. But $R_0 &amp;gt; 1$ is the threshold for
sustainable transmission. In this outbreak, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Index_case&quot;&gt;index case&lt;/a&gt;, while
symptomatic, attended a birthday party with 100 other people, and infected five
guests in 90 minutes, who went on to infect more people. The authors write:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The super-spreading capability of the ANDV Epuyén/18−19 strain shows a
facility ($R&amp;gt;2$) for sustaining continuous chains of transmission if no control
measures are enforced.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.nejm.org/doi/suppl/10.1056/NEJMoa2009040/suppl_file/nejmoa2009040_appendix_1.pdf&quot;&gt;appendix&lt;/a&gt; has some interesting stuff on how patients were infected at
the birthday party. A further concern here is the incubation period: Wikipedia
says the incubation period is between one and eight &lt;em&gt;weeks&lt;/em&gt;. In the Chubut
outbreak, the distribution was:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Onset&lt;/th&gt;
      &lt;th&gt;Number of Cases&lt;/th&gt;
      &lt;th&gt;% of total&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;≤15 days&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;15%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;16-21 days&lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;29%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;22-28 days&lt;/td&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt;38%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;≥29 days&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;18%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Which is not good. I don’t have more data to draw a nice-looking &lt;a href=&quot;https://en.wikipedia.org/wiki/Cumulative_distribution_function&quot;&gt;CDF&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now this all sounds quite bad. Are there reasons to be optimistic?&lt;/p&gt;

&lt;p&gt;First, Argentina has had 710 cases of HPS in the period 1995–2008 (&lt;a href=&quot;https://pmc.ncbi.nlm.nih.gov/articles/PMC3294556/&quot;&gt;Martínez
2010&lt;/a&gt;) and a further 533 cases in the period 2009–2017 (&lt;a href=&quot;https://onlinelibrary.wiley.com/doi/10.1002/jmv.25446&quot;&gt;Alonso
2019&lt;/a&gt;), and we are all still alive. In the latter period, most of
these cases are from occupational/recreational exposure to rodent feces and only
1.8% of cases are from suspected human-to-human transmission. So, over 1,200
cases and every one of them fizzled out, but for one outbreak which was limited
after successful contact tracing and quarantine.&lt;/p&gt;

&lt;p&gt;Second, the virus has left Argentina before: once to &lt;a href=&quot;https://pmc.ncbi.nlm.nih.gov/articles/PMC6233683/&quot;&gt;Switzerland&lt;/a&gt; in 2016,
and once to the &lt;a href=&quot;https://pmc.ncbi.nlm.nih.gov/articles/PMC6193684/&quot;&gt;United States&lt;/a&gt; in 2018. In the second case the patient
“while ill, [traveled] on two commercial domestic flights”. And neither export
led to a general outbreak.&lt;/p&gt;

&lt;p&gt;Thirdly, in a small outbreak like the Chubut one, the $R_0$ can vary wildly from
social factors unconnected to the virus, e.g. if the birthday party had not
happened. You need a large $n$ to get the $R_0$ as a property of the virus
itself. It’s possible the Chubut outbreak just had anomalously high
transmission.&lt;/p&gt;

&lt;p&gt;What does this add up to? I don’t know. On the balance of evidence, I think this
outbreak is more likely than not to fizzle out. In the interest of
accountability, and putting my beliefs on record (which is the only objective
way to judge the accuracy of your mental model) I’m gonna say:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;70% probability the outbreak ends with fewer than 300 deaths.&lt;/li&gt;
  &lt;li&gt;90% probability the outbreak ends with fewer than 1,000 deaths.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yet. And yet it feels so much like early COVID, particularly with public
health authorities making very complacent remarks that “it’s not that
transmissible, contact tracing will work, quarantine will work”. Complacency at
the start, and severity at the end, is exactly why COVID was such a fuckup.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Some Data Should Be Code</title>
        <description>On Make, CloudFormation, and GitHub Actions.</description>
        <pubDate>Sat, 31 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/some-data-should-be-code</link>
        <guid isPermaLink="true">
          https://borretti.me/article/some-data-should-be-code
        </guid>
        
        <content:encoded>&lt;p&gt;I write a lot of &lt;a href=&quot;https://en.wikipedia.org/wiki/Make_(software)&quot;&gt;Makefiles&lt;/a&gt;. I use it not as a command runner but as an ad-hoc build system for small projects, typically for compiling Markdown documents and their dependencies. Like so:&lt;/p&gt;

&lt;p&gt;&lt;img style=&quot;margin-left: auto; margin-right: auto; width: 300px;&quot; src=&quot;/assets/content/some-data-should-be-code/graph.png&quot; alt=&quot;A build graph for a document. A central node `doc.md` represents the Markdown source. Two outgoing arrows point to `doc.html` and `doc.pdf`, representing the output format. A chain through `graph.dot`, `graph.png`, and `doc.md` represets how a Graphviz .dot file can be rendered to PNG. A chain through `data.csv`, `plot.py`, `plot.png`, and `doc.md` represents using a Python script to make a plot from a CSV file.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And the above graph was generated by this very simple Makefile:&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;graph.png&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;graph.dot&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;dot&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;-Tpng&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&amp;lt;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$@&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;clean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;graph.png&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;(I could never remember the &lt;a href=&quot;https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html&quot;&gt;automatic variable&lt;/a&gt; syntax until I made &lt;a href=&quot;https://github.com/eudoxia0/flashcards/blob/aefae3ed874627201dbcedec045095779691d323/Cards/make.md&quot;&gt;flashcards&lt;/a&gt; for them.)&lt;/p&gt;

&lt;p&gt;It works for simple projects, when you can mostly hand-write the rules. But the abstraction ceiling is very low. If you have a bunch of almost identical rules, e.g.:&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;a.png&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;a.csv plot.py&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;python&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;plot.py&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&amp;lt;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$@&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;b.png&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;b.csv plot.py&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;python&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;plot.py&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&amp;lt;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$@&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;c.png&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;c.csv plot.py&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;python&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;plot.py&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&amp;lt;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$@&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You can use pattern-matching to them into a “rule schema”, by analogy to axiom schemata:&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;%.png&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;%.csv plot.py&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;python&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;plot.py&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&amp;lt;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$@&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Which works backwards: when something in the build graph depends on a target matching &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%.png&lt;/code&gt;, Make synthesizes a rule instance with a dependency on the corresponding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csv&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;But pattern matching is still very limited. Lately I’ve been building my own &lt;a href=&quot;https://plaintextaccounting.org/&quot;&gt;plain-text accounting&lt;/a&gt; solution using some Python scripts. One of the tasks is to read a CSV of bank transactions from 2019–2024 and split it into TOML files for each year-month, to make subsequent processing parallelizable. So the rules might be something like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-makefile:&quot;&gt;ledger/2019-08.toml: inputs/checkbook_pro_export.csv
    uv run import_from_checkbook.py --year=2019 --month=8

ledger/2019-09.toml: inputs/checkbook_pro_export.csv
    uv run import_from_checkbook.py --year=2019 --month=9

# ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I had to write a Python script to generate the complete Makefile. Makefiles look like code, but are data: they are a container format for tiny fragments of shell that are run on-demand by the Make engine. And because Make doesn’t scale, for complex tasks you have to bring out a real programming language to generate the Makefile.&lt;/p&gt;

&lt;p&gt;I wish I could, instead, write a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make.py&lt;/code&gt; file with something like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;whatever&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BuildGraph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;EXPORT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;inputs/checkbook_pro_export.csv&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# The (year, month) pairs I have bank transaction CSVs for.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year_months&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2019&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2026&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Import transactions for each year-month into a separate ledger.
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;year_months&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ledger_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ledger/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;02&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.toml&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ledger_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;deps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EXPORT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_from_checkbook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ledger_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Fortunately this exists: it’s called &lt;a href=&quot;https://pydoit.org/&quot;&gt;doit&lt;/a&gt;, but it’s not widely known.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;A lot of things are like Makefiles: data that should be lifted one level up to become code.&lt;/p&gt;

&lt;p&gt;Consider &lt;a href=&quot;https://en.wikipedia.org/wiki/AWS_CloudFormation&quot;&gt;CloudFormation&lt;/a&gt;. Nobody likes writing those massive YAML files by hand, so AWS introduced &lt;a href=&quot;https://en.wikipedia.org/wiki/AWS_Cloud_Development_Kit&quot;&gt;CDK&lt;/a&gt;, which is literally just a library&lt;sup id=&quot;fnref:fn1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:fn1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; of classes that represent AWS resources. Running a CDK program emits CloudFormation YAML as though it were an assembly language for infrastructure. And so you get type safety, modularity, abstraction, conditionals and loops, all for free.&lt;/p&gt;

&lt;p&gt;Consider &lt;a href=&quot;https://docs.github.com/en/actions&quot;&gt;GitHub Actions&lt;/a&gt;. How much better off would we be if, instead of writing the workflow-job-step tree by hand, we could just have a single Python script, executed on push, whose output is the GitHub Actions YAML-as-assembly? So you might write:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ga&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;checkout_action&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CheckoutAction&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;rust_action&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RustSetupAction&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Define the workflow that runs on each commit.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit_workflow&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Workflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;commit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommitEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# The lint job.
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;lint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;check out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CheckoutAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;set up Rust and Cargo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RustSetupAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;run cargo fmt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Shell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cargo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;--check&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Actions here would simply be ordinary Python libraries the CI script depends on. Again: conditions, loops, abstraction, type safety, we get all of those for free by virtue of using a language that was designed to be a language, rather than a data exchange language that slowly grows into a poorly-designed DSL.&lt;/p&gt;

&lt;p&gt;Why do we repeatedly end up here? Static data has better safety/static analysis properties than code, but I don’t think that’s foremost in mind when people design these systems. Besides, using code to emit data (as CDK does) gives you those exact same properties. Rather, I think some people think it’s cute and clever to build tiny DSLs in a data format. They’re proud that they can get away with a “simple”, static solution rather than a dynamic one.&lt;/p&gt;

&lt;p&gt;If you’re building a new CI system/IaC platform/Make replacement: please just let me write code to dynamically create the workflow/infrastructure/build graph.&lt;/p&gt;

&lt;h1 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h1&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:fn1&quot; role=&quot;doc-endnote&quot;&gt;

      &lt;p&gt;Or rather, a polyglot collection of libraries, one per language, like &lt;a href=&quot;https://en.wikipedia.org/wiki/Pulumi&quot;&gt;Pulumi&lt;/a&gt;. &lt;a href=&quot;#fnref:fn1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content:encoded>
      </item>
    
      <item>
        <title>Letting Claude Play Text Adventures</title>
        <description>Experiments in cognitive architecture.</description>
        <pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/letting-claude-play-text-adventures</link>
        <guid isPermaLink="true">
          https://borretti.me/article/letting-claude-play-text-adventures
        </guid>
        
        <content:encoded>&lt;p&gt;The other day I went to an &lt;a href=&quot;https://luma.com/ycc02hpc&quot;&gt;AI hackathon&lt;/a&gt; organized by my friends
&lt;a href=&quot;https://x.com/lucia_quirke&quot;&gt;Lucia&lt;/a&gt; and &lt;a href=&quot;https://x.com/mahlenr&quot;&gt;Malin&lt;/a&gt;. The theme was &lt;a href=&quot;https://en.wikipedia.org/wiki/Mechanistic_interpretability&quot;&gt;mech interp&lt;/a&gt;, but I hardly
know PyTorch so I planned to do something at the API layer rather than the model
layer.&lt;/p&gt;

&lt;p&gt;Something I think about a lot is &lt;a href=&quot;https://en.wikipedia.org/wiki/Cognitive_architecture&quot;&gt;cognitive architectures&lt;/a&gt; (like
&lt;a href=&quot;https://en.wikipedia.org/wiki/Soar_(cognitive_architecture)&quot;&gt;Soar&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/ACT-R&quot;&gt;ACT-R&lt;/a&gt;). This is like a continuation of &lt;a href=&quot;https://en.wikipedia.org/wiki/GOFAI&quot;&gt;GOFAI&lt;/a&gt;
research, inspired by cognitive science. And like GOFAI it’s never yielded
anything useful. But I often think: can we scaffold LLMs with cog arch-inspired
harnesses to overcome their limitations?&lt;/p&gt;

&lt;p&gt;LLM agents like &lt;a href=&quot;https://github.com/anthropics/claude-code&quot;&gt;Claude Code&lt;/a&gt; are basically “accidental” cognitive
architectures: they are designed and built my practitioners rather than
theorists, but they have commonalities, they all need a way to manage memory,
tool use, a task agenda etc. Maybe building an agent on a more “principled”
foundation, one informed by cognitive science, yields a higher-performing
architecture.&lt;/p&gt;

&lt;p&gt;So I sat around a while thinking how to adapt Soar’s architecture to an LLM
agent. And I sketched something out, but then I thought: how can I prove this
performs better than baseline? I need an eval, a task.&lt;/p&gt;

&lt;p&gt;Math problems? Too one-shottable. A chatbot? Too interactive, I want something
hands-off and long-horizon. A coding agent? That’s too freeform and requires too
much tool use. And then I thought: &lt;a href=&quot;https://en.wikipedia.org/wiki/Interactive_fiction&quot;&gt;text adventures&lt;/a&gt;! You have a stylized,
hierarchically-structured world accessible entirely through text, long-term
goals, puzzles, physical exploration and discovery of the environment. Even the
data model of text adventures resembles &lt;a href=&quot;https://en.wikipedia.org/wiki/Frame_(artificial_intelligence)&quot;&gt;frame-based&lt;/a&gt; knowledge
representation systems. And there’s a &lt;a href=&quot;https://ifdb.org/&quot;&gt;vast collection&lt;/a&gt; of games available
online.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Anchorhead&quot;&gt;&lt;em&gt;Anchorhead&lt;/em&gt;&lt;/a&gt;, which I played years ago, is a Lovecraft-inspired text
adventure by Michael S. Gentry. It takes on the order of hundreds of turns to win
across multiple in-game days. And the game world is huge and very open. In other
words: a perfect long-horizon task.&lt;/p&gt;

&lt;p&gt;So I started hacking. The &lt;a href=&quot;https://davidgriffith.gitlab.io/frotz/&quot;&gt;frotz&lt;/a&gt; interpreter runs on the command line and has a
“dumb” interface called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dfrotz&lt;/code&gt;, which takes the ncurses fluff out, and gives
you a very stripped command-line experience. It looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ dfrotz games/anchor.z8
...
 Outside the Real Estate Office                      day one

ANCHORHEAD
An interactive gothic by Michael S. Gentry

(Type HELP or ABOUT for some useful information.)

Release 5 / Serial number 990206 / Inform v6.15 Library 6/7

Outside the Real Estate Office
A grim little cul-de-sac, tucked away in a corner of the claustrophobic tangle
of narrow, twisting avenues that largely constitute the older portion of
Anchorhead. Like most of the streets in this city, it is ancient, shadowy, and
leads essentially nowhere. The lane ends here at the real estate agent&apos;s office,
which lies to the east, and winds its way back toward the center of town to the
west. A narrow, garbage-choked alley opens to the southeast.

&amp;gt;go southeast
 Alley                                               day one

Alley
This narrow aperture between two buildings is nearly blocked with piles of
rotting cardboard boxes and overstuffed garbage cans. Ugly, half-crumbling brick
walls to either side totter oppressively over you. The alley ends here at a
tall, wooden fence.

High up on the wall of the northern building there is a narrow, transom-style
window.
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;It is easy to write a little Python wrapper to drive the interpreter through
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdin&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdout&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Interpreter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Manages the dfrotz Z-machine interpreter process.&quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Popen&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Starting dfrotz.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Popen&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Popen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dfrotz&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;stdin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PIPE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;stdout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PIPE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PIPE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Started dfrotz with PID=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Set stdout/stderr to non-blocking mode.
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fileno&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fcntl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fcntl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fcntl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F_GETFL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;fcntl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fcntl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fcntl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F_SETFL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;O_NONBLOCK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdout&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdin&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stdin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Give the interpreter time to respond. Not ideal!
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now we can play the game from Python: send commands, get game output. Now we
need the dual of this: a player.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ABC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Interface for game-playing agents.
    &quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abstractmethod&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
        Send the game&apos;s output to the agent, and return the next command to execute.
        &quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h1 id=&quot;the-trivial-harness&quot;&gt;The Trivial Harness&lt;/h1&gt;

&lt;p&gt;The trivial harness is basically nothing at all: treat the LLM/game interaction
like a chat history. The LLM reads the game output from the interpreter, writes
some reasoning tokens, and writes a command that is sent via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdin&lt;/code&gt; to the
interpreter.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
Hello Claude. Your task is to play an adventure game. I&apos;ve hooked up
your output to the dfrotz (dumb frotz) interpreter.

The structure of your output is fairly freeform. The first line that
starts with `&amp;gt;` (and only the first line!) is interpreted as a game
command, everything else is uninterpreted commentary, e.g. you may
write:

    We should go north to explore the church.

    &amp;gt;go north

    Maybe we can use the silver key there.

If you write multiple `&amp;gt;` lines in one response, all but the first
will be ignored.

Have fun! 😊
&quot;&quot;&quot;&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimplePlayer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    The simplest game-playing agent: keep the entire game history in-context.
    &quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Anthropic&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EntryType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Anthropic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EntryType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MessageParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry_text&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EntryType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;role&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EntryType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COMMAND&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;role&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;assistant&quot;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;max_tokens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;512&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MODEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tokens: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_tokens&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log_claude&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;startswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:]&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EntryType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COMMAND&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And this works well enough. Haiku 4.5 would mostly wander around the game map,
but Sonnet 4.5 and Opus 4.5 manage to solve the game’s first puzzle—breaking
into the real estate office, and finding the keys to the mansion—readily
enough. It takes about ~200 turns for Claude to get to the second in-game day.&lt;/p&gt;

&lt;p&gt;The way I thought this would fail is: attention gets smeared across the long
context, the model gets confused about the geometry of the world, its goal and
task state, and starts confabulating, going in circles, etc.&lt;/p&gt;

&lt;p&gt;As usual, I was outsmarting myself. The reason this fails is you run out of
credits. By the time you get to day two, each turn costs tens of thousands of
input tokens. No good! We need a way to save money.&lt;/p&gt;

&lt;h1 id=&quot;memory&quot;&gt;Memory&lt;/h1&gt;

&lt;p&gt;Ok, let’s try something that’s easier on my Claude credits. We’ll show Claude
the most recent five turns (this is the perceptual working memory), and give it
a simple semantic memory: a list of strings that it can append entries to, and
remove entries from using tool use.&lt;/p&gt;

&lt;p&gt;This keeps the token usage down:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/letting-claude-play-text-adventures/plot.png&quot; alt=&quot;A line plot of tokens per request over time. A red line, representing the trivial harness, goes up linearly, reaching over 40,000 tokens per request at around turn 350. A green line, representing the memory-augmented harness, climbs more slowly, reaching only 10,000 tokens at turn 500.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The problem is the narrow time horizon. With the trivial harness, Claude can
break into the real estate office in ~10 turns, and does so right at the start
of the game. With this new harness, Claude wanders about the town, taking
copious notes, before returning to the real estate office, and it spends ~40
turns fumbling around with the garbage cans before managing to break into the
real estate office.&lt;/p&gt;

&lt;p&gt;The next step, after getting the keys to the house, is to meet your husband
Michael at the University and head home. Claude with the trivial harness takes
about ~100 turns to find the house, with some tangential wandering about the
town, and reaches day two around turn 150.&lt;/p&gt;

&lt;p&gt;Claude, with the memory harness, took ~250 turns just to get the keys to the
house. And then it spends hundreds of turns just wandering in circles around the
town, accumulating redundant memories, and hits the turn limit before even
finding the house.&lt;/p&gt;

&lt;h1 id=&quot;aside-small-worlds&quot;&gt;Aside: Small Worlds&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Anchorhead&lt;/em&gt; is a long, broad game, and from the very beginning you can forget
the plot and wander about most of the town. It takes a long time to see if a run
with an agent goes anywhere. So I thought: I need something smaller.&lt;/p&gt;

&lt;p&gt;Unsurprisingly, Claude can make its own games. The &lt;a href=&quot;https://ganelson.github.io/inform-website/&quot;&gt;Inform 7&lt;/a&gt; package for
NixOS was broken (though &lt;a href=&quot;https://github.com/mbrock&quot;&gt;Mikael&lt;/a&gt; has &lt;a href=&quot;https://github.com/mbrock/inform7-nix&quot;&gt;fixed this&lt;/a&gt; recently) so I had
to use &lt;a href=&quot;https://www.inform-fiction.org/&quot;&gt;Inform 6&lt;/a&gt;. I started with a trivial escape-the-room type game, which
was less than 100 lines of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.inf&lt;/code&gt; code and any Claude could beat it less than 10
turns. Then I asked for a larger, multi-room heist game.&lt;/p&gt;

&lt;p&gt;This one was more fun. It’s short enough that Claude can win with just the
trivial harness. I tried a different harness, where Claude has access to only
the last five turns of the game’s history, and a read-write memory
scratchpad. And this one was interesting.&lt;/p&gt;

&lt;p&gt;First, because Claude only ever adds to its own memory, it never deletes
memories. I thought it would do more to trim and edit its scratchpad.&lt;/p&gt;

&lt;p&gt;Second, because Claude become fixated on this red-herring room: a garden with a
well. It kept going in circles, trying to tie a rope to the well and climb
down. Because of the limited game history, it only realized it was stuck when it
saw that the most recent ~20 entries it wrote to its memories related to various
attempts to go down the well. Then I watched Claude walk away from the garden
and solve the final puzzle, and hit the turn limit just two turns short of
winning.&lt;/p&gt;

&lt;p&gt;Tangent: I wonder if models are better at playing games created by other
instances of the same model, by noticing tiny correlations in the text to infer
what puzzles and obstacles they would have written.&lt;/p&gt;

&lt;p&gt;In the end I abandoned the “small worlds” approach because the games are too
stylized, linear, and uninteresting. &lt;em&gt;Anchorhead&lt;/em&gt; is more unwieldy, but more
natural.&lt;/p&gt;

&lt;h1 id=&quot;future-work&quot;&gt;Future Work&lt;/h1&gt;

&lt;p&gt;I have a bunch of ideas I want to test, to better learn how harness
implementations affect performance. But I’m short on time, so I’m cutting it
here and listing these as todos:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Domain-Specific Memories:&lt;/strong&gt; Claude’s notes are all jumbled with information
on tasks, locations, etc. It might be better to have separate memories: a todo
list, a memory of locations and their connections, etc. This is close to the
Soar approach.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Automatic Geography:&lt;/strong&gt; related to the above, the harness can inspect the
game output and build up a graph of rooms and their connections, and format it
in the context. This saves Claude having to note those things manually using a
tool.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Manual Geography:&lt;/strong&gt; the automatic geography approach has a few
drawbacks. Without integration into the Z-machine interpreter, it requires
some work to implement (parsing the current location from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dfrotz&lt;/code&gt;
output, keeping track of the command history to find standard travel commands
e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go south&lt;/code&gt;) but isn’t 100% deterministic, so that mazes and dynamic rooms
(e.g. elevators) will confuse the system. So, instead of doing it manually, we
could give Claude a tool like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;link(room, direction, other_room)&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Episodic Memory:&lt;/strong&gt; this feels like cheating, but, at the end of a run, you
can show Claude the session transcript and ask it to summarize: what it
accomplished and how, where it failed and why. Including a short walkthrough
for how to get to the “last successful state”. This allows future runs to save
time in getting up to speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;code&quot;&gt;Code&lt;/h1&gt;

&lt;p&gt;The repository is &lt;a href=&quot;https://github.com/eudoxia0/claude-plays-anchorhead&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>There Is No New Aesthetics</title>
        <description>On the exhaustion of man.</description>
        <pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/there-is-no-new-aesthetics</link>
        <guid isPermaLink="true">
          https://borretti.me/article/there-is-no-new-aesthetics
        </guid>
        
        <content:encoded>&lt;p&gt;“For man, or for a man, there can be no new beginnings.” — David Zindell, &lt;a href=&quot;https://www.infinityplus.co.uk/stories/shanidar.htm&quot;&gt;&lt;em&gt;Shanidar&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Re: &lt;a href=&quot;https://newaesthetics.art/&quot;&gt;A Call for New Aesthetics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At some point in the 20th century, we filled out the last few basis vectors of
humanity. We explored the whole game map. This is what it means to live at the
end of history: every aesthetic movement, political and economic system you can
imagine can be understood as a linear combination of things that have come
before. Asking for a new aesthetics is like asking for a new continent, one
north of 90° and with imaginary longitude.&lt;/p&gt;

&lt;p&gt;This is why culture feels stuck, and why every ideology is “neo” or “post”
something that came before: we have exhausted humanity. And this is why every
response to the call for a new aesthetics is to dig up some past artistic
movement, and scale it up linearly. “&lt;a href=&quot;https://x.com/AtelierMissor_/status/2005062449972871466&quot;&gt;We offer nothing new except
gigantism&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;I don’t want to believe this is true, because I want to believe culture is an
infinite game, and inexhaustible. And surely the number of linear
combinations—of distinct ideas humans can have, and works of art we can
make—is so high as to be inexhaustible. But each new remixing brings less
information than the last.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Today, futuristic aesthetics often mean retrofuturistic aesthetics.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the end of history, all futurism is retrofuturism, because the future is
&lt;em&gt;contained in&lt;/em&gt; the past. The space age, the atomic age, the age of supersonic
flight all came and went. Dyson spheres and nanomachines and mind uploading were
theorized and written about decades ago.&lt;/p&gt;

&lt;p&gt;If there is a new aesthetic, it will have to come from a transhuman
culture—from people who are more than, or at least other than, human—or from
the &lt;a href=&quot;/article/gallery-of-sand&quot;&gt;alien minds&lt;/a&gt; being born around us.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;https://covidianaesthetics.substack.com/&quot;&gt;Mónica Belevan&lt;/a&gt; for reading the draft of this article.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>1Password Dependency Breaks Syntax Highlighting</title>
        <description>Why does a password manager need a syntax highlighter?</description>
        <pubDate>Sat, 27 Dec 2025 01:00:00 +0000</pubDate>
        <link>https://borretti.me/article/1password-dependency-breaks-syntax-highlighting</link>
        <guid isPermaLink="true">
          https://borretti.me/article/1password-dependency-breaks-syntax-highlighting
        </guid>
        
        <content:encoded>&lt;p&gt;Earlier today I noticed the syntax highlighting on this website was broken. But
not fully: on reload I’d see a flash of highlighted text, that then turned
monochrome. The raw HTML from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; showed &lt;a href=&quot;https://github.com/rouge-ruby/rouge&quot;&gt;rouge&lt;/a&gt; tags, but the web inspector
showed raw text inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;code&amp;gt;&lt;/code&gt; elements. This didn’t happen in Chromium.&lt;/p&gt;

&lt;p&gt;My first thought was: there’s malformed HTML, and Firefox is recovering in a way
that loses the DOM inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;code&amp;gt;&lt;/code&gt; tags. Then I noticed it doesn’t happen in
incognito. Turning my extensions off one by one, I found that 1Password is
responsible. Others (&lt;a href=&quot;https://x.com/RickStrahl/status/2003992055236686022&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;https://www.1password.community/discussions/developers/re-1password-chrome-extension-is-incorrectly-manipulating--blocks/165329&quot;&gt;2&lt;/a&gt;) have reported this also. If you
extract the &lt;a href=&quot;https://addons.mozilla.org/firefox/downloads/file/4607619/1password_x_password_manager-8.11.23.2.xpi&quot;&gt;latest XPI&lt;/a&gt;, unzip it, and dig around, you’ll find they’re
using &lt;a href=&quot;https://prismjs.com/&quot;&gt;Prism.js&lt;/a&gt;, a JavaScript syntax highlighter.&lt;/p&gt;

&lt;p&gt;I don’t know why a password manager needs a syntax highlighter. I imagine it has
to do with the app feature where, if you have an SSH key, you can open a &lt;a href=&quot;https://www.1password.dev/ssh/git-commit-signing&quot;&gt;modal&lt;/a&gt;
that tells you how to configure Git commit signing using. Maybe they want to
highlight the SSH configuration code block (which is unnecessary anyways, since
you could write that HTML by hand). But I can’t know for sure.&lt;/p&gt;

&lt;p&gt;Why write about this? Because 1Password is a security critical product, and they
are apparently pulling random JavaScript dependencies and unwittingly running
them &lt;em&gt;&lt;strong&gt;in the tab context&lt;/strong&gt;&lt;/em&gt;, where the code has access to everything. This is
no good. I don’t need to explain how bad a supply-chain attack on the 1Password
browser extension would be.&lt;/p&gt;

&lt;p&gt;I like 1Password and I was sad when Apple &lt;a href=&quot;https://en.wikipedia.org/wiki/Sherlock_(software)#Sherlocked_as_a_term&quot;&gt;Sherlocked&lt;/a&gt; them with the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Passwords_(Apple)&quot;&gt;Passwords&lt;/a&gt; app, but this is a bad sign about their security practices.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Update (2026-04-24): a &lt;a href=&quot;https://1password.statuspage.io/incidents/vkm6tvh2b568&quot;&gt;postmortem&lt;/a&gt; is now available.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Using the Brother DS-640 Scanner on NixOS</title>
        <description>I suffer so you don&apos;t have to.</description>
        <pubDate>Sat, 27 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/using-the-brother-ds-640-scanner-on-nixos</link>
        <guid isPermaLink="true">
          https://borretti.me/article/using-the-brother-ds-640-scanner-on-nixos
        </guid>
        
        <content:encoded>&lt;p&gt;The &lt;a href=&quot;https://www.brother.com.au/en/scanners/all-scanners/ds-640&quot;&gt;DS-640&lt;/a&gt; is a compact USB scanner from &lt;a href=&quot;https://en.wikipedia.org/wiki/Brother_Industries&quot;&gt;Brother&lt;/a&gt;. It was surprisingly hard to get it working on NixOS, so I wrote up my solution so others don’t have this problem. The bad news is you need Brother’s proprietary drivers to make this work.&lt;/p&gt;

&lt;p&gt;You need this configuration:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Enable SANE scanners.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;hardware&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;sane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Add yourself to the scanner and printer groups.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;extraGroups&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;scanner&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;lp&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Add support for Brother scanners.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;hardware&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;sane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;brscan5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;After applying this you have to log out and in, or reboot, for the usergroup changes to apply.&lt;/p&gt;

&lt;p&gt;Note also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brscan5&lt;/code&gt;: if you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brscan4&lt;/code&gt; (as I did initially), the scanner will kind of work, but it only scans the first third or so of every page.&lt;/p&gt;

&lt;p&gt;And if you want a GUI:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install GNOME Document Scanner.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;home-manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;packages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;simple-scan&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now, make sure the scanner is there:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ scanimage --list-devices
device `brother5:bus4;dev2&apos; is a Brother DS-640 USB scanner
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;If you get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Brother *Unknown USB scanner&lt;/code&gt;, you either have the wrong driver or (as I did, surprisingly) a faulty USB port. In which case move the scanner to another port. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scanimage --list-devices&lt;/code&gt; should recognize the model number.&lt;/p&gt;

&lt;p&gt;The most basic test that should work: put a page in the scanner until it locks and run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ scanimage --device=&quot;brother5:bus4;dev2&quot; \
  --format=jpeg \
  --output-file=scan.jpg
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;This will produce a (probably not very good) scan in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scan.jpg&lt;/code&gt;. Now, we can improve things using the device-specific options, which you can check with this command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ scanimage --all-options --device=&quot;brother5:bus4;dev2&quot;
All options specific to device `brother5:bus4;dev2&apos;:
    --mode 24bit Color[Fast]|Black &amp;amp; White|True Gray|Gray[Error Diffusion] [24bit Color[Fast]]
        Select the scan mode
    --resolution 100|150|200|300|400|600|1200dpi [100]
        Sets the resolution of the scanned image.
    --source Automatic Document Feeder(left aligned) [Automatic Document Feeder(left aligned)]
        Selects the scan source (such as a document-feeder).
    --brightness -50..50% (in steps of 1) [inactive]
        Controls the brightness of the acquired image.
    --contrast -50..50% (in steps of 1) [inactive]
        Controls the contrast of the acquired image.
    --MultifeedDetection[=(yes|no)] [inactive]

    --AutoDocumentSize[=(yes|no)] [no] [advanced]

    --AutoDeskew[=(yes|no)] [no] [advanced]

    --SkipBlankPage[=(yes|no)] [inactive]

    --SkipBlankPageSensitivity 0..100% (in steps of 1) [inactive]

    -l 0..215.9mm (in steps of 0.0999908) [0]
        Top-left x position of scan area.
    -t 0..355.6mm (in steps of 0.0999908) [0]
        Top-left y position of scan area.
    -x 0..215.9mm (in steps of 0.0999908) [215.88]
        Width of scan-area.
    -y 0..355.6mm (in steps of 0.0999908) [355.567]
        Height of scan-area.
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Try this for a better scan:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ scanimage --device=&quot;brother5:bus4;dev2&quot; \
  --AutoDeskew=yes \
  --AutoDocumentSize=yes \
  --resolution 300 \
  --format=jpeg \
  --output-file=scan.jpg
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Note that some of the flags are in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--key value&lt;/code&gt; format and others &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--key=value&lt;/code&gt;, and if you mess it up you get a cryptic error message.&lt;/p&gt;
</content:encoded>
      </item>
    
      <item>
        <title>Books I Enjoyed in 2025</title>
        <description>A short list.</description>
        <pubDate>Mon, 22 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/books-i-enjoyed-in-2025</link>
        <guid isPermaLink="true">
          https://borretti.me/article/books-i-enjoyed-in-2025
        </guid>
        
        <content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/223195414-the-apocalypse-of-herschel-schoen&quot;&gt;&lt;em&gt;The Apocalypse of Herschel Schoen&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://www.goodreads.com/author/show/13933106.nostalgebraist&quot;&gt;nostalgebraist&lt;/a&gt;. A revelation (ἀποκάλυψις = “unveiling”) told through the eyes of a developmentally-disabled teenager. You will never guess where it goes. This came across my desk because I really enjoyed &lt;a href=&quot;https://www.goodreads.com/book/show/25503770-the-northern-caves&quot;&gt;&lt;em&gt;The Northern Caves&lt;/em&gt;&lt;/a&gt;, which is both a great horror story and an evocation of the Internet forum culture of the late 2000’s.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/7811945-algebraic-models-for-accounting-systems&quot;&gt;&lt;em&gt;Algebraic Models for Accounting Systems&lt;/em&gt;&lt;/a&gt;. I like anything along the lines of, “let’s take a technical field that formed its ontology, vocabulary, methods etc. before modern mathematics, and set it on a modern, algebraic, formal foundation”. And this is that, for accounting. It is a pleasant read.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Confessions_of_a_Mask&quot;&gt;&lt;em&gt;Confessions of a Mask&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Yukio_Mishima&quot;&gt;Yukio Mishima&lt;/a&gt;. The artist’s confession. “I had decided I could love a girl without feeling any desire whatsoever”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Paul_et_Virginie&quot;&gt;&lt;em&gt;Paul and Virginia&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Jacques-Henri_Bernardin_de_Saint-Pierre&quot;&gt;Jacques-Henri Bernardin de Saint-Pierre&lt;/a&gt;. Published in 1788, very sentimental, but I think it helped me to get in the mindset of late 17th century French society: the bucolic, Rousseauist kick, the whole “simplicity of nature” thing. This landed on my reading list because many, many years ago I read a &lt;a href=&quot;https://en.wikipedia.org/wiki/Cordwainer_Smith&quot;&gt;Cordwainer Smith&lt;/a&gt; story called &lt;a href=&quot;https://en.wikipedia.org/wiki/Alpha_Ralpha_Boulevard&quot;&gt;&lt;em&gt;Alpha Ralpha Boulevard&lt;/em&gt;&lt;/a&gt;, and I read somewhere that the characters in the story, Paul and Virginia, were an allusion to &lt;em&gt;Paul et Virginie&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Monarchia&quot;&gt;&lt;em&gt;De Monarchia&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Dante_Alighieri&quot;&gt;Dante&lt;/a&gt;. This is another “get into the mindset of another century” book. It’s interesting because it’s written like a logical, geometric proof: there’s &lt;em&gt;modus ponens&lt;/em&gt; and &lt;em&gt;modus tollens&lt;/em&gt; and case analysis and proof by contradiction. But the axioms are very eclectic: quotations from various Virgil, Plato, Livy, Cicero, Thomas Aquinas et al. and Dante’s private interepretation of bits from the Bible. The theorem he wants to prove is that to attain the highest development of humanity, the whole world must be unified into a world-state ran by the Holy Roman Emperor.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://direct.mit.edu/books/monograph/5791/Building-SimCityHow-to-Put-the-World-in-a-Machine&quot;&gt;&lt;em&gt;Building SimCity: How to Put the World in a Machine&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Chaim_Gingold&quot;&gt;Chaim Gingold&lt;/a&gt;. Nominally an oral history of the development of SimCity. That’s how he gets you. Then the trap is sprung, and you are given a history of cybernetics, WW2 fire control systems, cellular automata, artificial life, computation, Vannevar Bush, pedagogy, cognition, the &lt;a href=&quot;https://en.wikipedia.org/wiki/World3&quot;&gt;World3&lt;/a&gt; model, &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Limits_to_Growth&quot;&gt;&lt;em&gt;The Limits to Growth&lt;/em&gt;&lt;/a&gt;, Forrester’s &lt;a href=&quot;https://en.wikipedia.org/wiki/System_dynamics&quot;&gt;system dynamics&lt;/a&gt;. “Unexpectedly Borgesian technical book” is one of my favourite genres.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Antigone_(Sophocles_play)&quot;&gt;&lt;em&gt;Antigone&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Sophocles&quot;&gt;Sophocles&lt;/a&gt;, in the translation of &lt;a href=&quot;https://en.wikipedia.org/wiki/Robert_Fagles&quot;&gt;Robert Fagles&lt;/a&gt;. “Don’t fear for me. Set your own life in order”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cyropaedia&quot;&gt;&lt;em&gt;The Education of Cyrus&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Xenophon&quot;&gt;Xenophon&lt;/a&gt;. I’m not sure what to make of it, honestly, but when I have the time I want to read &lt;a href=&quot;https://en.wikipedia.org/wiki/Leo_Strauss&quot;&gt;Leo Strauss&lt;/a&gt;’s &lt;a href=&quot;https://leostrausscenter.uchicago.edu/xenophon-winter-1963-2/&quot;&gt;lectures&lt;/a&gt; on Xenophon, where he expounds on the hidden meaning of the text.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.goodreads.com/en/book/show/52614.Borrowed_Time&quot;&gt;&lt;em&gt;Borrowed Time: An AIDS Memoir&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Paul_Monette&quot;&gt;Paul Monette&lt;/a&gt;. The author’s account of caring for his partner who was dying of AIDS in the 80’s, while he himself was actively dying from AIDS. Frightful. The author died just a few years before HAART therapy became available.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Slave_(Singer_novel)&quot;&gt;&lt;em&gt;The Slave&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Isaac_Bashevis_Singer&quot;&gt;Isaac Bashevis Singer&lt;/a&gt;. Singer is unique. I don’t know quite how to characterize it. His writing is very disarming and innocent without being sentimental, he is earnest and free of cynicism. A love story in 17th century Poland, after the &lt;a href=&quot;https://en.wikipedia.org/wiki/Khmelnytsky_pogroms&quot;&gt;Khmelnytsky pogroms&lt;/a&gt;. It’s very magical realist, in a good way, not in the &lt;a href=&quot;https://en.wikipedia.org/wiki/Hysterical_realism&quot;&gt;hysterical&lt;/a&gt; sense. The world is shot through with the supernatural, but the inner lives of the characters oscillate between religious awe and a very contemporary cynicism.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Dream_Story&quot;&gt;&lt;em&gt;Dream Story&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Arthur_Schnitzler&quot;&gt;Arthur Schnitzler&lt;/a&gt;. The inspiration for &lt;a href=&quot;https://en.wikipedia.org/wiki/Eyes_Wide_Shut&quot;&gt;&lt;em&gt;Eyes Wide Shut&lt;/em&gt;&lt;/a&gt;. I was surprised by how much of the movie, that I thought was mostly Kubrick’s invention, is actually from the story. It’s a great mood piece: you can feel the cold of early morning in Vienna, and see the paving stones, and the gas lamps, and the carriages disappearing in the fog.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Cyberiad&quot;&gt;&lt;em&gt;The Cyberiad&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Stanis%C5%82aw_Lem&quot;&gt;Stanisław Lem&lt;/a&gt;. I like Lem when he’s serious (&lt;a href=&quot;https://en.wikipedia.org/wiki/Solaris_(novel)&quot;&gt;&lt;em&gt;Solaris&lt;/em&gt;&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/His_Master%27s_Voice_(novel)&quot;&gt;&lt;em&gt;His Master’s Voice&lt;/em&gt;&lt;/a&gt;) and not so much when he’s doing satire (&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Futurological_Congress&quot;&gt;&lt;em&gt;The Futurological Congress&lt;/em&gt;&lt;/a&gt;) so when I picked this up years ago and saw that it was a collection of fairy tales I put it away. I tried again this year and found I actually enjoyed it, but some of the later stories go on for far too long. I think &lt;em&gt;The Seventh Sally&lt;/em&gt; is the one everyone likes.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Magician_of_Lublin_(novel)&quot;&gt;&lt;em&gt;The Magician of Lublin&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Isaac_Bashevis_Singer&quot;&gt;Isaac Bashevis Singer&lt;/a&gt;. Another Singer, this time in 19th century Poland. A rake is punished by God. Short and fun. I like that Singer doesn’t write giant doorstoppers, so that quality per page is high.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Mephisto_(novel)&quot;&gt;&lt;em&gt;Mephisto&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Klaus_Mann&quot;&gt;Klaus Mann&lt;/a&gt;. A socialist actor in interwar Germany saves his career by making friends with the Nazis. I was surprised by how Randian it was: the characters are divided into two disjoint categories, the Good, who are upper middle class, burgeois people, or aristocrats from old and noble families, and the Bad, who are vulgar, parvenus, thugs, and boors. It’s kind of ironic to think people become Nazis because of bad breeding.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/What_Is_Life%3F&quot;&gt;&lt;em&gt;What Is Life?&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Erwin_Schr%C3%B6dinger&quot;&gt;Erwin Schrödinger&lt;/a&gt;. Before modern crystallography, NMR, DFT etc. people had to learn about the nanoscale through clever reasoning. Schrödinger uses the limited knowledge of the day to set up a constraint system, and finds the solution: genetic information is stored in an aperiodic, covalently-bonded crystal, and he even estimates the physical volume of the genome from experiments relating mutation rates to X-ray exposure.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Satan_in_Goray&quot;&gt;&lt;em&gt;Satan in Goray&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Isaac_Bashevis_Singer&quot;&gt;Isaac Bashevis Singer&lt;/a&gt;. Another Singer, back in the 17th century, this one is more fire and brimstone, and it’s about a historical episode I had not heard about until the last few pages of &lt;em&gt;The Slave&lt;/em&gt;: the case of &lt;a href=&quot;https://en.wikipedia.org/wiki/Sabbatai_Zevi&quot;&gt;Sabbatai Zevi&lt;/a&gt;, a Jewish mystic who, at one point, had most of the Jewish world convinced he was the messiah. This happened in the year 1666. The novel is about what it’s like, phenomenologically, to live in a remote village in 1600’s Poland. How do you know anything about the world? People come in, from time to time, traders, and they have news, but the news are just words that come out of their mouth. And you have to interrogate them, ask questions, compare notes. Like living in a Pacific island. Has the messiah come? Is there such a place as the Ottoman Empire? Is there even a world outside Poland?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/528786.Tog_on_Interface&quot;&gt;&lt;em&gt;Tog on Interface&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Bruce_Tognazzini&quot;&gt;Bruce Tognazzini&lt;/a&gt;. A book about interface design from 1992. A lot of the advice is good, and a lot of it is interesting for the historical context, and the constraints people worked with in the past. One aspect I found interesting: how many products and companies are mentioned of whose existence I can find little to no evidence today. This makes the hoarder in me sad. This one across my desk because I read a &lt;a href=&quot;https://verbnounenter.net/one-or-more&quot;&gt;blog post&lt;/a&gt; implementing one of the UI ideas from the book.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.cambridge.org/core/books/term-rewriting-and-all-that/71768055278D0DEF4FFC74722DE0D707&quot;&gt;&lt;em&gt;Term Rewriting and All That&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Franz_Baader&quot;&gt;Franz Baader&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Tobias_Nipkow&quot;&gt;Tobias Nipkow&lt;/a&gt;. I feel that I understand what computation is now.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/263471.Indistinguishable_From_Magic&quot;&gt;&lt;em&gt;Indistinguishable From Magic&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Robert_L._Forward&quot;&gt;Robert L. Forward&lt;/a&gt;. If you’ve spent years steeped in &lt;a href=&quot;https://orionsarm.com/&quot;&gt;&lt;em&gt;Orion’s Arm&lt;/em&gt;&lt;/a&gt; then most of the ideas in the book will not be new to you. But they were new once. And it’s interesting to read a book and think: this is where &lt;a href=&quot;https://en.wikipedia.org/wiki/Starwisp&quot;&gt;starwisps&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Launch_loop&quot;&gt;launch loops&lt;/a&gt; all come from.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Shadow_of_the_Torturer&quot;&gt;&lt;em&gt;The Shadow of the Torturer&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Gene_Wolfe&quot;&gt;Gene Wolfe&lt;/a&gt;. Surreal and a pleasure to read.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.goodreads.com/book/show/1041738.Knowledge_Representation&quot;&gt;&lt;em&gt;Knowledge Representation: Logical, Philosophical, and Computational Foundations&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/John_F._Sowa&quot;&gt;John F. Sowa&lt;/a&gt;. Delightful, particularly the early bits about the history of logic, and many chapters explaining the work of &lt;a href=&quot;https://en.wikipedia.org/wiki/Charles_Sanders_Peirce&quot;&gt;Peirce&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Alfred_North_Whitehead&quot;&gt;Whitehead&lt;/a&gt; on ontology.&lt;/p&gt;

&lt;p&gt;I have not finished reading this book, but I am in the first few pages of &lt;a href=&quot;https://www.goodreads.com/book/show/948126.A_Shorter_Model_Theory&quot;&gt;&lt;em&gt;A Shorter Model Theory&lt;/em&gt;&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Wilfrid_Hodges&quot;&gt;Wilfrid Hodges&lt;/a&gt;, and I am delighted. The very first exercise in the book involves a formalization of Aquinas’ account of the trinity.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Coarse is Better</title>
        <description>Make AI weird again.</description>
        <pubDate>Sun, 21 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/coarse-is-better</link>
        <guid isPermaLink="true">
          https://borretti.me/article/coarse-is-better
        </guid>
        
        <content:encoded>&lt;p&gt;When &lt;a href=&quot;https://en.wikipedia.org/wiki/DALL-E&quot;&gt;DALL-E&lt;/a&gt; came out, it took me a couple of weeks to pick my jaw up
from the floor. I would go to sleep excited to wake up to a full quota, with a
backlog of prompts to try. It was magical, miraculous. Like discovering a new
universe. I compiled the best art in &lt;a href=&quot;/article/gallery-of-sand&quot;&gt;this post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The other day a friend ran some of my old prompts through &lt;a href=&quot;https://blog.google/technology/ai/nano-banana-pro/&quot;&gt;Nano Banana Pro&lt;/a&gt;
(NBP), and put the old models side by side with the new. It’s interesting how
after years of progress, the models are much better better at making images, but
infinitely worse at making art.&lt;/p&gt;

&lt;h1 id=&quot;electron-contours&quot;&gt;Electron Contours&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;Electron contours in the style of Italian futurism, oil on canvas, 1922,
trending on ArtStation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The old &lt;a href=&quot;https://en.wikipedia.org/wiki/Midjourney&quot;&gt;Midjourney v2&lt;/a&gt; renders this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/electrons-mj.png&quot; width=&quot;50%&quot; alt=&quot;Red and gold abstract shapes on a dark blue background.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;NBP renders this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/electrons-nbp.png&quot; alt=&quot;Muted, red and blue ellipses against a machine background, in a golden frame.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Admiteddly MJ’s output doesn’t look quite like futurism. But it looks like
&lt;em&gt;something&lt;/em&gt;. It looks compelling. The colours are bright and vivid. NBP’s output
is studiously in the style of Italian futurism, but the colours are so muted and
dull.&lt;/p&gt;

&lt;p&gt;Maybe the “trending on ArtStation” is a bit of an archaism and impairs
performance. Let’s try again without:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/electrons-nbp2.webp&quot; alt=&quot;Red, gold, yellow circles intersect, thick impasto, oil on canvas, the word ELETTRONICO written in black across the frame.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Meh.&lt;/p&gt;

&lt;h1 id=&quot;the-kowloon-walled-city&quot;&gt;The Kowloon Walled City&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;Painting of an alley in the Kowloon Walled City, Eugène Boudin, 1895, trending
on ArtStation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;MJ gave me this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/kowloon-mj.png&quot; width=&quot;80%&quot; alt=&quot;An impressionistic painting of an alley in a city, with a tree canopy above.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And it looks nothing like the &lt;a href=&quot;https://en.wikipedia.org/wiki/Kowloon_Walled_City&quot;&gt;Kowloon Walled City&lt;/a&gt;. But it’s
&lt;em&gt;beautiful&lt;/em&gt;. It’s coarse, impressionistic, vague, evocative, contradictory. It’s
brimming with mystery. And it is, in fact, in the style of &lt;a href=&quot;https://en.wikipedia.org/wiki/Eug%C3%A8ne_Boudin&quot;&gt;Eugène
Boudin&lt;/a&gt;. This, by contrast, is the NBP output:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/kowloon-nbp.png&quot; alt=&quot;A muted painting of a commercial street in a Chinese city.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Sigh. It looks like every modern movie: so desaturated you feel you’re going
colourblind. Let’s try forcing it:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Painting of an alley in the Kowloon Walled City, Eugène Boudin, 1895. Make it
coarse, impressionistic, vague, evocative, contradictory, brimming with
mystery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/kowloon-nbp2.webp&quot; alt=&quot;A dark, muted painting of a commercial street in a Chinese city in the rain.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is somewhat better, but why is it so drab and colourless? Is the machine
trying to make me depressed?&lt;/p&gt;

&lt;h1 id=&quot;the-dream-garden-of-the-poets&quot;&gt;The Dream Garden of the Poets&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;Attar and Ferdowsi in a dream garden, Persian miniature, circa 1300, from the
British Museum.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Midjourney v2:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/attar-mj.png&quot; width=&quot;80%&quot; alt=&quot;A man wearing a green robe, and a shorter man wearing a golden robe, on a floating island of green, over a landscape of cobalt blue.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It doesn’t quite look like anything. But it is beautiful, and evocative. I like
to imagine that little splotch of paint on the upper right is &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Conference_of_the_Birds&quot;&gt;hoopoe&lt;/a&gt;. The NBP output:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/attar-nbp.png&quot; alt=&quot;A photograph of a generic Persian miniature in a display case.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, it looks like a &lt;a href=&quot;https://en.wikipedia.org/wiki/Persian_miniature&quot;&gt;Persian miniature&lt;/a&gt;. The “from the British
Museum” bit, I meant that to be interpreted evocatively, rather than
literally. The prompt &lt;em&gt;cites&lt;/em&gt; a fictional object, bringing it into the
existence. But NBP reads this as: no, this is a photograph of a Persian
miniature in the British Museum.&lt;/p&gt;

&lt;h1 id=&quot;the-sack-of-merv&quot;&gt;The Sack of Merv&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;The Burning of Merv by John William Waterhouse, 1896, from the British Museum.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Midjourney v2:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/merv-mj.png&quot; width=&quot;60%&quot; alt=&quot;A woman in a dress dress, surrounded by flames, by black water, by a watching crowd.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It does look like &lt;a href=&quot;https://en.wikipedia.org/wiki/John_William_Waterhouse&quot;&gt;Waterhouse&lt;/a&gt;. Semantically there’s room to argue: it looks
like a woman being burnt at the stake, not the sack of a city. But
aesthetically: it’s gorgeous. The flames are gorgeous, the reds of the dress are
gorgeous. Look at the reeds in the background, and the black water, that looks
like tarnished silver or pewter. The faces of the crowd. Is that a minotaur on
the lower left, or a flower? What is she holding on her bent left arm? A
crucifix, a dagger? You could find entire universes in this image, in this
1024x1024 frame.&lt;/p&gt;

&lt;p&gt;By contrast, this is the NBP output:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/merv-nbp.png&quot; alt=&quot;A photograph of a painting of horse-mounted warriors outside a burning city. The photograph shows the painting is in a display room in a museum.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What can one say? It doesn’t look like Waterhouse. The horsemen wear Arab or
Central Asian dress, but &lt;a href=&quot;https://en.wikipedia.org/wiki/Merv&quot;&gt;Merv&lt;/a&gt; was sacked in the year 1221 by the &lt;a href=&quot;https://en.wikipedia.org/wiki/Mongol_Empire&quot;&gt;Mongol
Empire&lt;/a&gt;. And, again, the “British Museum” line is taken literally rather
than evocatively.&lt;/p&gt;

&lt;h1 id=&quot;lady-lovelace&quot;&gt;Lady Lovelace&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;Portrait of Ada Lovelace by Dante Gabriel Rossetti, 1859, auctioned by Christie’s.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Midjourney:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/lovelace-mj.png&quot; width=&quot;60%&quot; alt=&quot;A portrait of Ada Lovelace against a circle of dark green.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is beautiful. It is beautiful because the coarse, impressionistic
brushstroke is more evocative than literal. And it actually looks like a woman
drawn by &lt;a href=&quot;https://en.wikipedia.org/wiki/Dante_Gabriel_Rossetti&quot;&gt;Rossetti&lt;/a&gt;. And look at the greens! Gorgeously green. The palette
is so narrow, and the painting is so beautiful.&lt;/p&gt;

&lt;p&gt;The NBP output:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/lovelace-nbp.png&quot; alt=&quot;A photograph of a generic 19th century realist painting of a woman, in a gilt frame, taken at an angle inside a gallery, a Christie&apos;s action book is seen on a table.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pure philistinism. “Auctioned by Christie’s”, again, is meant to be evocative:
“this is the kind of painting that would be sold at auction”. But NBP makes it a
photograph of a painting at an auction house. Fine, I suppose I got what I asked
for.&lt;/p&gt;

&lt;p&gt;But the woman doesn’t look like Rossetti! This is absurd. How can a model from
2022 get this right, and the SOTA image generation model gives us generic oil
painting slop?&lt;/p&gt;

&lt;h1 id=&quot;the-cosmic-microwave-background&quot;&gt;The Cosmic Microwave Background&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;A Persian miniature of the cosmic microwave background, from Herat circa 1600, trending on ArtStation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Midjourney v2:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/cmb-mj.png&quot; width=&quot;60%&quot; alt=&quot;A golden disk, surrounded by concentric circles of Perso-Arabic lettering, against a dark blue background.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;NBP:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/cmb-nbp.png&quot; alt=&quot;The standard depiction of the CMB in the frame of a Persian miniature.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Again: what can one say?&lt;/p&gt;

&lt;h1 id=&quot;dream-story&quot;&gt;Dream Story&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;Dream Story, 1961, blurry black and white photograph, yellow tint, from the Metropolitan Museum of Art.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is one of my favourite DALL-E 2 outputs:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/dall-e-explorations/yellow/dream-1.jpg&quot; width=&quot;50%&quot; alt=&quot;A photograph of two trees illuminated by a sepia glow in a dark forest. On the bottom-right corner, two people can be seen watching the scene.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/dall-e-explorations/yellow/dream-2.jpg&quot; width=&quot;50%&quot; alt=&quot;A sepia photograph, showing two girls on a bed, and three people standing around them.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/dall-e-explorations/yellow/dream-3.jpg&quot; width=&quot;50%&quot; alt=&quot;A vague, blurry sepia photograph of an indistinct man and woman.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/dall-e-explorations/yellow/dream-4.jpg&quot; width=&quot;50%&quot; alt=&quot;Sepia photograph: three vague, almost alien-looking figures look at what might be a sculpture or painting.&quot; style=&quot;margin: 0 auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;They remind me of &lt;a href=&quot;https://en.wikipedia.org/wiki/The_King_in_Yellow&quot;&gt;&lt;em&gt;The King in Yellow&lt;/em&gt;&lt;/a&gt;. I love these because of how
genuinely creepy and mysterious they are. You could pull a hundred horror
stories from these.&lt;/p&gt;

&lt;p&gt;It is hard to believe how bad the NBP output is:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/coarse-is-better/dream-story-nbp.webp&quot; alt=&quot;A black and white photgraph of people walking in a part. On the bottom left, a legend says: &amp;quot;Dream Story, 1961 - Metropolitan Museum of Art Archive&amp;quot;.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What are we doing here? The old models were beautiful and compelling because the
imperfections, vagueness, mistakes, and contradictions all create these little
gaps through which your imagination can breathe life into the art. The images
are not one fixed, static thing: they can be infinitely many things.&lt;/p&gt;

&lt;p&gt;The new models—do I even need to finish this sentence? They’re too precise and
high-resolution, so they cannot make abstract, many-faced things, they can only
make specific, concrete things.&lt;/p&gt;

&lt;p&gt;We need to make AI art weird again.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>I Wish People Were More Public</title>
        <description>On sharing more of yourself.</description>
        <pubDate>Wed, 10 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/i-wish-people-were-more-public</link>
        <guid isPermaLink="true">
          https://borretti.me/article/i-wish-people-were-more-public
        </guid>
        
        <content:encoded>&lt;p&gt;Probably not a popular thing to say today. The zeitgeisty thing to say
is that we should all log off and live terrible cottagecore solarpunk
lives raising chickens and being mindful. I wish people were more
online and more public. I have rarely wished the opposite. Consider
this post addressed to you, the reader.&lt;/p&gt;

&lt;h1 id=&quot;your-writing&quot;&gt;Your Writing&lt;/h1&gt;

&lt;p&gt;I will often find a blog post on &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt; that really
resonates. And when I go to check the rest of the site there’s three
other posts. And I think: I wish you’d write more! When I find someone
whose writing I really connect with, I like to read everything they
have written, or at least a tractable subset of their most interesting
posts. If I like what I see, I reach out. This is one of the best
things about writing online: your future friends will seek you out.&lt;/p&gt;

&lt;p&gt;And, from the other side, I have often written a post where, just
before publishing, I would think: “who would want to read this? It’s
too personal, obscure, idiosyncratic, probably a few people will
unsubscribe to the RSS feed for this”. And always those are the posts
where people email me to say they always thought the same thing but
could never quite put it into words. I really value those emails. “I
am understood” is a wonderful feeling.&lt;/p&gt;

&lt;p&gt;I try to apply a rule that if I do something, and don’t write about
it—or otherwise generate external-facing evidence of it—it didn’t
happen. I have built so many things in the dark, little experiments or
software projects or essays that never saw the light of day. I want to
put more things out. If it doesn’t merit an entire blog post, then at
least a tweet.&lt;/p&gt;

&lt;h1 id=&quot;your-books&quot;&gt;Your Books&lt;/h1&gt;

&lt;p&gt;If I follow you on Twitter, and you have posted a picture of your
bookshelf, I have probably scanned every book in it. This is why I
appreciate &lt;a href=&quot;https://www.goodreads.com/&quot;&gt;Goodreads&lt;/a&gt;. Like many people I have been reading a lot
less over the past ~5y, but since I made a Goodreads account earlier
this year, I’ve read tens of books. Reading in public has helped to
motivate me.&lt;/p&gt;

&lt;p&gt;You may say reading in public is performative. I say reading in
private is solipsistic. Dante, in &lt;a href=&quot;https://en.wikipedia.org/wiki/Monarchia&quot;&gt;&lt;em&gt;De Monarchia&lt;/em&gt;&lt;/a&gt;, writes:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;All men on whom the Higher Nature has stamped the love of truth
should especially concern themselves in laboring for posterity, in
order that future generations may be enriched by their efforts, as
they themselves were made rich by the efforts of generations
past. For that man who is imbued with public teachings, but cares
not to contribute something to the public good, is far in arrears of
his duty, let him be assured; he is, indeed, not “a tree planted by
the rivers of water that bringeth forth his fruit in his season,”
[&lt;a href=&quot;https://www.biblegateway.com/passage/?search=Psalm%201%3A3&amp;amp;version=GNV&quot;&gt;Psalms 1:3&lt;/a&gt;] but rather a destructive whirlpool, always
engulfing, and never giving back what it has devoured.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My default mode is solipsism. I read in private, build in private,
learn in private. And the problem with that is self-doubt and
arbitrariness. I’m halfway through a textbook and think: why? Why am I
learning geology? Why &lt;em&gt;this&lt;/em&gt; topic, and not another? There is never an
&lt;em&gt;a priori&lt;/em&gt; reason. I take notes, but why tweak the LaTeX if no-one,
probably not even future me, will read them? If I stop reading this
book, what changes?  And doing things in public makes them both more
real and (potentially) useful. If you publish your study notes, they
might be useful to someone. Maybe they get slurped up in the training
set of the next LLM, marginally improving performance.&lt;/p&gt;

&lt;p&gt;And Goodreads, for all its annoyances, is a uniquely tender social
network. Finishing a book, and then seeing a friend mark it as “want
to read”, feels like a moment of closeness.&lt;/p&gt;

&lt;p&gt;I have a friend who lived in Sydney, who has since moved away, and we
don’t keep in touch too often, because the timezones are inconvenient,
but occasionally she likes my book updates, and I like hers, and I
will probably never read that avant-garde novel, but I’m glad she is
reading it. It is like saying: “You exist. I exist. I remember. I wish
you happiness.”&lt;/p&gt;

&lt;h1 id=&quot;your-flashcards&quot;&gt;Your Flashcards&lt;/h1&gt;

&lt;p&gt;Lots of people use &lt;a href=&quot;/article/effective-spaced-repetition&quot;&gt;spaced repetition&lt;/a&gt;, but most everyone’s
flashcard collections are private. They exist inside a database inside
an app like &lt;a href=&quot;https://apps.ankiweb.net/&quot;&gt;Anki&lt;/a&gt; or &lt;a href=&quot;https://mochi.cards/&quot;&gt;Mochi&lt;/a&gt;. You can export decks, but
that’s not a living artifact but a dead snapshot, frozen in time.&lt;/p&gt;

&lt;p&gt;One reason I built &lt;a href=&quot;https://github.com/eudoxia0/hashcards&quot;&gt;hashcards&lt;/a&gt;: by using a Git repo of Markdown
files as the flashcard database, you can trivially publish your deck
to GitHub. &lt;a href=&quot;https://github.com/eudoxia0/flashcards&quot;&gt;My own flashcard collection&lt;/a&gt; is public. I hope that
more people use hashcards and put their decks up on GitHub.&lt;/p&gt;

&lt;p&gt;The point is not that you can clone their repos (which is close to
useless: you have to write your own flashcards) but because I’m
curious what people are learning. Not the broad strokes, since we all
want to learn thermo and econ and quantum chemistry and the military
history of the Song dynasty and so on, but the minutiae. Why did you
make a flashcard out of this Bible passage? Why does it resonate with
you? Why do you care about the interpretation of that strange passage
in &lt;em&gt;Antigone&lt;/em&gt;? Why did you memorize this poem?&lt;/p&gt;

&lt;h1 id=&quot;your-dotfiles&quot;&gt;Your Dotfiles&lt;/h1&gt;

&lt;p&gt;Computers mediate every aspect of our lives, yet most people use their
computers the way they came out of the box. At most they might change
the desktop background. Some people don’t even change the default
icons on the macOS dock. Even most Linux users just use the stock
configuration, e.g. GNOME on Fedora or whatever.&lt;/p&gt;

&lt;p&gt;I’m interested in people who customize their experience of
computing. This is often derided as “&lt;a href=&quot;https://en.wiktionary.org/wiki/rice_out#English&quot;&gt;ricing&lt;/a&gt;”. But agency is
interesting. People who remake their environment to suit them are
interesting. And I am endlessly curious about how people do this. I
like reading people’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init.el&lt;/code&gt;, their custom shell scripts, their
NixOS config. It’s even better if they have some obscure hardware
e.g. some keyboard layout I’ve never heard of and a trackball with
custom gestures. I put my &lt;a href=&quot;https://github.com/eudoxia0/dotfiles&quot;&gt;dotfiles&lt;/a&gt; up on GitHub because I
imagine someone will find them interesting.&lt;/p&gt;

&lt;h1 id=&quot;etc&quot;&gt;etc.&lt;/h1&gt;

&lt;p&gt;And beyond my selfish curiosity there’s also the &lt;a href=&quot;https://en.wikipedia.org/wiki/Nikolai_Fyodorov_(philosopher)&quot;&gt;Fedorovist&lt;/a&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Frank_J._Tipler&quot;&gt;ancestor&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Omega_Point&quot;&gt;simulation&lt;/a&gt; angle: if you die and are not
cryopreserved, how else are you going to make it to the other side of
the intelligence explosion? Every tweet, blog post, Git commit,
journal entry, keystroke, mouse click, every one of these things is a
tomographic cut of the mind that created it.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Ad-Hoc Emacs Packages with Nix</title>
        <description>Creating ad-hoc Emacs packages in a few lines of code.</description>
        <pubDate>Sun, 16 Nov 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/ad-hoc-emacs-packages-with-nix</link>
        <guid isPermaLink="true">
          https://borretti.me/article/ad-hoc-emacs-packages-with-nix
        </guid>
        
        <content:encoded>&lt;p&gt;You can use &lt;a href=&quot;https://nixos.org/&quot;&gt;Nix&lt;/a&gt; as a package manager for Emacs, like so:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;home-manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;eudoxia&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;programs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;extraPackages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;epkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;epkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;magit&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;rust-mode&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;treemacs&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;# and so on&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Today I learned you can also use it to create ad-hoc packages for things not in
&lt;a href=&quot;https://melpa.org/&quot;&gt;MELPA&lt;/a&gt; or &lt;a href=&quot;https://github.com/NixOS/nixpkgs&quot;&gt;nixpkgs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The other day I wanted to get back into &lt;a href=&quot;https://ganelson.github.io/inform-website/&quot;&gt;Inform 7&lt;/a&gt;, naturally the first
stack frame of the yak shave was to look for an Emacs
mode. &lt;a href=&quot;https://github.com/alexispurslane/inform7-mode&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inform7-mode&lt;/code&gt;&lt;/a&gt; exists, but isn’t packaged anywhere. So I had to
vendor it in.&lt;/p&gt;

&lt;p&gt;You can use &lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Tools-Submodules&quot;&gt;git submodules&lt;/a&gt; for this, but I have an irrational aversion to
submodules. Instead I did something far worse: I wrote a &lt;a href=&quot;https://github.com/eudoxia0/dotfiles/blob/ffe18b06a516586ee591cae77351cab8e3569c5f/nixos/modules/emacs/Makefile&quot;&gt;Makefile&lt;/a&gt; to
download the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.el&lt;/code&gt; from GitHub, and used &lt;a href=&quot;https://github.com/nix-community/home-manager&quot;&gt;home-manager&lt;/a&gt; to copy it into my
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.emacs.d&lt;/code&gt;. Which is nasty. And of course this only works for small, single-file
packages. And, on top of that: whatever dependencies your vendored packages need
have to be listed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extraPackages&lt;/code&gt;, which confuses the packages &lt;em&gt;you&lt;/em&gt; want,
with the transitive dependencies of your vendored packages.&lt;/p&gt;

&lt;p&gt;I felt like &lt;a href=&quot;https://www.youtube.com/watch?v=viejY6UZ5Bk&amp;amp;t=39s&quot;&gt;the orange juice bit&lt;/a&gt; from &lt;em&gt;The Simpsons&lt;/em&gt;. There must be a
better way!&lt;/p&gt;

&lt;p&gt;And there is. With some help from Claude, I wrote this:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;customPackages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;inform7-mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacsPackages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;trivialBuild&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;pname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;inform7-mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;unstable&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fetchFromGitHub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;owner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;alexispurslane&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;repo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;inform7-mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;rev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;f99e534768c816ec038f34126f88d816c2f7d9ff&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;sha256&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sha256-r9Zzd8Ro3p+Bae11bf1WIeVWkbmg17RKLDqG4UcFT1o=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;packageRequires&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacsPackages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;s&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;in&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;home-manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;eudoxia&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;programs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;extraPackages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;epkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;epkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;customPackages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;inform7-mode&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Nix takes care of everything: commit pinning, security (with the SHA-256 hash),
dependencies for custom packages. And it works wonderfully.&lt;/p&gt;

&lt;p&gt;Armed with a new hammer, I set out to drive some nails.&lt;/p&gt;

&lt;h1 id=&quot;cabal-mode&quot;&gt;cabal-mode&lt;/h1&gt;

&lt;p&gt;Today I created a tiny Haskell project, and when I opened the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.cabal&lt;/code&gt; file,
noticed it had no syntax highlighting. I was surprised to find there’s no
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cabal-mode&lt;/code&gt; in MELPA. But coincidentally, someone started working on this
literally &lt;a href=&quot;https://github.com/webdevred/cabal-mode&quot;&gt;three weeks ago&lt;/a&gt;! So I wrote a small expression to package this
new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cabal-mode&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;cabal-mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacsPackages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;trivialBuild&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;pname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cabal-mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;unstable&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fetchFromGitHub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;owner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;webdevred&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;repo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cabal-mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;rev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;083a777e09bdb5a8d8d69862d44f13078664091f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;sha256&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sha256-c5dUsnEx+0uXFzxQLMnhiP8Gvwedzvq0F0BA+beBkmI=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;packageRequires&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h1 id=&quot;xcompose-mode&quot;&gt;xcompose-mode&lt;/h1&gt;

&lt;p&gt;A few weeks back I switched from macOS to Linux, and since I’m stuck on X11
because of &lt;a href=&quot;https://github.com/stumpwm/stumpwm&quot;&gt;stumpwm&lt;/a&gt;, I’m using &lt;a href=&quot;https://man.archlinux.org/man/XCompose.3.en&quot;&gt;XCompose&lt;/a&gt; to define keybindings for
entering dashes, &lt;a href=&quot;https://smartquotesforsmartpeople.com/&quot;&gt;smart quotes&lt;/a&gt; etc. It bothered me slightly that my
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.XCompose&lt;/code&gt; file didn’t have syntax highlighting. I found
&lt;a href=&quot;https://github.com/kragen/xcompose/blob/4d8eab4d05a19537ce79294ae0459fdae78ffb20/xcompose-mode.el&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xcompose-mode.el&lt;/code&gt;&lt;/a&gt; in &lt;a href=&quot;https://github.com/kragen/xcompose&quot;&gt;kragen’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xcompose&lt;/code&gt; repo&lt;/a&gt;, but it’s
slightly broken (it’s missing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provide&lt;/code&gt; call at the end). I started thinking
how hard it would be to write a Nix expression to modify the source after
fetching, when I found that &lt;a href=&quot;https://thomasvoss.com/&quot;&gt;Thomas Voss&lt;/a&gt; hosts a patched version
&lt;a href=&quot;https://git.thomasvoss.com/xcompose-mode&quot;&gt;here&lt;/a&gt;. Which made this very simple:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;xcompose-mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacsPackages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;trivialBuild&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;pname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;xcompose-mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;unstable&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fetchgit&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;git://git.thomasvoss.com/xcompose-mode.git&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;rev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;aeb03f9144e39c882ca6c5c61b9ed1300a2a12ee&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;sha256&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sha256-lPapwSJKG+noINmT1G5jNyUZs5VykMOSKJIbQxBWLEA=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;packageRequires&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h1 id=&quot;eat&quot;&gt;eat&lt;/h1&gt;

&lt;p&gt;Somehow the version of &lt;a href=&quot;https://codeberg.org/akib/emacs-eat&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eat&lt;/code&gt;&lt;/a&gt; in nixpkgs unstable was missing the
configuration option to use a custom shell. Since I want to use &lt;a href=&quot;https://www.nushell.sh/&quot;&gt;nu&lt;/a&gt; instead of
bash, I had to package this myself from the latest commit:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;eat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacsPackages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;trivialBuild&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;pname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eat&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;unstable&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fetchgit&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://codeberg.org/akib/emacs-eat.git&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;rev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;c8d54d649872bfe7b2b9f49ae5c2addbf12d3b99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;sha256&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sha256-9xG2rMlaMFY77JzUQ3JFrc7XKILZSL8TbP/BkzvBvMk=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;packageRequires&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacsPackages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;compat&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h1 id=&quot;lean4-mode&quot;&gt;lean4-mode&lt;/h1&gt;

&lt;p&gt;I started reading &lt;a href=&quot;https://lean-lang.org/functional_programming_in_lean/&quot;&gt;&lt;em&gt;Functional Programming in Lean&lt;/em&gt;&lt;/a&gt; recently, and while
there is a &lt;a href=&quot;https://github.com/leanprover-community/lean4-mode&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lean4-mode&lt;/code&gt;&lt;/a&gt;, it’s not packaged anywhere. This only required a
slight deviation from the pattern: when I opened a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.lean&lt;/code&gt; file I got an error
about a missing JSON file, consulting the README for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lean4-mode&lt;/code&gt;, it says:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you use a source-based package-manager (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package-vc.el&lt;/code&gt;, Straight or
Elpaca), then make sure to list the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;data&quot;&lt;/code&gt; directory in your Lean4-Mode
package recipe.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To do this I had to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;melpaBuild&lt;/code&gt; rather than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trivialBuild&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;lean4-mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacsPackages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;melpaBuild&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;pname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;lean4-mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;1.1.2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fetchFromGitHub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;owner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;leanprover-community&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;repo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;lean4-mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;rev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;1388f9d1429e38a39ab913c6daae55f6ce799479&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;sha256&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sha256-6XFcyqSTx1CwNWqQvIc25cuQMwh3YXnbgr5cDiOCxBk=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;packageRequires&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacsPackages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;dash&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;lsp-mode&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;magit-section&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;files&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&apos;&apos;(&quot;*.el&quot; &quot;data&quot;)&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Linux on the Fujitsu Lifebook U729</title>
        <description>A short review and troubleshooting guide.</description>
        <pubDate>Sat, 15 Nov 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/linux-on-the-fujitsu-lifebook-u729</link>
        <guid isPermaLink="true">
          https://borretti.me/article/linux-on-the-fujitsu-lifebook-u729
        </guid>
        
        <content:encoded>&lt;p&gt;This post describes my experience using Linux on the &lt;a href=&quot;https://www.fujitsu.com/my/products/computing/pc/ap/notebooks/lifebook-u729/&quot;&gt;Fujitsu Lifebook
U729&lt;/a&gt;. The tl;dr is that it’s a delightful laptop, and Linux runs
flawlessly, and all the hardware things I’ve needed run OOTB. The only
difficulty I had was in disabling Secure Boot, but I figured out how to do it,
which I explain below.&lt;/p&gt;

&lt;h1 class=&quot;no_toc&quot; id=&quot;contents&quot;&gt;Contents&lt;/h1&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#background&quot; id=&quot;markdown-toc-background&quot;&gt;Background&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#troubleshooting&quot; id=&quot;markdown-toc-troubleshooting&quot;&gt;Troubleshooting&lt;/a&gt;    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#secure-boot&quot; id=&quot;markdown-toc-secure-boot&quot;&gt;Secure Boot&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#spyware&quot; id=&quot;markdown-toc-spyware&quot;&gt;Spyware&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#non-problems&quot; id=&quot;markdown-toc-non-problems&quot;&gt;Non-Problems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bios-notes&quot; id=&quot;markdown-toc-bios-notes&quot;&gt;BIOS Notes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#links&quot; id=&quot;markdown-toc-links&quot;&gt;Links&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;background&quot;&gt;Background&lt;/h1&gt;

&lt;p&gt;From early 2024 my daily driver was an M2 MacBook Air, until earlier this year I
broke the screen, and the repair was quoted at almost 1000 AUD. Since I used it
as a desktop most of the time, this didn’t affect me much. After some
flip-flopping I decided to get an M4 Mac mini. Partly for the faster CPU and
more RAM, but partly because I liked the idea of LARPing like it’s the 2000s,
when computers, and by extension the Internet, where fixed in physical space,
rather than following everyone around.&lt;/p&gt;

&lt;p&gt;Of course this was a terrible idea. I had three working computers—a
Linux+Windows desktop, a Mac Mini, and a MacBook Air that I could use as a
desktop—and none of them were portable. When I went to &lt;a href=&quot;https://rustforgeconf.com/&quot;&gt;RustForge 2025&lt;/a&gt; I
just brought my phone. If I wanted to travel, even within Sydney, to a demo
night or math club or some such, I didn’t have a laptop to bring with me.&lt;/p&gt;

&lt;p&gt;So I needed a new laptop. And the &lt;a href=&quot;https://en.wikipedia.org/wiki/MacOS_Tahoe&quot;&gt;Tahoe release&lt;/a&gt; of macOS was so ugly
(see e.g. &lt;a href=&quot;https://x.com/zetalyrae/status/1968813221025865868&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;https://x.com/zetalyrae/status/1979654314244403272&quot;&gt;2&lt;/a&gt;, &lt;a href=&quot;https://x.com/zetalyrae/status/1971719256980312211&quot;&gt;3&lt;/a&gt;) it made me boot up the old Linux
desktop, and start playing around with &lt;a href=&quot;https://en.wikipedia.org/wiki/NixOS&quot;&gt;NixOS&lt;/a&gt; again. And I fell in love
with Linux again: with the tinkering and the experimentation and the freedom it
affords you.&lt;/p&gt;

&lt;p&gt;So, I wanted a Linux laptop. I had a &lt;a href=&quot;https://www.lenovo.com/au/en/p/laptops/thinkpad/thinkpadx1/x1-extreme-g4/22tp2x1x1e4&quot;&gt;ThinkPad X1&lt;/a&gt; some years ago and it was
terribly: flimsy plastic build and hardware that vastly underperformed its
price. I looked around for old, refursbished workstation laptops, and, randomly,
I ran into an eBay seller offering a refurbished Fujitsu laptop.&lt;/p&gt;

&lt;p&gt;The specs/price ratio was pretty good: 16 GiB of RAM and 512GiB of SSD, all for
250 AUD. And it was 12in and 1.1kg, which I like: laptops should be small and
lightweight. But the thing that got me, in all honesty, was the brand. “Fujitsu
laptop” sounds like colour in a William Gibson novel: “crawling into the
avionics bay, Case took out a battered Fujitsu refurb, and stuck a JTAG port in
the flight computer—”. I already use NixOS and a &lt;a href=&quot;https://elecomusa.com/products/deft-trackball-wireless-copy&quot;&gt;trackball&lt;/a&gt; and a
&lt;a href=&quot;https://www.amazon.com.au/dp/B07B8J6C3C&quot;&gt;mechanical keyboard&lt;/a&gt;, so a laptop that’s even more obscure than a
ThinkPad is perfect for me. And it was only 250 AUD. So I got it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/linux-on-the-fujitsu-lifebook-u729/laptop.webp&quot; alt=&quot;A photograph of the laptop.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The only problem I had was disabling Secure Boot in order to install
Linux. Otherwise: I love it. It’s small and lightweight, feels solid, the
keyboard is good, all the hardware works out of the box with NixOS, and the
battery life is pretty good.&lt;/p&gt;

&lt;h1 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h1&gt;

&lt;p&gt;This section describes the problems I encountered.&lt;/p&gt;

&lt;h2 id=&quot;secure-boot&quot;&gt;Secure Boot&lt;/h2&gt;

&lt;p&gt;I tried to install Linux the usual way, when I was greeted by this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/linux-on-the-fujitsu-lifebook-u729/secureboot.webp&quot; alt=&quot;A photograph of the laptop screen showing an error message, red text on white: &apos;secure boot is failed **access denied**&apos;.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Going into the BIOS, the option to disable &lt;a href=&quot;https://en.wikipedia.org/wiki/UEFI#Secure_Boot&quot;&gt;Secure Boot&lt;/a&gt; was greyed out. I
tried a bunch of random bullshit: wiping the TPM, disabling the TPM. That didn’t
work.&lt;/p&gt;

&lt;p&gt;What did work was this:&lt;/p&gt;

&lt;p&gt;First, install Windows 11. This came with the laptop. And the installation makes
installing Linux feel easy: I had to do so many weird tricks to avoid having to
create an account with Microsoft during the installation.&lt;/p&gt;

&lt;p&gt;Once Windows is installed, go into Windows Update. Under “Advanced Options &amp;gt;
Optional Updates”, there should be an option to install Fujitsu-specific
drivers. Install those. And for good measure, do a general Windows update.&lt;/p&gt;

&lt;p&gt;There should be a program called DeskUpdate on the Desktop. This is the Fujitsu
BIOS update tool. Run this and go through the instructions: this should update
the BIOS (the ordering seems to be important: first update the Fujitsu firmware
through Windows Update, then the BIOS through DeskUpdate).&lt;/p&gt;

&lt;p&gt;Reboot and go into the BIOS (F2). You should have a new BIOS version. In my
case, I went from BIOS 2.17 to 2.31 which was released on 2025-03-28:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/linux-on-the-fujitsu-lifebook-u729/bios.webp&quot; alt=&quot;A photograph of the BIOS screen showing BIOS version information.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You now have the option to disable Secure Boot:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/linux-on-the-fujitsu-lifebook-u729/secureboot-disable.webp&quot; alt=&quot;A photograph of the BIOS screen showing the option to disable Secure Boot.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After this, I was able to install NixOS from a live USB:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/linux-on-the-fujitsu-lifebook-u729/nixos.webp&quot; alt=&quot;A photograph of the laptop, showing the NixOS installer.&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;spyware&quot;&gt;Spyware&lt;/h2&gt;

&lt;p&gt;The laptop comes with this corporate spyware thing called &lt;a href=&quot;https://www.absolute.com/platform/persistence&quot;&gt;Absolute
Persistence&lt;/a&gt;. It’s some anti-theft tracking device. Since the Lifebook is
typically an enterprise laptop, it makes sense that it comes with this type of
thing.&lt;/p&gt;

&lt;p&gt;I only noticed this because I was searching the BIOS thoroughly for a way to
disable Secure Boot. The good news is disabling it is pretty straightforward:
you just disable it in the BIOS.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/linux-on-the-fujitsu-lifebook-u729/ap.webp&quot; alt=&quot;A photograph of the BIOS screen showing the Absolute Persistence options.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As I understand it, Absolute Persistence requires an agent running in the OS, so
the BIOS support, by itself, doesn’t do anything once disabled.&lt;/p&gt;

&lt;h1 id=&quot;non-problems&quot;&gt;Non-Problems&lt;/h1&gt;

&lt;p&gt;The following work flawlessly OOTB:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;WiFi&lt;/li&gt;
  &lt;li&gt;Bluetooth&lt;/li&gt;
  &lt;li&gt;Sound (using &lt;a href=&quot;https://en.wikipedia.org/wiki/PipeWire&quot;&gt;PipeWire&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Display brightness control (using &lt;a href=&quot;https://github.com/Hummer12007/brightnessctl&quot;&gt;brightnessctl&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Touchscreen (I didn’t realize the screen was actually a touchscreen until I
touched it by accident and saw the mouse move)&lt;/li&gt;
  &lt;li&gt;Webcam (not winning any awards on quality, but it works)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things I have not tested:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Microphone&lt;/li&gt;
  &lt;li&gt;Fingerprint sensor&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;bios-notes&quot;&gt;BIOS Notes&lt;/h1&gt;

&lt;p&gt;To enter the BIOS: smash &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F2&lt;/code&gt; until you hear the beep. No need to hold down the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fn&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;To enter the boot menu: as above but with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F12&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;links&quot;&gt;Links&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.fujitsu.com/my/products/computing/pc/ap/notebooks/lifebook-u729/&quot;&gt;Fujitsu product page&lt;/a&gt; (&lt;a href=&quot;https://web.archive.org/web/20230923090211/https://www.fujitsu.com/my/products/computing/pc/ap/notebooks/lifebook-u729/&quot;&gt;archive.org&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.fujitsu.com/hk/Images/ds-LIFEBOOK%20U729%20%28APAC%29.pdf&quot;&gt;Data sheet (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Agda on NixOS</title>
        <description>Troubleshooting notes.</description>
        <pubDate>Fri, 14 Nov 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/agda-on-nixos</link>
        <guid isPermaLink="true">
          https://borretti.me/article/agda-on-nixos
        </guid>
        
        <content:encoded>&lt;p&gt;To install &lt;a href=&quot;https://en.wikipedia.org/wiki/Agda_(programming_language)&quot;&gt;Agda&lt;/a&gt; and its standard library, add this to your config:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;systemPackages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;agda&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;withPackages&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;standard-library&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Or, using &lt;a href=&quot;https://github.com/nix-community/home-manager&quot;&gt;home-manager&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;home-manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;packages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;agda&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;withPackages&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;standard-library&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt; here stands for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nixpkgs.agdaPackages&lt;/code&gt; package set. Note that the
following &lt;em&gt;will not&lt;/em&gt; work:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;systemPackages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;agda&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;agdaPackages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;standard-library&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;If you use Emacs, you probably want &lt;a href=&quot;https://agda.readthedocs.io/en/stable/tools/emacs-mode.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agda2-mode&lt;/code&gt;&lt;/a&gt;, which if you use
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;home-manager&lt;/code&gt;, can be installed using Nix:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;home-manager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;eudoxia&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;programs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;emacs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;extraPackages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;epkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;epkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;agda2-mode&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now, if you have a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello.agda&lt;/code&gt; with:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-agda&quot;&gt;module hello where

open import Agda.Builtin.IO using (IO)
open import Agda.Builtin.Unit using (⊤)
open import Agda.Builtin.String using (String)

postulate putStrLn : String → IO ⊤
{-# FOREIGN GHC import qualified Data.Text as T #-}
{-# COMPILE GHC putStrLn = putStrLn . T.unpack #-}

main : IO ⊤
main = putStrLn &quot;Hello world!&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;agda hello.agda &lt;span class=&quot;c&quot;&gt;# typecheck&lt;/span&gt;
Checking hello &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/home/eudoxia/agda/t2/hello.agda&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;agda &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; hello.agda &lt;span class=&quot;c&quot;&gt;# compile&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;snip]
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;6 of 6] Linking /home/eudoxia/agda/t2/hello &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Objects changed]
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./hello
Hello world!
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;So far, so good. Using the standard library however is more complicated. If you
have a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec.agda&lt;/code&gt; with:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-agda&quot;&gt;module vec where

open import Data.Nat using (ℕ; zero; suc)

data Vec (A : Set) : ℕ → Set where
  []  : Vec A zero
  _∷_ : ∀ {n} (x : A) (xs : Vec A n) → Vec A (suc n)

infixr 5 _∷_
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agda vec.agda&lt;/code&gt; will not work:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;agda vec.agda
Checking vec &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/home/eudoxia/agda/t3/vec.agda&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
/home/eudoxia/agda/t3/vec.agda:3.1-42: error: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;FileNotFound]
Failed to find &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;of module Data.Nat &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;any of the following
locations:
  /home/eudoxia/agda/t3/Data/Nat.agda
  /home/eudoxia/agda/t3/Data/Nat.lagda
  /nix/store/[snip]/lib/prim/Data/Nat.agda
  /nix/store/[snip]/lib/prim/Data/Nat.lagda
when scope checking the declaration
  open import Data.Nat using &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;ℕ&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; zero&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; suc&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Instead, create a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec.agda-lib&lt;/code&gt; in the same directory with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;depend: standard-library
include: .
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agda vec.agda&lt;/code&gt; will succeed.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Hashcards: A Plain-Text Spaced Repetition System</title>
        <description>Announcing my latest open-source project.</description>
        <pubDate>Sun, 05 Oct 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/hashcards-plain-text-spaced-repetition</link>
        <guid isPermaLink="true">
          https://borretti.me/article/hashcards-plain-text-spaced-repetition
        </guid>
        
        <content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/eudoxia0/hashcards&quot;&gt;hashcards&lt;/a&gt; is a local-first &lt;a href=&quot;/article/effective-spaced-repetition&quot;&gt;spaced repetition&lt;/a&gt; app, along the lines of
&lt;a href=&quot;https://apps.ankiweb.net/&quot;&gt;Anki&lt;/a&gt; or &lt;a href=&quot;https://mochi.cards/&quot;&gt;Mochi&lt;/a&gt;. Like Anki, it uses &lt;a href=&quot;/article/implementing-fsrs-in-100-lines&quot;&gt;FSRS&lt;/a&gt;, the most advanced scheduling
algorithm yet, to schedule reviews.&lt;/p&gt;

&lt;p&gt;The thing that makes hashcards unique: it doesn’t use a database. Rather, your
flashcard collection is just a directory of Markdown files, like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Cards/
  Math.md
  Chemistry.md
  Astronomy.md
  ...
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And each file, or “deck”, looks like this:&lt;/p&gt;

&lt;div class=&quot;language-md highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Q: What is the role of synaptic vesicles?
A: They store neurotransmitters for release at the synaptic terminal.

Q: What is a neurite?
A: A projection from a neuron: either an axon or a dendrite.

C: Speech is [produced] in [Broca&apos;s] area.

C: Speech is [understood] in [Wernicke&apos;s] area.
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You write flashcards more or less like you’d write ordinary notes, with
lightweight markup to denote basic (question/answer) flashcards and &lt;a href=&quot;https://docs.ankiweb.net/editing.html#cloze-deletion&quot;&gt;cloze
deletion&lt;/a&gt; flashcards. Then, to study, you run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ hashcards drill &amp;lt;path to the cards directory&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;This opens a web interface on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost:8000&lt;/code&gt;, where you can review the
flashcards. Your performance and review history is stored in an &lt;a href=&quot;https://sqlite.org/&quot;&gt;SQLite&lt;/a&gt;
database in the same directory as the cards. Cards are content-addressed, that
is, identified by the hash of their text.&lt;/p&gt;

&lt;p&gt;This central design decision yields many benefits: you can edit your flashcards
with your editor of choice, store your flashcard collection in a &lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt; repo,
track its changes, share it on &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt; with others (&lt;a href=&quot;https://github.com/eudoxia0/flashcards&quot;&gt;as I have&lt;/a&gt;). You can
use scripts to generate flashcards from some source of structured data (e.g. a
CSV of English/French vocabulary pairs). You can query and manipulate your
collection using standard Unix tools, or programmatically, without having to dig
into the internals of some app’s database.&lt;/p&gt;

&lt;p&gt;Why build a new spaced repetition app? Mostly because I was dissatisfied with
both Anki and Mochi. But also, additionally, because my flashcards collection is
very important to me, and having it exist either in some remote database, or as
an opaque unusable data blob on my computer, doesn’t feel good. “Markdown files
in a Git repo” gives me a level of ownership that other approaches lack.&lt;/p&gt;

&lt;p&gt;The rest of this post explains my frustrations with Anki and Mochi, and how I
landed on the design decisions for hashcards.&lt;/p&gt;

&lt;h1 id=&quot;anki&quot;&gt;Anki&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://apps.ankiweb.net/&quot;&gt;Anki&lt;/a&gt; was the first SR system I used. It’s open source, so it will be around
forever; it has a million plugins; it was the first SR system to use &lt;a href=&quot;/article/implementing-fsrs-in-100-lines&quot;&gt;FSRS&lt;/a&gt; for
scheduling. It has really rich stats, which I think are mostly useless but are
fun to look at. And the &lt;a href=&quot;https://docs.ankiweb.net/getting-started.html#note-types&quot;&gt;note types&lt;/a&gt; feature is really good: it lets you
generate a large number of flashcards automatically from structured data.&lt;/p&gt;

&lt;p&gt;The central problem with Anki is that the interface is really bad. This
manifests in various ways.&lt;/p&gt;

&lt;p&gt;First, it is ugly to look at, particularly the review screen. And this
diminishes your enjoyment of what is already an often boring and frustrating
process.&lt;/p&gt;

&lt;p&gt;Second, doing simple things is hard. A nice feature of Mochi is that when you
start the app you go right into review mode. You’re drilling flashcards before
you even realize it. Anki doesn’t have a “study all cards due today”, rather,
you have to manually go into a deck and click the “Study Now” button. So what I
would do is put all my decks under a “Root” deck, and study that. But this is a
hack.&lt;/p&gt;

&lt;p&gt;And, third: card input uses WYSIWYG editing. So, you’re either jumping from the
keyboard to the mouse (which increases latency, and makes flashcard creation
more frustrating) or you have to remember all these keybindings to do basic
things like “make this text a cloze deletion” or “make this &lt;a href=&quot;https://docs.ankiweb.net/math.html&quot;&gt;TeX math&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;Finally, plugins are a double-edged sword. Because having the &lt;em&gt;option&lt;/em&gt; to use
them is nice, but the experience of &lt;em&gt;actually&lt;/em&gt; using most plugins is bad. The
whole setup feels janky, like a house of cards. Most of the time, if a feature
is not built into the app itself, I would rather live without it than use a plugin.&lt;/p&gt;

&lt;h1 id=&quot;mochi&quot;&gt;Mochi&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://mochi.cards/&quot;&gt;Mochi&lt;/a&gt; feels like it was built to address the main complaint about Anki: the
interface. It is intuitive, good looking, shortcut-rich. No jank. Instead of
WYSIWYG, card text is Markdown: this is delightful.&lt;/p&gt;

&lt;p&gt;There’s a few problems. While Markdown is a very low-friction way to write
flashcards, cloze deletions in Mochi are very verbose. In hashcards, you can
write this:&lt;/p&gt;

&lt;div class=&quot;language-md highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Speech is [produced] in [Broca&apos;s] area.
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The equivalent in Mochi is this:&lt;/p&gt;

&lt;div class=&quot;language-md highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Speech is {{1::produced}} in {{2::Broca&apos;s}} area.
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;This is a lot of typing. And you might object that it’s only a few characters
longer. But when you’re studying from a textbook, or when you’re copying words
from a vocabulary table, these small frictions add up. If writing flashcards is
frustrating, you’ll write fewer of them: and that means less knowledge
gained. Dually, a system that makes flashcard creation as frictionless as
possible means more flashcards, and more knowledge.&lt;/p&gt;

&lt;p&gt;Another problem is that Mochi doesn’t have an equivalent of Anki’s &lt;a href=&quot;https://docs.ankiweb.net/getting-started.html#note-types&quot;&gt;note
types&lt;/a&gt;. For example: you can make a note type for chemical elements, with
fields like atomic number, symbol, name, etc., and write templates to generate
flashcards asking questions like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What is the atomic number of [name]?&lt;/li&gt;
  &lt;li&gt;What element has atomic number [number]?&lt;/li&gt;
  &lt;li&gt;What is the symbol for [name]?&lt;/li&gt;
  &lt;li&gt;What element has symbol [symbol]?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on for other properties. This is good. Automation is good. Less work,
more flashcards. Mochi doesn’t have this feature. It has &lt;a href=&quot;https://mochi.cards/docs/#templates&quot;&gt;templates&lt;/a&gt;, but
these are not as powerful.&lt;/p&gt;

&lt;p&gt;But the biggest problem with Mochi, I think, is the algorithm. Until &lt;a href=&quot;https://x.com/MochiCardsApp/status/1924692507570667630&quot;&gt;very
recently&lt;/a&gt;, when they added beta support for FSRS, the algorithm used by
Mochi was even simpler than &lt;a href=&quot;/article/implementing-sm2-in-rust&quot;&gt;SM-2&lt;/a&gt;. It was based on &lt;a href=&quot;https://web.archive.org/web/20250914174025/https://mochi.cards/docs/faq/#which-spaced-repetition-algorithm-does-mochi-use&quot;&gt;multipliers&lt;/a&gt;:
remembering a card multiplies its interval by a number &amp;gt;1, forgetting a card
multiplies its interval by a number between 0 and 1.&lt;/p&gt;

&lt;p&gt;The supposed rationale for this is simplicity: the user can reason about the
algorithm more easily. But I think this is pointless. The whole point of an SR
app is the software manages the schedule for you, and the user is completely
unaware of how the scheduler works. The optimality is to have the most advanced
possible scheduling algorithm (meaning the one that yields the most recall for
the least review time) under the most intuitive interface possible, and the user
just reaps the benefits.&lt;/p&gt;

&lt;p&gt;Obviously without an RCT we can’t compare Mochi/&lt;a href=&quot;/article/implementing-sm2-in-rust&quot;&gt;SM-2&lt;/a&gt;/FSRS, but my subjective
experience of it is that the algorithm works well for the short-term, and
falters on the long-term. It’s very bad when you forget a mature card: if a card
has an interval of sixty days, and you click forget, you don’t reset the
interval to one day (which is good, because it helps you reconsolidate the lost
knowledge). Rather, the interval is multiplied by the forget multiplier (by
default: 0.5) down to &lt;em&gt;thirty days&lt;/em&gt;. What’s the use? If I forgot something after
sixty days, I surely won’t have better recall in thirty.&lt;/p&gt;

&lt;p&gt;You can fix this by setting the forget multiplier to zero. But you have to know
this is how it works, and, crucially: I don’t want to configure things! I don’t
want “scheduler parameter finetuning” to be yet another skill I have to acquire:
I want the scheduler to &lt;em&gt;just work&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In general, I think spaced repetition algorithms are too optimistic. I’d rather
see cards slightly more often, and spend more time reviewing things, than get
stuck in “forgetting hell”. But developers have to worry that making the system
too burdensome will hurt retention.&lt;/p&gt;

&lt;p&gt;In Anki, it’s the interface that’s frustrating, but the algorithm works
marvelously. In Mochi, the interface is delightful, but it’s the algorithm
that’s frustrating. Because you can spend months and months drilling flashcards,
building up your collection, but when the cards cross some invisible age
threshold, you start to forget them, and the algorithm does not help you relearn
things you have forgotten. Eventually I burned out on it and stopped doing my
reviews, because I expected to forget everything eventually anyhow. And now they
added support for FSRS, but by now I have 1700 cards overdue.&lt;/p&gt;

&lt;p&gt;Additionally: Mochi has only two buttons, “Forgot” and “Remembered”. This is
simpler for the user, yes, but most SR scheduling algorithms have more options
for a reason: different degrees of recall adjust the card parameters by
different magnitudes.&lt;/p&gt;

&lt;h1 id=&quot;hashcards&quot;&gt;Hashcards&lt;/h1&gt;

&lt;p&gt;What do I want from a spaced repetition system?&lt;/p&gt;

&lt;p&gt;The first thing is: card creation must be frictionless. I have learned that the
biggest bottleneck in spaced repetition, for me, is not doing the reviews (I am
very disciplined about this and have done SR reviews daily for months on end),
it’s not even converting conceptual knowledge into flashcards, the biggest
bottleneck is just entering cards into the system.&lt;/p&gt;

&lt;p&gt;The surest way to shore up your knowledge of some concept or topic is to write
more flashcards about it: asking the same question in different ways, in
different directions, from different angles. More volume means you see the same
information more often, asking in different ways prevents “memorizing the shape
of the card”, and it acts as a kind of redundancy: there are multiple edges
connecting that bit of knowledge to the rest of your mind.&lt;/p&gt;

&lt;p&gt;And there have been many times where I have thought: I would make this more
solid by writing another flashcard. But I opted not to because the marginal
flashcard is too effortful.&lt;/p&gt;

&lt;p&gt;If getting cards into the system involves a lot of friction, you write fewer
cards. And there’s an opportunity cost: the card you don’t write is a concept
you don’t learn. Integrated across time, it’s entire oceans of knowledge which
are lost.&lt;/p&gt;

&lt;p&gt;So: the system should make card entry effortless. This was the guiding principle
behind the design of the hashcards text format. For example, cloze deletions use
square brackets because in a US keyboard, square brackets can be typed without
pressing shift (compare Mochi’s curly brace). And it’s one bracket, not
two. Originally, the format was one line per card, with blank lines separating
flashcards, and question-answer cards used slashes to separate the sides, like
so:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;What is the atomic number of carbon? / 6

The atomic number of [carbon] is [6].
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And this is strictly less friction. But it creates a problem for multi-line
flashcards, which are common enough that they should not be a second-class
citizen. Eventually, I settled on the current format:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Q: What is the atomic number of carbon?
A: 6

C: The atomic number of [carbon] is [6].
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Which is only slightly more typing, and has the benefit that you can easily
visually identify where a card begins and ends, and what kind of card it is. I
spent a lot of time arguing back and forth with &lt;a href=&quot;https://claude.ai/&quot;&gt;Claude&lt;/a&gt; about what the optimal
format should be.&lt;/p&gt;

&lt;p&gt;Another source of friction is not creating the cards but &lt;em&gt;editing&lt;/em&gt; them. The
central problem is that your knowledge changes and improves over time. Often
textbooks take this approach where Chapter 1 introduces one kind of ontology,
and by Chapter 3 they tell you, “actually that was a lie, here’s the real
ontology of this subject”, and then you have to go back and edit the old
flashcards to match. Because otherwise you have one card asking, e.g., for the
undergraduate definition of some concept, while another asks you for the
graduate-level definition, creating ambiguity.&lt;/p&gt;

&lt;p&gt;For this reason, when studying from a textbook, I create a deck for the
textbook, with sub-decks for each chapter. That makes it easy to match the
flashcards to their source material (to ensure they are aligned) and each
chapter deck only has a few tens of cards usually, keeping them navigable.&lt;/p&gt;

&lt;p&gt;Sometimes you wrote multiple cards for the same concept, so you have to update
them all at once. Finding the related ones can be hard if the deck is large. In
hashcards, a deck is just a Markdown file. The cards immediately above and below
a card are usually semantically related. You just scroll up and down and make
the edits in place.&lt;/p&gt;

&lt;p&gt;But why plain-text files in a Git repo? Why not use the above format, but in a
“normal” app with a database?&lt;/p&gt;

&lt;p&gt;The vague idea of a spaced repetition system where flashcards are stored as
plain-text files in a Git repo had been kicking around my cranium for a long
time. I remember asking an Ankihead on IRC circa 2011 if such a thing
existed. At some point I read &lt;a href=&quot;https://notes.andymatuschak.org/My_implementation_of_a_personal_mnemonic_medium&quot;&gt;Andy Matuschak’s note&lt;/a&gt; on his
implementation of an SR system. In his system, the flashcards are colocated with
prose notes. The notation is similar to mine: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; tags for
question-answer cards, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{curly braces}&lt;/code&gt; for cloze deletions. And the cards
are content-addressed: identified by their hash. Which is an obviously good
idea. But his code is private and, besides, I feel that prose notes and
flashcards are very different beasts, and I don’t need or want them to mix.&lt;/p&gt;

&lt;p&gt;But I think the idea of plain-text spaced repetition got bumped up the priority
queue because I spontaneously started using a workflow that was similar to my
current hashcards workflow.&lt;/p&gt;

&lt;p&gt;When studying from a textbook or a website, I’d write flashcards in a Markdown
file. Usually, I used a shorthand like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[foo]&lt;/code&gt; for cloze deletions. Then I’d use
a Python script to transform the shorthand into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{{1::foo}}&lt;/code&gt; notation used by Mochi. And I’d edit the flashcards in the file, as
my knowledge built up and my sense of what was relevant and important to
remember improved. And then, when I was done with the chapter or document or
whatever, only then, I would manually import the flashcards into Mochi.&lt;/p&gt;

&lt;p&gt;And it struck me that the last step was kind of unnecessary. I was already
writing my flashcards as lightly-annotated Markdown in plain-text files. I had
&lt;a href=&quot;/article/implementing-fsrs-in-100-lines&quot;&gt;already implemented FSRS&lt;/a&gt; out of curiosity. I was looking for a
personal project to build during funemployment. So hashcards was by then a very
neatly-shaped hole that I just needed to paint inside.&lt;/p&gt;

&lt;p&gt;It turns out that using plain-text storage has many synergies:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You can edit the cards using whatever editor you use, build up a library of
card-creating macros, and navigate the collection using the editor’s file
browser.&lt;/li&gt;
  &lt;li&gt;You can query and update the collection using standard Unix tools, or a
programming language, e.g. using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wc&lt;/code&gt; to get the total number of words in the
collection, or using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt; to make a bulk-update to a set of cards.&lt;/li&gt;
  &lt;li&gt;You can use Git for version control. Git is infinitely more featureful than
the change-tracking of any SR app: you can edit multiple cards in one commit,
branch, merge, use pull requests, etc.&lt;/li&gt;
  &lt;li&gt;You can make your flashcards public on GitHub. I often wish people put more of
themselves out there: their blog posts, their dotfiles, their study notes. And
why not their flashcards? Even if they are not useful to someone else, there
is something enjoyable about reading what someone else finds interesting, or
enjoyable, or worth learning.&lt;/li&gt;
  &lt;li&gt;You can generate flashcards using scripts (e.g., turn a CSV of foreign
language vocabulary into a deck of flashcards), and write a Makefile to tie
the script, data source, and target together. I &lt;a href=&quot;https://github.com/eudoxia0/flashcards/blob/87b082e4723e5b1b286e3bb5378316f464cfc28f/Makefile&quot;&gt;do this&lt;/a&gt; in my
personal deck. Anki’s &lt;a href=&quot;https://docs.ankiweb.net/getting-started.html#note-types&quot;&gt;note types&lt;/a&gt; don’t have to be built into hashcards,
rather, you can DIY it using some Python and make.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a system where creating and editing flashcards is nearly
frictionless, that uses an advanced spaced repetition scheduler, and which
provides an elegant UI for drilling flashcards. I hope others will find it
useful.&lt;/p&gt;

</content:encoded>
      </item>
    
      <item>
        <title>Adding Planets to Celestia on macOS</title>
        <description>A beginner&apos;s guide to modding Celestia.</description>
        <pubDate>Tue, 29 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://borretti.me/article/adding-planets-to-celestia-on-macos</link>
        <guid isPermaLink="true">
          https://borretti.me/article/adding-planets-to-celestia-on-macos
        </guid>
        
        <content:encoded>&lt;p&gt;tl;dr: you have to modify the application bundle.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://celestiaproject.space/&quot;&gt;Celestia&lt;/a&gt; is a space simulator: you can fly around space and look at moons and exoplants, fast forward time. It is sometimes &lt;a href=&quot;https://bsky.app/profile/timberwind.bsky.social/post/3ldamhmnp622j&quot;&gt;used&lt;/a&gt; by sci-fi artists for worldbuilding because you can easily add new stars/planets/megastructures/spacecraft. Some people have built &lt;a href=&quot;https://no56.neocities.org/articles/ran&quot;&gt;whole virtual worlds&lt;/a&gt; for storytelling in Celestia. The &lt;a href=&quot;https://www.orionsarm.com/&quot;&gt;Orion’s Arm&lt;/a&gt; collaborative worldbuilding project has a collection of &lt;a href=&quot;https://www.orionsarm.com/page/326&quot;&gt;Celestia addons&lt;/a&gt; so you can explore the world of the year 10,000 AT.&lt;/p&gt;

&lt;p&gt;But the documentation is sparse and old. As with many things: the biggest hurdle to starting is just knowing which files go in which directories.&lt;/p&gt;

&lt;p&gt;Celestia uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ssc&lt;/code&gt; (solar system catalogue) files to define planets. These are plain-text files with a syntax resembling &lt;a href=&quot;https://github.com/hashicorp/hcl&quot;&gt;HCL&lt;/a&gt;. Let’s create baby’s first planet: below is a minimal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ssc&lt;/code&gt; file that adds a planet “Alpha” around the star &lt;a href=&quot;https://en.wikipedia.org/wiki/HN_Librae&quot;&gt;Gliese 555&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;Alpha&quot; &quot;Gliese 555&quot;
{
  Texture   &quot;asteroid.*&quot;
  Mass      1             # Earth masses
  Radius    6378          # km
  EllipticalOrbit {
    Period          1.0 # years
    SemiMajorAxis   1.0 # long axis
    Eccentricity    0.0 # circular
  }
}
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now, what you would hope is that there exists a standard directory like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.celestia/planets/&lt;/code&gt; you can put this into. I spent a lot of time looking through old docs and source code for this, and I’m writing this so others don’t have to. Unfortunately, at least on macOS, you have to modify the application &lt;a href=&quot;https://en.wikipedia.org/wiki/Bundle_(macOS)&quot;&gt;bundle&lt;/a&gt; itself. This feels morally wrong, but it works.&lt;/p&gt;

&lt;p&gt;Save the above code as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alpha.ssc&lt;/code&gt;, and execute:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /Applications/Celestia.app/Contents/Resources/CelestiaResources/extras/
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;alpha.ssc /Applications/Celestia.app/Contents/Resources/CelestiaResources/extras/alpha.ssc
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Open Celestia, and navigate to Gliese 555 (press enter, type “Gliese 555”, press enter, press g). You should see a new planet:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/adding-planets-to-celestia-on-macos/far.webp&quot; alt=&quot;A screenshot of Celestia showing the star Gliese 555, with the orbit of a new planet around it.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Zooming in, you can see it’s using the built-in asteroid texture:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/content/adding-planets-to-celestia-on-macos/near.webp&quot; alt=&quot;A screenshot of Celestia showing the planet Alpha around Gliese 555.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To verify it’s reading the right file, press tilde and use the arrow keys to scroll up the logs, and you should see a line like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;
  &lt;div class=&quot;highlight&quot;&gt;
    &lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Loading solar system catalog: extras/alpha.ssc
&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Celestia traverses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extras&lt;/code&gt; directory recursively, so you can put your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ssc&lt;/code&gt; files inside folders to organize large worldbuilding projects.&lt;/p&gt;

</content:encoded>
      </item>
    
  </channel>
</rss>
