12 августа 2026 года Simon Willison — автор sqlite-utils и Datasette — поручил Codex и модели GPT-5.6 Sol Ultra собрать кроссплатформенную версию своей библиотеки для работы с базами данных. За вечер агент выдал проект alchemy-utils: поддержка PostgreSQL, DuckDB и SQLite через SQLAlchemy, 164 прошедших теста и готовый CLI. Первый запуск импорта CSV занял почти час — после просьбы оптимизировать Codex сократил его до ~35 секунд. Ниже сам кейс: промпты, решения агента, цифры и границы применимости.
Это перевод-адаптация поста Simon Willison — alchemy-utils 0.1a0 (12.08.2026) и опубликованного им транскрипта сессии Codex.
Зачем Уиллисону библиотека на SQLAlchemy
sqlite-utils — библиотека и CLI Уиллисона для работы с SQLite: создание таблиц, вставка, upsert, интроспекция схемы. Привязка к одному движку ограничивает её там, где данные лежат в PostgreSQL или DuckDB. Идея сделать такой же API поверх SQLAlchemy, чтобы один и тот же код работал на разных движках, висела у автора давно:
I’ve long pondered what a database agnostic version of my sqlite-utils Python library and CLI utility might look like. This morning (literally a shower project) I tasked Codex and GPT-5.6 Sol Ultra with building a prototype.
«Я давно размышлял, как могла бы выглядеть не привязанная к конкретной БД версия моей библиотеки sqlite-utils. Этим утром (буквально проект из душа) я поручил Codex и GPT-5.6 Sol Ultra собрать прототип.» (Simon Willison, 12.08.2026)
Для тех, кто встречает такие кейсы впервые: Codex CLI — официальный терминальный агент OpenAI (Apache-2.0), модели по умолчанию — семейство GPT-5.6 (Sol/Terra/Luna). Установка, вход и модели разобраны в нашем гиде по Codex CLI. В этом кейсе Уиллисон использовал конфигурацию Sol Ultra с максимальной глубиной рассуждений — именно на ней он гоняет тяжёлые агентные задачи.
Один промпт вместо ТЗ
Задачу Уиллисон сформулировал одним промптом:
Do a research spike to see what it would take to build a library with the same core API as SQLite-utils - in particular the insert and upsert and insert_all and upsert_all and create and update methods, and the table introspection stuff - but backed by SQLalchemy so it works for multiple database engines
Test against PostgreSQL and SQLite and duckdb
Use ~/dev/sqlite-utils for reference
Create a git repo for this and commit and early and often - use uv init to start the project - use red/green TDD and pytest, see ~/dev/django-sql-dashboard for one idea as to how the PostgreSQL tests could work
«Проведи research-spike: оцени, что нужно для библиотеки с тем же ядром API, что у SQLite-utils, — прежде всего методы insert, upsert, insert_all, upsert_all, create, update и интроспекцию таблиц, — но на SQLAlchemy, чтобы она работала с несколькими движками БД. Проверяй на PostgreSQL, SQLite и DuckDB. Используй ~/dev/sqlite-utils как референс. Создай git-репозиторий и коммить часто и пораньше; проект начни через uv init; используй red/green TDD и pytest; идею тестов под PostgreSQL посмотри в ~/dev/django-sql-dashboard.» (транскрипт сессии)
В промпте шесть требований, и каждое проверяемо: референс (локальный checkout sqlite-utils), движки (три), способ ведения проекта (uv + git), методология (red/green TDD + pytest) и образец для тестов PostgreSQL. Это готовый шаблон для собственных задач: вместо «напиши библиотеку» — «сделай X, проверь на Y и Z, следуй процессу W, ориентируйся на A».

Что агент сделал сам, без уточнений
Транскрипт сессии — отдельный gist, последнее обновление 12 августа в 19:44; в начале видно «32 previous messages», то есть опубликована не вся сессия, а её концовка. По записи видно, что из одного промпта Codex развернул целый процесс:
- инициализация:
uv init --lib+git init, первые коммиты «Initial uv library scaffold» и «Add SQLAlchemy and database test dependencies» (SQLAlchemy 2.x, duckdb-engine, psycopg, pytest, testing.postgresql); - изучение референса: grep по
db.pysqlite-utils — сигнатуры create/insert/upsert/update, классы Database/Table/View, дата-классы Column/ForeignKey/Index; - red/green циклы: сначала тест (
tests/test_crud.py), потом реализация, запуск pytest, коммит; - первый прогон на трёх движках: 18 кейсов CRUD зелёные везде.
Уже в первых выводах агент флагнул проблему, которую человек мог бы пропустить:
One important design constraint is already visible: the natural sqlalchemy-utils name collides with the established third-party package; I’ll keep that provisional name for the spike and call it out explicitly in the findings rather than silently treating it as publishable.
«Уже видно важное ограничение: естественное имя sqlalchemy-utils конфликтует с существующим сторонним пакетом. Оставлю его как рабочее название для спайка и явно отмечу это в выводах, а не буду молча считать его публикуемым.» (транскрипт сессии)
Затем — запрос на рефакторинг: «Refactor the code so engine-specific portability stuff lives in a file for that specific engine — maybe a Database subclass is the best way to do that» («вынеси зависящий от движка код в файл под этот движок — возможно, лучше всего через подкласс Database»). Агент согласился и развёл три реализации — об этом следующий раздел.

Что получилось: API и CLI
К концу сессии библиотека повторяет стиль sqlite-utils «сначала таблица»: методы возвращают тот же объект Table, так что цепочки вызовов пишутся естественно. Пример из README:
from alchemy_utils import Database
db = Database("sqlite:///:memory:")
people = db["people"].insert(
{"id": 1, "name": "Ada", "profile": {"language": "Python"}},
pk="id",
)
people.upsert({"id": 1, "name": "Ada Lovelace"})
people.insert_all([{"id": 2, "name": "Grace"}, {"id": 3, "name": "Katherine"}])
people.update(2, {"name": "Grace Hopper"})
assert people.pks == ["id"]
assert people.columns_dict["profile"] is dict
assert people.get(1)["name"] == "Ada Lovelace"
Движок меняется одной строкой — только URL:
postgres = Database("postgresql+psycopg://user:password@localhost/app")
duckdb = Database("duckdb:///analytics.duckdb")

CLI даёт те же операции из терминала: create-table, insert, upsert, update, а для чтения — tables, views, schema, columns, indexes, foreign-keys, rows, get, count. Ввод — JSON, JSONL, CSV, TSV, файлы или stdin. Уиллисон показал однострочник для своей базы блога в локальном PostgreSQL:
uvx --with 'alchemy-utils[postgresql]' alchemy-utils rows 'postgresql+psycopg://simon@localhost:5432/simonwillisonblog' redirects_redirect
Три движка — три набора граблей
Самый ценный материал кейса — где именно SQLAlchemy не спасает. В RESEARCH.md Уиллисон честно разложил, что переносимо, а что пришлось адаптировать.
Проблема первая: upsert не входит в общий API SQLAlchemy. insert().on_conflict_do_update() есть только у диалектных конструкций (PostgreSQL, SQLite); DuckDB понимает синтаксис PostgreSQL и использует ту же форму. Вторая: DuckDB через duckdb-engine 0.17.0 не отдаёт первичные ключи при рефлексии, JSON видит как VARCHAR, список индексов пустой, счётчик обновлённых строк не сходится. Третья: сгенерированные первичные ключи — duckdb-engine наследует DDL от PostgreSQL и рендерит SERIAL, который DuckDB не принимает; решение — DuckDB Sequence как server default.
The prototype proves the requested surface on SQLite, PostgreSQL, and DuckDB.
«Прототип доказывает требуемую поверхность API на SQLite, PostgreSQL и DuckDB» — и дальше по тексту: портируемый контракт нужно явно определить там, где sqlite-utils сегодня завязан на специфику SQLite. (RESEARCH.md)

TDD как способ разговора с агентом
Ключ к воспроизводимости кейса — требование red/green в промпте. Каждая фича начинается с падающего теста, который фиксирует контракт, затем идёт минимальная реализация до PASS и коммит.

Тесты защищают и самого агента: после переименования проекта полный прогон (pytest -q, ruff check) подтверждал, что ничего не сломалось. Финальная проверка включала сборку wheel, установку в чистую Python 3.10-окружение и round-trip create/insert/read через CLI — это уже сценарий релиза, а не просто «тесты зелёные».
Follow-up промпты и оптимизация CSV
«Very few follow-up prompts» — Уиллисон не публиковал каждый из них, но в транскрипте видны минимум четыре содержательных:
- рефакторинг — разнести движкозависимый код по подклассам Database (раздел выше);
- CLI — добавить консольные команды create-table, insert, upsert, update, rows, schema, indexes с чтением JSON/JSONL/CSV/TSV и stdin;
- переименование — «Let’s rename this to alchemy-utils»: агент сам проверил PyPI (404) и переименовал пакет, модуль, CLI и все тесты;
- дебаг-вопрос — «Why does this break? uv run alchemy-utils schema ‘duckdb:///test.duck’». Агент воспроизвёл команду, выяснил, что базы test.duck на месте нет (DuckDB молча создал пустую), и сам признал причину: при уборке репозитория случайно перенёс файл базы. Вернул его (780 КБ) и честно написал:
Sorry—I mistook your database for a generated test artifact.
«Прости — я принял вашу базу за сгенерированный тестовый артефакт.» (транскрипт сессии)
Эпизод показательный: агент не только нашёл собственный баг, но и не стал о нём молчать. Для пользователей таких инструментов это аргумент в пользу открытых журналов действий.
Последний штрих кейса — производительность. Уиллисон показал однострочник импорта каталога деревьев Сан-Франциско в DuckDB:
curl 'https://raw.githubusercontent.com/simonw/sf-tree-history/refs/heads/main/Street_Tree_List.csv' | uvx --with 'alchemy-utils[duckdb]' alchemy-utils insert 'duckdb:////tmp/trees.db' trees - --csv
That took nearly an hour the first time I ran it, so I had Codex optimize it and got it down to around 35 seconds.
«В первый раз это заняло почти час, поэтому я попросил Codex оптимизировать — он сократил время примерно до 35 секунд.» (Simon Willison, 12.08.2026)

Сократить время с часа до 35 секунд удалось за счёт пакетной записи: CSV и TSV читаются и вставляются порциями — по умолчанию 100 записей, размер меняется флагом --batch-size.
Что получилось к концу вечера
Итог — библиотека alchemy-utils (Apache-2.0), релиз 0.1a0 12 августа в 19:51, в репозитории 38 коммитов.
| Показатель | Значение |
|---|---|
| Промптов | 1 исходный + «очень немного» follow-up (в транскрипте видны 4 содержательных) |
| Коммитов | 38, почти все — агент |
| Тестов | 164 PASS + 1 expected skip (partial index DuckDB), ruff clean |
| Движки | SQLite, PostgreSQL (testing.postgresql), DuckDB 1.5.5 |
| Релиз | 0.1a0 (pre-release), 12.08.2026 19:51 |
| Оптимизация | импорт CSV: ~1 час → ~35 сек |
| Стоимость токенов | не опубликована |
Последняя строка — про деньги — важная оговорка. В отличие от прошлого кейса Уиллисона с Codex Desktop, где он публиковал оценку AgentsView ($23,28 за 52-минутную сессию; разбор — в кейсе про игру), в этом посте точные затраты на токены не названы. Прикинуть бюджет можно по правилу «длинная агентная сессия по API — десятки долларов», но цифра за этим конкретным вечером остаётся неизвестной.

Как это повторить
Рецепт из кейса переносится на обычные задачи:
- Промпт-ТЗ вместо промпта-пожелания. Шесть пунктов Уиллисона закрывают всё: цель, референс, критерии проверки (движки), процесс (TDD, git), инструменты (uv), образец (django-sql-dashboard).
- Red/green как страховка от «красивого, но сломанного». Падающий тест фиксирует контракт до реализации; агент не может «забыть» требование, если оно записано в тесте. В кейсе про легаси тесты называли главным инструментом, но и главной ловушкой — валидность тестов всё равно проверяет человек.
- Follow-up вместо одного гигантского запроса. Библиотека не вышла за один промпт: рефакторинг, CLI, переименование и дебаг пришли отдельными короткими вопросами. Агент на сложной задаче работает итерациями, когда каждый шаг проверяем.
- Открытый журнал действий. Транскрипт и коммиты показывают, что агент решал сам и где ошибался — включая собственную ошибку с test.duck.
Что кейс даёт и чего не даёт
Ограничения стоит назвать так же прямо, как это сделал сам автор.
- Это спайк, а не обещание совместимости. В README это первая строка: «An executable research spike… This is a spike, not a published compatibility promise». Ряд функций sqlite-utils не перенесён вовсе: hash_id, extracts, conversions, FTS, полные transforms.
- Память и типовая адаптация — в задел. Bulk-вставка материализуется в памяти (batch_size принят, но потоковой записи пока нет);
alter=Trueдобавляет только nullable-колонки; DuckDB expression-indexes разбираются «best-effort». - Оценка production. В RESEARCH.md — прямо: довести ядро до документированного v0.1 — примерно 3–5 недель работы опытного инженера; полный паритет с sqlite-utils — «многомесячная работа». Вечер агента — это прототип и контракт API, а не готовая библиотека.
- Стоимость не раскрыта, репозиторий маленький (5 звёзд на момент публикации), API без стабильных гарантий — использовать на проде рано.
Российские реалии
Кейс строится на трёх слоях, и доступность у них разная.
Codex и модели OpenAI. Вход и оплата — главные ограничения из РФ. OpenAI блокирует доступ с российских IP с декабря 2022 года: для входа в Codex нужен VPN с иностранным IP («ChatGPT: как пользоваться нейросетью в России»). Платные подписки российскими картами не оплатить: рабочие схемы — зарубежная виртуальная карта, карты банков Казахстана/Грузии/Армении, посредники; OpenAI отклоняет карты и может блокировать аккаунт при входе с российского IP (личный опыт с суммами). Бесплатный план Codex в ChatGPT не требует карты и хватает на десятки небольших задач в неделю. Есть и обходной путь — переключить Codex CLI на российский инференс (разбор подключения к API Яндекса). Свежее сравнение Codex, Claude Code и Kimi на одной задаче — Habr, 4 августа 2026.
Сам alchemy-utils и его окружение. Библиотека открытая (Apache-2.0) и живёт на GitHub и PyPI — оба доступны из РФ без VPN. SQLAlchemy, DuckDB, SQLite, psycopg — тоже открытые пакеты. То есть повторить сам кейс («написать библиотеку на SQLAlchemy») из России можно полностью: мешает только доступ к агенту, а не к инструментам.
Русскоязычных кейсов «агент написал библиотеку за вечер» на момент публикации не нашлось — тема свежая, и этот материал по сути один из первых разборов на русском. Ближайшее, что есть в рунете, — гайды по установке и настройке Codex CLI (Habr) и общие сравнения агентов. Честно: механика кейса от страны не зависит, а вот воспроизводимость на платном доступе упирается в оплату.
Вывод
Один продуманный промпт дал за вечер рабочий прототип библиотеки с 164 тестами и CLI: процесс (референс, движки, red/green, uv+git) оказался важнее объёма текста в задании. Ценность кейса не в «ИИ написал библиотеку», а в том, что границы возможного видны изнутри: агент сам нашёл конфликт имён, сам починил собственный баг и сам ускорил импорт примерно в 100 раз — а довести результат до продакшена всё равно нужно человеку на 3–5 недель. Это реалистичная картина агентного кодинга в 2026-м: скорость прототипирования — да, гарантии зрелости — нет.
По серии: общая вводная — «Что такое агентный кодинг», установка и настройка Codex CLI — «Codex CLI от OpenAI», сравнение агентов — «Лучший ИИ-агент для кодинга в 2026», смежные кейсы — «Переписываем легаси с ИИ-агентами» и «Игра с ИИ-агентом за час».
