<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>macos on Saurabh Kumar</title><link>https://saurabh-kumar.com/tags/macos/</link><description>Recent content in macos on Saurabh Kumar</description><generator>Hugo</generator><language>en-US</language><copyright>Copyright © 2025, Saurabh Kumar.</copyright><lastBuildDate>Thu, 08 Oct 2026 12:00:00 +0000</lastBuildDate><atom:link href="https://saurabh-kumar.com/tags/macos/index.xml" rel="self" type="application/rss+xml"/><item><title>Wispr Flow</title><link>https://saurabh-kumar.com/til/1791460800/</link><pubDate>Thu, 08 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791460800/</guid><description>&lt;p&gt;Back on &lt;a href="https://wisprflow.ai/" rel="noopener external"&gt;Wispr Flow&lt;/a&gt; after six months on local speech-to-text (&lt;a href="https://github.com/altic-dev/FluidVoice" rel="noopener external"&gt;FluidVoice&lt;/a&gt; with Parakeet, plus Whisper). Local transcription is fine. The cleanup is what&amp;rsquo;s missing: fixing misheard jargon ate the time dictation saved. Flow&amp;rsquo;s correction pass, OS-level integration and phone keyboard win. It matters more now because models follow high-level intent well: I speak the intent plus constraints in one go and let the LLM structure it. Whisper mode works even when you speak softly. Want to try it? My &lt;a href="https://wisprflow.ai/r?SAURABH2220" rel="noopener external"&gt;referral link&lt;/a&gt; gets you a free month of Pro, and I get one once you&amp;rsquo;ve dictated 2,000 words.&lt;/p&gt;&#10;</description></item><item><title>Recordly</title><link>https://saurabh-kumar.com/til/1783944000/</link><pubDate>Mon, 13 Jul 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1783944000/</guid><description>&lt;p&gt;&lt;a href="https://github.com/webadderallorg/Recordly" rel="noopener external"&gt;Recordly&lt;/a&gt; does video recording, screenshots, and demo editing in one open-source Electron app. Not in Homebrew core, but easy to ship via a &lt;a href="https://github.com/theskumar/homebrew-tap" rel="noopener external"&gt;personal tap&lt;/a&gt;: &lt;code&gt;brew install --cask theskumar/tap/recordly&lt;/code&gt;. Replacing my previous screenshot/recording workflow with this as the single default (see &lt;a href="https://saurabh-kumar.com/articles/2025/11/default-apps-2025/" rel="noopener external"&gt;Default Apps 2025&lt;/a&gt;).&lt;/p&gt;&#10;</description></item><item><title>Moshi</title><link>https://saurabh-kumar.com/til/1781524800/</link><pubDate>Mon, 15 Jun 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1781524800/</guid><description>&lt;p&gt;&lt;a href="https://getmoshi.app/" rel="noopener external"&gt;Moshi&lt;/a&gt; is the best SSH client I&amp;rsquo;ve found for iPhone — connects to your laptop&amp;rsquo;s tmux sessions over &lt;a href="https://mosh.org/" rel="noopener external"&gt;Mosh&lt;/a&gt;, making it rock-solid on mobile networks with roaming support. Perfect for an agent view on the go; I use it to monitor and interact with running pi/Claude sessions from my iPhone without losing the session on network switches.&lt;/p&gt;&#10;</description></item><item><title>My Local LLM On A 32 GB M1 Pro</title><link>https://saurabh-kumar.com/articles/2026/06/my-local-llm-on-a-32-gb-m1-pro/</link><pubDate>Sun, 14 Jun 2026 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2026/06/my-local-llm-on-a-32-gb-m1-pro/</guid><description>&lt;p&gt;My pi setup has a &lt;code&gt;fast&lt;/code&gt; mode: a slot I wired into my own config for the small, high-volume questions, pointed at Bedrock&amp;rsquo;s Haiku 4.5. It is a good model. Cheap, responsive, and it answers the kind of small, contained question I throw at it twenty times a day without complaining. It runs on third-party hardware, and those questions send code to a third party. I wanted to try running that slot locally.&lt;/p&gt;&#10;&lt;p&gt;None of that is a crisis. Bedrock is fast and it works. But the machine that could do the same job is sitting on my desk, and when Kyle Howells &lt;a href="https://ikyle.me/blog/2026/how-to-setup-a-local-coding-agent-on-macos" rel="noopener external"&gt;got Gemma 4 26B-A4B running locally on an M1 Max at 72 tokens per second&lt;/a&gt; and the &lt;a href="https://news.ycombinator.com/item?id=48507020" rel="noopener external"&gt;Hacker News thread&lt;/a&gt; filled up with people on smaller Macs reporting the same setup was workable, I wanted to know whether my own laptop could host that slot.&lt;/p&gt;&#10;&lt;p&gt;The catch is that my laptop is an M1 Pro with 32 GB of unified memory: half the RAM and roughly half the memory bandwidth of the machine in Kyle&amp;rsquo;s post. Copying his commands verbatim was never going to work. I needed to know what to change, in what order, and what to give up. So I did not copy the commands. I gave pi the article and asked it to build me a plan for my machine, and to interrogate me before it wrote a line.&lt;/p&gt;&#10;&lt;h2 id="i-handed-the-guide-to-an-llm-and-made-it-grill-me"&gt;I Handed The Guide To An LLM And Made It Grill Me&lt;/h2&gt;&#10;&lt;p&gt;The whole thing started with one message. Typos and all, this is what I actually sent:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Act local AI setup expert working on M1 silicon with 32GB ram. Read though [the article] and also pull all the comments on this article on news.ycombinator.com article id 48507020. I&amp;rsquo;ve ollama and muse already installed there are models installed as well. Prep and action plan to set this up in the most performant and maintaibale way. Not sure quite sure if i need to uninstall previous ollama app and it&amp;rsquo;s modetls. Create and write a plan after analysing things. &lt;code&gt;/grill-me&lt;/code&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The &lt;code&gt;/grill-me&lt;/code&gt; at the end is a small skill in my pi setup. It is two paragraphs of instructions that tell the model to walk down the decision tree one node at a time, recommend an answer at each step, and wait for my reply before moving on. It is not magic. It is a way of forcing the conversation I would otherwise have with myself in a notebook, except the model will not let me skip a step.&lt;/p&gt;&#10;&lt;p&gt;The interview was mostly the model proposing and me confirming in monosyllables. My replies, verbatim, were things like:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;A&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;i would prefer ~/code/localLLM&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;A, also note i&amp;rsquo;ve &amp;ldquo;llm&amp;rdquo; cli already installed, and alias would be nice.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;let&amp;rsquo;s go with recommendation, write the plan.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;That is the entire texture of it. A simple opening prompt that points at an existing guide and names my constraints, then a handful of one-word answers steering the branches. The model did the reading (the article and the full HN thread) and the arithmetic. I made the calls.&lt;/p&gt;&#10;&lt;p&gt;And it pushed back, which is the point. When I leaned toward the 65 K context window from the article, it did the memory math and showed me I would be swapping inside the first generation on 32 GB, so we settled on 32 K. When I waffled on keeping Ollama &amp;ldquo;just in case,&amp;rdquo; it pointed out I was at 89 % disk with a thirteen-gigabyte Ollama models directory I was not using. When I asked for an alias, it caught that &lt;code&gt;llm&lt;/code&gt; was already taken by &lt;a href="https://llm.datasette.io" rel="noopener external"&gt;Simon Willison&amp;rsquo;s tool&lt;/a&gt; and proposed &lt;code&gt;lcl&lt;/code&gt; instead. None of these are insights I lacked. They are checks I would have skipped because I was already mentally on the next step.&lt;/p&gt;&#10;&lt;p&gt;By the end there was a written record of every decision and its reason, in the repo, not in my head. That is where it should be.&lt;/p&gt;&#10;&lt;h2 id="the-decisions-that-fell-out"&gt;The Decisions That Fell Out&lt;/h2&gt;&#10;&lt;p&gt;The interview converged on a short list, and the reasoning behind each one is worth more than the choice itself.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Model: Gemma 4 26B-A4B, the MoE, not a dense model.&lt;/strong&gt; The single most important fact about local inference on a Mac is not how much RAM you have, it is how fast it can be read. Generation is bottlenecked on memory bandwidth. The M1 Max in Kyle&amp;rsquo;s article runs at 400 GB/s; my M1 Pro at roughly 200. Everything else being equal, I should expect about half his throughput. His 72 tokens per second becomes my 36. That is still faster than I read. It is also why a mixture-of-experts model is the right answer here: Gemma 4 26B-A4B holds 26 billion parameters but activates only four billion per token. The full weights sit in RAM, but each forward pass streams a fraction of them. The dense 27B sibling has the same disk footprint and meaningfully worse generation rate. It is probably smarter. It does not matter, because it is too slow for the slot I am filling.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Speculative decoding, on.&lt;/strong&gt; Gemma 4 ships an MTP draft head, a tiny network that predicts the next several tokens cheaply, which the main model accepts or rejects in one pass. When acceptance is high, you get most of those tokens almost for free. Kyle measured +24 % on his machine. The HN thread is full of caveats about acceptance rates and MoE models benefiting less. None of it changes the decision: turn it on, measure, keep what you get.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Context: 32 K, not 65 K.&lt;/strong&gt; The arithmetic the model made me do. A 65 K KV cache plus 16 GB of weights plus a draft model does not leave enough headroom on 32 GB once Chrome and an IDE are open.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;mise, not a shell script.&lt;/strong&gt; The article uses &lt;code&gt;huggingface-cli&lt;/code&gt; to fetch the GGUFs and a hand-written tmux script to launch the server. Both work. Both are also infrastructure you now own and will forget you configured. llama.cpp&amp;rsquo;s &lt;code&gt;-hf&lt;/code&gt; flag does the download without a Python virtualenv. mise&amp;rsquo;s &lt;code&gt;github:&lt;/code&gt; backend installs the prebuilt Metal binary and rolls it forward with &lt;code&gt;mise upgrade&lt;/code&gt;. The tmux script becomes a few named tasks in a &lt;code&gt;.mise.toml&lt;/code&gt;. The choice is not about elegance, it is about which moving piece you are willing to own. I already own mise.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Ollama goes.&lt;/strong&gt; I had it from earlier experiments, with &lt;code&gt;gemma4:latest&lt;/code&gt; and a couple of other models taking 13 GB, plus the app idling in the background on port 11434. Ollama is a wrapper around llama.cpp. Once you run &lt;code&gt;llama-server&lt;/code&gt; directly, the wrapper is in the way. So the app, the models directory, and the port all go, and the 13 GB comes back on a disk that was at 89 %.&lt;/p&gt;&#10;&lt;h2 id="the-setup-start-to-finish"&gt;The Setup, Start To Finish&lt;/h2&gt;&#10;&lt;p&gt;This is the reproducible part. You can follow it by hand, or point an LLM at this section and let it do the work. That is roughly how it got built in the first place. The numbers below are the actual results on my M1 Pro, not estimates.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;1. Decommission Ollama&lt;/strong&gt; (skip if you never had it):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;osascript -e &lt;span class="s1"&gt;&amp;#39;tell application &amp;#34;Ollama&amp;#34; to quit&amp;#39;&lt;/span&gt; 2&amp;gt;/dev/null&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkill -f Ollama&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -rf ~/.ollama /Applications/Ollama.app &lt;span class="c1"&gt;# reclaimed 13 GB&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2. Install llama.cpp via mise.&lt;/strong&gt; The prebuilt arm64 binary has Metal and Accelerate baked in:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mise use --global &lt;span class="s1"&gt;&amp;#39;github:ggml-org/llama.cpp@latest&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;llama-server --help &lt;span class="p"&gt;|&lt;/span&gt; grep -E -- &lt;span class="s1"&gt;&amp;#39;--spec-type|-hf&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# confirm MTP + HF download support&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The one thing worth verifying is that &lt;code&gt;--spec-type draft-mtp&lt;/code&gt; is present, since MTP support landed only recently. On my install (build &lt;code&gt;b9631&lt;/code&gt;) it was there. If it ever is not, a source build with &lt;code&gt;-DGGML_METAL=ON&lt;/code&gt; is the five-minute fallback.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;3. A project with mise tasks&lt;/strong&gt; at &lt;code&gt;~/code/localLLM/.mise.toml&lt;/code&gt;. The &lt;code&gt;[env]&lt;/code&gt; block makes the model cache live with the project; the tasks replace the shell script:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;LLAMA_CACHE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{{ config_root }}/models&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;github:ggml-org/llama.cpp&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;latest&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Start llama-server (Gemma 4 26B-A4B + MTP) in tmux&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;tmux has-session -t llama 2&amp;gt;/dev/null &amp;amp;&amp;amp; { echo running; exit 0; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;tmux new-session -d -s llama -c &amp;#34;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;PWD&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; \\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&lt;/span&gt;&lt;span class="nx"&gt;llama-server&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;-hf&lt;/span&gt; &lt;span class="nx"&gt;unsloth&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gemma-4-26B-A4B-it-GGUF&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;UD-Q4_K_XL&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;-hfd&lt;/span&gt; &lt;span class="nx"&gt;unsloth&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;gemma-4-26B-A4B-it-GGUF&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Q8_0-MTP&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;--spec-type&lt;/span&gt; &lt;span class="nx"&gt;draft-mtp&lt;/span&gt; &lt;span class="nx"&gt;--spec-draft-n-max&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;-ngl&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt; &lt;span class="nx"&gt;-fa&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;-c&lt;/span&gt; &lt;span class="mi"&gt;32768&lt;/span&gt; &lt;span class="nx"&gt;--parallel&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;--reasoning&lt;/span&gt; &lt;span class="nx"&gt;off&lt;/span&gt; &lt;span class="nx"&gt;--reasoning-budget&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;--temp&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt; &lt;span class="nx"&gt;--repeat-penalty&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt; &lt;span class="nx"&gt;--repeat-last-n&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;--host&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="nx"&gt;--port&lt;/span&gt; &lt;span class="mi"&gt;8082&lt;/span&gt; &lt;span class="err"&gt;\\&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;tee&lt;/span&gt; &lt;span class="nx"&gt;-a&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;llama-server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tmux kill-session -t llama 2&amp;gt;/dev/null &amp;amp;&amp;amp; echo stopped || echo &amp;#39;not running&amp;#39;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;curl -fsS http://127.0.0.1:8082/v1/models &amp;gt;/dev/null &amp;amp;&amp;amp; echo up || echo down&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A note on the port. The article uses 8080. On my machine 8080 and 8081 were already taken by Google &lt;code&gt;cloud-run&lt;/code&gt; processes, so the server silently answered on a port that was not mine until I noticed the responses carried a &lt;code&gt;Server: Google Frontend&lt;/code&gt; header. I moved everything to &lt;strong&gt;8082&lt;/strong&gt;. Check &lt;code&gt;lsof -i :8080&lt;/code&gt; before you assume a port is free.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;4. A one-key alias&lt;/strong&gt; in &lt;code&gt;~/.zshrc&lt;/code&gt;. &lt;code&gt;llm&lt;/code&gt; was taken, so &lt;code&gt;lcl&lt;/code&gt; for &amp;ldquo;local&amp;rdquo;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lcl&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; mise -C &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/code/localLLM&amp;#34;&lt;/span&gt; run &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lcl start &lt;span class="c1"&gt;# boot the server (first run downloads the model)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lcl status &lt;span class="c1"&gt;# is it up?&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lcl stop &lt;span class="c1"&gt;# free the ~22 GB of RAM&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;5. First boot and download.&lt;/strong&gt; &lt;code&gt;lcl start&lt;/code&gt; triggers the download through llama.cpp&amp;rsquo;s &lt;code&gt;-hf&lt;/code&gt;. The real footprint came in smaller than the article&amp;rsquo;s &amp;ldquo;~21 GB&amp;rdquo; estimate. The total was &lt;strong&gt;18 GB&lt;/strong&gt;: 16 GB for the Q4_K_XL main model, &lt;strong&gt;1.2 GB&lt;/strong&gt; for the MTP head (an auxiliary head, not a full second model), and a 441 MB multimodal projector that gets pulled automatically even though I do not need images. Add &lt;code&gt;--no-mmproj&lt;/code&gt; if you want to skip it.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;6. Point pi at it.&lt;/strong&gt; A &lt;code&gt;local&lt;/code&gt; provider in &lt;code&gt;~/.pi/agent/models.json&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;providers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;local&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Local llama.cpp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;baseUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://127.0.0.1:8082/v1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;openai-completions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;apiKey&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;models&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Gemma 4 26B-A4B Q4 + MTP&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;reasoning&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;contextWindow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;maxTokens&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;cost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;output&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;cacheRead&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;cacheWrite&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And a &lt;code&gt;local&lt;/code&gt; mode in &lt;code&gt;modes.json&lt;/code&gt; pointing at it. The benchmark answers came back clean, so I gave it real work, and that is where it broke.&lt;/p&gt;&#10;&lt;h2 id="the-thinking-spiral"&gt;The Thinking Spiral&lt;/h2&gt;&#10;&lt;p&gt;The first real task I handed Gemma was trivial: rewrite a Hugo template so a list of tags renders as &amp;ldquo;tagged A, B and C&amp;rdquo; instead of &amp;ldquo;A B C&amp;rdquo;. A ten-line edit. Haiku does it in one tool call. Gemma read the file, started thinking, and never stopped. The session export shows a single reasoning block of &lt;strong&gt;32,000 characters&lt;/strong&gt;: it derived the correct off-by-one logic, then re-derived it, then second-guessed the whitespace, then re-read its own conclusion and started over. It never emitted the edit. I aborted it. I told it &amp;ldquo;you are going in loop.&amp;rdquo; It thought about that, too, and aborted again.&lt;/p&gt;&#10;&lt;p&gt;This is the failure mode the chat-window benchmarks hide. Gemma 4 IT ships with thinking on, and llama.cpp&amp;rsquo;s default reasoning budget is unlimited (&lt;code&gt;-1&lt;/code&gt;). In a chat that is fine; the model thinks, then answers. Inside an agent loop, where the next move is supposed to be a tool call, an unbounded thinking budget on a small model is a trap. There is nothing forcing the transition from reasoning to acting, and a 4B-active MoE is not big enough to find the exit on its own. The default sampling makes it worse: &lt;code&gt;--repeat-penalty&lt;/code&gt; defaults to &lt;code&gt;1.0&lt;/code&gt;, which is to say off, so nothing discourages the model from looping on its own reasoning.&lt;/p&gt;&#10;&lt;p&gt;My first instinct was the biggest hammer: turn thinking off entirely. It worked. But &amp;ldquo;it worked&amp;rdquo; is where most debugging stops and most of the understanding gets lost, so I made the model prove which flag was actually doing the work. I had pi reconstruct the exact failing scenario from the session export, same system prompt, same tools, same file, same prompt, and replay it against two server configs:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;--reasoning off --reasoning-budget 0 # A: no thinking&#10;--reasoning auto --reasoning-budget 1024 --repeat-penalty 1.1 # B: bounded thinking&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Same prompt, same machine, here is what came back:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Config&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Reasoning emitted&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Outcome&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Original (unbounded, no repeat penalty)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;32,000 chars&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;spiral, aborted, no edit&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;A — reasoning off&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0 chars&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;clean &lt;code&gt;read&lt;/code&gt; → &lt;code&gt;edit&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;B — bounded to 1024&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;518 chars&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;clean &lt;code&gt;read&lt;/code&gt; → &lt;code&gt;edit&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The lesson is in the third row. Capping the budget alone stopped the spiral: the model thought for 518 characters, decided, and acted. So the bug was never &amp;ldquo;thinking is bad.&amp;rdquo; It was &amp;ldquo;&lt;em&gt;unbounded&lt;/em&gt; thinking is bad,&amp;rdquo; and &lt;code&gt;--reasoning off&lt;/code&gt; is just the most aggressive way to bound it to zero. Both configs produced the same edit, the same slightly-clumsy Hugo logic. The extra reasoning in config B bought nothing on this task; that is a 4B-active model hitting its ceiling, not a budget I can tune my way around.&lt;/p&gt;&#10;&lt;p&gt;Two more things I only learned by testing. &lt;code&gt;--repeat-penalty&lt;/code&gt; is an orthogonal fix and worth keeping regardless: at &lt;code&gt;1.0&lt;/code&gt; it is off, and it guards the &lt;em&gt;content&lt;/em&gt; against loops, not just the thinking. And &lt;code&gt;--reasoning off&lt;/code&gt; does not lobotomise the model the way the name suggests. I gave it a reasoning trap with thinking off, and it still worked the problem out, step by step, &lt;em&gt;in its visible answer&lt;/em&gt;, and got it right. What the flag removes is the separate, hidden, budget-less thinking phase, not the model&amp;rsquo;s ability to reason. For an agent loop that is the right trade: when reasoning happens it is visible in the content and bounded by the task, instead of a hidden phase that can run away.&lt;/p&gt;&#10;&lt;p&gt;So for the &lt;em&gt;fast&lt;/em&gt; slot I keep it off. The whole point of the slot is a quick, direct tool call; the test showed bounded thinking adds latency with no quality gain here, and if I want real reasoning I switch to a cloud mode. That is also why &lt;code&gt;&amp;quot;reasoning&amp;quot;&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; in the model config above, with thinking disabled at the server there is nothing for pi to render. The middle ground is real and now proven, though: if I ever stand up a &lt;code&gt;local-deep&lt;/code&gt; mode for harder offline work, &lt;code&gt;--reasoning auto --reasoning-budget 1024&lt;/code&gt; is a configuration I have watched stay out of the ditch. It just wants its own server on its own port, because &lt;code&gt;--reasoning off&lt;/code&gt; is a hard gate you cannot toggle back on per request.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;What it actually does&lt;/strong&gt;, measured on the M1 Pro:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Metric&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Result&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Generation&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;34–36 tok/s&lt;/strong&gt; (predicted half of Kyle&amp;rsquo;s 72, got it)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Prompt ingestion&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;77 tok/s&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Time to first token&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;~376 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MTP acceptance&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;~65 %&lt;/strong&gt; (e.g. 1350 of 2090 drafted tokens accepted)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Half the bandwidth, half the tokens, just as predicted. The MTP acceptance beat what I feared. The article saw +24 % on a dense workload; here, two thirds of the drafted tokens landed.&lt;/p&gt;&#10;&lt;h2 id="how-i-actually-use-a-fast-model"&gt;How I Actually Use A Fast Model&lt;/h2&gt;&#10;&lt;p&gt;A &lt;code&gt;fast&lt;/code&gt; model in pi is not the thing I have a conversation with. It is the model that does the small, high-volume, latency-sensitive jobs around the edges of the real work, where Opus is overkill and the round-trip cost is what matters. Those are the jobs I would rather keep on my own machine, and the place where 36 tokens per second is plenty.&lt;/p&gt;&#10;&lt;p&gt;In my setup, that means:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;/answer&lt;/code&gt; and &lt;code&gt;Ctrl+.&lt;/code&gt;&lt;/strong&gt; — a &lt;a href="https://github.com/theskumar/agent-stuff/blob/main/extensions/answer.ts" rel="noopener external"&gt;small pi extension I run&lt;/a&gt; that grabs the last assistant message when it ends on a batch of clarifying questions, extracts them into structured JSON via a side LLM call, and opens a TUI so I can answer them one at a time. The extraction is a contained, schema-shaped task, the sort of thing a fast model handles well. It defaults to &lt;code&gt;claude-bridge/claude-haiku-4-5&lt;/code&gt; and is configurable at &lt;code&gt;~/.pi/agent/extensions/answer.config.json&lt;/code&gt;, the line I now point at &lt;code&gt;local&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Branch summaries and compaction.&lt;/strong&gt; When I navigate away from a branch on &lt;code&gt;/tree&lt;/code&gt;, or a session outgrows its context window, pi summarizes the work to carry forward. By default those calls run on the active session model, which means a &lt;code&gt;/tree&lt;/code&gt; hop can cost an Opus summary. A &lt;a href="https://github.com/theskumar/agent-stuff/blob/main/extensions/tree-fast-summary.ts" rel="noopener external"&gt;small extension I run&lt;/a&gt; reroutes both to whatever the &lt;code&gt;fast&lt;/code&gt; mode points at, so the throwaway housekeeping never touches the expensive model.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;/mode fast&lt;/code&gt;&lt;/strong&gt; for the genuinely small questions, the &amp;ldquo;what&amp;rsquo;s the flag for X&amp;rdquo; and &amp;ldquo;rename this&amp;rdquo; and quick grep-and-explain ones, where I switch the whole session to the fast slot and switch back.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;None of these need a frontier model. They need something that responds now and gets the structure right. That is the bar Gemma 4 has to clear to take the slot from Haiku.&lt;/p&gt;&#10;&lt;p&gt;So I gave it the slot. I went back and forth here. The cautious move was to add a separate &lt;code&gt;local&lt;/code&gt; mode, keep &lt;code&gt;fast&lt;/code&gt; on Haiku, and switch into the local one when I felt like testing it. I know how that goes: I would never remember to switch, and a week later I would have no data. So &lt;code&gt;fast&lt;/code&gt; now points at Gemma 4 and the Haiku mode moved to &lt;code&gt;lcl&lt;/code&gt;, there if I want to compare. Local models can be brilliant in a chat window and brittle inside an agent loop, as the thinking spiral already showed, and the only way I learn which one Gemma 4 is here is by living with it in the slot that fires twenty times a day. If it holds up, on tool calling, on diff quality, on the ten-line edits that fill my day, it stays. If it does not, the rollback is one line in &lt;code&gt;modes.json&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="what-i-am-not-doing-yet"&gt;What I Am Not Doing Yet&lt;/h2&gt;&#10;&lt;p&gt;I am not putting the server behind launchd. At ~22 GB resident on a 32 GB machine, leaving it running alongside Chrome and an IDE is asking for swap. On-demand &lt;code&gt;lcl start&lt;/code&gt; / &lt;code&gt;lcl stop&lt;/code&gt; is honest about the cost in a way always-on is not.&lt;/p&gt;&#10;&lt;p&gt;I am not running a second model on a second port. Maybe Qwen 3.6 35B-A3B earns a slot for the cases where Gemma is too weak. Maybe one model is enough. I will know in a week.&lt;/p&gt;&#10;&lt;h2 id="what-the-planning-helped-with"&gt;What the Planning Helped With&lt;/h2&gt;&#10;&lt;p&gt;The model researched the options; I chose the setup. The conversation made me write down why I chose each part, so I can refer back to it later.&lt;/p&gt;&#10;&lt;p&gt;I still need to see how the local model handles everyday work. If it does not hold up, I can switch the slot back.&lt;/p&gt;&#10;</description></item><item><title>How I Optimized My Zsh Startup Time</title><link>https://saurabh-kumar.com/articles/2026/05/how-i-optimized-my-zsh-startup-time/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2026/05/how-i-optimized-my-zsh-startup-time/</guid><description>&lt;p&gt;My zsh prompt was taking close to a second to appear on a fresh terminal. Not catastrophic. But you feel it every time tmux spawns a new pane, and for me that is constantly. The config had grown for years, one &lt;code&gt;eval&lt;/code&gt; and one plugin at a time, and nobody had been auditing it, least of all me.&lt;/p&gt;&#10;&lt;p&gt;What follows is the audit, and the eight things I changed. The numbers are estimates, not measurements, and I will say so again at the end.&lt;/p&gt;&#10;&lt;h2 id="measure-first"&gt;Measure first&lt;/h2&gt;&#10;&lt;p&gt;Before I touched anything, I measured. Two tools did the work:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Cold-start benchmark (run a few times, watch the variance):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; i in &lt;span class="o"&gt;{&lt;/span&gt;1..5&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; zsh -i -c exit&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# In-shell flamegraph:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1. Uncomment `zmodload zsh/zprof` at the top of .zshrc&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2. Add `zprof` at the bottom&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 3. Open a new shell&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zprof &lt;span class="p"&gt;|&lt;/span&gt; head -40&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;zprof&lt;/code&gt; is the one that helped most. It prints which function calls and &lt;code&gt;eval&lt;/code&gt;s are burning time, sorted by self-time and call count. That is enough to stop guessing.&lt;/p&gt;&#10;&lt;h2 id="1-stop-forking-brew---prefix-five-times-per-shell"&gt;1. Stop forking &lt;code&gt;brew --prefix&lt;/code&gt; five times per shell&lt;/h2&gt;&#10;&lt;p&gt;My macOS config had this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix coreutils&lt;span class="k"&gt;)&lt;/span&gt;/libexec/gnubin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix findutils&lt;span class="k"&gt;)&lt;/span&gt;/libexec/gnubin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix gnu-sed&lt;span class="k"&gt;)&lt;/span&gt;/libexec/gnubin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix grep&lt;span class="k"&gt;)&lt;/span&gt;/libexec/gnubin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix curl&lt;span class="k"&gt;)&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Five forks of &lt;code&gt;brew&lt;/code&gt;, which is a Ruby program, on every shell, just to resolve paths that always end up under &lt;code&gt;${HOMEBREW_PREFIX}/opt/&amp;lt;formula&amp;gt;&lt;/code&gt; anyway. Each fork is about 35 ms on Apple Silicon, so that is roughly 180 ms gone before I have typed a single character.&lt;/p&gt;&#10;&lt;p&gt;Fix:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;_brew_opt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOMEBREW_PREFIX&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/opt/homebrew&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/opt&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt; -d &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;_brew_opt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/local/opt&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/coreutils/libexec/gnubin:&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/findutils/libexec/gnubin:&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/gnu-sed/libexec/gnubin:&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/grep/libexec/gnubin:&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/curl/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;unset&lt;/span&gt; _brew_opt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A path-prefix lookup costs nothing. The &lt;code&gt;/usr/local/opt&lt;/code&gt; fallback is there for Intel macOS, where &lt;code&gt;HOMEBREW_PREFIX&lt;/code&gt; may be unset, for example in bash without &lt;code&gt;brew shellenv&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Saves about 180 ms.&lt;/p&gt;&#10;&lt;h2 id="2-defer-plugins-with-zinits-wait-ice"&gt;2. Defer plugins with zinit&amp;rsquo;s &lt;code&gt;wait&lt;/code&gt; ice&lt;/h2&gt;&#10;&lt;p&gt;If you use a plugin manager, this is probably the biggest single win on the table. Syntax highlighting, autosuggestions, history-substring-search, completion add-ons: none of these need to load before the first prompt renders. They only need to be there by the time you type something.&lt;/p&gt;&#10;&lt;p&gt;With &lt;a href="https://github.com/zdharma-continuum/zinit" rel="noopener external"&gt;zinit&lt;/a&gt;&amp;rsquo;s &lt;code&gt;wait&lt;/code&gt; ice, you defer them until after the first prompt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zinit &lt;span class="nb"&gt;wait&lt;/span&gt; lucid light-mode &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; atinit&lt;span class="s2"&gt;&amp;#34;ZINIT[COMPINIT_OPTS]=-C; zicompinit; zicdreplay&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zdharma-continuum/fast-syntax-highlighting &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; atload&lt;span class="s2"&gt;&amp;#34;_zsh_autosuggest_start&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zsh-users/zsh-autosuggestions &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; blockf atpull&lt;span class="s1"&gt;&amp;#39;zinit creinstall -q .&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zsh-users/zsh-completions &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; atload&lt;span class="s2"&gt;&amp;#34;bindkey &amp;#39;^[[A&amp;#39; history-substring-search-up; bindkey &amp;#39;^[[B&amp;#39; history-substring-search-down&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zsh-users/zsh-history-substring-search &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; wfxr/forgit&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;atinit&lt;/code&gt; ice on the first plugin runs &lt;code&gt;compinit&lt;/code&gt; once and replays cached completions via &lt;code&gt;zicdreplay&lt;/code&gt;. One &lt;code&gt;compinit&lt;/code&gt; covers every turbo-loaded plugin. The &lt;code&gt;atload&amp;quot;_zsh_autosuggest_start&amp;quot;&lt;/code&gt; line re-initializes autosuggestions after deferred loading. Without it the plugin silently no-ops, which took me an embarrassing amount of time to work out.&lt;/p&gt;&#10;&lt;p&gt;Saves 100-300 ms.&lt;/p&gt;&#10;&lt;h2 id="3-compile-zcompdump-to-bytecode"&gt;3. Compile &lt;code&gt;.zcompdump&lt;/code&gt; to bytecode&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;compinit&lt;/code&gt; parses thousands of completion functions on every startup. Compiling its dump file to bytecode means subsequent shells skip the parser:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;zcompdump&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ZDOTDIR&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.zcompdump&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[[&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$zcompdump&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;! -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;zcompdump&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.zwc&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$zcompdump&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -nt &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;zcompdump&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.zwc&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; zcompile &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$zcompdump&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It recompiles only when the source is newer than the cached &lt;code&gt;.zwc&lt;/code&gt;. Saves 30-80 ms.&lt;/p&gt;&#10;&lt;h2 id="4-pick-one-version-manager"&gt;4. Pick one version manager&lt;/h2&gt;&#10;&lt;p&gt;I had both &lt;code&gt;fnm&lt;/code&gt; and &lt;code&gt;mise&lt;/code&gt; active, which is one more than anyone needs. Mise manages everything I actually use, Node, Python, Go; &lt;code&gt;fnm env&lt;/code&gt; in &lt;code&gt;.zshrc&lt;/code&gt; was a leftover that nothing depended on. Deleting it saved another 30-80 ms per shell.&lt;/p&gt;&#10;&lt;p&gt;Mise itself has a second optimization, &lt;code&gt;--shims&lt;/code&gt; mode:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;~/.local/bin/mise activate zsh --shims&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Default &lt;code&gt;mise activate&lt;/code&gt; installs a &lt;code&gt;precmd&lt;/code&gt; hook that runs on every prompt to keep tool versions in sync with &lt;code&gt;.mise.toml&lt;/code&gt;. Shim mode replaces that hook with symlinks on &lt;code&gt;PATH&lt;/code&gt; that resolve the right version the first time you call each tool. That is about 17 ms saved at startup, paid back as 10-30 ms on the first invocation per shell. For interactive use it is a fine trade.&lt;/p&gt;&#10;&lt;h2 id="5-cache-the-slow-evals"&gt;5. Cache the slow &lt;code&gt;eval&lt;/code&gt;s&lt;/h2&gt;&#10;&lt;p&gt;A lot of tools want you to run &lt;code&gt;eval &amp;quot;$(tool init zsh)&amp;quot;&lt;/code&gt; at startup. Each one forks the tool, runs its setup, and pipes the result back. You pay that cost every shell, even though the output rarely changes between runs.&lt;/p&gt;&#10;&lt;p&gt;A small helper fixes it:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_cache_eval&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.zsh/cache/&lt;span class="nv"&gt;$name&lt;/span&gt;.zsh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -s &lt;span class="nv"&gt;$cache&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt; -nt &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/dev/null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; mkdir -p ~/.zsh/cache&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_cache_eval zoxide zoxide init zsh --cmd j&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_cache_eval uv uv generate-shell-completion zsh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;$cache -nt ${commands[$1]:-/dev/null}&lt;/code&gt; test compares mtimes. When brew or mise upgrades the binary, the cache invalidates on its own. No stale-cache surprises after a &lt;code&gt;brew upgrade&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Keep &lt;code&gt;starship init zsh&lt;/code&gt; eager. You need it for the prompt anyway, and it is already fast.&lt;/p&gt;&#10;&lt;p&gt;Saves 50-100 ms.&lt;/p&gt;&#10;&lt;h2 id="6-audit-plugins-cut-what-you-dont-use"&gt;6. Audit plugins; cut what you don&amp;rsquo;t use&lt;/h2&gt;&#10;&lt;p&gt;I was loading the &lt;a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git" rel="noopener external"&gt;Oh My Zsh git plugin&lt;/a&gt; via zinit&amp;rsquo;s snippet feature. The plugin ships roughly 150 aliases.&lt;/p&gt;&#10;&lt;p&gt;So I grepped my last few months of shell history against its alias list. Five OMZ-only aliases came back: &lt;code&gt;gsw&lt;/code&gt;, &lt;code&gt;glo&lt;/code&gt;, &lt;code&gt;gf&lt;/code&gt;, &lt;code&gt;gclean&lt;/code&gt;, &lt;code&gt;gss&lt;/code&gt;. Everything else I either did not use or had already redefined to my own taste. I copied those five into &lt;code&gt;shell/.aliases&lt;/code&gt; and dropped the &lt;code&gt;OMZP::git&lt;/code&gt; snippet from zinit.&lt;/p&gt;&#10;&lt;p&gt;There is a second reason to do this, and it has nothing to do with startup time. Because the OMZ plugin loaded in turbo mode, after my eager &lt;code&gt;shell/.aliases&lt;/code&gt;, it was quietly overwriting aliases I had defined myself. My &lt;code&gt;gc='git commit -m'&lt;/code&gt; was getting clobbered to &lt;code&gt;git commit --verbose&lt;/code&gt;, and &lt;code&gt;gg='lazygit'&lt;/code&gt; to &lt;code&gt;git gui&lt;/code&gt;. The plugin &amp;ldquo;worked&amp;rdquo; because turbo-loaded aliases land before you type, but the behavior was not the one I had configured. Dropping the plugin put my own aliases back in charge.&lt;/p&gt;&#10;&lt;p&gt;While I was at it I found &lt;code&gt;zsh/.zsh/plugins/git/git.plugin.zsh&lt;/code&gt; in my dotfiles, 150 lines of git aliases, sourced from nowhere. Years of dead code I had been carrying around. Gone.&lt;/p&gt;&#10;&lt;h2 id="7-move-path-and-env-to-zshenv"&gt;7. Move PATH and env to &lt;code&gt;.zshenv&lt;/code&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;.zshrc&lt;/code&gt; runs only for interactive shells. If you set &lt;code&gt;PATH&lt;/code&gt; there, then cron jobs, &lt;code&gt;zsh -c &amp;quot;command&amp;quot;&lt;/code&gt; invocations, and IDE-spawned non-interactive shells do not see your tools. That is how you end up burning half a day on &amp;ldquo;works in my terminal, breaks in CI.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;The right home is &lt;code&gt;.zshenv&lt;/code&gt;, which runs for every zsh invocation:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.zshenv&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PNPM_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Library/pnpm&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;typeset&lt;/span&gt; -U path PATH &lt;span class="c1"&gt;# auto-dedupe&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.local/bin&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PNPM_HOME&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.cargo/env&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; . &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.cargo/env&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;typeset -U path PATH&lt;/code&gt; ties the &lt;code&gt;path&lt;/code&gt; array to the &lt;code&gt;PATH&lt;/code&gt; scalar and deduplicates automatically. No more 8 KB &lt;code&gt;PATH&lt;/code&gt; strings with the same entry six times because nested shells kept prepending.&lt;/p&gt;&#10;&lt;p&gt;This is not a cold-start win, since &lt;code&gt;.zshenv&lt;/code&gt; still runs. It is a correctness fix. A shorter &lt;code&gt;PATH&lt;/code&gt; does make command lookups faster, so you get a little of both.&lt;/p&gt;&#10;&lt;h2 id="8-fix-the-things-you-didnt-know-were-broken"&gt;8. Fix the things you didn&amp;rsquo;t know were broken&lt;/h2&gt;&#10;&lt;p&gt;While I was in there, &lt;code&gt;zprof&lt;/code&gt; and a slow re-read of the config surfaced a stack of small bugs that had nothing to do with cold start. They were just wrong:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;~/.profile&lt;/code&gt; was being sourced twice, once from &lt;code&gt;.zshrc&lt;/code&gt; and once from &lt;code&gt;.zshenv&lt;/code&gt;. 20-80 ms of pure waste.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;compinit&lt;/code&gt;&amp;rsquo;s cache-staleness check had inverted-looking logic and a misleading comment. It happened to do the right thing, but only by accident.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;zsh/.zsh/lib/completions.zsh&lt;/code&gt; referenced &lt;code&gt;$ZSH&lt;/code&gt;, an Oh My Zsh leftover that was unset in my setup. The completion cache-path was silently expanding to &lt;code&gt;/cache/&lt;/code&gt;, the filesystem root. Nothing got written there because the directory was not writable, so completions had no cache at all. Caching had quietly been off the whole time.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;compdef&lt;/code&gt; calls from eager tool initializations were arriving &lt;em&gt;before&lt;/em&gt; turbo-mode &lt;code&gt;compinit&lt;/code&gt; had run, so completions for those tools were not registering. I fixed it with a small shim that queues the calls until &lt;code&gt;compinit&lt;/code&gt; is ready:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; ! &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;+functions[compdef]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;typeset&lt;/span&gt; -ga __deferred_compdefs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; compdef&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;__deferred_compdefs&lt;/span&gt;&lt;span class="o"&gt;+=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;(j: :)@&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then in the turbo block&amp;rsquo;s &lt;code&gt;atinit&lt;/code&gt;, after &lt;code&gt;zicompinit; zicdreplay&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;__deferred_compdefs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; c in &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;__deferred_compdefs&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;compdef &lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;unset&lt;/span&gt; __deferred_compdefs&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;None of those moved the startup number. They were still bugs, and now they are not.&lt;/p&gt;&#10;&lt;h2 id="doing-this-on-your-own-config"&gt;Doing this on your own config&lt;/h2&gt;&#10;&lt;p&gt;Measure before you touch anything. &lt;code&gt;time zsh -i -c exit&lt;/code&gt; for cold start, &lt;code&gt;zprof&lt;/code&gt; for the in-shell breakdown. Skip that step and you will spend an afternoon optimizing the wrong thing. I have done exactly that.&lt;/p&gt;&#10;&lt;p&gt;Then grep your shell history against the alias and function lists of whatever plugins you load. You will usually find you are loading hundreds of definitions to use a handful.&lt;/p&gt;&#10;&lt;p&gt;Defer what can be deferred. First-prompt latency is what you feel, and almost nothing actually has to load before you can type. Cache expensive &lt;code&gt;eval&lt;/code&gt;s whose output only changes when the binary changes, and key the cache on the binary&amp;rsquo;s mtime so upgrades invalidate it for you. Hardcode the constants: &lt;code&gt;brew --prefix coreutils&lt;/code&gt; is always &lt;code&gt;${HOMEBREW_PREFIX}/opt/coreutils&lt;/code&gt;. Forking a Ruby program to ask is silly.&lt;/p&gt;&#10;&lt;p&gt;And keep environment variables in &lt;code&gt;.zshenv&lt;/code&gt;, not &lt;code&gt;.zshrc&lt;/code&gt;. Future-you, debugging why something works in iTerm but not in a cron job, will be grateful.&lt;/p&gt;&#10;&lt;h2 id="estimated-total"&gt;Estimated total&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Change&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Savings&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stop forking &lt;code&gt;brew --prefix&lt;/code&gt; × 5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;~180 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Defer plugins with &lt;code&gt;zinit wait&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100-300 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Cache remaining &lt;code&gt;eval&lt;/code&gt;s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;50-100 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Compile &lt;code&gt;.zcompdump&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30-80 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Drop &lt;code&gt;fnm&lt;/code&gt; (mise covers it)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30-80 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;~400-700 ms&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;These are estimates, not measurements. Your actual numbers depend on your hardware, your plugin set, and how much cruft you had accumulated over the years. Under 200 ms on Apple Silicon is doable with stock zsh and a small, well-deferred plugin set.&lt;/p&gt;&#10;&lt;p&gt;My dotfiles, with all of this applied, are at &lt;a href="https://github.com/theskumar/dotfiles" rel="noopener external"&gt;github.com/theskumar/dotfiles&lt;/a&gt;. The &lt;code&gt;zsh/OPTIMIZATION.md&lt;/code&gt; file there is the running log of what is done and what is still open.&lt;/p&gt;&#10;&lt;p&gt;I plan to repeat the audit as the config changes.&lt;/p&gt;&#10;</description></item><item><title>FluidVoice</title><link>https://saurabh-kumar.com/til/1775995201/</link><pubDate>Sun, 12 Apr 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1775995201/</guid><description>&lt;p&gt;&lt;a href="https://github.com/altic-dev/FluidVoice" rel="noopener external"&gt;FluidVoice&lt;/a&gt; with paraquet v2 is soemthing i&amp;rsquo;ve been using now as my default speech-to-text on Mac, after evaluating WisperFlow, Handy and VoiceInk.&lt;/p&gt;&#10;</description></item><item><title>thaw</title><link>https://saurabh-kumar.com/til/1775995200/</link><pubDate>Sun, 12 Apr 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1775995200/</guid><description>&lt;p&gt;&lt;a href="https://github.com/stonerl/Thaw" rel="noopener external"&gt;thaw&lt;/a&gt; a fork of Ice. I&amp;rsquo;m switching to it, as Ice seems to the inactive now.&lt;/p&gt;&#10;</description></item><item><title>Handy</title><link>https://saurabh-kumar.com/til/1770897600/</link><pubDate>Thu, 12 Feb 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1770897600/</guid><description>&lt;p&gt;&lt;a href="https://handy.computer/" rel="noopener external"&gt;Handy&lt;/a&gt; Looks like a promising tool for to replace WisperFlow and VoiceInk for speech to text conversion. &lt;a href="https://dannysmith.notion.site/Danny-s-Software-313c769fa9c04819a37d813da0485c78" rel="noopener external"&gt;via&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>VoiceLink</title><link>https://saurabh-kumar.com/til/1768219201/</link><pubDate>Mon, 12 Jan 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1768219201/</guid><description>&lt;p&gt;Learnt about &lt;a href="https://tryvoiceink.com/" rel="noopener external"&gt;VoiceLink&lt;/a&gt;, an alternative to [Wispr Flow] that have been using as my STT software, with it&amp;rsquo;s pricing model VoiceLink might be better replacement if it performs as good as WisprFlow&lt;/p&gt;&#10;</description></item><item><title>New tools</title><link>https://saurabh-kumar.com/til/1766232000/</link><pubDate>Sat, 20 Dec 2025 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1766232000/</guid><description>&lt;p&gt;New tools:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/gjtorikian/gh-actions-lockfile" rel="noopener external"&gt;gh-actions-lockfile&lt;/a&gt; to generate and verify lockfiles for gh-actions&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://proxyman.com/" rel="noopener external"&gt;Proxyman&lt;/a&gt; is more user-friendly http debugging proxy than Charles Proxy on MacOS&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>