07 Sep 2026
Planet Python
Graham Dumpleton: Live tracing with wrapture
When I wrote about unit testing with wrapture the pattern in every test was the same: create a binding on a method, open a timeline(), run the code, and read the recorded calls off the tape. What I did not say at the time is that nothing about a binding is specific to testing. A binding observes a call site and emits events, and what happens to those events is decided by whoever is listening. In a test the listener is a tape. Take the tape away and register something else, and the same binding narrates a running program as it goes.
That is the whole idea behind the tracing side of wrapture, and this post is the minimal version of it: the shop from the testing series, three bindings, and one sink.
The shop
The code is the order service from the earlier posts, grown just enough to have something worth watching. A card number now travels with the order, the gateway declines cards ending in four zeros, and each order belongs to a tenant.
class CardDeclined(Exception):
pass
class Gateway:
def charge(self, amount, card):
if card.endswith("0000"):
raise CardDeclined(f"card ending {card[-4:]} declined")
return {"id": f"ch_{amount}", "amount": amount}
def refund(self, charge_id):
return {"id": f"re_{charge_id}"}
class Ledger:
def record(self, entry):
return f"led_{entry['id']}"
class Notifier:
def send(self, message):
return True
class OrderService:
def __init__(self, gateway=None, ledger=None, notifier=None):
self.gateway = Gateway() if gateway is None else gateway
self.ledger = Ledger() if ledger is None else ledger
self.notifier = Notifier() if notifier is None else notifier
def place(self, amount, card, tenant):
charge = self._take_payment(amount, card)
try:
self.ledger.record(charge)
except Exception:
self.gateway.refund(charge["id"])
raise
self.notifier.send(f"order {charge['id']} placed")
return charge
def _take_payment(self, amount, card):
return self.gateway.charge(amount, card)
That lives in shop.py. A second module, orders.py, places three orders, one of which will be declined:
from shop import CardDeclined, OrderService
ORDERS = [
(500, "4111-1111-1111-1111", "acme"),
(250, "4000-0000-0000-0000", "globex"),
(120, "5555-4444-3333-2222", "globex"),
]
def run():
service = OrderService()
for amount, card, tenant in ORDERS:
try:
service.place(amount, card, tenant=tenant)
except CardDeclined:
pass
The question to answer is a simple one. When an order is placed, what actually happens? Which methods run, with what, and what comes back? A log line would answer that only in the places where someone had already thought to add one, and this code has none.
Three bindings and a sink
The entry point applies a binding to each of the three methods that matter and registers a Printer, which is the simplest sink wrapture ships: it prints each event to standard error as it happens.
import wrapture
from shop import Gateway, Ledger, OrderService
import orders
wrapture.binding(OrderService, "place").apply()
wrapture.binding(Gateway, "charge").apply()
wrapture.binding(Ledger, "record").apply()
wrapture.add_sink(wrapture.Printer())
orders.run()
There is no timeline() anywhere in that. The bindings are applied for the life of the process, the sink is registered for the life of the process, and events flow from one to the other. Running it, the output is:
shop:OrderService.place(amount=500, card='4111-1111-1111-1111', tenant='acme')
shop:Gateway.charge(amount=500, card='4111-1111-1111-1111')
shop:Gateway.charge -> {'id': 'ch_500', 'amount': 500} [8us]
shop:Ledger.record(entry={'id': 'ch_500', 'amount': 500})
shop:Ledger.record -> 'led_ch_500' [6us]
shop:OrderService.place -> {'id': 'ch_500', 'amount': 500} [239us]
shop:OrderService.place(amount=250, card='4000-0000-0000-0000', tenant='globex')
shop:Gateway.charge(amount=250, card='4000-0000-0000-0000')
shop:Gateway.charge !! CardDeclined [5us]
shop:OrderService.place !! CardDeclined [60us]
shop:OrderService.place(amount=120, card='5555-4444-3333-2222', tenant='globex')
shop:Gateway.charge(amount=120, card='5555-4444-3333-2222')
shop:Gateway.charge -> {'id': 'ch_120', 'amount': 120} [4us]
shop:Ledger.record(entry={'id': 'ch_120', 'amount': 120})
shop:Ledger.record -> 'led_ch_120' [3us]
shop:OrderService.place -> {'id': 'ch_120', 'amount': 120} [104us]
Each operation gets a line when it begins, indented by how deeply it is nested, and a closing line with the outcome and how long it took. A -> marks a return value and !! marks an exception, so the declined card is visible at a glance, and so is the fact that Ledger.record never ran for that order. These are the real arguments and the real results, the same -> and !! markers that tape.tree() uses in a test, only arriving live rather than being reconstructed afterwards.
The first thing I noticed in that output is something the trace should not contain. The card numbers are in it, in full, because the bindings captured the arguments as given. The same redact() capture policy the testing series used for keeping secrets off a tape works here, since the binding is the same object:
wrapture.binding(OrderService, "place", capture=wrapture.redact("card")).apply()
wrapture.binding(Gateway, "charge", capture=wrapture.redact("card")).apply()
With that in place the opening lines read card='<redacted>' and everything else is unchanged. I have left it on for the rest of the post, since a trace that is going to be looked at, streamed to a file, or sent anywhere, is exactly the place a card number should not be.
What it costs when nobody is listening
The obvious worry about leaving bindings applied in a program is what they cost when nothing is being traced. The recording gate in wrapture is not "is there a timeline" but "is anything listening". A tape scoped to a test is one kind of listener, a process sink is another, and when neither is present an applied binding constructs no event at all. The wrapped method runs with only wrapt's own dispatch on top, which the documentation puts at about half a microsecond per call on the machine it was measured on. That is what makes it reasonable to bind the interesting methods once, in the entry point, and let the sink decide whether anything is recorded.
Seeing less
Three orders is a readable trace. Three thousand is not, and the answer is rarely to bind fewer things, because the point of binding the layers is to have them there when a question comes up. The tools for narrowing sit either at the sink or at the binding.
At the sink, combinators wrap a sink and gate what reaches it. Depth(1, ...) forwards only the roots of each tree, which turns the trace into one opening and one closing line per order:
wrapture.add_sink(wrapture.Depth(1, wrapture.Printer()))
shop:OrderService.place(amount=500, card='<redacted>', tenant='acme')
shop:OrderService.place -> {'id': 'ch_500', 'amount': 500} [149us]
shop:OrderService.place(amount=250, card='<redacted>', tenant='globex')
shop:OrderService.place !! CardDeclined [33us]
shop:OrderService.place(amount=120, card='<redacted>', tenant='globex')
shop:OrderService.place -> {'id': 'ch_120', 'amount': 120} [46us]
At the binding, when= takes a predicate that is consulted before any event exists. A falsey answer means no event is constructed, no arguments are captured and nothing is delivered, which is the cheap way to narrow a hot call site. Here it records orders for one tenant only:
def acme_only(instance, args, kwargs):
return kwargs.get("tenant") == "acme"
place = wrapture.binding(OrderService, "place", when=acme_only,
capture=wrapture.redact("card")).apply()
Running the three orders again gives this:
shop:OrderService.place(amount=500, card='<redacted>', tenant='acme')
shop:Gateway.charge(amount=500, card='<redacted>')
shop:Gateway.charge -> {'id': 'ch_500', 'amount': 500} [7us]
shop:Ledger.record(entry={'id': 'ch_500', 'amount': 500})
shop:Ledger.record -> 'led_ch_500' [6us]
shop:OrderService.place -> {'id': 'ch_500', 'amount': 500} [245us]
shop:Gateway.charge(amount=250, card='<redacted>')
shop:Gateway.charge !! CardDeclined [5us]
shop:Gateway.charge(amount=120, card='<redacted>')
shop:Gateway.charge -> {'id': 'ch_120', 'amount': 120} [4us]
shop:Ledger.record(entry={'id': 'ch_120', 'amount': 120})
shop:Ledger.record -> 'led_ch_120' [4us]
The globex orders are gone, but their gateway and ledger calls are not. A when= decline skips exactly one event, the declined operation's own, and whatever records beneath it still records, now with nothing above it, so each inner call turns up as an anonymous root with no place to explain it. Sometimes that is exactly what you want, since a binding whose only job is to intervene in a call should not silence what runs beneath it. When the intent is "nothing from here down", tree=True says so:
place = wrapture.binding(OrderService, "place", when=acme_only, tree=True,
capture=wrapture.redact("card")).apply()
shop:OrderService.place(amount=500, card='<redacted>', tenant='acme')
shop:Gateway.charge(amount=500, card='<redacted>')
shop:Gateway.charge -> {'id': 'ch_500', 'amount': 500} [7us]
shop:Ledger.record(entry={'id': 'ch_500', 'amount': 500})
shop:Ledger.record -> 'led_ch_500' [6us]
shop:OrderService.place -> {'id': 'ch_500', 'amount': 500} [251us]
Now the decline covers the whole extent of the declined operation, and the trace is one tenant's orders and nothing else. The skipped calls are not simply lost, either. Each binding counts the operations it declined on filtered_calls, and after this run place, charge and record report 2, 2 and 1 respectively (the second globex order raised before reaching the ledger), so a trace shorter than expected can be explained rather than guessed at.
Where this leaves things
The whole intervention is a few lines in the program's entry point: bind the methods that matter, register a sink, and the program describes what it is doing as it runs, with real arguments and real results, and costs next to nothing when nothing is listening. The sink protocol itself is three notifications, so a sink that counts, samples, filters, or writes somewhere of your own is a small class, and the ad-hoc tracing page of the documentation covers that side, along with the other combinators and the collectors that keep numbers rather than events.
Those few lines in the entry point are still lines in the program, though. For code you cannot or would rather not edit, they can move out of the program entirely, into a file that sits next to it.
07 Sep 2026 1:39am GMT
06 Sep 2026
Planet Twisted
Glyph Lefkowitz: ... but what about video games?
I get asked this rhetorical question a lot, in various forms:
Sure, datacenters might use a lot of energy, but you don't have to use a hosted frontier model to do software development. What if I just run a local open-weights model to do some coding, with an open-source coding agent? Video games also use my GPU. Is local model development any worse than playing a video game?
So I want to write down my comprehensive answer to this: Yes, using an LLM to write some code is worse than playing a video game, for a few reasons.
Video Games Are Interactive, LLMs Are Batch Jobs
Video games use compute to respond to human input. You are using your GPU while you are looking at a screen, displaying an image. When you are done playing, you shut off the game, and your computer goes back to idle. It's much less energy. By contrast, agentic loops with evals (the only kind of "AI" that is meaningfully any good at coding) are running hot, for days. To use the most recent example of such a thing, a very rough first sketch of an implementation of a Windows graphics API backend to help port a paint program to other platforms, it took 3 weeks of Claude time, "day and night". Do you play a lot of video games for 500 hours to make it past the tutorial level, while also using other computers for other things, as well as the rest of your carbon footprint?
Video Games Need Development, LLMs Need Training
Video games use compute to respond to human input during development, too. Your game has to be made, but your LLM has to be trained. LLMs use a historically extreme amount of power, probably using more than the entire Internet, but it's kind of hard to say. Still, it seems a reasonable estimate to within several orders of magnitude that even over a multi-year project with hundreds of developers, the power used to develop an individual video game is nowhere close to training even a small LLM.
This is true even for local models. OpenAI has openly claimed that DeepSeek "stole its intellectual property", and I have heard grumblings that none of the open-weights generalist models could realistically exist without the massive lift that the frontier labs are doing with their training, in various other ways too. Secrecy throughout the industry makes this kind of impossible to understand rigorously, but it seems fair to say that you are partially culpable for all that famously energy-intensive frontier lab training if you're using a local model.
And They Keep Needing Training
You also can't dismiss this as a sunk cost, because in order to stay current with industry developments, models need to be updated with new information from the rest of the world, which means that you need to keep training them. Beyond the energy for your own use, if you want a real-life agentic workflow that actually does useful stuff, practically speaking you would still need to update your local models over and over again, at least once every few months, which means you would be incentivizing continued energy consumption by whoever was doing that training for you, including the energy cost of scraping.
Let's Be Real Here, You Aren't Actually Using A Local Model
This question is a hypothetical thought experiment. Despite synthetic benchmarks that keep showing there isn't much difference between open weight and frontier models, nobody's actually using local models for much of anything beyond sharing those talking points. Depending on which benchmark you're looking at, maybe it's good enough or maybe it's worse.
As an inveterate AI hater, all these systems seem pretty bad to me, but it seems that people who find them useful tend to subjectively believe the frontier models are worth the premium, and that's what they're actually using. Once you have accepted that it is OK to use LLMs for coding at all, it seems like a very quick slippery slope on down to "we'll go ahead and use the frontier models for now anyway, but we could be ethically better in the future by switching to an open weights one, that option is always available".
There's A Reason We Have Data Centers
Devolving power usage to local LLMs might be good to make users responsible for their costs and decrease the impacts to communities that are physically next to huge concentrations of power utilization, not to mention generation. However, there's a reason that it makes sense for the providers to build these giant facilities: economies of scale reduce total power consumption, they don't increase it. If you do all the same stuff with a local model that they have to do in hosted environments, it will probably take more power, even though you will be incentivized to do different stuff. This incentive to "do different stuff" is why although local models can hypothetically hold their own against the frontier labs for some tasks, when people or businesses take their inference costs in-house they often find that it's too painful and move back to hosted LLMs.
There Are Problems Other Than Power
These are subjects for a different post, but you have to consider a lot of other externalities: AI psychosis, de-skilling, comprehension debt, cultivating a dependency, introducing security defects, limiting your design space based on what LLMs can understand, context rot, wasting time on invalid solutions, introducing unpredictability into your workflows. You still have to consider the total cost benefit ratio.
To Sum Up
Local LLMs might alleviate some of the harms from using the hosted frontier providers. There are fewer privacy concerns, you can measure your power utilization and be more directly responsible for it, you can build interfaces with affordances that are less oriented towards addiction and dependency than the major frontier labs' harnesses.
But they're not automatically "the same as playing a video game" just because they can use the same GPU.
Acknowledgments
Thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor!
06 Sep 2026 10:57pm GMT
Planet Python
Glyph Lefkowitz: ... but what about video games?
I get asked this rhetorical question a lot, in various forms:
Sure, datacenters might use a lot of energy, but you don't have to use a hosted frontier model to do software development. What if I just run a local open-weights model to do some coding, with an open-source coding agent? Video games also use my GPU. Is local model development any worse than playing a video game?
So I want to write down my comprehensive answer to this: Yes, using an LLM to write some code is worse than playing a video game, for a few reasons.
Video Games Are Interactive, LLMs Are Batch Jobs
Video games use compute to respond to human input. You are using your GPU while you are looking at a screen, displaying an image. When you are done playing, you shut off the game, and your computer goes back to idle. It's much less energy. By contrast, agentic loops with evals (the only kind of "AI" that is meaningfully any good at coding) are running hot, for days. To use the most recent example of such a thing, a very rough first sketch of an implementation of a Windows graphics API backend to help port a paint program to other platforms, it took 3 weeks of Claude time, "day and night". Do you play a lot of video games for 500 hours to make it past the tutorial level, while also using other computers for other things, as well as the rest of your carbon footprint?
Video Games Need Development, LLMs Need Training
Video games use compute to respond to human input during development, too. Your game has to be made, but your LLM has to be trained. LLMs use a historically extreme amount of power, probably using more than the entire Internet, but it's kind of hard to say. Still, it seems a reasonable estimate to within several orders of magnitude that even over a multi-year project with hundreds of developers, the power used to develop an individual video game is nowhere close to training even a small LLM.
This is true even for local models. OpenAI has openly claimed that DeepSeek "stole its intellectual property", and I have heard grumblings that none of the open-weights generalist models could realistically exist without the massive lift that the frontier labs are doing with their training, in various other ways too. Secrecy throughout the industry makes this kind of impossible to understand rigorously, but it seems fair to say that you are partially culpable for all that famously energy-intensive frontier lab training if you're using a local model.
And They Keep Needing Training
You also can't dismiss this as a sunk cost, because in order to stay current with industry developments, models need to be updated with new information from the rest of the world, which means that you need to keep training them. Beyond the energy for your own use, if you want a real-life agentic workflow that actually does useful stuff, practically speaking you would still need to update your local models over and over again, at least once every few months, which means you would be incentivizing continued energy consumption by whoever was doing that training for you, including the energy cost of scraping.
Let's Be Real Here, You Aren't Actually Using A Local Model
This question is a hypothetical thought experiment. Despite synthetic benchmarks that keep showing there isn't much difference between open weight and frontier models, nobody's actually using local models for much of anything beyond sharing those talking points. Depending on which benchmark you're looking at, maybe it's good enough or maybe it's worse.
As an inveterate AI hater, all these systems seem pretty bad to me, but it seems that people who find them useful tend to subjectively believe the frontier models are worth the premium, and that's what they're actually using. Once you have accepted that it is OK to use LLMs for coding at all, it seems like a very quick slippery slope on down to "we'll go ahead and use the frontier models for now anyway, but we could be ethically better in the future by switching to an open weights one, that option is always available".
There's A Reason We Have Data Centers
Devolving power usage to local LLMs might be good to make users responsible for their costs and decrease the impacts to communities that are physically next to huge concentrations of power utilization, not to mention generation. However, there's a reason that it makes sense for the providers to build these giant facilities: economies of scale reduce total power consumption, they don't increase it. If you do all the same stuff with a local model that they have to do in hosted environments, it will probably take more power, even though you will be incentivized to do different stuff. This incentive to "do different stuff" is why although local models can hypothetically hold their own against the frontier labs for some tasks, when people or businesses take their inference costs in-house they often find that it's too painful and move back to hosted LLMs.
There Are Problems Other Than Power
These are subjects for a different post, but you have to consider a lot of other externalities: AI psychosis, de-skilling, comprehension debt, cultivating a dependency, introducing security defects, limiting your design space based on what LLMs can understand, context rot, wasting time on invalid solutions, introducing unpredictability into your workflows. You still have to consider the total cost benefit ratio.
To Sum Up
Local LLMs might alleviate some of the harms from using the hosted frontier providers. There are fewer privacy concerns, you can measure your power utilization and be more directly responsible for it, you can build interfaces with affordances that are less oriented towards addiction and dependency than the major frontier labs' harnesses.
But they're not automatically "the same as playing a video game" just because they can use the same GPU.
Acknowledgments
Thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor!
06 Sep 2026 10:57pm GMT
The Python Coding Stack: How I Code (Late 2026 Version)
You open a blank .py file in your favourite IDE. You have a blank page in front of you. You start writing code.
This is how it used to be. And perhaps it's how it still is for you. This is how I wrote computer programs just over half a year ago, too. But things started changing gradually for me earlier in 2026.
Last week, I ran a live course. In the first few minutes of the first live session, I opened a blank .py file in my IDE. There was nothing on my screen. Then I wrote code. Word by word. Line by line.
And about half an hour into this first session, it dawned on me: the last time I had gone through this process of opening a blank file and writing code from scratch was the previous time I had to run a live course a couple of months earlier.
My Agent and I
Every bit of code I had worked on in between these two live courses was written by my AI agent. I was very much involved in those processes, and I read and reviewed lots of code. But I didn't write any.
Like many, I have mixed feelings about this. I'm getting a lot more done. But I miss the process of writing code, exploring options, putting a project together, function by function, class by class, module by module.
The Coding Gym
I'm going to force myself to write code. I'll make time for it, as I enjoy it too much and I don't want to lose the skills and fluency. But it will be like going for a run or lifting weights in the gym. I don't run because I need to get from point A to point B quickly and I don't lift weights because I need to move those weights from one place to another. I do those things to keep my body healthy and strong. I'll code by hand to do the same thing for my mind.
A Real World Example
But let's go back to how I code now, in late 2026. And let me give you an example of something I worked on last week and used successfully this weekend.
It's not Python code. It's a Google Sheets spreadsheet. But that doesn't matter. The process is the same.
The Scenario
I'm a member of an athletics club (or 'track and field', depending on which flavour of English you speak). This weekend we had our club championships and I was tasked to take care of results and overall points. We award a shield for the best track performance and another for the best field performance.
The Pain Point
Last year I was asked to help out at the last minute (and by "last minute" I mean it almost literally, as it was 20 minutes before the meeting started). It was a nightmare. I wasn't responsible for collating the results, that was done by someone else manually as if it was 1965. I just had to work out their performance points by tapping in lots of numbers into an online calculator using my phone. A laptop would have been easier, but I was only tasked with this job on the day and I only had a phone with me.
This year I had more notice, so I chose to make my life easier... and the whole process smoother.
The Problem
The problem we're trying to solveโฆ
โฆis not rocket science. Officials on the track or in the field write results on result sheets and they're passed on to the result room by a runner. These sheets contain the name of the event, the athletes' bib numbers, and their performances. So a result slip may look like this:
| Event: U16 Boys 100m-1 | |
| ---------------------- | --------------- |
| Athlete | Performance (s) |
| 23 | 11.5 |
| 47 | 11.8 |
| 13 | 11.9 |Here's what happened last year:
-
My colleague had to cross-reference bib numbers with a printed-out sheet of registered athletes. She matched names and age groups, then she sorted results into separate sheets for each event and age group.
-
I had to look up each athlete's date of birth from a spreadsheet I'd been given that morning (browsing spreadsheets on a phone screen is not fun), then tap their age, event, and performance into an online calculator, one by one, and write the resulting points on a separate sheet.
The points allow you to compare performances by athletes of different ages in different events, so that we could then find the single best track performance and the single best field performance.
I was already thinking, at last year's event, of the Python program I could write to automate all of this.
The Solution (I would have never bothered with in 2025)
So, fast-forward to a couple of weeks ago. This time I had some time to come up with something. But I opted to create a Google Sheet instead of a Python program for two reasons:
-
It's much easier for others in the club to use it and share it in the future.
-
My usual objection that it's easier and more fun to write a Python program than create a complex spreadsheet full of linked tabs and formulae didn't apply. Either way, it was my agent who was going to do the hard work.
And here's the thing. If I was doing this last year, when I was still coding most things manually (and occasionally opening a ChatGPT window to ask a few things), I still wouldn't have done this. I wouldn't have had the time.
But this year was different. Sure, if anything I was busier this year than I was last year. But this year I had agents at my fingertips. Agents who know me and have been working alongside me for a while.
Here's why I wouldn't have bothered doing this myself:
-
The age group cut-off dates are different for younger age groups (31 August), older age groups (31 December), and Masters athletes (over 35s, where the age group is determined on the day of the competition.)
-
There are published points tables to compare performances in different events for senior athletes.
-
There are published points tables to compare performances in the same event for older athletes, above 30.
-
There are older tables to deal with age comparisons for under 30s
The system needs to take care of all this. None of it is too difficult to write in a Python program or to create one of those power-spreadsheets that link everything together. But it would have required some time and patience, which I didn't have.
But here's what I had time forโฆ
I was using an agent I communicate with through Discord, which I also have on my phone. I have a good dictation tool on my phone, too. So in the past week, whenever I was preparing dinner, or waiting in line at the shops, or sitting on the sofa in the evening watching TV, I could have a chat with my agent to guide him (it?) to what I wanted. I knew what I wanted. I just didn't have the time or desire to do it myself.
And after a few days of these on-and-off conversations, I had a Google Sheet with 20 tabs, including all the points tables for the various scenarios, all the age group rules, all the registered athletes, who-knows-how-many formulae linking columns, rows, and tabs, and, importantly, just two tabs to input results, one for track events and one for field events.
You just enter the event session (selecting from a drop-down menu), the bib number, and the performance. And that's it. The spreadsheet works out each age group results, the points for each athlete, it shows a live points leaderboard, and so on.
As I said, none of this is rocket science. I know I would have been able to create this spreadsheet by hand, or write Python code that does the same thing. But I would have needed more time. And I wouldn't have been able to multitask as I did last week.
Oh, and one more thing: I also asked my agent to check the spreadsheet every 15 minutes during our club championships, read the latest results, and post them on a Telegram channel I could share with everyone at the track. So we had live results published online too. I'd never have bothered to set that up manually.
Programming Will Never Be The Same
The real-world example I described above doesn't represent every programming task I work on with the help of my AI agents. This was a hobby-type project that I probably wouldn't have worked on otherwise because I didn't have the time. It wasn't too difficult, but it would have been time-consuming to do by hand. The stakes weren't high. Sure, we didn't want to make mistakes when assigning medals and shields, but it was easy to spot obvious mistakes, and this wasn't the Olympic Games, either!
I'll write about other case studies soon, including ones where I was more closely involved in the nitty-gritty of the Python code even though I didn't write any, nor make any changes to the code by hand.
Do you want to master Python and programming one article at a time, even in this age of AI? Then don't miss out on the articles in The Club which are exclusive to premium subscribers here on The Python Coding Stack
Coming Soonโฆ Exploring SOLID Through AI-Assisted Coding
I'm starting a series on the SOLID principles here on The Python Coding Stack soon. I'll write posts about each of the five principles, and I'll do it my way, the same style I always use when tackling Python topics.
I'll also have a series of posts, which will include video, where I'll work on a project from beginning to end using my AI agent and reviewing the code it writes. This project will meander through several OOP concepts, and you'll be able to see the SOLID principles come in naturally into the project as solutions to problems we might encounter as the project grows.
Stay tuned.
How far are you in the traditional-coding-to-AI-coding arc? Leave a comment and let's compare notes!
Join The Club, the exclusive area for paid subscribers for more Python posts, videos, a members' forum, and more.
You can also support this publication by making a one-off contribution of any amount you wish.
For more Python resources, you can also visit Real Python-you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You'd like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com
06 Sep 2026 8:25pm GMT
04 Sep 2026
Django community aggregator: Community blog posts
Issue 353: DjangoCon US Recaps Galore!
News
Django Developers Survey 2026 results
The fifth annual survey run with JetBrains is out, with the full report, infographics, and a companion writeup titled "The State of Django 2026: Boring is so back."
Help test Python 3.15!
Python release manager Hugo van Kemende kindly requests you add 3.15 and allow-prereleases: true to your GitHub Actions matrix and publish wheels before the October 1 release.
Releases
Django bugfix release issued: 6.1.1
Twelve fixes, nearly all of them 6.1 regressions: admin changelist search crashes, ModelAdmin.list_display traversing multiple relations, __in returning empty querysets, and DecimalField without precision on SQLite.
Python 3.15.0 candidate 2 is here!
The last planned release candidate, carrying 144 bugfixes from 76 contributors since rc1, ahead of the October 1 final.
Django Software Foundation
DEP 0019: Technical Governance for Django
Now accepted, DEP 19 supersedes DEP 10 and DEP 12 as Django's single technical governance document, and trades hard eligibility rules for eight qualitative traits that Steering Council candidates should show three or more of. The five-member council keeps binding authority over technical decisions, with elections triggered by the final feature release of a major release series, a drop below three elected members, or a council vote.
DSF member of the month - Benjamin Balder Bach
The django-money maintainer and Django Day Copenhagen organizer on closing the distance between developers and the people who use what they build.
Djangonaut Space News
Djangonaut Space - Session 7 Accepting Applications
Applications for the eight-week mentorship program close September 6 Anywhere on Earth, with the session starting October 12.
Python Software Foundation
The 2026 PSF Board Election is Open!
Eligible members can approve up to 17 candidates for four open seats, and ballots cannot be changed once cast, so read the nominee statements before voting closes September 15 at 2:00 pm UTC.
Inaugural Python Packaging Council Election: Voting is now open!
The first Packaging Council election is open to members who affirmed their intent to vote, and closes at the same September 15 deadline.
Metadata requests no longer tracked in PyPI download counts
PyPI now counts only .whl, .tar.gz, and .zip requests, so BigQuery data breaks permanently at 2026-08-24: about 39% of urllib3's earlier counts turned out to be metadata and other non-distribution files.
Wagtail CMS News
Our DjangoConUS 2026 photo album ๐ท
Meagen Voss shares photos from Chicago rather than a talk recap, including her first main-stage talk on Wagtail's approach to AI.
Updates to Django
Today, "Updates to Django" is presented by Raffaella from Djangonaut Space! ๐
Last week we had 5 pull requests merged into Django by 5 different contributors - including 2 first-time contributors! Congratulations to Iaroslav and Tyler Russin for having their first commits merged into Django - welcome on board!
News in Django 6.1:
- As default model ordering is now applied to combined querysets,
union(),difference(), andintersection()raiseDatabaseErrorwhen a field inOptions.orderingisn't selected byvalues()orvalues_list(). Callorder_by()without arguments before combining to clear the default ordering.
- Fixed a bug where
DecimalFieldwithoutmax_digitsanddecimal_placescaused a crash when retrieving values on SQLite (#37275).
- Fixed a regression that caused a crash when iterating a
QuerySetof a model overridingModel.from_db()without the newfetch_modekeyword argument. Such overrides now work again, but are deprecated and should be updated to acceptfetch_mode(#37259).
Playwright is replacing Selenium for integration tests ๐
Django Fellow Reports
Django Fellow Report - Jacob
I had a rejuvenating week attending, presenting at, and sprinting during DjangoCon US. I'm still relatively new to this community, so I'm still allowed to be impressed with everyone's gracious and welcoming attitudes. A smattering of things falling under the usual categories this week.
Django Fellow Report - Sarah
Was in Chicago (๐ฌ๏ธ ๐๏ธ ๐ญ ๐บ๐ธ) for DjangoCon US ๐. It was a fantastic conference and a lovely city. Delivered a keynote which went "good enough" and my baby boy managed with the jetlag reasonably well ๐. Came away from the conference with a few ideas and energy from engaging with our community
Django Fellow Report - Natalia
A week of holidays ๐๏ธ ๐ก ๐ฆ followed by a week of DjangoCon US! ๐ โ๏ธ ๐ฅ ๐ค
Sponsored

Until September 10, receive 30% off all new or renewal licenses, with 100% of the proceeds going directly to the Django Software Foundation.
Articles
Why We Started Building on the Django 6.1 Alpha
Divio started building on Django 6.1 at the first alpha, months before the August release. Here's why they picked the pre-release and what they found while testing it every day.
A Dolly Parton Developer
Following Rikki Endsley's "Willie Nelson developer," Trey Hunner makes the case for Dolly Parton as the model: know your rights (she refused to hand over publishing on "I Will Always Love You" when Elvis's team demanded half), exit with grace (she paid Porter Wagoner $1 million to leave his show and kept the friendship), and write the next one. She recorded close to 1,000 songs and wrote thousands more, which is a better target than being a rockstar.
Store lists in a single Django column without joins?
After a decade of development, version 1.0.0 of django-select-multiple-field is here, bringing full support for modern Python and Django versions to store multiple choices in a single database column without extra join tables.
Make Your Django Application Editable
The CMS doesn't need to own your data to make it editable.
htmx and Django LiveView, side by side
Seven worked cases showing where stateless htmx requests and LiveView's persistent WebSocket diverge, with the conclusion that they are complementary rather than interchangeable.
Nifty Django Feature: Use Index for Custom Migration Operations
Override create_sql() and remove_sql() on a models.Index subclass and arbitrary table-level SQL rides along in Meta.indexes, managed by migrations for free.
Django and deployments
A proposed manage.py deploy namespace of lower-level commands that start by printing their expected inputs and outputs, leaving the actual automation to packages and plugins.
Agents All the Way Down
The annotated script of Josh Thomas's DjangoCon US talk on how AI coding agents changed the way he writes Django, written to land for skeptics and true believers alike.
Postgres 19: How Our Advice Has Changed Since...
JIT is off by default, LZ4 replaces pglz for TOAST, and async I/O means the old "an index always beats a parallel sequential scan" assumption is worth re-testing.
"Premature" optimization
The full Knuth quote licenses optimizing the critical 3%, and dropping "small" from "small efficiencies" turned it into a blanket excuse to skip the design work that is cheapest to do up front.
DjangoCon US Recaps
Yes, a standalone category since so many posts on it this week!
DjangoCon US 2026 Recap - Jonathan Peacher
Jonathan Peacher's notes on attending the conference this year in Chicago, highlighting various talks and projects.
My Time at DjangoCon US 2026 - Jason Judkins
Jason Judkins transcribed the talks so he could go back over them, and this recap is the trailer for a longer per-talk series. He picks out a theme running through Paolo Melchiorre's UUID history, Drishti Jain's GeoDjango talk, and Abigail Gbadago's polyglot persistence talk: push the work down a layer, because the database usually knows how to do it better than you do. AI turned up in nearly every talk, with almost nobody uncritical about it.
DjangoCon US 2026 Recap - Tim Schilling
Tim Schilling's fifth DjangoCon, spent chairing sprints with Kudzayi Bamhare, working on Django Simple Deploy with Colin Copeland, and meeting Djangonaut Space members in person for the first time.
My DjangoCon US 2026 - Paolo Melchiorre
Paolo Melchiorre on giving "The Django UUID Story," staffing the DSF booth, and fielding questions at the DSF members open space.
DjangoCon US 2026 - Dwayne McDaniel
Dwayne McDaniel's recap runs talk by talk: Karen Tracey on Django 6's background tasks, CSP support, and template partials, Natalia Bidart on keeping templates the source of truth with HTMX, and Elizabeth Christensen on UUIDv7, graph queries, and OAuth 2.0 in PostgreSQL 18 and 19. His through line is that frameworks, databases, and browsers keep absorbing work that used to need extra layers, with Kasey Kelly's 16,000-line AI-generated frontend file as the cautionary case.
Events
Django On the Med
September 23, 2026 in Pescara, Italy ๐ฎ๐น.
Django Day Copenhagen 2026
October 2, 2026 in Copenhagen ๐ฉ๐ฐ.
Django Job Board
Two new listings this week, plus the DSF still looking for its first Executive Director.
Machine Learning Engineer (Hybrid) at Provision ๐
Django Developer at The Cruise Brothers ๐
Full Stack Software Engineer (Hybrid) at Provision
Executive Director at Django Software Foundation
AI-Assisted Software Engineer, Web Applications at Logical Media Group
Projects
django-danceschool/django-danceschool
Django CMS project with comprehensive features for running a partnered social dance school.
p-r-a-v-i-n/django-fast-multipart
An experimental Rust-backed multipart parser that plugs into Django's parser extension point, so upload handlers, request limits, request.POST, and request.FILES all keep working as they do now. Requires CPython 3.12 or later and Django 6.1, with prebuilt wheels for Linux, macOS, and Windows.
04 Sep 2026 3:00pm GMT
02 Sep 2026
Django community aggregator: Community blog posts
Django and deployments
I have been pondering the wider deployment space in Django for a while and from various angles. This includes my released package django-prodserver but also wondering if the DSF could provide hosting as a small scale commercial operation or what via alternatives I could offer in hosting for Django specifically. Then also I have considered what the wider API in Django could be for deployments.
These thoughts come at a good time, Will Vincent has done two talks on deploying python projects this year and I think his talks would serve as a great theoretical starting point to ensure we cover 90% of what is required. Then after DjangoCon US last week, Paolo made toot suggesting it's time for a deploy command. That toot triggered two things, first a memory of the chats I had in Athens this year and DjangoCon Europe and that I had been meaning to write about this topic for a while.
First let's consider the high level conceptual stages when deploying a project:
- Prepare the overall environment - signing up for an account, creating a project or just booting up a VPS
- Prepare Django and it's settings - these are changes made to the project repository
- Get the Django project from source control to the environment
- Do the first time setup - ideally this would be idempotent.
- Start the production process
- Doing a second deployment - because code always changes and then repeat step 5.
From this list, I think a single managed.py deploy might be too much magical to begin with, but I do think it's possible eventually. I'm thinking it's more likely deploy to be a command that stitches together several lower level commands and each of those commands correspond to a step in the above list. So we could have something like:
manage.py init_deploy_envmanage.py productionizemanage.py deploy_project --firstmanage.py initialize --productionmanage.py prodserver webandmanage.py workermanage.py deploy_project
A couple of very important points, first those names are simply examples for this post to communicate the idea and perhaps it would be best to have them all within a namespace of deploy, so manage.py deploy productionize etc.
Second and most importantly, I am very aware of the numerous possible combinations that exist when it comes to how a project can be deployed today and I am very much NOT suggesting Django support any of them. What I am suggesting is that we focus on the common API inside Django and we have packages and plugins like Eric has with django-simple-deploy. My approach here would be create an API that explicitly does nothing but simply prints expected inputs and outputs from each step. We can then start to automate the parts worth automating in a package, which may get us to a single deploy command.
Let me know your thoughts! As the maintainer of django-prodserver I have a vested interest in this space! :D
PS It's worth noting that there have been years of packages that have done similar things and we should use as reference, django-production is one such package or dj-lite for sqlite configuration in production.
02 Sep 2026 5:00am GMT
01 Sep 2026
Django community aggregator: Community blog posts
"Premature" optimization
"Premature optimization is the root of all evil" - our field's favorite half-sentence, quoted far more often than the sentence it was cut from. Usually it serves as permission: build it fast, profile later, fix a thing or two, done. Let's put the sentence back together and ask what it licenses: when is optimization premature, and when is "premature" the excuse?

01 Sep 2026 10:00am GMT
06 Aug 2026
Planet Twisted
Hynek Schlawack: Production-ready Python Docker Containers with uv
Starting with 0.3.0, Astral's uv brought many great features, including support for cross-platform lock files uv.lock. Together with subsequent fixes, it has become Python's finest workflow tool for my (non-scientific) use cases. Here's how I build production-ready containers, as fast as possible.
06 Aug 2026 12:00am GMT
23 Jun 2026
Planet Twisted
Glyph Lefkowitz: Adversarial Communication
As I have discussed in previous posts, "AIs" can make mistakes. In fact, they do make mistakes, and their mistake-making patterns are such that where and how they will make mistakes is both uncertain and constantly changing.
Thus, in any scenario where you want to attempt to make "productive" use of "AI", you must have a system in place for checking every result. Not checking some results; checking every result. If each result might have a consequence for you (and if it didn't have a consequence, why bother automating it?) and you cannot predict in advance which kinds of results will need verification, then verification is always required.
The verification often ends up being just as expensive as doing the work in the first place, which means that if you want your usage of "AI" to be personally profitable, you have to find someone else to externalize the cost of verification onto. This person becomes your adversary, and, if you are successful, your "AI's" victim.
The Ladder-Climber And Their Reverse-Centaur Rungs
One way that this constellation of facts can straightforwardly assemble themselves into a dystopian nightmare is the phenomenon, described by Cory Doctorow, of the reverse centaur. This is when your employer non-consensually turns you into the verification system. The "AI" does the fun part of initially performing the work, and then you do the boring part where you check if the robot is right and clean up its messes, even if everyone already knows that it would, in aggregate, be cheaper for you to do the work in the first place.
Reverse centaurs can be made from any automation, not only "AI" automation. I think that there is a reason that this term happens to have emerged in the "age of AI", though, and not with earlier automation technologies (even those which were considerably more viscerally horrific). That reason is: the wrongness of "AI" output is not merely a technical feature that must be compensated for, it is a generalized externality.
As I mentioned above, if you are responsible for the entirety of the work, both extruding the "AI" output and checking it, it's usually cheaper to have humans do the entirety of the work to begin with. When humans do the writing directly, we can check as we go, and thus verification doesn't need to be as comprehensive.
When "AI" coding advocates say "code review is the bottleneck", what they are observing is that the LLM is still rolling the dice for each PR, and a human is still necessary to verify that each of those rolls is a winner. But calling this process "code review" is a bit of a misnomer; it's not really "code review" in the traditional sense, it's human understanding.
Before the advent of "AI", the human understanding was implicit in the process of writing the code in the first place1, and the code review was a way of diffusing and extending that understanding. Now that the code can be authored with no initial understanding taking place, that cost has not gone away, it has moved.
Human understanding was always the bottleneck.
However, this is taking a collaborative view of a software project, where satisfying the needs and solving the problems of your customers are the goals. We can see that "AI" is a bad tool to satisfy those goals, because all it's doing is converting the first half of the work, that of understanding the code as you write it, to understanding the agent's output as you read it.
What if, instead, we were to take the view that every software company is a Hobbesian nightmare, red in tooth and claw? In this view, the only goal of a software project is for the individual developers to make their promo cycles and get their bonuses. Given that there is only a certain amount of money to go around, this is a zero-sum game where each programmer wants to look more productive than their colleagues.
Pretty much every organization finds it easy to reward "productivity" as expressed by lines of code emitted, but the benefits of doing thorough and thoughtful design, analysis, and code review very difficult to reward. In this world, an LLM is an invaluable tool for the sociopathic ladder-climber, particularly if your legacy organization is still structuring their workflows as if the person prompting the bot is "writing" the code, and then they get to foist off the act of "reviewing" the code onto someone else.
Here, the prompter effectively externalizes the cost of the LLM's failures but internalizes any benefits. The prompter will vibe-code a big feature, so large that the assigned reviewer can't possibly comprehend it all effectively. When this happens, the reviewer will, eventually, be pressured to approve it, even if they can try to spot a few problems along the way. The reviewer has their own work to get back to, after all, the obligation to review the prompter's (read: the bot's) code is a drain on their time that they are not going to get rewarded for.
If this feature is a big success, the prompter gets a promotion. If it causes a big issue, well, the reviewer must not have been careful enough.
This is why LLMs are "good for coding", and also why their biggest promoters keep having outages.
The Generative Gish Galloper
Coding is the biggest "success story" of this type of adversarial communication, but it is by far not the only instance of such a thing. LLMs create a new form of leverage that can turn Brandolini's law from a linear advantage into an exponential one. If you are engaged in a political debate where you want to overwhelm the other side in nonsense, an LLM can generate bullshit faster than it is physically possible for a human being to type, let alone respond thoughtfully. There is an asymmetry to the utility of this weapon as well: only one side of the political spectrum wants to flood the zone and destroy trust in institutions and the concept of truth. There's a good reason that the fascists love it.
Straightforward Spam and Fraud
This is kind of obvious, but LLMs can generate lightly-customized, plausible-looking text much more quickly than any human being. This facilitates their use in fraud, spam, and scams. In a spamming or fraudulent interaction, once again, the costs are externalized onto the victim: the recipient of a spam message has to do all the work of "checking" the LLM's output. Spammers already expect very low hit rates from boilerplate, and if the LLM can increase those percentages from 1% to 5% the technology will pay for itself; they don't need anything like reliable accuracy.
Customer "Support"
If you have any kind of commercial relationship with a company, I probably don't even need to mention this: customer "support" bots are a misery. Everybody knows it at this point. But customer support is usually conceptualized by businesses as an adversarial interaction, because it is a cost center. They maintain internal metrics on time-to-resolution and try to optimize them. Implicitly, this creates a dynamic where the goal of the customer service agent's job is not to solve your problem, but to emit noise that will cause you to think your problem is resolved, or to give up, as fast as possible. Unsurprisingly, LLMs can emit this noise faster than humans can, getting those customers off the phone. But those customers will remember those interactions, and the story outside the TTR metrics is horrible.
Similarly to the situation in software development, LLMs can look very good on paper for customer support, but mostly what they are doing is illuminating the problems with the industry's existing metrics, by turning "winning the metrics battle against the customer" into a more obvious and immediate defeat for the company's long term reputation.
"Education"
In 2026 it is sadly a fact of life that students cheat all the time using "AI", and that this cheating is very successful, in that the teachers find it very hard to detect.
LLMs are great for cheating on schoolwork because the student is externalizing the work of the checking onto the teachers, who are often starting at a disadvantage to begin with, at least in the US.
My view is that this is happening because of a divergence in the way that students vs. teachers (or, more accurately, "the broader educational system") view grading.
When a student is asked to write an essay, the teachers see the effort as both intrinsically worthwhile for the student, as well as useful as a pedagogical tool to evaluate and react to the student's progress. The student, by contrast, sees a stumbling block designed to knock them off the path to success and into a permanent underclass. It is no wonder that the student sees "AI" as useful to their own goals and has no compunction about deploying it.
There is a bitter irony that the ability to understand the inherent value of actually writing the essay on their own is the sort of thing that students can really only learn by writing a bunch of essays. There's no way that I can think of which makes the benefit legible as long as a shortcut is available.
The net effect here is a downward spiral, where the already-wobbling educational system is sustaining an attack that it doesn't have the resources to recover from. The individual students' attacks against their teachers and their schools' grading systems might appear to momentarily succeed, but they will win the battle and lose the war.
Spamming "For Good"?
Usually when we talk about someone unilaterally choosing to enter into an adversarial relationship, that's an "attack" and for good reasons we have a negative impression of the attacker. However, I would be remiss if I did not point out that there are some cases where the relationship was already adversarial; just because you're the attacker doesn't mean that you are evil.
For example we might imagine use-cases like automatically filing appeals for prior authorizations against health insurance. It's relatively well-known at this point that the main way for-profit insurers maintain their margins is by denying claims right up to the line of the policies themselves being fraud, so using a spamming tool to fight them might be entirely justifiable2 in that case.
Similarly, using an LLM could be justified in a fight against a company refusing to honor a warranty. One could imagine using an LLM to immediately generate replies and escalations.
However, even in imagined cases like these, the underlying problem is that the insurers and the vendors already have a tremendous amount of structural power, so it is more likely that they will have the advantage in deploying a communications weapon like an LLM, as well as enacting policies to simply ignore any LLM-based communication that you might submit. Worse, if these strategies were to become widespread, they might provide an excuse to reject any communications by feeding them into an unreliable "LLM detector" and issuing an automated "computer says no" even to hand-written correspondence.
It is also worth stressing that these cases are imagined, as compared to the very real coworker-abuse, spam, scam, fraud, and disinformation campaigns being waged in real life today.
Therefore, while legitimate uses might exist, it's hard to imagine that there's anywhere they would be genuinely valuable and sustainable. In the best case "AI" will provide a temporary advantage for underdogs that will provoke an arms race which the resource-advantaged adversaries will win in the long run, in the worst case the arms race itself will cement permanent structural change that will make things worse.
"Search" By Stealing
Most of the adversarial utility of "AI" is on the "write" side, since write-amplification is more obviously aggressive than reading. But the "read" side of LLMs - summarization and question-answering - can be a form of attack as well.
To begin with, the act of reading itself is currently enormously destructive, but that's arguably not a fundamental aspect of this technology. They could set reasonable rate-limits and respect things like robots.txt, as search engines have for decades now. They could also refrain from committing criminal levels of copyright infringement. But, today, using "AI" tools does suborn this sort of out-of-control crawling.
More insidiously, consider the scenario described in this YouTube video. The LTT Bros decided to try Linux again, and in the course of so doing, they had problems. When trying to solve these problems, they were faced with a choice: they could consult Reddit, or they could ask an LLM. Asking an LLM would "gaslight the heck out of" them, but they still found it preferable, because they would at least get an answer without getting yelled at.
Initially this sounds great. But it also means that you want to extract knowledge from a community, while mechanically eliding any values or norms that the community may want to impart as part of offering that knowledge. As someone who spent many years in a community tech support role, this is worrying. Many requests for support are people asking how to do things that will momentarily solve a superficial problem but create a long-term reliability problem or even an immediate security risk, that the question-asker doesn't want to hear about. Consider the question "I'm tired of entering my password so much, how do I make it so my laptop unlocks automatically". An obsequious chatbot will helpfully tell you how to do this without pushback.
But, this is also a sort of ethically murky area. The Linux community is somewhat famously, for many years now, a toxic cesspool of general hostility, misogyny, etc. It is certainly a good thing that people can get access to this knowledge without subjecting themselves to abuse. But it also means that the people with the power and the privilege to change the community for the better can just quietly withdraw, rather than fixing the problems. It also means that the positive elements of culture cannot be transmitted, and people will have no opportunity to learn about unknown unknowns.
In this case, the "adversarial" communication is with society. The thing that using an LLM for search lets you do is withdraw from society and avoid forming any personal connections. There are some personal connections which are painful and annoying, and so that can feel like a momentary balm. But the need to make connections in general is, like, the concept of society itself.
Who Am I Hurting?
LLMs are good at adversarial communication. They are so good at it, relative to their other benefits, that they will tend to make communications adversarial if you are not remaining vigilant about the possibility that it might do so. My request to you, dear reader, if you are going to use such tools, is to always ask yourself, "who might I be hurting, if I use an LLM for this?"
If you're using an "AI", who is its adversary? If you haven't given it one yet, who might the "AI" turn into an adversary? Who might you overwhelm with an asymmetric amount of output, or, if you're receiving information and not sending it, who are you taking that information from without consulting?
Figure out the answers to these questions and conduct yourself accordingly; the answer might be "yourself".
Acknowledgments
Thank you to my patrons who are supporting my writing on this blog. If you like what you've read here and you'd like to read more of it, or you'd like to support my various open-source endeavors, you can support my work as a sponsor!
-
One of the reasons that software developers tend to prefer greenfield development is that when you are given a blank page, you can project your own specific understanding onto it. You can structure the codebase in a way that works for your brain, down to the variable naming conventions and the module layouts. LLM-assisted development makes everything into instant brownfield work, which makes developers instantly miserable; even those who are excited about the technology will frequently complain about how it feels like their agency has been stolen and their joy in the work has been diminished. But I digress. โฉ
-
Modulo the massive amount of other externalities involved in using LLMs, of course, but I don't have the time or energy to get into those here. โฉ
23 Jun 2026 8:06pm GMT