Simon Willison’s llm is one of the few command-line tools that survived my first month of using a local model and has been on my dock ever since. The 0.33 release, dated 22 August 2026, is the kind of small version bump that does not show up in any product announcement and does not need to. Most of the work is below the surface: a rewritten HTTP client, key handling that finally matches between embeddings and completions, repeatable templates that turn the CLI into a slightly more programmable thing than it was a week ago. None of those changes justify a blog post on their own. Together, they are why I keep the tool installed.
If you have never used llm, here is the short version. It is a CLI (command-line interface) wrapper around a long list of hosted and local model providers. You run llm "prompt", pick a model with -m, and it pipes the answer to stdout. Pipelines are easy because the tool is happy to take input from stdin, save a fragment as a named template, and reuse it. The 0.33 notes from the upstream changelog (referenced as GitHub issues #1608, #1631, #1600, #1620, #757 in the release post) cover six changes that practitioners will hit in the first week. Five of them are quiet. One, the HTTP client swap, is the kind of fix you only notice if you read the diff.
The HTTP rewrite nobody asked for, but everyone needed
The single most user-visible item in 0.33 is also the least glamorous. The CLI was upgraded to the OpenAI Python library 3.x line and switched its HTTP transport from httpx to httpx2. The author shipped a 0.32.1 quick fix the day before this release, but the changelog calls 0.33 the more comprehensive fix. That is the engineer-honest version of “we broke something and we are sorry.” The patch in 0.32.1 closed the worst symptom, and the 0.33 line quietly reworks the whole HTTP path so the bug class stops showing up.
For most users this is invisible, which is the point. The behavior you want, same CLI surface, same flags, same outputs, keeps working. The behavior that was unstable on some network conditions is now stable. If you ever saw llm time out on a long streaming response, or saw a retry loop that did not seem to back off correctly, that is the path that got rewritten.
A short list of the practical wins:
- Stream stability on long completions is back to “I forget the CLI is even there” levels, which is what you want.
- Retry and backoff now match the rest of the OpenAI client family, so any tooling that observes the CLI’s network behavior gets sane numbers.
- The dependency line on
httpx2(a fork that carries the bug fixes the original is still arguing about) is the kind of decision that makes CLI tools reliable on machines that have to run for months without intervention.
None of this is novel. It is the unglamorous work of keeping a tool on a stable dependency line, and the only reason it shows up in release notes is that the previous version had a real bug. The fix landed in two steps, which is the honest way to ship a breaking change: stop the bleeding first, clean up the underlying mess in the next release.
Embedding key handling, finally consistent with the rest of the tool
Embedding models have always been second-class citizens inside llm, in the sense that they lived behind a slightly different code path than regular chat or text completions. The CLI accepted keys for chat models via --key (and through the LLM_OPENAI_API_KEY-style environment variables) but the embedding commands had to inherit whatever key was set on the model globally. That was a long-standing papercut for anyone running multiple embeddings jobs back-to-back against different keys.
Version 0.33 closes the gap. llm embed and llm embed-multi both accept --key now, and the Python side (EmbeddingModel.embed(), EmbeddingModel.embed_multi(), Collection.embed(), Collection.embed_multi()) accepts key= too. The resolved per-call key is passed to embedding plugins without changing the shared model state, which is the right shape. Existing plugins that read self.key still work, via a compatibility fallback. GitHub user ChrisJr404 contributed this; the upstream issues are #757 and #1620.
The practical difference is small but real. If you run llm embed-multi inputs.csv --model text-embedding-3-small --key $WORK_KEY, the WORK_KEY is used for that call only, and any subsequent call that does not pass --key falls back to the model default. Before 0.33, you had to set the key on the model globally and reset it after the call. The new shape makes embedding jobs in pipelines and one-off scripts much easier to keep correct. The shape that previously would have leaked a key across calls no longer does.
Repeatable templates, and why that unlocks a small workflow
The third change in this round is the one I will use most. llm prompt -t (or --template) used to be a one-shot: you passed a template, you got a prompt, you moved on. As of 0.33, you can pass -t more than once, and the templates are merged in order. That is a small syntactic change but it enables a workflow that was genuinely awkward to do before.
Here is the canonical pattern, straight from the changelog:
llm -m gpt-5.6-luna -o reasoning_effort high --save lhigh
llm "Generate an SVG of a pelican riding a bicycle" --save pelican
llm -t lhigh -t pelican
The first call saves a model-plus-options bundle (lhigh) that pins the model and a particular option (reasoning_effort high). The second saves a prompt fragment (pelican). The third call combines the two. Before 0.33 you could only pass one template, so this kind of “options bundle plus prompt bundle” pattern was not expressible without copying the model flag every time.
A short list of what this unlocks in practice:
- Save a model-and-options bundle once per project, then stop typing the same
-mand-oflags every session. - Save a small prompt library as named templates, and combine a model bundle with a prompt library entry at call time.
- Run a chain of templates from a shell script without juggling environment variables between calls.
- Keep prompt experiments and model choice decoupled. Switch the model, keep the prompt. Switch the prompt, keep the model.
The “unlock” word is doing a lot of work in that sentence, but I think it is fair. Templates were the part of the CLI I used least before this release. After one afternoon with the repeatable flag, they are the part I reach for first.
Reasoning summaries on the Responses API
The last of the major changes is also the most provider-specific. Reasoning-capable Responses API models (OpenAI’s protocol for streaming structured responses back from the model) now accept a reasoning_summary option with three values: auto, concise, and detailed. The CLI exposes this through llm openai endpoint --responses, and the upstream issue is #1600.
Why this matters: a number of model providers other than OpenAI now imitate the Responses API contract. If you want to exercise one of those alternative providers, you want the same knobs you have on the original. The new flag means the CLI does not treat OpenAI as a special case. The same reasoning options pass through to any provider speaking the same dialect. For someone running the CLI against multiple backends in a single session, that is the right shape.
The auto value lets the provider pick the level of summary it thinks makes sense for the model. concise is the short version, useful when you are running thousands of calls in a batch and just want the verdict. detailed is the long version, useful when you are debugging why a particular chain-of-thought step went the way it did. None of these replace reading the actual reasoning trace, but they let you control how much of the reasoning trace the CLI surfaces alongside the final answer.
Trade-offs and what is still rough
A few honest caveats, because the release notes are short and most of the friction lives below them.
The HTTP client swap means anyone pinning httpx somewhere in their dependency tree may have to bump. That is the cost of being on a non-frozen stack. If you wrap llm from another Python tool and you imported httpx from the same venv, you will need to test that path.
The embedding key fallback for old plugins that read self.key is a compatibility shim, not a long-term API. Plugin authors should plan to read the new per-call key. The shim will be there for a release or two and then it will not.
Repeatable templates are syntactically additive. They do not change any existing template behavior, but they do change the mental model of what a template is. A template is no longer just “a prompt with substitutions.” It is now “a partial invocation.” That is a richer abstraction, but it does mean the saved-template file format will grow new fields over the next couple of releases. If you author templates, expect to read the migration note.
The reasoning summary option is provider-mediated. Some providers will implement auto differently from others, and a few will ignore the flag entirely. If you depend on the summary level, test against the specific provider you ship to.
Bottom line
Upgrade if any of these four changes touch something you do today: you run the CLI on long streams and hit occasional instability; you script embeddings with multiple keys; you save templates for prompts and wish you could save model-and-options bundles too; you exercise the Responses API through any provider. Skip the upgrade if you run the CLI only for short completions against a single key and have no template library yet. The release is a quiet one, and most of its value is the fact that the underlying stack is finally on a stable line.
For me, 0.33 is the version where the CLI stops feeling like a thin wrapper and starts feeling like a small piece of plumbing that I can build real pipelines on top of. That is a higher bar than 0.32 set, and the changelog does not oversell the gap.