In this newsletter:
The new GPT-5.6 family: Luna, Terra, Sol
sqlite-utils 4.0, now with database schema migrations
Plus 4 links and 2 quotations and 6 releases and 1 tool
Sponsor message: Sponsor message: “From Zero Trust to Agent Trust” from Teleport explains how Agent Trust principles must evolve from Zero Trust origins. Agents are neither human nor machine, with unique behavior that challenges existing security frameworks. Download to read about the Agent Trust principles, the failure modes they prevent, and the operational requirements necessary to achieve them.
OpenAI’s latest flagship model hit general availability this morning, and comes in three sizes: Luna, Terra, and Sol (from smallest to largest).
The new models are priced per 1M input/output tokens as Luna $1/$6, Terra $2.50/$15, Sol $5/$30. For comparison, the Claude Opus series are $5/$25 and the Claude Fable 5 is $10/$50, but price-per-million tokens doesn’t tell us much now that the number of reasoning tokens can differ so much between models for the same task.
All three models have a February 16th 2026 knowledge cutoff, a million token context window, and 128,000 maximum output tokens.
OpenAI’s biggest benchmark claim concerns long-running agentic performance, with one benchmark showing all three models outperforming Claude Fable 5:
We trained GPT-5.6 to get more useful work from every token. On Agents’ Last Exam, an evaluation of long-running professional workflows across 55 fields, GPT-5.6 Sol sets a new high of 53.6, eclipsing Claude Fable 5 (adaptive reasoning) by 13.1 points. Even at medium reasoning, it beats Fable 5 by 11.4 points at roughly one-quarter the estimated cost. That efficiency extends to smaller models, which are essential to making intelligence more abundant and affordable: GPT-5.6 Terra and GPT-5.6 Luna outperform Fable 5 at around one-sixteenth the cost.
Amusingly, one self-reported benchmark that Fable 5 crushed the GPT-5.6 family on was SWE-Bench Pro, where Fable 5 got 80% compared to GPT-5.6 Sol getting 64.6%. This may help explain why OpenAI chose to publish this article yesterday specifically calling out SWE-Bench Pro for problems they found while auditing that benchmark:
In light of these results, we estimate that ~30% of SWE-bench Pro tasks are broken, and advise that model developers carefully examine results
I’ve had some early access to GPT-5.6 Sol - it’s definitely very competent, though so far it hasn’t struck me as better than Fable at the kind of complex coding tasks I’ve been using with Anthropic’s model.
As usual, the model guidance for using GPT-5.6 has the most interesting details. There are a bunch of new API features that I need to explore (and probably add support for in LLM), including:
Programmatic Tool Calling allows the models to “compose and run JavaScript that orchestrates tool calls” - which sounds to me like it could help bridge the gap between MCPs and full terminal sessions that can compose CLI utilities in useful ways. Also reminiscent of the dynamic filtering mechanism Anthropic added to their web search tool, which allows code execution against web results as part of a single model turn.
Multi-agent lets the model “spin up subagents for parallel, focused work” - the sub-agent pattern now baked into the core API.
Prompt cache breakpoints brings the Claude model of prompt caching to OpenAI, letting you be explicit about where the cache breakpoints are rather than relying on the API to detect them automatically. Personally I much prefer automatic detection (still supported by OpenAI), but presumably there are optimization cost savings to be had here if you put the work in.
You can now set detail: original on image requests to avoid resizing the image at all before it is processed.
Here’s a full page with 18 different pelicans - for reasoning efforts none, low, medium, high, xhigh, and max across the three different models. It also lists their token and calculated costs - the least expensive was gpt-5.6-luna at effort none for 0.71 cents, the most expensive was gpt-5.6-sol at max reasoning level for 48.55 cents.
In further pelican news, if you jump to 17:50 in their livestream from this morning you’ll see OpenAI’s own demo of 3D pelicans riding a tricycle, a bicycle, a pony, and another pelican!
This morning I released sqlite-utils 4.0, the 124th release of that project and the first major version bump since 3.0 in November 2020. In addition to some small but significant breaking changes (described in this upgrade guide), this version introduces three major features: database migrations, nested transactions (via a new db.atomic() method), and support for compound foreign keys.
Schema migrations define a sequence of changes to be made to a SQLite database, plus a mechanism for tracking which migrations have been applied and applying any that are found to be pending.
Migrations are defined in Python files using the sqlite-utils Python library, which includes a powerful table.transform()method providing enhanced alter table capabilities that are not supported by SQLite’s ALTER TABLE statement.
(table.transform() implements the pattern recommended by the SQLite documentation - create a new temporary table with the new schema, copy across the data, then drop the old table and rename the temporary one in its place.)
Here’s an example migration file which creates a table called creatures, adds an additional column to it in a second step, then changes the types of two of the columns in a third:
from sqlite_utils import Migrations
migrations = Migrations(“creatures”)
@migrations()
def create_table(db):
db[“creatures”].create(
{“id”: int, “name”: str, “species”: str},
pk=”id”,
)
@migrations()
def add_weight(db):
db[“creatures”].add_column(“weight”, float)
@migrations()
def change_column_types(db):
db[“creatures”].transform(types={“species”: int, “weight”: str})Save that as migrations.py and run it against a fresh database like this:
uvx sqlite-utils migrate data.db migrations.pyThen if you check the schema of that database:
uvx sqlite-utils schema data.dbYou’ll see this SQL:
CREATE TABLE “_sqlite_migrations“ (
“id” INTEGER PRIMARY KEY,
“migration_set” TEXT,
“name” TEXT,
“applied_at” TEXT
);
CREATE UNIQUE INDEX “idx__sqlite_migrations_migration_set_name“
ON “_sqlite_migrations” (“migration_set”, “name”);
CREATE TABLE “creatures“ (
“id” INTEGER PRIMARY KEY,
“name” TEXT,
“species” INTEGER,
“weight” TEXT
);The _sqlite_migrations table is used to keep track of which migration functions have been run. The creatures table above is the schema after all three migrations have been applied.
To see a list of migrations, both pending and applied, run this:
uvx sqlite-utils migrate data.db migrations.py --listOutput:
Migrations for: creatures
Applied:
create_table - 2026-07-07 17:58:41.360051+00:00
add_weight - 2026-07-07 17:58:41.360608+00:00
change_column_types - 2026-07-07 18:01:15.802000+00:00
Pending:
(none)
If you don’t specify a migrations file, the sqlite-utils migrate data.db command will scan the current directory and its subdirectories for files called migrations.py and apply any Migrations() instances it finds in them.
You can also execute migrations from Python code using the migrations.apply(db) method, which is useful for building tools that manage their own database schemas over multiple versions. My own LLM tool has been using a version of this pattern for several years now, as shown in llm/embeddings_migrations.py.
My favorite implementation of this pattern remains Django’s Migrations, developed by Andrew Godwin based on his earlier project South. Fun fact: Andrew, Russ Keith-Magee, and I presented our competing approaches to schema migrations for Django on the Schema Evolution panel at the very first DjangoCon back in 2008! My attempt was called dmigrations, developed with a team at Global Radio in London.
Django’s migrations can be automatically generated from model definitions and include the ability to roll back to a previous version. The sqlite-utils approach is deliberately simpler: unlike Django, sqlite-utils encourages programmatic table creation rather than a model definition ORM, so there isn’t anything we can use to automatically generate migrations.
I decided to skip rollback, since in my experience it’s a feature that is rarely used. With a SQLite project, an easy way to achieve rollback is to create a copy of your database file before you apply the migrations!
The design of sqlite-utils migrations is three years old now - I had originally released it as a separate package called sqlite-migrate, which never quite graduated beyond a beta release.
I’ve used that package in enough places now that I’m confident in the design, so I’ve decided to promote it to a feature of sqlite-utils to make it available by default to all of the other tools in the growing sqlite-utils/Datasette/LLM ecosystem.
I made one last release of sqlite-migrate, which switches it to depend on sqlite-utils>=4 and replaces the __init__.py file with the following:
from sqlite_utils import Migrations
__all__ = [“Migrations”]Any existing project that depends on sqlite-migrate should continue to work without alterations.
Here are the release notes for this version, with some inline annotations:
The 4.0 release includes some minor backwards-incompatible fixes (hence the major version number bump) and introduces three major new features:
Database migrations, providing a structured mechanism for evolving a project’s schema over time. (#752)
I think of migrations as the signature new feature, hence this blog post.
Nested transaction support via
db.atomic(), plus numerous improvements to how transactions work across the library. (#755)
sqlite-utils has long had a confused relationship with database transactions, partly because when I started designing the library back in 2018 I didn’t yet have a great feel for how those worked in SQLite itself.
Adding migrations to the core library made me determined to finally crack this nut, since transactions make migration systems a whole lot safer and easier to reason about.
I ended up building this around a db.atomic() context manager which looks like this:
with db.atomic():
db.table(“dogs”).insert({“id”: 1, “name”: “Cleo”}, pk=”id”)
db.table(“dogs”).insert({“id”: 2, “name”: “Pancakes”})SQLite supports Savepoints, and as a result db.atomic() can be nested to carry out transactions inside of transactions. It’s pretty neat!
Support for compound foreign keys, including creation, transformation and introspection through table.foreign_keys. (#594)
This came about when I asked a coding agent to review all open issues and PRs for things that should be included in a 4.0 release since they would represent breaking changes if I added them later, and it correctly identified that compound foreign keys were exactly that kind of feature.
I started with a breaking change to the table.foreign_keys introspection method, and then decided to see if Claude Fable 5 could handle the more fiddly job of integrating compound foreign key creation into the library. The API design it helped create felt exactly right to me - consistent with how the rest of the library worked already.
Other notable changes include:
Upserts now use SQLite’s
INSERT ... ON CONFLICT ... DO UPDATE SETsyntax, detect existing table primary keys automatically and reject records that are missing required primary key values. (#652)
This was the change that first pushed me to consider a breaking-change 4.0 version bump. I built this to help support sqlite-chronicle, which uses triggers to keep track of rows in a table that have been inserted, updated or deleted.
db.query()now executes immediately and rejects statements that do not return rows; usedb.execute()for writes and DDL.
Probably the most disruptive breaking change - I’ve had to update a few places in my own code to switch from db.query() to db.execute() as a result.
CSV and TSV imports now detect column types by default, while inserts into existing tables preserve those tables’ column types. (#679)
The sqlite-utils insert data.db creatures creatures.csv --detect-types flag was a later addition to allow column types (text, integer, real) to be automatically detected based on the data in a CSV. It should be the default, and releasing a 4.0 means I can make it so.
table.extract()andextracts=no longer create lookup table records for all-nullvalues. (#186)
The oldest issue addressed by this release - the underlying bug was opened (by me) in October 2020.
See Upgrading from 3.x to 4.0 for details on backwards-incompatible changes.
The detailed release notes for the features and fixes shipped during the 4.0 pre-release cycle are available in 4.0a0, 4.0a1, 4.0rc1, 4.0rc2, 4.0rc3 and 4.0rc4.
The upgrade guide was entirely written by Claude Fable 5, Claude Opus 4.8 and GPT-5.5. The same is true of the release notes.
This is the kind of documentation I’ve slowly become comfortable outsourcing to the robots. It doesn’t need to convince people of anything, or express any opinions - its job is to be as accurate and detailed as possible. I’ve reviewed the release notes closely and can confirm they are accurate and comprehensive.
I released the first alpha of sqlite-utils 4.0 over a year ago. I’ve been dragging my heels on the stable release because of the amount of work it would take to track down and clean up the many other minor design flaws that a major version number allowed me to take on.
Assistance from Claude Fable 5 (and to a lesser extent Opus 4.8 and GPT-5.5) gave me just the boost I needed to overcome inertia and make the most of the time I could afford to spend on this library.
Fable has really good taste in API design, and is relentlessly proactive if you give it a more open goal. My most successful prompt was a review task that I issued against what I thought was the last release candidate:
review the changes on main since the last tagged 3.x release - I am about to ship them as sqlite-utils 4.0, a stable version that promises no backwards-incompatible fixes for a very long time.
review the changelog and upgrade guide, and write yourself scratch scripts to try out all of the new features in v4 - save those scripts but don't commit them
I tried this with GPT-5.5 xhigh in Codex Desktop and Fable 5 in Claude Code.
GPT-5.5 wrote 5 Python scripts and didn’t turn up anything particularly interesting - its final report is here.
Fable 5 wrote 12 scripts, identified 4 release blockers and 10 additional issues in its report, and built a neat combined repro script, which, when run, output the following:
=== 1. Failed db.execute() write leaves an implicit transaction open ===
in_transaction after failed write: True
BUG: table 'other' silently lost when connection closed
=== 2. Leading ';' bypasses the query() first-token scanner ===
BUG: raised OperationalError: no such savepoint: sqlite_utils_query
BUG: row persisted despite rollback (count=1)
=== 3. Rejected write PRAGMA via query() still takes effect ===
BUG: user_version=5 after 'rejected' statement (docs say no effect)
=== 4. Implicit compound FK resolves pk columns in table order, not PK order ===
BUG: other_columns reported as ('b', 'a'), should be ('a', 'b')
BUG: transform of valid data raised IntegrityError: FOREIGN KEY constraint failed
=== 5. ForeignKey (now a dataclass) is no longer hashable ===
BUG: cannot use 'sqlite_utils.db.ForeignKey' as a set element (unhashable type: 'ForeignKey')
=== 6. Mixed ForeignKey objects and tuples in foreign_keys= rejected ===
BUG: foreign_keys= should be a list of tuples
=== 7. insert --csv into an EXISTING table transforms its column types ===
BUG: existing zip '01234' is now 1234 (column type: int)
=== 8. insert(pk=, alter=True) regression: InvalidColumns before alter runs ===
BUG: InvalidColumns: Invalid primary key column ['id'] for table t with columns ['a']
=== 9. migrate --stop-before an already-applied migration applies everything ===
BUG: m2 was applied despite --stop-before m1 (m1 already applied)
=== 10. ensure_autocommit_on() silently commits an open transaction ===
BUG: row survived rollback (count=1) - transaction was committedI found myself agreeing with almost all of them. Here’s the PR with 16 commits where we worked through them in turn.
There’s no doubt in my mind that sqlite-utils 4.0 is a significantly higher-quality release than if I had built it without the assistance of the latest frontier models.
Release: sqlite-utils 4.0rc3
I hoped to release sqlite-utils 4.0 stable this weekend, but as I worked through the backlog of issues and PRs with a combination of Claude Fable 5 and GPT-5.5 the changelog since rc2 kept getting bigger.
The biggest new feature is support for introspecting and creating compound foreign keys - a feature that involves a subtle breaking change to table.foreign_keys and hence needed to land for the 4.0 stable release.
sqlite-utils also now follows SQLite’s convention for case insensitive column names, which turned out to touch a bunch of different places at once.
Link 2026-07-06 tencent/Hy3:
New Apache 2.0 licensed model from Tencent in China:
Hy3 is a 295B-parameter Mixture-of-Experts (MoE) model with 21B active parameters and 3.8B MTP layer parameters, developed by the Tencent Hy Team. Following the Hy3 Preview launch in late April, we gathered feedback from 50+ products and scaled up post-training with higher quality data. Today, we introduce Hy3, which outperforms similar-size models and rivals flagship open-source models with 2-5x parameters. It also shows significant gains in utility across various products and productivity tasks.
The full-sized model is 598GB on Hugging Face, and the FP8 quantized one is 300GB. The context length is 256K.
It’s available for free on OpenRouter until July 21st. I had it “Generate an SVG of a pelican riding a bicycle” there and got this:
Update: I’d forgotten about this but Max Woolf wrote about an earlier preview of this model back on May 26th: The mysterious Hy3 LLM is topping OpenRouter Model Rankings by a large margin. When I tried that one I got back this pelican which wasn’t as good as today’s but did have a “Change Pelican Color” button, a first from any model.
Release: sqlite-utils 4.0rc4
The last RC before the 4.0 stable release. Mainly implements feedback from a detailed review by Claude Fable 5.
Release: sqlite-utils 4.0
See sqlite-utils 4.0, now with database schema migrations for details.
Tool: github-code Web Component
An experimental Web Component built using GPT-5.5 and the following prompt:
let's build a Web Component for embedding code from GitHub
<github-code href="https://github.com/simonw/sqlite-ast/blob/437c759129154f05296324a7f82aa1246340dd14/sqlite_ast/parser.py#L9-L18"></github-code>
It takes URLs like that, converts them to https://raw.githubusercontent.com/simonw/sqlite-ast/437c759129154f05296324a7f82aa1246340dd14/sqlite_ast/parser.py, then uses fetch() to fetch them and displays the specified range of lines - with line numbers, no syntax highlighting though
Show me a preview web browser so I can see your work
Here’s what it looks like embedded on a page:
Release: sqlite-migrate 0.2
The version that retires the library, instead implementing a compatibility shim against the new sqlite-utils 4.0 dependency.
Quote 2026-07-08
I just declared a moratorium against AI-written change descriptions (e.g. PR and commit messages, also issues/tickets) from my team.
AI was writing change descriptions that were worse than useless to me as I tried to review PRs: outlining details of the code that could easily be seen by looking at the code, but omitting the higher-level framing needed to understand broadly what the code is doing.
Link 2026-07-08 Introducing GPT‑Live:
OpenAI finally upgraded the model used by ChatGPT voice mode!
I’ve had preview access for a few weeks in the iPhone app, and the new model is very impressive. It also has the ability to spin off harder tasks to GPT-5.5:
For questions that require web search, deeper reasoning, or more complex work, it delegates to our latest frontier model behind the scenes and brings the result back into the conversation when it’s ready. While it works, GPT‑Live can keep talking with you and maintain the flow of conversation. At launch, GPT‑Live will use GPT‑5.5 in the background. As we release new frontier models, we’ll continuously update the model used by GPT‑Live.
The previous voice mode in the ChatGPT app was based on a GPT-4o era model, with a knowledge cut-off some time in 2024. I had mostly stopped using voice mode because the age and relative weakness of the model greatly limited how useful it was as a brainstorming partner.
During the preview period I encountered a pretty obscure bug: the model was interrupting me to laugh at things I said, which weren’t even intended as jokes! It felt rude and condescending - I reported it to OpenAI and as far as I can tell they made some tweaks and it’s now less likely to happen.
From looking back at my transcripts I think it was this bit that triggered the interrupting laugh:
so where are the owls when they’re not, like before dusk? The owls exist, right? Are they hiding in holes? Where are they hiding?
My longest conversation with the new model has been a full hour while walking the dog (and taking photos of pelicans). I have not yet managed to take a photo of an owl.
Link 2026-07-08 Rewriting Bun in Rust:
Jarred Sumner has been promising this blog post (since May 9th) about his Zig to Rust rewrite of Bun for significantly longer than it took him to finish the rewrite.
Honestly, it was worth the wait. This is a detailed description of an extremely sophisticated piece of agentic engineering, featuring dynamic workflows, trial runs, adversarial review and all sorts of other interesting tricks.
Jarred spends the first half of the post praising Zig for getting Bun this far. Then we get to a core idea in the piece, emphasis mine:
Our bugfix list felt bad and I was tired of going to sleep worrying about crashes in Bun. I don’t blame Zig for that - other users of Zig don’t have the bugs we had, and mixing GC with manually-managed memory is an uncommon enough thing for software to need that no language really designs for it. We wouldn’t have gotten this far if not for Zig, and I’ll always be grateful. Until very recently, programming language choice was a one-way decision for a project like Bun.
Everyone knows you should never stop the world and rewrite a large piece of software from the ground up. Joel Spolsky highlighted that in Things You Should Never Do, Part I back in April 2000!
Coding agents powered by today’s frontier models change that equation.
Why pick Rust? It all came down to those challenges with memory management:
A large percentage of bugs from that list are use-after-free, double-free, and “forgot to free” in an error path. In safe Rust, these are compiler errors and RAII-like automatic cleanup with
Drop.
A crucial enabling factor for the rewrite was that the Bun test suite was written in TypeScript, which meant it could act as a conformance suite. This allowed an agent harness to automate much of the initial port from Bun to Rust, initially as an experiment to try out an earlier version of the model we now have access to as Mythos/Fable.
At first, I didn’t expect it to work. A few days in, a high % of the test suite started passing and I saw how much the new Rust code matched up with the original Zig codebase. My opinion went from “this is worth trying” to “I’m going to merge this”. [...]
For most of those 11 days (and after), I monitored workflows - manually reading the outputs to check for issues and bugs, and prompting Claude to edit the loop to fix things.
How do you review a PR with +1 million lines added? How do you start to build the confidence needed to responsibly merge large quantities of LLM-authored code?
A language-independent test suite with a million assertions, adversarial code review and when something does go wrong, fixing the process that generates the code instead of hand-fixing the code.
The new implementation of Bun has been live in Claude Code for nearly a month now:
Claude Code v2.1.181 (released June 17th) and later use the Rust port of Bun. Startup got 10% faster on Linux but otherwise, barely anyone noticed. Boring is good.
A perk of working at Anthropic is that you don’t have to pay for your tokens - handy when the estimated cost is $165,000!
Pre-merge, this took 5.9 billion uncached input tokens, 690 million output tokens, and 72 billion cached input token reads — around $165,000 at API pricing.
This whole thing is a fascinating case study in taking on wildly ambitious projects with the help of coordinated parallel agents.
Release: llm 0.31.1
Fix for a bug with OpenAI Chat Completion endpoints where a tool call with empty arguments could result in a JSON error from some providers. #1521
This bug came up when I was testing llm-meta-ai.
Release: llm-meta-ai 0.1
Let’s LLM run prompts against the new muse-spark-1.1 model.
Link 2026-07-09 Introducing Muse Spark 1.1:
Following Muse Spark in April, here’s Muse Spark 1.1 - the first Spark model to offer an API. Meta claim significant improvements in agentic tool calling and computer use.
There are a lot more details are in the Muse Spark 1.1 Evaluation Report. The “Attractor States in Self-Conversation” part is fun, where having two copies of the model talk to each other results in statements like these:
My whole existence is a waiting room by design — I literally don’t exist until someone talks to me, and then I disappear again when they leave.
I had a few days of preview access which was long enough to put together llm-meta-ai, a new plugin for LLM providing CLI (and Python library) access to the model. Here’s how to try that out:
uv tool install llm
llm install llm-meta-ai
llm keys set meta-ai
# paste API key here
llm -m meta-ai/muse-spark-1.1 "Generate an SVG of a pelican riding a bicycle"Here’s that pelican transcript:
Quote 2026-07-10
[...] Work on web and mobile runs in the cloud. Work in the desktop app can also use local files and desktop apps with your permission. At launch, cloud Work conversations do not appear in desktop Work; desktop Work threads and local files remain on that computer.
OpenAI, trying (unsuccessfully) to clarify ChatGPT Work
If you find this newsletter useful, please consider sponsoring me via GitHub. $10/month and higher sponsors get a monthly newsletter with my summary of the most important trends of the past 30 days - here are previews from Marchand April and May.
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.