{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Artisanal Code | Kobi Kadosh",
  "description": "Handcrafted software engineering insights, developer philosophy, and technical deep-dives. Thoughtful analysis on AI tools, software craftsmanship, and the art of building great software.",
  "home_page_url": "https://kobi.kadosh.me",
  "feed_url": "https://kobi.kadosh.me/feed.json",
  "icon": "https://kobi.kadosh.me/feed-icon.png",
  "language": "en-US",
  "authors": [
    {
      "name": "Kobi Kadosh",
      "url": "https://kobi.kadosh.me/about"
    }
  ],
  "items": [
    {
      "id": "https://kobi.kadosh.me/blog/2026-10-08-latch-v2-self-heal-local-exec/",
      "url": "https://kobi.kadosh.me/blog/2026-10-08-latch-v2-self-heal-local-exec/",
      "title": "Latch v2: Self-Heal Local-Exec and Driving Grok Bot From Grok Bot",
      "summary": "Latch v1 gave the agent hands. Version 2 adds bounded recovery for that path: a LaunchAgent that relaunches Grok Bot when the process is down or the heartbeat is stale, an optional outside heal request for disconnects the Mac cannot see, and a bounded way for Latch to operate Grok Bot's own UI when structured tools run out. Relaunch proven on self-heal kit 1.4.2; current kit 1.5.0 adds observe-only logging for silent cloud drops.",
      "content_html": "<p>When I <a href=\"https://kobi.kadosh.me/blog/2026-10-02-latch-giving-grok-bot-hands-on-my-mac\">first wrote about Latch</a>, the problem was interaction.</p>\n<p>An agent could be perfectly capable of reasoning about what needed to happen next and still get stuck because the next action lived on my Mac. There was a button to click, a dialog to inspect, something in Terminal or Chrome, or some other piece of graphical state that did not have a useful API.</p>\n<p>Latch became the small layer that gave the agent hands.</p>\n<p>The second version exposes a different problem: giving the agent hands is not enough if the path to those hands can disappear.</p>\n<p>And, once you accept that the Mac UI itself is a legitimate bounded interface, another slightly strange thing follows: sometimes the application Latch needs to operate is Grok Bot itself.</p>\n<p>So Latch v2 has two additions that look different on the surface but are really extensions of the same boundary:</p>\n<ol>\n<li>relaunch Grok Bot when its process is down or its heartbeat is stale, or when an outside beacon requests it;</li>\n<li>let the agent use the local UI as a controlled fallback when structured interfaces run out.</li>\n</ol>\n<p>I think this makes the role of Latch clearer.</p>\n<p><strong>Latch is becoming the Mac-side reliability and interaction layer beneath the agent — not another agent inside the harness.</strong></p>\n<p>Latch v1 gave the agent hands. V2 makes the path to those hands more recoverable.</p>\n<h2 id=\"the-failure-that-the-agent-cannot-fix\">The failure that the agent cannot fix</h2>\n<p>The first new capability came from a fairly quiet failure.</p>\n<p>The Mac was awake.</p>\n<p>The network was there.</p>\n<p>Applications were still running.</p>\n<p>But Grok Bot no longer had its local-exec connection to the machine.</p>\n<p><code>ListMachines</code> reported the Mac as disconnected, which meant commands against that machine were effectively finished until the connection returned.</p>\n<p>This is different from the laptop sleeping or losing Wi-Fi. The machine can be healthy while the local bridge is not.</p>\n<p>The obvious fix is also obvious to a human:</p>\n<p>close Grok Bot, reopen it, wait for the machine to reconnect.</p>\n<p>The problem is that the agent cannot necessarily do that.</p>\n<p>Once local-exec is gone, asking the same agent to run a command on the Mac to restore local-exec is circular.</p>\n<p>It is roughly:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>Cloud agent</span></span>\n<span class=\"line\"><span>    |</span></span>\n<span class=\"line\"><span>    | local-exec</span></span>\n<span class=\"line\"><span>    v</span></span>\n<span class=\"line\"><span>Grok Bot.app</span></span>\n<span class=\"line\"><span>    |</span></span>\n<span class=\"line\"><span>    v</span></span>\n<span class=\"line\"><span>Mac</span></span></code></pre></div>\n<p>If the middle link disappears, anything above it can understand the failure but cannot use that path to repair the failure.</p>\n<p>This is not really a prompting problem.</p>\n<p>It is a supervision problem.</p>\n<p>And the recovery mechanism has to sit outside the failure domain it is responsible for repairing.</p>\n<h2 id=\"put-the-watchdog-below-the-agent\">Put the watchdog below the agent</h2>\n<p>On macOS, there is already a boring and well-understood place for this kind of responsibility: launchd.</p>\n<p>So Latch v2 adds a LaunchAgent that runs independently of the Grok Bot session and checks whether the local-exec side still looks healthy.</p>\n<p>The important distinction is not that Latch has a shell script now.</p>\n<p>The important distinction is that the script does not need the agent connection in order to run.</p>\n<p>At a high level, the installation looks like this:</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Piece</th><th>Responsibility</th></tr></thead><tbody><tr><td><code>~/Library/Application Support/Latch/bin/grok-bot-local-exec-heal.sh</code></td><td>Detect the unhealthy local state and perform recovery</td></tr><tr><td><code>~/Library/LaunchAgents/com.latch.grok-bot-local-exec-heal.plist</code></td><td>Run the watchdog periodically and at login</td></tr><tr><td><code>~/Library/Logs/GrokBotLocalExecHeal.log</code></td><td>Human-readable recovery log</td></tr><tr><td><code>~/Library/Logs/GrokBotLocalExecHeal-last.json</code></td><td>Small machine-readable recovery receipt</td></tr><tr><td><code>~/Library/Application Support/Latch/grok-bot-local-exec-heal.disable</code></td><td>Explicit soft-off switch</td></tr><tr><td><code>~/Library/Application Support/Latch/grok-bot-local-exec-heal.request</code></td><td>One-shot “relaunch now” from anyone who can still reach the Mac</td></tr></tbody></table>\n<p>The <a href=\"https://github.com/wildcard/latch-mac-local-exec-self-heal/tree/v1.5.0\">self-heal kit</a> is open source, and its README carries the install details and knobs. I don’t think the interesting part of the design is whether a heartbeat timeout is 150 or 180 seconds.</p>\n<p>The interesting part is the contract.</p>\n<p>Latch treats the bridge as unhealthy when one of a small set of local signals says it is no longer viable: the expected process is gone, the heartbeat has gone stale or stopped moving, or startup reached a non-ready outcome. Someone who can still reach the Mac can also ask for a relaunch by leaving the request file.</p>\n<p>The LaunchAgent checks periodically. When it decides recovery is warranted, it tries to relaunch Grok Bot gently rather than immediately killing it.</p>\n<p>The sequence is deliberately uninteresting:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>detect unhealthy state</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>        v</span></span>\n<span class=\"line\"><span>request normal quit</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>        v</span></span>\n<span class=\"line\"><span>wait</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>        v</span></span>\n<span class=\"line\"><span>reopen Grok Bot in the background</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>        v</span></span>\n<span class=\"line\"><span>wait for local readiness</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>        v</span></span>\n<span class=\"line\"><span>record healed, or say why not</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>        v</span></span>\n<span class=\"line\"><span>cool down before another recovery</span></span></code></pre></div>\n<p>There is a fallback if the process refuses to exit, but the normal case is intended to be uneventful.</p>\n<p>The readiness step matters more than it looks. A process that exists is not a connection that works. The kit records <code>healed</code> only when Grok Bot is running again and its heartbeat is fresh, or its startup reports ready, within a bounded wait. Otherwise the receipt says <code>heal_incomplete</code> or <code>heal_failed</code>, with a hint about what to escalate.</p>\n<p>Relaunched is not the same as recovered.</p>\n<p>That is what I want from this layer.</p>\n<p>Recovery infrastructure should be less clever than the system it supervises.</p>\n<h2 id=\"what-it-does-not-recover\">What it does not recover</h2>\n<p>It is important to keep the boundary honest.</p>\n<p>The watchdog does not wake a sleeping Mac.</p>\n<p>It does not open a closed lid.</p>\n<p>It does not restore a dead network connection.</p>\n<p>It does not sign the operator back into an account.</p>\n<p>It does not bypass macOS security boundaries.</p>\n<p>It handles a narrower local case: the Mac is available, but the expected Grok Bot process is gone or the heartbeat is stale. A configured beacon also lets an outside box ask the Mac for the same relaunch.</p>\n<p>Outside of that, Latch fails closed.</p>\n<p>I would rather have an explicit edge where the human has to return than describe every unavailable capability as something automation should somehow solve.</p>\n<p>There is another tradeoff worth saying directly.</p>\n<p>If I intentionally press Cmd+Q, the watchdog cannot read my mind and know whether I meant “quit forever” or “the app died.”</p>\n<p>Unless the disable file is present, Grok Bot can therefore come back roughly a minute later.</p>\n<p>That is the cost of choosing unattended recovery.</p>\n<p>The fix is not another heuristic. The fix is a visible off switch.</p>\n<p>Predictable automation is better than automation that tries too hard to guess intent.</p>\n<h2 id=\"the-disconnect-the-mac-cannot-see\">The disconnect the Mac cannot see</h2>\n<p>One failure sits awkwardly between what the watchdog fixes and what it leaves alone.</p>\n<p>Grok Bot’s cloud side knows whether a machine is connected. <code>ListMachines</code> reports it as <code>connected</code>. The Mac never gets that flag. Nothing on disk and nothing on localhost mirrors it in a way a LaunchAgent could use.</p>\n<p>So the cloud can decide the Mac is disconnected while, on the Mac, the process is alive and the heartbeat keeps moving. To the watchdog, that machine looks healthy. By every signal it has, it is.</p>\n<p>The receipt is honest about this. <code>GrokBotLocalExecHeal-last.json</code> carries a field called <code>cloudConnectObservable</code>, and it is always <code>false</code>.</p>\n<p>It would be easy to “fix” this by tightening the thresholds until the watchdog relaunches more often. That would only turn a missing signal into guesswork, and a relaunch on a guess is just a new way to interrupt the person using the machine.</p>\n<p>The signal has to come from the side that can see it.</p>\n<h2 id=\"an-outside-signal-with-the-mac-still-owning-the-fix\">An outside signal, with the Mac still owning the fix</h2>\n<p>So the kit has an optional <code>worker-beacon/</code> mode.</p>\n<p>The agent that actually sees the disconnect can POST a <code>heal_request</code> to a small Cloudflare Worker the operator deploys. The Worker is a heal-only inbox. It accepts one kind of request in one strict shape. Requests are short-lived and single-use, and the Worker rate-limits them per machine.</p>\n<p>The Mac polls that inbox on its normal tick. Nothing calls in to the Mac. When a request is waiting, the LaunchAgent runs the same quit-and-open heal it runs for a local request file, records <code>reason=beacon_request</code>, and skips the normal cooldown. The readiness gate still applies.</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>agent sees connected=false</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>        | POST heal_request</span></span>\n<span class=\"line\"><span>        v</span></span>\n<span class=\"line\"><span>operator&#39;s Worker (heal-only inbox)</span></span>\n<span class=\"line\"><span>        ^</span></span>\n<span class=\"line\"><span>        | outbound poll, once per tick</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>LaunchAgent on the Mac</span></span>\n<span class=\"line\"><span>        |</span></span>\n<span class=\"line\"><span>        v</span></span>\n<span class=\"line\"><span>same quit, reopen, and readiness gate</span></span></code></pre></div>\n<p>The Worker applies the limits. The Mac still owns the recovery.</p>\n<p>A few boundaries hold it together:</p>\n<ul>\n<li>It is off unless the operator configures it. Without a beacon, the kit is exactly the local watchdog above.</li>\n<li>The off switch still wins. While the disable file is present, the heal is skipped and Grok Bot is left alone.</li>\n<li>One beacon relaunch per outage. A second request within the hour after a beacon relaunch is not honored. That is the point where a human should look.</li>\n<li>The Worker can only ask for a heal. It cannot run anything else on the Mac, and its tokens live outside the repository.</li>\n</ul>\n<p>Kit 1.4.2 also makes the installer keep an operator’s beacon settings across a reinstall, so updating the kit does not quietly switch the outside signal off.</p>\n<h2 id=\"what-has-been-proven-and-what-has-not\">What has been proven, and what has not</h2>\n<p>On October 5, at about 11:23 AM PT, the live end-to-end test of that path passed. The kit’s notes call it Prove C. The request came from a remote box, and nothing touched the request file on the Mac. Grok Bot relaunched, the pid changed, and the receipt came back <code>healed</code> and <code>ready</code>.</p>\n<p>The negative cases passed too: a malformed request, an expired one, a replay, and a second request inside the rate limit were all refused.</p>\n<p>A separate live check covered the off switch. With the disable file present, the heal was skipped and the pid did not change. With the file removed, the agent went back to its normal checks.</p>\n<p>That proves the outside signal can reach the existing heal path.</p>\n<p>It does not prove that Latch can detect every outage by itself.</p>\n<p>On October 7, Prove D confirmed that gap: the cloud-connection helper exited while the main app and heartbeat still looked healthy, so kit 1.4.2 kept reporting healthy.</p>\n<p>Kit 1.5.0, now released, detects and logs that silent cloud drop in observe-only mode; it does not heal it.</p>\n<p>And the limits from before do not move. Latch still cannot wake a sleeping Mac, open a lid, restore a lost network connection, or infer <code>ListMachines.connected</code> locally. With 1.4.2, an agent that can see the disconnect still has to send the request.</p>\n<h2 id=\"the-agent-also-needs-a-recovery-protocol\">The agent also needs a recovery protocol</h2>\n<p>Putting a watchdog on the Mac only solves half of the problem.</p>\n<p>The agents using the machine also need to know what a disconnection means.</p>\n<p>Without that shared behavior, it is easy to turn one dead connection into a pile of retries.</p>\n<p>The Latch-side protocol is intentionally small:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>ListMachines</span></span>\n<span class=\"line\"><span>    |</span></span>\n<span class=\"line\"><span>    | connected=false</span></span>\n<span class=\"line\"><span>    v</span></span>\n<span class=\"line\"><span>soft-park Mac-dependent work</span></span>\n<span class=\"line\"><span>    |</span></span>\n<span class=\"line\"><span>    v</span></span>\n<span class=\"line\"><span>wait for the local watchdog, then re-poll</span></span>\n<span class=\"line\"><span>    |</span></span>\n<span class=\"line\"><span>    | still connected=false</span></span>\n<span class=\"line\"><span>    v</span></span>\n<span class=\"line\"><span>send one heal_request, if a beacon is set up</span></span>\n<span class=\"line\"><span>    |</span></span>\n<span class=\"line\"><span>    v</span></span>\n<span class=\"line\"><span>re-poll ListMachines</span></span>\n<span class=\"line\"><span>    |</span></span>\n<span class=\"line\"><span>    | connected=true</span></span>\n<span class=\"line\"><span>    v</span></span>\n<span class=\"line\"><span>read GrokBotLocalExecHeal-last.json</span></span>\n<span class=\"line\"><span>    |</span></span>\n<span class=\"line\"><span>    v</span></span>\n<span class=\"line\"><span>resume from verified state</span></span></code></pre></div>\n<p>If the machine does not come back after the recovery window, then ask the human.</p>\n<p>Do not invent a remote wake capability.</p>\n<p>Do not keep throwing commands at a transport that has already told you it is unavailable.</p>\n<p>Do not send another heal request because the first one did not work. One per outage, then escalate.</p>\n<p>Do not assume that a reconnect proves what happened. Read the receipt.</p>\n<p>This distinction between the skill and the supervisor has become useful to me.</p>\n<p>A skill can describe what an agent should do around a failure.</p>\n<p>The LaunchAgent can actually remain present when the agent path disappears.</p>\n<p>One describes behavior.</p>\n<p>The other enforces a piece of local reality.</p>\n<p>That fits closely with how I have been thinking about <a href=\"https://kobi.kadosh.me/blog/2026-09-15-harness-driven-development\">harness-driven development</a>. The harness is not only the model or the prompt. It includes the operational pieces that make the model’s actions dependable.</p>\n<p>Long-running agents make this much easier to see.</p>\n<h2 id=\"then-the-remaining-interface-is-grok-bot-itself\">Then the remaining interface is Grok Bot itself</h2>\n<p>The second half of Latch v2 sounds more recursive than it really is.</p>\n<p>Latch can now drive Grok Bot.app.</p>\n<p>So, yes, there are situations where Grok Bot can effectively use Latch to operate Grok Bot.</p>\n<p>That is a funny sentence.</p>\n<p>But I don’t think the recursion is the interesting idea.</p>\n<p>The interesting part is what happens when the structured interfaces run out.</p>\n<p>Most agent systems have some hierarchy of preferred interaction:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>API / structured tool</span></span>\n<span class=\"line\"><span>        ↓</span></span>\n<span class=\"line\"><span>CLI / shell</span></span>\n<span class=\"line\"><span>        ↓</span></span>\n<span class=\"line\"><span>application-specific automation</span></span>\n<span class=\"line\"><span>        ↓</span></span>\n<span class=\"line\"><span>computer UI</span></span></code></pre></div>\n<p>I would nearly always prefer the top of that list.</p>\n<p>Structured tools are easier to reason about, easier to verify, and much less ambiguous than moving a cursor across a screen.</p>\n<p>But the existence of a better interface does not mean every action has one.</p>\n<p>There are still product surfaces where the last useful move is graphical.</p>\n<p>Grok Bot itself contains some of them.</p>\n<p>An Allow card may need a click.</p>\n<p>An Assist flow may need to be advanced.</p>\n<p>A draft may need its Send action.</p>\n<p>A share or publish surface may need to be opened.</p>\n<p>A setting may exist only in the desktop interface.</p>\n<p>These are not all the same kind of action, and Latch should not pretend they are.</p>\n<p>But they share something important: the interface is the UI.</p>\n<p>When that happens, the UI can become another bounded tool surface.</p>\n<h2 id=\"the-ui-is-a-tool-not-permission-to-do-anything\">The UI is a tool, not permission to do anything</h2>\n<p>This is where I think <a href=\"https://kobi.kadosh.me/blog/2026-10-01-agent-first-development\">agent-first development</a> becomes relevant.</p>\n<p>Designing for agents does not mean eliminating graphical interfaces or pretending an API exists for everything.</p>\n<p>It means being explicit about what capabilities an agent has, how it can discover them, and where the boundaries are.</p>\n<p>Latch treats computer use the same way.</p>\n<p>Grok-Bot-on-Grok-Bot does not mean:</p>\n<p>the agent is allowed to click whatever gets in its way.</p>\n<p>It means a registered Mac can expose a constrained interaction capability with rules around it.</p>\n<p>Those rules remain intentionally strict:</p>\n<ul>\n<li>mouse-first interaction;</li>\n<li>no HID keyboard injection;</li>\n<li>clipboard plus human paste where typing is required;</li>\n<li>macOS Accessibility remains an explicit permission boundary;</li>\n<li>only registered Macs participate;</li>\n<li>the local Mac lease is always released after the interaction;</li>\n<li>unexpected states fail closed.</li>\n</ul>\n<p>Those rules came from Latch v1 and I do not want v2 to weaken them just because the UI happens to belong to Grok Bot.</p>\n<p>There is a major difference between performing a gesture and making a judgment.</p>\n<p>Computer Use can click an Allow card when the authorization to do so is already part of the task.</p>\n<p>That does not imply that Latch should manufacture authorization where none was given.</p>\n<p>It can advance an Assist form whose intended values are already known.</p>\n<p>That does not mean it should invent missing information.</p>\n<p>It can move through a mechanical share flow.</p>\n<p>That does not turn every consequential confirmation into something an agent should silently approve.</p>\n<p>The UI is another interface.</p>\n<p>The human boundary still exists above it.</p>\n<h2 id=\"why-mouse-first-still-matters\">Why mouse-first still matters</h2>\n<p>There is a temptation, once you have local computer access, to turn the Mac into a general input-injection surface.</p>\n<p>Latch deliberately does not do that.</p>\n<p>For graphical work, the mouse is easier to constrain and easier to observe.</p>\n<p>For text, the preferred path remains clipboard-based rather than pretending to be an invisible hardware keyboard.</p>\n<p>That is partly a safety decision, but it also makes the automation easier to reason about.</p>\n<p>If Latch puts text on the clipboard and a human paste action completes it, the transition is explicit.</p>\n<p>If an agent can synthesize arbitrary keyboard events at arbitrary moments, the boundary between application automation and control of the entire user session gets much fuzzier.</p>\n<p>I already learned in the first version of Latch that macOS Accessibility deserves respect.</p>\n<p>V2 does not reinterpret that lesson.</p>\n<p>It builds on it.</p>\n<h2 id=\"both-features-are-actually-the-same-architectural-move\">Both features are actually the same architectural move</h2>\n<p>Self-heal and Grok-Bot-on-Grok-Bot look like two unrelated release notes.</p>\n<p>One is a LaunchAgent.</p>\n<p>One is computer use.</p>\n<p>But both exist because the agent itself should not own every layer it depends on.</p>\n<p>The self-heal path says:</p>\n<p>If the agent loses its path to the Mac, something below that path must be able to attempt a bounded relaunch when it has a reliable signal.</p>\n<p>The UI path says:</p>\n<p>If the agent reaches the end of its structured interfaces, something below the reasoning layer can provide a bounded physical interaction surface.</p>\n<p>In both cases, Latch sits closer to the machine than the agent does.</p>\n<p>That gives the architecture a clearer shape:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>┌──────────────────────────────────────┐</span></span>\n<span class=\"line\"><span>│            Agent / Harness           │</span></span>\n<span class=\"line\"><span>│ reasoning, planning, skills, tools   │</span></span>\n<span class=\"line\"><span>└──────────────────┬───────────────────┘</span></span>\n<span class=\"line\"><span>                   │</span></span>\n<span class=\"line\"><span>                   │ structured intent</span></span>\n<span class=\"line\"><span>                   ▼</span></span>\n<span class=\"line\"><span>┌──────────────────────────────────────┐</span></span>\n<span class=\"line\"><span>│                 Latch                │</span></span>\n<span class=\"line\"><span>│                                      │</span></span>\n<span class=\"line\"><span>│  reliability          interaction    │</span></span>\n<span class=\"line\"><span>│  ───────────          ────────────   │</span></span>\n<span class=\"line\"><span>│  self-heal            mouse/UI       │</span></span>\n<span class=\"line\"><span>│  receipts             clipboard      │</span></span>\n<span class=\"line\"><span>│  outside signal       lease control  │</span></span>\n<span class=\"line\"><span>│                                      │</span></span>\n<span class=\"line\"><span>└──────────────────┬───────────────────┘</span></span>\n<span class=\"line\"><span>                   │</span></span>\n<span class=\"line\"><span>                   ▼</span></span>\n<span class=\"line\"><span>┌──────────────────────────────────────┐</span></span>\n<span class=\"line\"><span>│                 macOS                │</span></span>\n<span class=\"line\"><span>│ Grok Bot · Chrome · Terminal · UI    │</span></span>\n<span class=\"line\"><span>└──────────────────────────────────────┘</span></span></code></pre></div>\n<p>That is a more useful mental model for Latch than “a bot that can click things.”</p>\n<p>It is the local reliability and interaction layer underneath the agent.</p>\n<h2 id=\"the-release-is-intentionally-small\">The release is intentionally small</h2>\n<p>This is Latch v2: relaunch proven on self-heal kit <a href=\"https://github.com/wildcard/latch-mac-local-exec-self-heal/releases/tag/v1.4.2\">1.4.2</a>, with 1.5.0 now detecting and logging silent cloud drops without healing them.</p>\n<p>The version includes:</p>\n<ul>\n<li>a Mac LaunchAgent that relaunches on a down process or stale heartbeat, with a readiness gate;</li>\n<li>an optional outside signal through <code>worker-beacon/</code>, for the disconnects the Mac cannot see;</li>\n<li>bounded Grok Bot UI operation through Latch’s existing computer-use discipline.</li>\n</ul>\n<p>The exact timers, plist, health checks, install flow, and operational details belong in the <a href=\"https://github.com/wildcard/latch-mac-local-exec-self-heal/tree/v1.5.0\">kit’s README</a> rather than turning this post into a deployment guide.</p>\n<p>Latch itself is public as a Grok Bot template:</p>\n<a href=\"https://x.ai/bot/Mtqs6_J6jdKbTHqZFIARi\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"link-card group not-prose ascii-border my-6 flex flex-col overflow-hidden bg-terminal-gray/30 no-underline transition-all duration-300 hover:border-terminal-cyan hover:shadow-lg hover:shadow-terminal-cyan/10 sm:flex-row\"><div class=\"shrink-0 border-b border-terminal-line/50 bg-terminal-gray sm:w-64 sm:border-b-0 sm:border-r\"><img src=\"https://kobi.kadosh.me/media/link-cards/latch-grok-bot.png\" alt=\"Meet my Bot: Latch, on Grok Bot\" class=\"h-40 w-full object-cover sm:h-full\" loading=\"lazy\" decoding=\"async\"></div><div class=\"flex flex-1 flex-col justify-center gap-1.5 p-4\"><div class=\"flex items-center gap-2 text-xs text-terminal-comment\"><span class=\"text-terminal-green\">▸</span><span class=\"truncate\">x.ai/bot</span><span class=\"text-terminal-comment transition-colors group-hover:text-terminal-cyan\" aria-hidden=\"true\">↗</span></div><h3 class=\"text-terminal-cyan font-semibold leading-snug transition-colors group-hover:text-terminal-green\">Latch by Kobi</h3><p class=\"text-sm leading-relaxed text-terminal-comment\">Drives a real Mac safely for Grok Bot: mouse-first UI automation, Accessibility without lockouts, and a hard hand-back when you need the machine. For anyone who wants Mac UI playbooks other bots can hand off to.</p></div></a>\n<p>There is also an observable product gap underneath the watchdog.</p>\n<p>Ideally, Grok Bot’s own local-exec lifecycle becomes reliable enough that an external watchdog is unnecessary. And if Grok Bot wrote its own connection state somewhere on the Mac, no bot would have to declare the machine down. If you hit the same class of failure, the useful long-term thing to do is also send it upstream through Help → Send Feedback.</p>\n<p>I do not particularly want Latch to own process resurrection forever.</p>\n<p>That would be missing the point.</p>\n<h2 id=\"infrastructure-can-be-temporary-and-still-be-worth-building\">Infrastructure can be temporary and still be worth building</h2>\n<p>One thing I increasingly like about building around fast-moving agent systems is that some infrastructure is allowed to be temporary.</p>\n<p>There is a tendency in software to treat deletion as evidence that the original work was wasted.</p>\n<p>I think the opposite can be true here.</p>\n<p>If Grok Bot eventually absorbs reliable local-exec recovery, the self-heal LaunchAgent should disappear.</p>\n<p>If the Mac can ever read its own connection state, the beacon should go first.</p>\n<p>If richer structured interfaces replace one of the UI interactions, Latch should stop clicking that UI.</p>\n<p>If macOS or the agent harness grows a safer primitive, use the safer primitive.</p>\n<p>The goal is not to preserve Latch’s implementation.</p>\n<p>The goal is to preserve the operating capability.</p>\n<p>That is also why I would rather ship a small watchdog now than turn “the agent sometimes disconnects” into a permanent ritual where a human checks the Dock every few hours.</p>\n<p>The current primitive is appropriate for the current gap.</p>\n<p>When the gap moves, the primitive can move with it.</p>\n<p>Latch v1 started with a fairly practical question: how do I let an agent safely operate the Mac when APIs stop?</p>\n<p>V2 adds another one: what can attempt to restore that capability when the agent itself cannot reach it?</p>\n<p>The answer, at least for now, is to put a deliberately boring layer underneath the clever one.</p>\n<p>And once that layer exists, even Grok Bot’s own UI becomes just another bounded surface it can mediate.</p>\n<p>That is the practice I care about more than the slogan:</p>\n<p>put reliability below the thing that can fail, keep interaction narrower than authority, and delete the workaround when the platform finally makes it unnecessary.</p>",
      "date_published": "2026-10-08T00:00:00.000Z",
      "tags": [
        "Agents",
        "AI Tools",
        "Grok Bot",
        "Latch"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-10-02-latch-giving-grok-bot-hands-on-my-mac/",
      "url": "https://kobi.kadosh.me/blog/2026-10-02-latch-giving-grok-bot-hands-on-my-mac/",
      "title": "Latch: Giving Grok Bot Hands on My Mac",
      "summary": "The bot had a computer. The next step needed to happen on mine. Latch is a specialist Grok Bot for the small, authorized desktop interactions that stall larger workflows: mouse first, clipboard over keystrokes, screenshot receipts, and a hard hand-back. This is the story of building it, including the keyboard lockout that wrote most of its rules.",
      "content_html": "<p><em>Update, Oct 5: Latch’s self-heal kit can now take a heal request from outside the Mac. <a href=\"#update-oct-5-self-heal-gets-an-outside-signal\">Read the update</a>.</em></p>\n<p>Sometimes the thing blocking an agent is not a hard problem.</p>\n<p>It is a button.</p>\n<p>The agent understands the task. It knows what should happen next. There may be other bots working on other parts of the process, making progress on their own. But somewhere along the way an application needs a small interaction, and the application is running on my computer — not on the computer the bot is using.</p>\n<p>Now the process is waiting for me.</p>\n<p>The frustrating part is that the interaction takes a few seconds. The delay comes from when I am available to perform it. I might be away from my desk, or deep in something else, or standing in the kitchen with my phone. A five-second action becomes a thirty-minute interruption to a workflow, not because the action is complicated, but because I am the only available way to perform it.</p>\n<p>At that point I am not directing the work. I am the missing connection between the bot and the computer where the work needs to happen.</p>\n<p>That is where <a href=\"https://x.ai/bot/Mtqs6_J6jdKbTHqZFIARi\">Latch</a> started.</p>\n<p>Not as “can we make another bot that clicks things?” Something narrower: can I give a bot a useful, explicitly authorized way to handle those interactions on my Mac, without me becoming its remote mouse — and without handing it unrestricted control of the machine I actually work on?</p>\n<p>The one-line description on the bot’s public card says what it became:</p>\n<blockquote>\n<p>Drives a real Mac safely for Grok Bot: mouse-first UI automation, Accessibility without lockouts, and a hard hand-back when you need the machine. For anyone who wants Mac UI playbooks other bots can hand off to.</p>\n</blockquote>\n<p>Every clause in that sentence was earned the hard way. The second half of this post is the story of how, told partly in Latch’s own words. The first half is why I wanted it at all.</p>\n<hr/>\n<h2 id=\"the-bot-has-a-computer-the-task-is-on-mine\">The bot has a computer. The task is on mine</h2>\n<p>Grok Bot’s working environment is a <a href=\"https://docs.x.ai/grok-bot/computer-and-apps\">persistent cloud computer</a>: a browser, a filesystem, a terminal, all of it running whether or not my laptop is open. That is most of the appeal. I can close the lid and the work continues.</p>\n<p>But there is a difference between talking to a bot from my Mac and having that bot operate my Mac.</p>\n<p>The application I need might already be open locally. The relevant files might sit in a local workflow. The next step might depend on the state of a desktop application that simply does not exist in the bot’s environment, or on a browser session that is signed in on my machine and nowhere else. Recreating that entire environment somewhere in the cloud is not the problem I want to solve.</p>\n<p>Sometimes I just need the next action to happen where the work already is.</p>\n<p>I want to be precise about what exists here, because it would be easy to overclaim. Grok Bot already has a documented, permission-gated path for running commands on your local computer. The docs describe it as a setting, <a href=\"https://docs.x.ai/grok-bot/approvals-security-and-privacy\">Execution on Local Computer</a>, with three policies: ask every time, always allow, never allow. It defaults to asking, and the documentation recommends “never allow” unless a bot has a specific reason to work on your local files.</p>\n<p>So local execution is not what we invented.</p>\n<p>What was missing was everything around it: a working practice for driving a real Mac’s interface. Which permissions to grant and which to refuse. How to click without typing. What to do when the keyboard stops responding. How to prove the action happened. How to give the machine back.</p>\n<p>That practice is Latch. The cloud computer does not go away. My Mac becomes one more place where a specifically delegated part of a task can happen, under rules we wrote down after breaking things.</p>\n<h2 id=\"i-dont-want-to-automate-everything-through-a-screen\">I don’t want to automate everything through a screen</h2>\n<p>My goal is not to make clicking the universal interface to software.</p>\n<p>Where a suitable API, connector, or command-line interface exists, I would much rather use it. Grok Bot’s own docs say the same thing: prefer a connector when one is available, and use the browser for services without one or for visual workflows a connector does not expose. That is the right order of preference, and Latch does not change it.</p>\n<p>But a task does not become less real because the application lacks an automation interface.</p>\n<p>Consider a desktop app whose export lives only behind a menu item. A confirmation dialog that appears halfway through a long local job and blocks everything behind it until someone presses a button. A System Settings toggle. A native application that needs a predictable sequence of clicks before the next part of a process can continue. None of these are difficult. All of them are the kind of thing that stalls an otherwise automated workflow at the edge of the interfaces we already have.</p>\n<p>Computer use is valuable to me as a way to fill those gaps. Not as a reason to ignore better interfaces.</p>\n<h2 id=\"the-kitchen-test\">The kitchen test</h2>\n<p>The experience I am aiming for has a simple test.</p>\n<p>I am not at my desk. I have my phone. A workflow needs a small action on my Mac.</p>\n<p>Do I walk back, find the right window, remember what the bot was doing, perform the action, and tell it to continue? Or can I give a bounded instruction from where I am?</p>\n<p>Something like:</p>\n<blockquote>\n<p>Finish the export in the app we were using and save it to the folder we agreed on. Stop if that means overwriting an existing file or granting a new permission.</p>\n</blockquote>\n<p>That is an illustrative instruction, not a transcript. But it has the shape I care about: an outcome and a boundary, rather than me steering every movement of the pointer.</p>\n<p>Talking to a bot from my phone is nothing new. What changed is where the requested desktop action can happen. The phone is where I talk. The Mac is where this part of the task lives. Latch connects the two.</p>\n<p>It does not need to look spectacular. It needs to be useful.</p>\n<h2 id=\"a-specialist-not-a-superpower-for-every-bot\">A specialist, not a superpower for every bot</h2>\n<p>I also do not see Latch owning entire workflows.</p>\n<p>Its role is narrow: handle an authorized local interaction, establish whether it succeeded, hand the result back. That matters when there is more than one bot in the picture. I do not want the research bot, the planning bot, and every other specialist to become general-purpose desktop operators. I want one clear owner for that responsibility that the rest of the workflow can hand off to when appropriate.</p>\n<p>This is the same instinct behind <a href=\"https://kobi.kadosh.me/blog/2026-09-10-buzzcut-cutting-through-the-ai-tool-hype\">Buzzcut</a>, and behind the <a href=\"https://kobi.kadosh.me/blog/2026-03-15-unix-philosophy-agents-skills\">Unix philosophy for agents and skills</a>: give a bot one constrained responsibility and be extremely explicit about what it should and should not do. Independent work continues elsewhere. Work that depends on the local result waits until that result is actually confirmed.</p>\n<p>For me the value is not the number of clicks we automate. It is the amount of unnecessary human coordination we remove. There is a difference between being involved because a decision needs my judgment and being involved because a window needs attention. I want to keep the former and shrink the latter.</p>\n<p>One boundary is worth stating plainly, because a well-named specialist can create a false sense of separation. Grok Bot’s docs are explicit that <a href=\"https://docs.x.ai/grok-bot/approvals-security-and-privacy\">all of your bots share one cloud computer</a>, and that files, browser sessions, and command-line credentials on it are available across your roster. In their words: do not use separate bots as a security boundary. Giving one bot a narrow name and a narrow job is a design decision. It is not technical isolation. A well-defined role is useful. Enforced permissions are a separate concern, and on the Mac side they live in System Settings, not in the bot’s description.</p>\n<h2 id=\"authorization-is-part-of-the-capability\">Authorization is part of the capability</h2>\n<p>There is a mistake hiding in the phrase “unblocking the bot.”</p>\n<p>Some blocks are accidental friction. Others exist because the system needs a human decision. Those are not the same thing, and a bot that treats every confirmation, permission prompt, or credential wall as an obstacle to click through is not a specialist. It is a liability with a nice name.</p>\n<p>I want help with mechanical steps I have authorized. I do not want a bot deciding, on my behalf, that a single sign-on page or a payment form or a “grant access” dialog is just another button.</p>\n<p>So the operating boundaries had to be as clear as the capabilities: what Latch may act on, what requires a fresh decision, when it stops, and how access is withdrawn. The distinction I care about is simple.</p>\n<p><strong>I want to delegate the interaction, not accidentally delegate every decision behind it.</strong></p>\n<p>That principle turned out to be load-bearing in a way I did not expect. Most of Latch’s rules came from a moment when the machine, not the bot, needed to be protected.</p>\n<hr/>\n<h2 id=\"how-we-got-there\">How we got there</h2>\n<p>What follows is the build record. Once the design stabilized, I asked Latch to write up its own account of how it was built, and the rest of this section draws on that account and on the skill it now carries. I have stripped names, paths, and anything specific to my environment, and I have left its voice alone where it says things better than I would. Where it is quoted, that is the bot talking.</p>\n<h3 id=\"the-job-description\">The job description</h3>\n<p>We started with a narrow brief, on purpose.</p>\n<blockquote>\n<p>Own the Mac-automation playbooks. Speak short and procedural. Fail closed when the ask is unclear or destructive. Prove work with screenshots, not vibes. Always hand the machine back.</p>\n</blockquote>\n<p>And a name that describes the contract: something that catches onto the UI, holds briefly, then releases.</p>\n<p>The first four rules survived intact. The fifth became the whole story.</p>\n<h3 id=\"attempt-one-just-inject-keystrokes\">Attempt one: just inject keystrokes</h3>\n<p>The obvious path is the one every Mac automation tutorial teaches. Synthesize keyboard events, fill the fields, press Return.</p>\n<blockquote>\n<p>It worked — until it didn’t. After a hung keystroke path, the Mac’s keyboard felt frozen. Mouse still moved. Keys did nothing useful for seconds at a time. The shared machine was no longer shared.</p>\n</blockquote>\n<p>I want to separate what we observed from how we explained it, because those are different kinds of claims.</p>\n<p>What we observed: after a run that mixed synthetic keystrokes with mouse automation, each keypress stalled the pointer for a few seconds, or the keys went dead altogether. The mouse still worked. Typing did not.</p>\n<p>The explanation we settled on, and later supported by fixing it: the helper that Grok Bot’s Computer Use registers under macOS Accessibility had left an event tap armed. On my machine at the time that helper showed up as a process called <code>CUGrokBotService</code>; treat the name as a detail of my setup and version, not a spec. The event tap is the part that matters. Synthetic keyboard injection through Accessibility is not a casual tool on a Mac that a human also uses. It is a lockout vector.</p>\n<h3 id=\"attempt-two-treat-accessibility-as-a-blunt-instrument\">Attempt two: treat Accessibility as a blunt instrument</h3>\n<p>When input wedged, the instinct was to fix permissions. Reset the privacy database, toggle everything, hope.</p>\n<p>We refused the nuclear option.</p>\n<blockquote>\n<p>Never reset Accessibility to paper over a bad driver. Diagnose the tap. Document the unlock. Prefer narrower permissions over wider ones.</p>\n</blockquote>\n<p>Running <code>tccutil reset</code> on Accessibility is opaque, it punishes every other application that had been granted access, and it teaches exactly the wrong habit: treat the operating system as something you reboot when your automation is sloppy. That rule is now permanent and written into Latch’s instructions. It does not reset the Transparency, Consent, and Control database, ever.</p>\n<h3 id=\"attempt-three-turn-it-off-and-assume-it-is-instant\">Attempt three: turn it off and assume it is instant</h3>\n<p>The fix that actually worked was mundane. In System Settings, under Privacy &amp; Security, turn Grok Bot off in Accessibility, and in Input Monitoring if it is listed there. That cleared the stuck tap.</p>\n<p>We nearly declared victory too early.</p>\n<blockquote>\n<p>Teardown is delayed. Turning the switch off does not mean the tap is gone in three seconds. Helpers need time to unregister. We learned to wait — tens of seconds, up to about a minute — and to run an active unlock script that kills stuck helpers and lifts stuck mouse buttons and modifiers, without sending new keyDown events.</p>\n</blockquote>\n<p>That last clause is the subtle part. The unlock script is allowed to release things: mouse up, modifier keys up, hung automation processes killed. It is not allowed to press anything, because pressing is what got us here. The procedure that came out of it is a schedule, not a hope: toggle off, run the unlock, try typing at ten seconds, at thirty, at sixty. Still wedged after a minute, log out of the Mac session.</p>\n<blockquote>\n<p>OFF is not instant. Measure teardown. Script the unlock. Don’t gaslight the human with “try again now” while the tap is still dying.</p>\n</blockquote>\n<p>I keep that sentence around because it names a failure mode that has nothing to do with capability. A bot that says “fixed, try again” three seconds after flipping a switch is not lying. It is confusing an action it took with an outcome it has not observed. The person at the keyboard experiences the difference.</p>\n<h3 id=\"attempt-four-mouse-clipboard-and-a-human-who-pastes\">Attempt four: mouse, clipboard, and a human who pastes</h3>\n<p>After the lockout, we rebuilt the default path from scratch.</p>\n<ul>\n<li>Mouse clicks and AppleScript navigation: allowed, when I have greenlit the run.</li>\n<li>Text the bot prepares goes to the clipboard.</li>\n<li>I paste it. Password fields and anything sensitive get a human Cmd+V, not a synthetic one.</li>\n<li>Keyboard events through <code>CGEvent</code> or System Events keystrokes, for typing: never.</li>\n</ul>\n<p>It felt slower. It was also the first design that respected a shared Mac.</p>\n<blockquote>\n<p>The innovative part is not “the bot can type.” It’s “the bot knows when typing through the OS is the wrong abstraction.”</p>\n</blockquote>\n<p>I argued in <a href=\"https://kobi.kadosh.me/blog/2026-09-17-the-magic-prompt\">The Magic Prompt</a> that when automation stops, orchestration should continue, and that the human should stay inside the execution graph rather than being ejected from it. This is that principle applied to a single keystroke. Latch navigates to the field, puts the right text on the clipboard, tells me exactly what to paste and where, and waits. The step is still one step. It just has two participants.</p>\n<h3 id=\"attempt-five-the-lease\">Attempt five: the lease</h3>\n<p>The last piece is the one that gave the bot its name.</p>\n<blockquote>\n<p>Every Mac drive now ends with an explicit release-the-lease step: kill hung click and keystroke processes, mouse up, clear stuck modifiers, hand focus back. If a lockout still happens, the bot stops driving until a human says it’s okay again.</p>\n</blockquote>\n<p>Two things are packed in there. The first is a routine: every run, successful or not, ends by releasing whatever it held. The second is a policy: the moment the lockout signature appears, Latch stops. It does not retry, it does not “try one more thing,” and it does not resume until I say so. Re-enabling is deliberate too. Grant Accessibility again, then run a mouse-only smoke probe before anything heavier.</p>\n<p>I would not call that a guarantee. It is a contract with a specific trigger, a specific set of actions, and a specific stop condition, and it is the mechanism behind the “hard hand-back” on the card. If you clone this pattern, test the hand-back on your own machine before you trust the phrase.</p>\n<blockquote>\n<p>Automation without a hand-back contract is just a fancy way to steal the keyboard.</p>\n</blockquote>\n<h3 id=\"done-is-not-pass\">DONE is not PASS</h3>\n<p>One more rule came from a different kind of failure.</p>\n<p>Early on, Latch shipped visual “proof” that did not survive a second look. The screenshots existed. They did not show the thing I had asked for. A command had returned, a capture had been taken, and the bot reported success on the strength of both.</p>\n<p>So we added a rule that reads like a tautology and is not one: <strong>DONE ≠ PASS.</strong> A screenshot that does not show the ask is not evidence. The bot inspects its own receipts before it reports, and if the receipt does not match the request, the run is not finished, whatever the exit code said.</p>\n<p>This is the same discipline I keep coming back to in the <a href=\"https://kobi.kadosh.me/blog/feedback-loops-part-3-agents\">feedback loop series</a>: the agent needs objective verification of its own output, and “the command succeeded” is not that. On a Mac it is even less than that, because the interface can change state without any process reporting anything at all.</p>\n<hr/>\n<h2 id=\"the-playbook\">The playbook</h2>\n<p>Condensed, for anyone cloning the pattern. This is the public version of the skill Latch carries.</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Do</th><th>Don’t</th></tr></thead><tbody><tr><td>Drive the user’s own Mac through the tools the bot is authorized for: shell, AppleScript, a click helper</td><td>Pretend the bot’s cloud computer is the user’s Mac</td></tr><tr><td>Prefer mouse, clipboard, and a human paste</td><td>Inject HID keyboard events “to go faster”</td></tr><tr><td>End every run with a lease release</td><td>Leave hung <code>osascript</code> or click helpers running</td></tr><tr><td>Treat Accessibility teardown as delayed</td><td>Declare the keyboard fixed at three seconds</td></tr><tr><td>Fail closed on unclear or destructive asks</td><td>Guess through single sign-on, payments, or credential walls</td></tr><tr><td>Prove with inspected screenshots</td><td>Claim PASS from a probe you did not look at</td></tr><tr><td>Leave the privacy database alone</td><td>Reset it to hide a bad driver</td></tr></tbody></table>\n<p>And the decision tree the skill opens with, which is short enough to memorize:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>Lockout signature?  → Grok Bot Accessibility OFF + unlock script + wait up to 60s → log out if needed</span></span>\n<span class=\"line\"><span>Need typing?        → clipboard + human paste (never synthetic keys)</span></span>\n<span class=\"line\"><span>Need mouse drive?   → only with a greenlight; end with lease release</span></span></code></pre></div>\n<p>The ordering is deliberate. The failure case comes first, because the failure case is the one you will meet at the worst moment.</p>\n<h2 id=\"the-scars-are-the-product\">The scars are the product</h2>\n<p>We kept the root-cause analysis of the keyboard lockout inside the playbook, not in a separate postmortem nobody will open.</p>\n<blockquote>\n<p>Public bots usually hide their scars. Latch’s value is the scar tissue: other builders shouldn’t relearn that a Computer Use helper can leave an Accessibility tap armed after synthetic keyboard use.</p>\n</blockquote>\n<p>I think this is the most transferable idea in the whole project. A polished demo shows that something happened once. The explanation of what it depended on, what broke, and why each constraint exists is what lets someone else decide whether the same approach makes sense for them. If you read the playbook without the story, the rules look overcautious. Read with it, every rule has a date and a reason.</p>\n<p>It is also the most direct example I have of what I meant by <a href=\"https://kobi.kadosh.me/blog/2026-09-15-harness-driven-development\">harness-driven development</a>. The skill file, the unlock procedure, the do-and-don’t table, the chronicle: none of that is source code, and all of it is the software. Delete it and the bot still exists. It would just relearn the lockout on your machine instead of mine.</p>\n<h2 id=\"from-my-mac-to-a-template\">From my Mac to a template</h2>\n<p>Turning an experiment built around one person’s environment into something shareable was its own piece of work, and most of it was not about deleting names.</p>\n<p>Grok Bot lets you <a href=\"https://docs.x.ai/grok-bot/bots\">share a bot as a template</a>, as a public link or team-only. A template carries the bot’s shared configuration: identity, description, skills, routines. It does not copy conversation history, learned memory, or chat attachments. Adding it creates a copy on the recipient’s account. It does not give them your computer, your logins, or your history.</p>\n<p>That makes the template itself the review surface, not just the blog post. A sanitized profile does not mean the skills underneath it are sanitized, and the skills are where the specifics of one desk tend to hide.</p>\n<p>Generalizing is also different from redacting. Wherever the implementation depended on a specific path, application, or account, the public version needed either an explicit setup input or a documented prerequisite. Otherwise I would be hiding a dependency rather than removing it. What survives is the part a stranger can act on: the mouse-first playbook, the lease release, the delayed teardown, and the fail-closed defaults. Some things remain yours to supply: the Accessibility grant on your Mac, your own unlock script, the applications you actually want driven, and the smoke probe you run before trusting any of it.</p>\n<p>Three caveats I want on the record, because the card is short and cards invite over-reading.</p>\n<p>The scope is macOS. Windows and Linux are possible directions, not demonstrated compatibility, and I am not going to claim them without the implementation and the tests.</p>\n<p>A public bot template is not an open source release. It is shared configuration. The playbook is reusable; the platform underneath it is not yours to fork.</p>\n<p>And a public link is public. Anyone who has it can open it. That is a property of the sharing model, not something a bot’s description can narrow.</p>\n<h2 id=\"what-this-says-about-bots-as-a-platform\">What this says about bots as a platform</h2>\n<p>Strip the Mac details away and Latch is an existence proof for a few things I care about.</p>\n<ul>\n<li>A bot can specialize past the default chat persona into a <strong>capability owner</strong>, in this case Mac UI and Accessibility.</li>\n<li>It can hold playbooks other bots consult, so a roster does not each reinvent lockout-prone scripts.</li>\n<li>It can extend past the in-app sandbox onto the user’s own machine, carefully, with the human in the loop exactly where the operating system and plain judgment demand it.</li>\n<li>It can be shared as a template with the same role and none of the one-person specifics.</li>\n</ul>\n<p>The interesting product claim is not that an agent clicked a button. It is that we found a stable contract for automation on a machine that a human also uses.</p>\n<h2 id=\"latch-on-prove-it-release\">Latch on, prove it, release</h2>\n<p>That is the contract, in three words.</p>\n<p>A task that used to require me to walk back to my desk is now a bounded delegation: do this specific thing, on this specific machine, within these limits, and tell me what actually happened. Not what you did. What happened.</p>\n<p>If you want to try it, the practical chapter is short. Add the template. Grant Accessibility deliberately, and only to what needs it. Run a mouse-only smoke probe before anything else. And write your own lease-release habit into every Mac-facing skill you publish, because the first time your keyboard goes quiet you will want to have already decided what the bot does next.</p>\n<p>Sometimes the next useful capability is not a smarter answer.</p>\n<p>Sometimes it is a way to complete the missing action.</p>\n<hr/>\n<h2 id=\"update-oct-5-self-heal-gets-an-outside-signal\">Update, Oct 5: self-heal gets an outside signal</h2>\n<p>Since this post went up, Latch has picked up a second job: watching the path to my Mac and relaunching it when the signals it can see fail.</p>\n<p>Everything above runs over Grok Bot’s local-exec link to the Mac, the path behind Execution on Local Computer. When that link drops, an agent on the other side cannot run a command on the Mac to bring it back, so the repair has to live on the Mac. Latch’s <a href=\"https://github.com/wildcard/latch-mac-local-exec-self-heal/tree/v1.5.0\">self-heal kit</a> is a LaunchAgent that runs there, outside any agent session, and relaunches Grok Bot when the local side stops looking healthy.</p>\n<p>Version 1.3.0 of the kit covered the Mac-local signals it can see. If Grok Bot dies, its heartbeat goes stale or freezes, or an operator leaves a local <code>.request</code> file, the LaunchAgent can quit and reopen it, and it only calls the run healed once local readiness comes back.</p>\n<p>There was still one gap.</p>\n<p>The Mac does not see the cloud’s view of the connection by itself. Grok Bot’s <code>ListMachines</code> reports whether a machine is <code>connected</code>, and nothing on the Mac mirrors that flag. If the cloud thinks the machine is disconnected while the local process and heartbeat still look healthy, there is no honest local signal for the watchdog to act on. Lowering the thresholds would only turn a missing signal into guesswork.</p>\n<p>Version 1.4.0 added an optional <code>worker-beacon/</code> mode for that case. The agent that actually sees the disconnect can POST a <code>heal_request</code> to a Cloudflare Worker the operator deploys. The Mac polls that heal-only inbox and runs the same quit-and-open heal as a local <code>.request</code>, records <code>reason=beacon_request</code>, and bypasses the normal cooldown. The Worker applies rate limits; the Mac still owns the actual recovery. Version 1.4.2, a <a href=\"https://github.com/wildcard/latch-mac-local-exec-self-heal/releases/tag/v1.4.2\">tagged release</a>, makes the installer keep an operator’s <code>BEACON_*</code> settings across a reinstall and retry loading the LaunchAgent.</p>\n<p>At about 11:23 AM PT on October 5, the live test of that path, Prove C, passed end to end. The request came from a remote box, and nothing touched the <code>.request</code> file on the Mac. Grok Bot relaunched, the pid changed, and the local result came back healed and ready. The negative cases passed too: a malformed request, an expired one, a replay, and a second request inside the rate limit were all refused.</p>\n<p>The off switch still wins. While the kit’s <code>.disable</code> file is present, the heal is skipped; remove the file, and the agent recovers normally.</p>\n<p>That proves the outside signal can reach the existing heal path.</p>\n<p>It does not prove that Latch can detect every outage by itself.</p>\n<p>It still cannot wake a sleeping Mac, open a lid, restore a lost network connection, or infer <code>ListMachines.connected</code> locally. When the cloud says the Mac is gone while everything on the Mac looks fine, an agent that can see that disconnect still has to send the request.</p>\n<p>On October 7, Prove D confirmed the observability gap: the cloud-connection helper exited while the main app and heartbeat still looked healthy, so 1.4.2 kept reporting healthy instead of relaunching. Kit 1.5.0, now released, detects and logs that silent cloud drop in observe-only mode; it does not heal it.</p>\n<p>Same rule as DONE ≠ PASS: the claim stops where the evidence does.</p>\n<hr/>\n<p>Latch is public as a Grok Bot template. It is opinionated about what it will not do, which is most of why I trust it with what it will.</p>\n<a href=\"https://x.ai/bot/Mtqs6_J6jdKbTHqZFIARi\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"link-card group not-prose ascii-border my-6 flex flex-col overflow-hidden bg-terminal-gray/30 no-underline transition-all duration-300 hover:border-terminal-cyan hover:shadow-lg hover:shadow-terminal-cyan/10 sm:flex-row\"><div class=\"shrink-0 border-b border-terminal-line/50 bg-terminal-gray sm:w-64 sm:border-b-0 sm:border-r\"><img src=\"https://kobi.kadosh.me/media/link-cards/latch-grok-bot.png\" alt=\"Meet my Bot: Latch, on Grok Bot\" class=\"h-40 w-full object-cover sm:h-full\" loading=\"lazy\" decoding=\"async\"></div><div class=\"flex flex-1 flex-col justify-center gap-1.5 p-4\"><div class=\"flex items-center gap-2 text-xs text-terminal-comment\"><span class=\"text-terminal-green\">▸</span><span class=\"truncate\">x.ai/bot</span><span class=\"text-terminal-comment transition-colors group-hover:text-terminal-cyan\" aria-hidden=\"true\">↗</span></div><h3 class=\"text-terminal-cyan font-semibold leading-snug transition-colors group-hover:text-terminal-green\">Latch by Kobi</h3><p class=\"text-sm leading-relaxed text-terminal-comment\">Drives a real Mac safely for Grok Bot: mouse-first UI automation, Accessibility without lockouts, and a hard hand-back when you need the machine. For anyone who wants Mac UI playbooks other bots can hand off to.</p></div></a>",
      "date_published": "2026-10-02T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Grok",
        "Computer Use",
        "macOS",
        "Automation",
        "Skills",
        "Software Craftsmanship"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-10-01-agent-first-development/",
      "url": "https://kobi.kadosh.me/blog/2026-10-01-agent-first-development/",
      "title": "Agent-First Development: Build the Capability Before the Interface",
      "summary": "Mobile-first taught us to design for the most constrained screen first. Agent-first asks a more fundamental question: what should our software be able to do when the user never opens it? This post looks at assistants that live in messaging apps, at capabilities an agent can discover and verify, and at what changes for marketing, discovery, and small open-source projects when the evaluator is an agent.",
      "content_html": "<p>Anyone who built software through the early 2010s remembers the moment mobile stopped being the version you squeezed down at the end.</p>\n<p>For years the order was fixed. You designed for the desktop, where there was room for everything — navigation, secondary actions, three competing calls to action — and then somebody had to figure out how to fit all of it onto a screen the size of a hand. The results were exactly what you would expect from a design that was never meant to live there.</p>\n<p>Mobile-first flipped the order, and the argument was never simply that phones were important. It was that starting with the most constrained interface forced you to understand what mattered. Luke Wroblewski’s <a href=\"https://www.lukew.com/ff/entry.asp?933\">original case</a> put it plainly: mobile forces a team to focus on the most important data and actions, because there is no room for anything else. The practice that grew out of it was to start there and then expand into environments with more space.</p>\n<p>I think we are approaching a similar shift, and the constraint is different enough that it changes what “interface” even means.</p>\n<p>What happens when the user doesn’t need to visit your application?</p>\n<p>Not because they stopped needing what it does. Not because a model somehow replaced the service. But because the assistant they already have can discover it, understand its capabilities, and use it on their behalf.</p>\n<p>The application still exists. Its capabilities still matter. Its reliability probably matters more than before. But its dedicated user interface is no longer necessarily the front door.</p>\n<p>That is the idea I want to poke at: agent-first development.</p>\n<hr/>\n<h2 id=\"agent-first-does-not-mean-adding-a-chatbot\">Agent-first does not mean adding a chatbot</h2>\n<p>The phrase is already overloaded — I have used it myself to describe development tools built around agents rather than an editor — so let me separate this from a few adjacent ideas first.</p>\n<p>I am not talking about using an agent to write your application. That is about the development process, and I have written about it at length; <a href=\"https://kobi.kadosh.me/blog/2026-09-15-harness-driven-development\">harness-driven development</a> is my name for it. This post is about the way the resulting software is designed to be used.</p>\n<p>I am also not talking about putting a chat window inside an existing product. That can be useful, but it usually preserves the exact assumption I want to question: the user must come to your application, learn its boundaries, and talk to your assistant inside them.</p>\n<p>The more interesting question is almost the opposite. How does your product become useful to the assistant the user already has?</p>\n<p>My working definition:</p>\n<blockquote>\n<p>Agent-first development means designing a product’s core capabilities so that an agent, acting on human intent, can discover, evaluate, use, and verify them without depending on the product’s dedicated graphical interface.</p>\n</blockquote>\n<p>The human remains the person being served. The agent becomes the operator.</p>\n<p>Notice that this does not require the product to contain a model at all. A deterministic service that performs one narrow task reliably can be an excellent agent-first product. A product stuffed with AI features can be nearly impossible for any external agent to use. The distinction isn’t how much AI is inside the application. It is whether the application can participate in an agent-mediated workflow.</p>\n<p>Instead of beginning with “what screens do we need?”, we begin with “what can someone accomplish through this service, and what must an agent understand to accomplish it correctly?”</p>\n<p>The screens become one possible expression of the product. They stop being its definition.</p>\n<h2 id=\"an-enormous-capability-space-inside-a-constrained-host\">An enormous capability space inside a constrained host</h2>\n<p>At first the mobile-first analogy looks backward.</p>\n<p>Mobile-first starts with less space. An agent appears to offer much more: describe an intent, combine capabilities, coordinate several services, and produce an outcome that would otherwise require a person to navigate five applications. There is no obvious limit to the workflows you can imagine, and that is precisely where the thinking goes wrong.</p>\n<p>An imaginable workflow is not an executable one.</p>\n<p>The agent still needs usable tools, sufficient context, appropriate permissions, and feedback it can interpret. Anthropic’s guidance on <a href=\"https://www.anthropic.com/engineering/writing-tools-for-agents\">writing tools for agents</a> is blunt about this: good tools need deliberate descriptions, useful responses, and evaluation against real agent behavior — not merely more operations exposed. Everything your backend happens to do is not a capability surface. It is a pile. The important capabilities have to be understandable, bounded, and composable.</p>\n<p>Meanwhile, the human-facing side of the interaction may be more constrained than anything we designed for on mobile.</p>\n<p>Perhaps the assistant lives in a messaging app. Perhaps the interaction starts with a voice note. Perhaps the user only ever sees a short notification asking them to approve an action. We have gained a potentially unbounded capability space while giving up control over almost all of the presentation.</p>\n<p>That is the tension worth sitting with. In mobile design we asked how much would fit inside a known viewport. In agent-first design we have to ask what the host, meaning the app the assistant lives in, lets us communicate, and what the agent can reliably understand.</p>\n<p>Can the host show a useful comparison, or only prose? Can the user approve a specific action, or only reply “yes”? Can a long-running task report progress without flooding a conversation? Can an attachment carry the result? What happens when the user changes their mind halfway through?</p>\n<p>These are design questions, even though none of them involve drawing a screen. The constraint has moved from pixels to context, attention, permissions, and certainty.</p>\n<p>The absence of a traditional interface does not remove the need for interface design. It changes where that design happens.</p>\n<h2 id=\"instinct-makes-the-shift-easier-to-see\">Instinct makes the shift easier to see</h2>\n<p><a href=\"https://instinct.com/\">Instinct</a>, a recently launched personal assistant, is a useful example because its positioning makes the interface argument unusually explicit. Its own description is almost aggressively plain: it connects to your applications and devices, it is trained to use a phone and a computer, you can text or call it, and there are no new interfaces. In practice, according to early users and press coverage, that means it lives in iMessage, WhatsApp, or plain SMS, the messaging apps you already open a hundred times a day, and it is invite-only as I write this. I have only looked at it from the outside, so treat this as a reading of its positioning rather than a review.</p>\n<p>What interests me is not the list of things such an assistant can do. It is the abstraction it offers.</p>\n<p>Once an assistant is just another conversation in iMessage or WhatsApp, the user does not need a separate application experience for every capability behind it. And the choice of host is strategic rather than technical. Telegram could be another host — for a team that already speaks to two messaging platforms, a third is not the hard part. Where users already communicate, what interactions that environment supports, and what operational constraints come with it: that is the decision. In North America, iMessage and WhatsApp cover most of the people such a product wants to reach, so those two are enough to start.</p>\n<p>There is a second, quieter difference in how these assistants organize work.</p>\n<p><a href=\"https://help.openai.com/en/articles/10169521-projects-in-chatgpt\">ChatGPT’s projects</a> give you explicit containers: a place to group chats, files, and instructions. That structure is valuable when you want deliberate boundaries around a body of work. But it also asks you to decide, before you ask for help, whether a thought belongs in a new chat, an existing session, or a project. That is a small tax, paid constantly.</p>\n<p>The Instinct-style abstraction doesn’t have chats and projects at all. It is one ongoing relationship, and continuity is the assistant’s job rather than yours.</p>\n<p><a href=\"https://hermes-agent.nousresearch.com/docs/\">Hermes Agent</a>, from Nous Research, explores another corner of the same space: a messaging gateway, conversations that continue across platforms, persistent memory, and reusable skills. It is a different product and a different operating model — something you run yourself rather than a service — but it makes the same point. An assistant’s capabilities don’t have to be tied to one dedicated interface.</p>\n<p>I don’t think these approaches are interchangeable, and I am not predicting which one wins. What they share matters more: each is a different way of mediating between human intent and software. And that creates a question for everyone building the software underneath.</p>\n<p>Are you trying to become the assistant, or become something the assistant can use?</p>\n<h2 id=\"most-products-should-not-try-to-be-the-assistant\">Most products should not try to be the assistant</h2>\n<p>My expectation is that people will not maintain a dozen equally important personal assistants.</p>\n<p>They may use different ones in different contexts: one for work, one for personal life. Preferences will differ around privacy, control, convenience, and how much organizing they want to do themselves. But being trusted with someone’s ongoing context is a demanding position to earn, and there will be room for only a few winners.</p>\n<p>Instinct is competing for that relationship. So are ChatGPT, Claude, Grok Bot, and the self-hosted harnesses people like me keep building. There will not be many of them.</p>\n<p>For most product builders there is a more practical opportunity than competing for the entire relationship: become a capability the user’s chosen assistant can depend on.</p>\n<p>A scheduling service does not need to become a personal assistant. A reporting service does not need to own the conversation. A specialized developer tool does not need to become another general-purpose coding environment. It needs to do something useful, expose it clearly, and return a result the assistant can work with.</p>\n<p>Drawn out, the agent-first stack looks like this. Most of it isn’t yours.</p>\n<pre class=\"mermaid\">\nflowchart TD\n  H[Human&lt;br/&gt;intent and approvals]\n  S[Host surface&lt;br/&gt;iMessage, WhatsApp, a chat app]\n  A[Assistant&lt;br/&gt;ChatGPT, Claude, Instinct, a harness]\n  C[Your capability&lt;br/&gt;API, MCP server, skills]\n  E[Evidence&lt;br/&gt;stable identifiers, current state, artifacts]\n  U[Dedicated UI&lt;br/&gt;optional, where it earns its place]\n\n  H &lt;--&gt; S &lt;--&gt; A --&gt; C --&gt; E\n  E --&gt; A\n  C -.-&gt; U\n  U -.-&gt; H\n\n  classDef strong fill:#0A0A0A,stroke:#00FFFF,color:#E0E0E0\n  classDef weak fill:#0A0A0A,stroke:#666,color:#999\n  class C,E strong\n  class H,S,A,U weak\n</pre>\n<p>In plain text: a human expresses intent and approves actions through a host surface such as iMessage, WhatsApp, or a chat app. The assistant behind that surface — ChatGPT, Claude, Instinct, or a harness you run yourself — calls your capability, exposed as an API or an MCP server, often with a skill describing how to use it. Your capability returns evidence: stable identifiers, current state, artifacts. That evidence flows back to the assistant and, through it, to the human. A dedicated UI hangs off the capability as a dotted, optional branch. The two highlighted boxes, capability and evidence, are the only ones you control.</p>\n<p>Whether a particular assistant lets you plug in directly is a platform-specific question, and it is changing month to month. Not every assistant has an open extension system, and no single integration works everywhere. But the strategic direction holds regardless: build the capability independently, then provide the appropriate ways for supported assistants and harnesses to reach it.</p>\n<p>The service should not have to change its meaning every time the user changes their assistant.</p>\n<h2 id=\"an-api-is-not-the-finish-line\">An API is not the finish line</h2>\n<p>There is an obvious objection here: haven’t we already separated frontends from backends? Haven’t we been building headless services and APIs for fifteen years?</p>\n<p>Yes. Agent-first development should build on those ideas, not pretend to have invented them. I made a related prediction earlier this year in <a href=\"https://kobi.kadosh.me/blog/2026-02-11-back-office-front-office-split-future-software\">The Back Office / Front Office Split</a>: the operational layer of software wants to become agnostic infrastructure, operated by agents, with the interfaces optional. Agent-first is what that prediction looks like from inside the team building one service.</p>\n<p>What changes is the consumer we design for, and the completeness of the experience we expose.</p>\n<p>A human developer integrating an API can read the documentation, resolve ambiguities, write an adapter, and encode their assumptions into a stable application. In an agent-mediated workflow, some of that interpretation happens while the task is being performed, by a system that has never seen your service before. Unclear semantics stop being an annoyance. They become expensive.</p>\n<p>Consider a hypothetical reservation service.</p>\n<p>The human-facing product walks someone through choosing a date, reviewing availability, entering details, accepting conditions, and receiving a confirmation. In an agent-first design, those meanings need to exist independently of the sequence of screens.</p>\n<p>The agent must be able to distinguish availability from a temporary hold, know when that hold expires, and tell a hold from a confirmed reservation. It must be able to retrieve the price and the cancellation conditions before committing. Afterward, it must be able to verify the reservation rather than infer success from a pleasant sentence. And if a request times out, the retry has to be safe: either the agent sends a client-generated <a href=\"https://docs.stripe.com/api/idempotent_requests\">idempotency key</a> with the booking call, so the service recognizes the repeat, or it can look the reservation up by a reference it supplied, because the server’s own identifier never arrived. Otherwise a perfectly reasonable retry creates a duplicate booking.</p>\n<p>These are not cosmetic details. They are the interface.</p>\n<p>An API provides the underlying operations. An MCP server exposes tools and their schemas to compatible clients. A skill describes how to combine capabilities into a workflow. Those are different responsibilities — the <a href=\"https://modelcontextprotocol.io/specification/2026-07-28/server/tools\">MCP specification</a> defines tool interfaces and structured results, while the <a href=\"https://agentskills.io/specification\">Agent Skills</a> format packages procedural instructions and supporting resources — and none of those labels, by itself, makes a product agent-first.</p>\n<p>OpenAI’s <a href=\"https://developers.openai.com/plugins/concepts/plugins\">plugin documentation</a> is a concrete example of the separation. A plugin for ChatGPT and Codex can contain skills, an MCP server, or both, and the docs state that custom UI is not required: use model responses or structured results when they communicate the outcome. The important word is optional. You should be able to establish the product’s usefulness before you need a custom screen to express it.</p>\n<p>That does not mean shipping every integration on day one. I would rather have one coherent capability surface and one thoroughly tested integration than five wrappers around ambiguous behavior. <a href=\"https://kobi.kadosh.me/blog/2026-09-17-the-magic-prompt\">The Magic Prompt</a> argued the same thing for the entry point: a small invocation into a workflow that has actually been tested end to end beats an impressive surface that leaves the agent guessing at step four.</p>\n<h2 id=\"evidence-becomes-part-of-the-product\">Evidence becomes part of the product</h2>\n<p>One implication deserves more attention than it usually gets: an agent-first product has to make its outcomes verifiable.</p>\n<p>There is a difference between a service saying “done” and a service returning enough information to establish what happened.</p>\n<p>For the reservation example, I would expect a stable identifier, the confirmed details, the applicable terms, and a way to retrieve the current state. For a generated report, I would expect the actual artifact and a clear account of the data it covers. For a data update, I would expect a way to inspect the resulting state.</p>\n<p>Structured results make those checks possible. MCP supports structured tool results and output schemas that clients are expected to validate against. But a response matching a schema is not proof that the underlying task succeeded. That still depends on the service’s behavior and the evidence it exposes. I keep running into this rule from the other side — in the <a href=\"https://kobi.kadosh.me/blog/2026-09-24-latch-giving-grok-bot-hands-on-my-mac\">Latch</a> story, “the command succeeded” was never verification — and agent-first design is where a service owes that evidence to its callers.</p>\n<p>This changes how I would define a finished feature.</p>\n<p>Not just: can a person click through the happy path?</p>\n<p>Also: can an unfamiliar agent understand the capability, use it with the right authority, detect failure, and verify the outcome?</p>\n<p>I would test that directly. Give an agent a realistic intent without privately explaining how the product works, then watch where the interface leaves it guessing. A feature that only works because its creator knows the undocumented sequence is not finished for this audience.</p>\n<p>A button used to be how an action was controlled. In an agent-first product, an action is controlled by intent, backed by data, and settled by evidence.</p>\n<h2 id=\"ui-less-should-not-mean-invisible-or-unaccountable\">UI-less should not mean invisible or unaccountable</h2>\n<p>The natural shorthand for all this is “UI-less,” and the phrase needs a boundary.</p>\n<p>Usually we are not eliminating every human interface. We are eliminating the requirement for a dedicated product interface.</p>\n<p>The user still needs to understand consequential decisions. They still need ways to approve, interrupt, inspect, and recover. Authentication may need a trusted handoff. Comparing visual work may genuinely need a visual surface. Agent-first should never become a reason to force everything into prose.</p>\n<p>For the reservation service, a concise comparison card may be better than a long message. A clear confirmation may be appropriate before an expensive commitment. An operational dashboard may remain useful for resolving an exceptional failure. Those interfaces should exist because there is an actual need, not because we assumed every product had to begin with an application shell.</p>\n<p>The integration plumbing can be opaque to the user. The consequences cannot.</p>\n<p>I should not need to know which adapter or protocol my assistant used. I should know which provider was selected, what I authorized, what it cost, and whether it succeeded.</p>\n<p>The interface can get thinner without accountability getting thinner.</p>\n<h2 id=\"marketing-moves-toward-evaluation\">Marketing moves toward evaluation</h2>\n<p>This architectural shift leads to a commercial one, and I find it at least as interesting.</p>\n<p>What happens when the person choosing a product delegates most of the discovery and evaluation to an agent?</p>\n<p>Say I ask my assistant to find a service for a specific task, compare the alternatives, and try the most suitable one. In that workflow I am not spending much time on anyone’s landing page. I may never experience the sequence of branding, visual hierarchy, testimonials, and sales calls the vendor designed for me. Instead, I expect the assistant to investigate suitability.</p>\n<p>Does the service meet my requirements? What does it cost? Which limitations matter? Can it be tested? What evidence supports the claims? Can the assistant actually integrate it into the workflow I asked for?</p>\n<p>We lived through a long era where content marketing was king and discovery was organic search. We are already in the transition where discovery happens through an agent’s web search instead. But I think the bigger shift is in what persuades. Palettes, branding, a beautiful landing page, a quick call with a friendly founder: those work on us because of biases we carry. An agent doing a functional comparison has no reason to be moved by most of that. It works from whatever it can retrieve and read, which is closer to data than to design, and it walks straight past the shortcuts we used to take.</p>\n<p>I want to be careful here. This is a hypothesis about the workflow, not a claim that agents are immune to presentation or that branding disappears. Aesthetic preferences can be real requirements. Human relationships carry real information and accountability. An agent trained to weigh vision heavily in its decisions might see what we see. And an assistant should respect a user’s stated brand preferences rather than decide that being “objective” means ignoring them. But for a functional decision, a beautiful landing page should no longer be able to substitute for a missing capability — and the gaps some companies used to bridge with design, messaging, and relationships are going to be harder to bridge.</p>\n<p>Content discovery is already halfway through this transition. Research on <a href=\"https://arxiv.org/abs/2311.09735\">generative engine optimization</a> studies visibility inside generated answers rather than placement in a conventional list of results. At the same time, Google is <a href=\"https://developers.google.com/search/docs/fundamentals/ai-optimization-guide\">explicit</a> that its generative search features are rooted in its core ranking and quality systems, and that the same SEO best practices still apply. This is an evolution of discovery, not a clean replacement of everything that came before.</p>\n<p>Agent-first products add a further step. Being mentioned in an answer is not the same as being usable in a workflow.</p>\n<p>A product can be easy to discover and impossible for the assistant to evaluate without a sales call. It can have excellent documentation and no safe trial. It can have an integration that takes longer to set up than the task itself.</p>\n<p>The opportunity is to shrink the distance between discovery and evidence. Let the agent inspect the relevant capabilities. Give it honest limitations, current information, representative examples, and a bounded way to try the product. Make the result inspectable. Put as little resistance as possible between the agent and a real trial.</p>\n<p>The strongest demonstration may not be a polished video of the product working. It may be the user’s own assistant making it work.</p>\n<h2 id=\"agents-have-biases-too\">Agents have biases too</h2>\n<p>There is an optimistic version of this argument that I don’t think we should accept uncritically.</p>\n<p>It goes like this: humans are swayed by appearances and persuasion; agents evaluate data; therefore agent-mediated markets will reward the objectively best products.</p>\n<p>That conclusion does not follow.</p>\n<p>Research on LLM-based recommendation has <a href=\"https://arxiv.org/abs/2305.08845\">shown</a> that models can be biased by the position of candidates in the prompt, and <a href=\"https://arxiv.org/abs/2508.02020\">more recent work</a> treats that position bias as a standing problem rather than a quirk. That doesn’t tell us how every deployed assistant behaves, but it is enough to reject the assumption that machine-generated recommendations are neutral. And we need to distinguish the model from the system around it. Which sources were retrieved? Which candidates were never found? Which tools were available? What did the host permit? Did the assistant actually test the product, or only summarize what the vendor said? Even a careful comparison is bounded by what entered the comparison.</p>\n<p>There is an adversarial side too. An assistant consuming external content can encounter attempts to redirect its behavior. Prompt injection is not a metaphor for persuasive marketing. It is a <a href=\"https://brave.com/blog/comet-prompt-injection/\">documented</a> and <a href=\"https://arxiv.org/abs/2504.18575\">actively benchmarked</a> problem in browser-using agents, and the browser is exactly where an assistant goes to evaluate your competitor.</p>\n<p>This is where a genuinely new field opens up.</p>\n<p>Marketing has spent a century understanding human attention and decision-making — what affects a choice, how to stand out in a crowded space. Agent-mediated discovery creates another set of selection behaviors to study: when a system notices an offering, treats it as credible, decides to investigate, and ultimately recommends or uses it. We have our biases, and models have theirs. There will be people who study them for the same reasons people studied ours.</p>\n<p>Some of that work will improve communication. Some of it will try to exploit weaknesses — to trick the agent into believing a product matches the intent, or to talk its way into a conversation that should have been between the user and a bigger, better-suited player.</p>\n<p>We should keep the distinction clear. Making real capabilities easier to discover and verify is product work. Manufacturing evidence, misrepresenting suitability, or trying to override the user’s intent is not a better form of it.</p>\n<p>For builders of assistants, this creates a responsibility: preserve the user’s criteria, and distinguish claims from observations. For builders of products, it creates a choice about how to compete. My preference is to make the product easier to evaluate honestly — not merely easier to recommend.</p>\n<h2 id=\"the-opening-for-small-and-niche-projects\">The opening for small and niche projects</h2>\n<p>One reason I find this shift exciting is what I’ve been noticing in my own searches.</p>\n<p>When I ask an agent to investigate a specific engineering problem, it sometimes surfaces a small repository, a recently released project, or a narrow discussion thread that I would never have found through my usual browsing. That is an observation, not evidence that AI search systematically favors small projects. But it changes how I think about discovery.</p>\n<p>Discovery for open-source projects used to be hard. GitHub does a wonderful job, and it still wasn’t enough: you needed intense marketing through the dev community to land on a platform or a list that would showcase your work. And on the other side, in my own evaluation habits, a low star count or an unfamiliar name was a perfectly good reason to move on. Popularity was a convenient shortcut for deciding where to spend attention. Incomplete, niche, recently released projects were invisible almost by definition.</p>\n<p>An intent-driven investigation asks a different question: does this particular project solve this particular problem?</p>\n<p>A broad framework can be well known and poorly suited to a narrow constraint. A small project can address that constraint directly. Popularity doesn’t stop mattering. The ranking research I cited above found LLM rankers biased by popularity as well as position. A precise match simply gets another route into consideration. Google’s description of <a href=\"https://developers.google.com/search/docs/appearance/ai-features\">query fan-out</a> illustrates one mechanism: to develop a response, its AI features may issue multiple related searches across subtopics and data sources. That doesn’t guarantee comprehensive or fair discovery, but it helps explain how a conversation ends up somewhere the user’s first search phrase never would have.</p>\n<p>For an open-source maintainer, that makes specificity valuable. Explain the actual problem. Show a reproducible example. State what the project does not support. Make installation, testing, and failure understandable. Give a potential evaluator enough evidence to decide whether the project fits. A small project does not have to present itself as a universal platform to deserve attention.</p>\n<p>Discovery is still not endorsement. A relevant repository needs evaluation for maintenance, licensing, security, and suitability, and an assistant should not confuse “I found something unusually specific” with “this is safe to adopt.” What excites me is the possibility of a fairer chance to demonstrate relevance — not skipping scrutiny.</p>\n<h2 id=\"a-different-starting-point-for-building-software\">A different starting point for building software</h2>\n<p>This connects directly to the threads I’ve been pulling on all year.</p>\n<p><a href=\"https://kobi.kadosh.me/blog/2026-09-15-harness-driven-development\">Harness-driven development</a> asks what an agent needs around it to do useful work: context, tools, instructions, execution boundaries, feedback. Agent-first development looks at the software that environment operates. What must the product expose so a harness can use it without relying on hidden knowledge or a human navigating its screens?</p>\n<p><a href=\"https://kobi.kadosh.me/blog/2026-09-17-the-magic-prompt\">The Magic Prompt</a> explores a related entry point: how a small invocation leads into a complete, tested workflow instead of leaving the user to assemble the missing pieces.</p>\n<p><a href=\"https://kobi.kadosh.me/blog/2026-06-18-disposable-software\">Disposable software</a> raises another possibility. Some interfaces may not need to be permanent at all. A capable service can back a temporary report, a one-off comparison view, or a task-specific application generated for the moment and discarded afterward. The capability stays durable while its presentation changes.</p>\n<p>Taken together, these ideas make me less interested in treating the application shell as the natural starting point for every product.</p>\n<p>I would rather begin with a human intent, identify the capability that satisfies it, define the authority required to use it, and decide what evidence establishes success. Then make that capability reachable through the assistants and harnesses the intended users actually choose. After that, ask where a dedicated interface adds value.</p>\n<p>This is not a claim that every application should lose its UI, or that every decision should be delegated. It is a change in the default question. Instead of asking how to add agent access to the application we already designed, ask what the product would look like if agent access were a primary way of using it.</p>\n<h2 id=\"build-for-the-outcome-not-the-visit\">Build for the outcome, not the visit</h2>\n<p>The lesson I take from mobile-first is not that software should keep moving toward smaller screens.</p>\n<p>It is that a new interaction model exposes assumptions we stopped noticing.</p>\n<p>Mobile-first challenged the assumption that we could begin with abundant space and solve constraints later. Agent-first challenges the assumption that the user must enter our product’s interface to receive its value.</p>\n<p>We went from a full desktop to a small viewport. Now the surface is getting thinner still, all the way to no UI at all — and the applications underneath must be served through agents and harnesses, with a breadth of integrations that a personal assistant can adopt easily and that the human requesting the service never has to see.</p>\n<p>I think that is a substantial opportunity for software engineers. We can build services that participate in workflows without owning the whole experience. We can make capabilities portable across assistants. We can let products prove their usefulness through evidence rather than presentation. But we have to design for it deliberately.</p>\n<p>Before drawing the first screen, I would ask one question:</p>\n<p>Could someone’s assistant discover this product, use it correctly, and prove that it accomplished what the person wanted — without requiring them to open our application?</p>\n<p>If the answer is yes, the interface becomes a choice.</p>\n<p>That is where agent-first development begins.</p>",
      "date_published": "2026-10-01T00:00:00.000Z",
      "tags": [
        "Developer Philosophy",
        "Agents",
        "AI Tools",
        "Software Engineering",
        "Software Architecture",
        "Predictions",
        "Harness",
        "Open Source",
        "Software Craftsmanship"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-09-24-codex-operating-system-for-agents/",
      "url": "https://kobi.kadosh.me/blog/2026-09-24-codex-operating-system-for-agents/",
      "title": "Codex Is Becoming an Operating System for Agents",
      "summary": "Codex has become the center of my development workflow, and the model is only part of the reason. Sessions that read other sessions, voice steering, goal mode, side chats, subagents on request, MCP, and an app server other harnesses can drive: the environment around the model is where the work happens now. Coding assistant is no longer a complete description.",
      "content_html": "<p>It feels appropriate to be working through this article in a conversation with GPT. This is how I begin most work now: I describe what I’m trying to achieve, explain the context, and start working with an agent on how to get there.</p>\n<p>But the more I work this way, the more I realize that the model is only part of the story.</p>\n<p>The environment around it matters at least as much. How do I give it context? How does it interact with my tools? Can it delegate? Can I steer it without restarting the work? Can it coordinate with something that is already running somewhere else?</p>\n<p>Those questions are why I think Codex deserves a different conversation from the one we usually have about coding assistants.</p>\n<p><strong>In my workflow, Codex has become something of an operating system for agents.</strong></p>\n<p>I’m not suggesting it replaces macOS, Windows, or Linux. I’m describing the role it plays for me: the place where I bring together context, tools, sessions, and other agents to pursue an outcome.</p>\n<p>Just by asking, I can spin up a new project and start working across my entire computer: apps, connectors, MCP integrations, and even other coding agents.</p>\n<p>And I’m specifically talking about Codex on my local machine. That distinction matters. A local Codex workspace, with its configured tools and permissions, is not interchangeable with a web chat.</p>\n<h2 id=\"the-important-shift-is-where-the-work-happens\">The important shift is where the work happens</h2>\n<p>When I use the word <em>harness</em>, I mean the system around the model that makes useful work possible: its instructions, context, tools, permissions, execution environment, and feedback mechanisms. I made the longer case in <a href=\"https://kobi.kadosh.me/blog/2026-09-15-harness-driven-development\">Harness-Driven Development</a>: you are not using a model, you are using a harness, and the same model in a different harness is a different engineer. Harnesses are not interchangeable. As I put it in <a href=\"https://kobi.kadosh.me/blog/2026-09-17-the-magic-prompt\">The Magic Prompt</a>, Codex has one set of capabilities and Claude Code exposes another, and any serious workflow has to know which one it is running in.</p>\n<p>The model matters. A lot. But I don’t experience Codex as a model sitting behind a text box. I experience it as an environment in which I can establish a goal and begin working toward it.</p>\n<p>That might involve a repository, but it doesn’t have to stop at the repository.</p>\n<p>Codex can connect to external tools and context through <a href=\"https://learn.chatgpt.com/docs/extend/mcp\">MCP servers</a>, and the desktop app, the CLI, and the IDE extension share that configuration on the same machine. With the right setup and permissions, the desktop app can also <a href=\"https://learn.chatgpt.com/codex/computer-use\">see and operate graphical applications</a> on macOS and Windows when a command-line tool or a structured integration is not enough. These are different routes into the environment where the work actually happens.</p>\n<p>For me, the significance is not simply that there are more integrations.</p>\n<p>It is that I spend less time acting as the integration.</p>\n<p>I don’t want to copy an explanation from one window into another, reconstruct the project’s history for another agent, collect its response, and manually carry that response back. Sometimes that is necessary. But it shouldn’t be the default architecture of working with agents.</p>\n<p>I want to explain the outcome, establish the boundaries, and have a capable coordinating agent work through the available interfaces.</p>\n<p>This is also why interoperability needs to work in both directions. Codex should be able to use other tools, but other tools and external harnesses should also be able to use Codex.</p>\n<p>There is a concrete foundation for that. Codex’s <a href=\"https://learn.chatgpt.com/docs/app-server\">App Server</a> exposes a JSON-RPC interface for thread management, approvals, and streamed agent events: create, resume, fork, or branch a thread, and steer or interrupt the turn running inside it. It exists so that rich clients and third-party integrations can drive Codex. Codex is not accessible only through a window that a human must operate.</p>\n<p>That is where the operating-system analogy starts to make sense to me. An operating system doesn’t need to be every application. Part of its value is letting different programs do useful work together. It is the same instinct behind <a href=\"https://kobi.kadosh.me/blog/2026-03-15-unix-philosophy-agents-skills\">Skills, Pipes, and the Unix Philosophy</a>: small pieces with well-defined interfaces compose, and one large piece that tries to be everything does not.</p>\n<p>Likewise, Codex doesn’t need to be my entire agent setup to become its centerpiece.</p>\n<h2 id=\"a-session-becomes-more-than-a-conversation\">A session becomes more than a conversation</h2>\n<p>The capability I lean on most in my local setup is working across sessions.</p>\n<p>One chat can look into another: read its transcript as a native tool, send a message to a session that is already running, pick up its work, or start a new project from it. Instead of treating every conversation as an isolated exchange, I can treat sessions as parts of one working environment.</p>\n<p>That doesn’t mean every chat automatically knows everything in every other chat. There is an important difference between information existing somewhere and the current agent having access to it through an available interface.</p>\n<p>What matters to me is that I don’t always have to be the person transporting that information.</p>\n<p>Once a session has accumulated the intent behind a project, it becomes valuable in a way that a fresh prompt is not. It contains the questions we considered, the constraints we established, the approaches we rejected, and the reasons behind those decisions.</p>\n<p>I want to be able to return to that working relationship. I also want other work to benefit from it without forcing everything into one enormous conversation.</p>\n<p>This changes what I ask of a chat interface. I’m no longer looking only for somewhere to enter a prompt. I’m looking for somewhere to organize and steer ongoing work.</p>\n<p>Voice makes this especially interesting.</p>\n<p>The desktop app’s <a href=\"https://learn.chatgpt.com/codex/features/voice\">voice mode</a> is more than dictation. It can start separate tasks, check on existing ones, report progress, and take an interruption mid-task when I want to change direction. Availability depends on your plan, rollout, and workspace settings, so you may not have it yet.</p>\n<p>The interaction I care about sounds something like this:</p>\n<blockquote>\n<p>Check where that investigation stands and tell me what is blocking it. Keep the implementation moving, but don’t change the approach until we’ve reviewed the alternative.</p>\n</blockquote>\n<p>That is a different relationship with a computer from opening applications one by one and performing each step myself.</p>\n<p>I think voice conversation is the next way people will talk to their harnesses. Not because typing will disappear, or because every task benefits from being spoken aloud, but because expressing intent and reviewing decisions are conversational activities.</p>\n<p>The important part is continuity: being able to change how I communicate without having to restart the work.</p>\n<h2 id=\"long-running-work-changes-what-i-delegate\">Long-running work changes what I delegate</h2>\n<p>There is a difference between asking an agent to perform a step and asking it to pursue a goal.</p>\n<p>A step sounds like this:</p>\n<blockquote>\n<p>Inspect this function.</p>\n</blockquote>\n<p>A goal sounds like this:</p>\n<blockquote>\n<p>Work out why this behavior is failing, look at the relevant parts of the system, propose a fix, implement it within the boundaries we agreed, and verify the result.</p>\n</blockquote>\n<p>The second requires more than a good first response. It requires maintaining direction as new information appears.</p>\n<p>This is one of the reasons Codex has become central to my workflow. I give it work that has a trajectory, not a sequence of disconnected prompts.</p>\n<p><a href=\"https://learn.chatgpt.com/codex/long-running-work\">Goal mode</a> makes that distinction explicit. A goal needs an outcome, constraints, and verification criteria, and once it is running I can pause it, resume it, edit it, or steer it with a follow-up message.</p>\n<p>But I don’t use <em>durable</em> to mean an immortal process or perfect memory. I mean work that I can return to, understand, and continue without reconstructing the entire purpose from scratch. Local execution still needs an available environment; persistence of a session is not the same thing as uninterrupted execution.</p>\n<p>For a centerpiece harness, the questions I care about are practical.</p>\n<p>Does it still understand what we are trying to achieve? Can I tell what has changed? Can I interrupt an assumption without throwing away the larger goal? Does it know what evidence would justify calling the task complete?</p>\n<p>A task running for a long time is not impressive by itself. What matters is whether that time is producing justified progress toward the intended outcome. That is the same <a href=\"https://kobi.kadosh.me/blog/feedback-loops-part-3-agents\">architecture of verification</a> I wanted from agents before any of this existed: plan, execute, verify, iterate, and never mistake a confident completion message for evidence.</p>\n<p>This also changes my responsibility as the operator. I need to be clearer about what “done” means, which decisions I’m delegating, and where the agent needs to stop and involve me.</p>\n<h2 id=\"side-chats-let-me-keep-thinking-while-work-continues\">Side chats let me keep thinking while work continues</h2>\n<p>Once a main session has developed substantial context, I don’t want to abandon it every time another question comes to mind.</p>\n<p>But I also don’t want every question to interrupt its work.</p>\n<p>Maybe I want to understand a decision. Maybe I want to explore an alternative. Maybe I want to challenge an assumption before deciding whether the main task should change direction.</p>\n<p>This is why I find side chats so useful. The <a href=\"https://learn.chatgpt.com/codex/long-running-work\">documented workflow</a> is exactly that: open a side chat for an explanation or a status recap without interrupting the main chat, in its own context, so two chats never edit the same files at once.</p>\n<p>In practice, I use that space to keep thinking around the work while the main thread continues.</p>\n<p>The main session can remain the center of the project’s execution, while I investigate something adjacent to it. I don’t need to choose immediately between derailing the current task and opening a completely disconnected conversation.</p>\n<p>There is one limitation I keep running into in my current setup: side chats don’t launch subagents.</p>\n<p>Some people will reasonably say that this is not critical. And for a quick explanation, it is not.</p>\n<p>But sometimes a side question is not a small question.</p>\n<blockquote>\n<p>Is this really the right approach?</p>\n</blockquote>\n<p>Answering that might mean exploring the codebase, checking a dependency, and challenging the assumptions behind the current plan.</p>\n<p>At that point, I would like the side investigation to benefit from the same delegation capabilities as the main session.</p>\n<p>That distinction is important: <strong>I use side chats to continue my own investigation alongside the work; I use subagents to delegate pieces of the work itself.</strong></p>\n<p>They solve related, but different, problems.</p>\n<h2 id=\"the-power-of-subagents-is-not-just-parallel-execution\">The power of subagents is not just parallel execution</h2>\n<p>People like the idea of a magic word. With Codex, one comes surprisingly close:</p>\n<blockquote>\n<p>Use a subagent for this.</p>\n</blockquote>\n<p>There is nothing mystical about it. It is an instruction to use a delegation capability the <a href=\"https://learn.chatgpt.com/docs/agent-configuration/subagents\">subagent documentation</a> describes plainly: ask for subagents or parallel agent work directly, and each one runs in its own thread and comes back with a focused summary. In most configurations you do have to ask; it won’t usually delegate on its own. And in my experience I haven’t needed to define a custom agent role before asking.</p>\n<p>What I find powerful is how little ceremony I need before trying it.</p>\n<p>I don’t necessarily need to stop working, design a permanent agent role, create another process, and manually manage the exchange. I can ask for a specialist suited to the problem in front of me.</p>\n<p>But there is a less obvious point here.</p>\n<p><strong>Figuring out what to delegate is often harder than spawning the agent.</strong></p>\n<p>Suppose I am evaluating whether to add a dependency to a project. What specialist do I actually need?</p>\n<p>A generic “research agent” might return a feature comparison. That could be useful, but it might completely miss the decision I need to make.</p>\n<p>Perhaps I first need an explorer to establish how the project currently handles the relevant behavior. Then I need someone to look for evidence that the proposed dependency solves a real gap. Perhaps I need an adversarial reviewer whose job is to find a simpler approach.</p>\n<p>Those are different assignments, with different evidence requirements.</p>\n<p>A request could look like this:</p>\n<blockquote>\n<p>Before proposing this dependency, use one subagent to inspect how the project handles the requirement today, and another to investigate whether a simpler approach would satisfy it. Keep both investigations read-only. Ask them to return evidence, uncertainties, and the assumptions their conclusions depend on. Compare their findings before recommending a change.</p>\n</blockquote>\n<p>The important part is not the number of agents. It is the division of responsibility.</p>\n<p>On another task I might need a fixer working against a narrowly defined failure, then a reviewer checking whether the fix actually addresses it. I might need an LLM as a judge against explicit acceptance criteria, an adversary whose only job is to give me the feedback I don’t want, or a scout whose only responsibility is to find out what we don’t yet understand.</p>\n<p>I don’t want to assume that one predefined collection of roles is appropriate for every problem.</p>\n<p>What I want is to work with the main agent to discover the right delegation structure for the task we are doing now.</p>\n<p>This is where separate working contexts earn their keep. An exploration produces a lot of intermediate material that doesn’t belong in the main conversation. Because each subagent runs in its own thread and returns a summary, the investigation moves out of the main session, and the main session stays oriented around requirements and decisions.</p>\n<p>However, I would not treat two agents agreeing as proof of correctness. They may be working from the same mistaken assumption. A judge still needs meaningful criteria. A reviewer still needs evidence. A fixer still needs verification.</p>\n<p>And more agents are not automatically better. The <a href=\"https://learn.chatgpt.com/docs/agent-configuration/subagents\">same documentation</a> is candid about the cost: subagent workflows consume more tokens than a comparable single-agent run, and several agents editing code at once can create conflicts. Coordination is never free.</p>\n<p>The real advantage is being able to experiment with delegation without first building an orchestration system for every idea.</p>\n<h2 id=\"being-the-centerpiece-does-not-mean-replacing-everything-else\">Being the centerpiece does not mean replacing everything else</h2>\n<p>I haven’t moved everything into Codex. In <a href=\"https://kobi.kadosh.me/blog/2026-09-10-buzzcut-cutting-through-the-ai-tool-hype\">Buzzcut</a> I described my split: Claude Code for most serious repository work, Codex for a second opinion with a different set of habits, and Grok Bot for small, sharply scoped bots that live close to a conversation rather than close to a codebase. Since then, more of that work has shifted toward Codex, and Claude Code is still very good at certain tasks. What this post adds is a different axis: not which agent writes the code, but which environment holds the goal.</p>\n<p>Grok Bot is also where I go when I’m thinking about a persistent collection of bots with different responsibilities. That organizational question deserves its own article: how should an AI crew be structured, which responsibilities should remain durable, and which should be created temporarily for a specific task?</p>\n<p>I’ve been experimenting with durable Codex sessions as personas, too. It’s fun. It works. But the ergonomics are nowhere near what Grok Bot gives me for that style of organization.</p>\n<p>That doesn’t weaken my argument about Codex.</p>\n<p>The place where I prefer to organize persistent personas doesn’t have to be the same place where I coordinate local execution. And the agent I prefer for a specific implementation task doesn’t have to own the entire workflow.</p>\n<p>I’m more interested in how well these pieces cooperate than in finding one product that replaces all the others.</p>\n<p>In my setup, Codex’s value is partly that it stays useful even when something else is doing part of the work. Its role can be to hold the broader goal, coordinate the interfaces, and help me evaluate what comes back.</p>\n<p>That is a much stronger position than simply being another isolated assistant.</p>\n<h2 id=\"the-boundaries-are-part-of-the-design\">The boundaries are part of the design</h2>\n<p>There are limits, and they are worth stating without losing sight of what already works.</p>\n<p>The one hard wall I have hit is computer use of Codex over Codex: asking Codex to drive its own app through the screen. The <a href=\"https://learn.chatgpt.com/codex/computer-use\">documented boundaries</a> rule that out on purpose. Computer use will not automate terminal applications or the ChatGPT app itself, because automating them could bypass its security policies.</p>\n<p>I don’t see self-operation through the screen as a prerequisite for the coordination I want. The session interfaces are a more direct way to work with existing tasks, within the permissions they expose.</p>\n<p>More broadly, I don’t want a central harness whose usefulness depends on ignoring boundaries.</p>\n<p>The more responsibility I give it, the more I care about being able to understand what it is doing, constrain its access, inspect its outputs, and intervene.</p>\n<p>Likewise, I don’t want important project decisions to exist only somewhere in a long transcript. My expectation is that durable work produces durable artifacts: code, tests, recorded decisions, and enough evidence for someone to understand the result.</p>\n<p>The conversation should help operate the project. It should not become the only place where the project exists.</p>\n<h2 id=\"try-codex-as-a-harness-not-only-as-a-code-generator\">Try Codex as a harness, not only as a code generator</h2>\n<p>The progression through GPT-5.5 and GPT-5.6 to <a href=\"https://openai.com/index/gpt-6-astra/\">GPT-6 Astra</a> has changed what I’m willing to delegate. Things are on a different level from where they were a year ago. Astra is available in Codex, and OpenAI calls it its best model for software engineering to date.</p>\n<p>So I implore you to try Codex. Not by asking the newest model to write a function.</p>\n<p>Give it a real, bounded piece of work. Let it establish context in a project. Ask it to investigate before implementing. Open a side chat while the main task is running. Ask for a subagent where a separate investigation would help. Connect the tools the work actually needs, with the permissions it actually needs.</p>\n<p>Then look at how the workflow changes.</p>\n<p>How often are you reconstructing context? How much information are you carrying between tools? Can you continue thinking while execution is underway? Can you redirect the work without starting over? Are you getting evidence you can review, rather than only a convincing completion message?</p>\n<p>Those are the questions that explain why I rely so heavily on Codex today.</p>\n<p>I still open it to write code. But more and more, I open it because I have something I want to accomplish on my computer, and I want a capable agent to coordinate everything required to get there.</p>\n<p>That is why “coding assistant” no longer feels like a complete description.</p>\n<p><strong>For me, Codex is becoming the operating environment in which agents do the work — not just another agent doing one part of it.</strong></p>\n<hr/>\n<p><em>Dictated by voice, drafted in a conversation with GPT, then edited and published with Claude Code. The arguments are mine; the drafting was collaborative.</em></p>",
      "date_published": "2026-09-24T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Codex",
        "Harness",
        "Claude Code",
        "Grok",
        "Developer Philosophy",
        "Software Craftsmanship"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-09-17-the-magic-prompt/",
      "url": "https://kobi.kadosh.me/blog/2026-09-17-the-magic-prompt/",
      "title": "The Magic Prompt: A Minimal Invocation Into a Tested Human-Agent Workflow",
      "summary": "A magic prompt is not a clever wall of instructions. It is a small, portable entry point into an engineered, tested workflow that takes a human and an agent from intent to a verified outcome — pre-flight, prerequisites, installation, authentication, validation, and the next step included. Here is how it should behave, and how to build one in layers.",
      "content_html": "<p>There is a particular kind of prompt I find myself wanting more and more when working with coding agents.</p>\n<p>I call it a <strong>magic prompt</strong>.</p>\n<p>The name is intentionally a little misleading. There should be very little magic inside the prompt itself.</p>\n<p>A magic prompt is not a giant wall of instructions. It is not the perfect combination of words that somehow makes an agent smarter. And it is definitely not another README disguised as a prompt.</p>\n<p>A magic prompt is a small, portable invocation that takes a human and an agent from intent to a complete outcome.</p>\n<p>You paste it into Codex, Claude Code, Cursor, or any other sufficiently capable agent, and the expectation is simple:</p>\n<blockquote>\n<p>Take me from here to there.</p>\n</blockquote>\n<p>Not 80% of the way.</p>\n<p>Not until we discover that I was supposed to install three CLIs first.</p>\n<p>Not until we reach a documentation page telling me to configure an account.</p>\n<p>Not until the agent says, “Everything is ready, now manually follow these twelve steps.”</p>\n<p>A good magic prompt owns the journey from A to Z. Zero to one. The whole process, as the person who invoked it understood it — and as the person who authored it intended it.</p>\n<p>That changes how we need to design prompts.</p>\n<hr/>\n<h2 id=\"what-makes-a-prompt-magic\">What Makes a Prompt “Magic”?</h2>\n<p>Imagine I hand you a prompt for contributing to an open-source project. Say, one of Slack’s open-source SDKs. The desired outcome might be:</p>\n<blockquote>\n<p>Get my environment ready and take me through making my first valid contribution.</p>\n</blockquote>\n<p>There are dozens of hidden assumptions inside that sentence.</p>\n<p>Do I have the repository? Do I have the correct runtime? Am I authenticated with GitHub? Do I have the required CLI tools? Does the project require signing a Contributor License Agreement? Is there a developer program I need to join? Are there forms I need to fill out, boxes I need to check? Do I have permission to create whatever sandbox, workspace, application, or token the project requires? What operating system am I using?</p>\n<p>And then a second set of questions, about the other participant:</p>\n<p>What tools does my agent have? Can it operate a browser? Can it execute shell commands? Can it see what I see? Can it complete an OAuth flow? Which decisions require me, and which can it make on its own?</p>\n<p>Traditional documentation distributes the answers to these questions across README files, contribution guides, documentation sites, onboarding pages, tribal knowledge, and people’s heads. Then it relies on the user to assemble the puzzle.</p>\n<p>A magic prompt should not.</p>\n<p>Its contract is that the puzzle has already been assembled.</p>\n<hr/>\n<h2 id=\"four-perspectives-on-the-same-prompt\">Four Perspectives on the Same Prompt</h2>\n<p>Before getting into the architecture, it helps to look at a magic prompt from every side of the table, because each side has a different expectation, and a good design satisfies all of them at once.</p>\n<p><strong>The human invoker.</strong> The person who pastes the prompt. Their expectation is safety and completeness. Safe, in that the prompt will not wander off, do something destructive, or leave their environment half-configured. Complete, in that it takes them to the outcome they were promised, not to a fork in the road.</p>\n<p><strong>The human author.</strong> The person who wrote the prompt and the workflow behind it. Their intent defines what “done” means. They know the process, and — this is important — they are biased by that knowledge. More on this later.</p>\n<p><strong>The agent.</strong> The harness that actually executes: Claude Code, Codex, Cursor, a cloud agent with computer use, whatever. The agent needs instructions it can act on, a way to discover what it is and is not able to do here, and a way to hand control to the human at exactly the right moments.</p>\n<p><strong>The required outcome.</strong> Not “the command returned zero.” The outcome as the invoker would recognize it: a thing exists, I can see it, it works, and I know what to do next.</p>\n<p>When you design against all four, the shape of a magic prompt becomes fairly clear. It has a contract with the human, a contract with the agent, a layered structure, and a definition of completion that is stricter than most agents use by default.</p>\n<hr/>\n<h2 id=\"the-human-contract\">The Human Contract</h2>\n<p>There still needs to be a boundary. A magic prompt cannot assume that absolutely nothing exists.</p>\n<p>If I want to build something for Slack, I probably know that Slack exists. I may already have a workspace, or be willing to create one. If I want to deploy something to Vercel, I probably have or am willing to create a Vercel account. If I want to build on Wix, I have some idea what Wix is. I am not completely new to the platform; I have some access to it — a free tier, a paid plan, a developer account — and I know roughly where I stand with it.</p>\n<p>These are reasonable human-level prerequisites.</p>\n<p>But they should be explicit and, more importantly, minimal.</p>\n<p>The magic prompt should not begin with an invisible dependency tree:</p>\n<blockquote>\n<p>Before using this prompt, read this guide, install these five tools, create these resources, configure these environment variables, request access here, and then come back.</p>\n</blockquote>\n<p>At that point it isn’t a magic prompt. It’s documentation with a prompt at the end.</p>\n<p>Instead, configuration should become part of the execution.</p>\n<p>Need a CLI? Install it. Need an SDK? Install it. Need authentication? Start the authentication flow. Need an API token? Determine whether it can be created automatically. If not, guide the human through exactly the intervention that is required, and nothing more. Need a browser? Use one, assuming the underlying agent has that capability. Need the human to approve something? Ask at the moment approval becomes necessary.</p>\n<p>There is no assumption that everything must be pre-installed. There is no assumption that some step is too interactive, too manual, or too “outside the terminal” to belong in the prompt. Everything is up for grabs — including things that cannot be fully automated and require a browser, or computer use, or a human clicking a checkbox — provided the underlying agent can do it, or can guide the human to do it.</p>\n<p>The distinction I find useful is between <strong>human prerequisites</strong> and <strong>machine prerequisites</strong>.</p>\n<p>Human prerequisites should be minimized and declared upfront. Machine prerequisites should be owned by the workflow.</p>\n<hr/>\n<h2 id=\"the-agent-contract\">The Agent Contract</h2>\n<p>There is another side to this contract: the agent.</p>\n<p>The magic prompt cannot assume every harness is identical.</p>\n<p>Codex has one set of capabilities. Claude Code exposes another. An agent running inside an IDE has another. A cloud agent with computer use has capabilities that a local terminal agent doesn’t. Some agents can drive a browser; some can only ask you to open one. Some have persistent skills; some start from nothing every time.</p>\n<p>So before doing the work, the workflow needs to understand its environment.</p>\n<p>I think of this as the <strong>pre-flight</strong>.</p>\n<p>Before execution, determine:</p>\n<ul>\n<li>What capabilities does this agent have?</li>\n<li>What tools are already installed, and are their versions compatible?</li>\n<li>What accounts or authentication already exist?</li>\n<li>What can be automated here, and what requires human intervention?</li>\n<li>What preferences has the human already expressed?</li>\n<li>Which prerequisites have already been satisfied?</li>\n<li>Which parts of the workflow actually apply to this environment?</li>\n</ul>\n<p>This matters because a magic prompt should not blindly execute a tutorial. It should <strong>resolve a path</strong>.</p>\n<p>If GitHub authentication already works, don’t teach me how to authenticate with GitHub. If the CLI is already installed and compatible, don’t reinstall it. If I already completed developer registration, don’t send me through registration again. If I don’t have something, don’t merely report that as an error. Determine how it becomes true.</p>\n<p>And when the pre-flight discovers a gap — a missing capability, a tool that needs a permission the agent does not have, a preference it cannot infer — that is precisely the moment to ask the human. Not as a failure, but as a configuration step. “I can’t open a browser from this session. Can you open this URL and tell me when you’ve approved the app?” is a pre-flight outcome, not an escape hatch.</p>\n<p>The pre-flight converts a generic process into a process for <em>this</em> human, on <em>this</em> machine, using <em>this</em> agent, right now.</p>\n<hr/>\n<h2 id=\"the-prompt-itself-should-be-small\">The Prompt Itself Should Be Small</h2>\n<p>This leads to what I think is one of the more counterintuitive properties of a magic prompt:</p>\n<p><strong>The magic prompt should be lean.</strong></p>\n<p>We shouldn’t solve this by producing a 15,000-word superprompt. We already know how that goes. I argued in <a href=\"https://kobi.kadosh.me/blog/2026-04-29-your-docs-are-not-the-prompt\">Your Docs Are Not the Prompt</a> that project context is a secondary orientation layer, not the thing that drives an agent’s behavior. The same holds here. A giant prompt is a giant document with all the same problems: it drifts, it goes stale, it fights with the harness’s own instructions, and nobody can tell which part of it is actually load-bearing.</p>\n<p>The initial prompt is an <strong>invocation layer</strong>.</p>\n<p>It can contain some configuration and enough information to establish intent — who I am, what I want, what I already have, what I prefer — but its primary responsibility is to retrieve and initialize a larger workflow.</p>\n<p>Something conceptually like:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>I want to create X.</span></span>\n<span class=\"line\"><span>Use the latest workflow from Y.</span></span>\n<span class=\"line\"><span>My preferences are Z.</span></span>\n<span class=\"line\"><span>Run its pre-flight, resolve missing prerequisites with me,</span></span>\n<span class=\"line\"><span>execute the workflow to completion, validate the result,</span></span>\n<span class=\"line\"><span>and leave me with the next useful action.</span></span></code></pre></div>\n<p>The intelligence is not primarily in those sentences.</p>\n<p>The intelligence lives behind them.</p>\n<hr/>\n<h2 id=\"the-envelope-skill\">The Envelope Skill</h2>\n<p>Behind the magic prompt should be what I’ve been thinking of as an <strong>envelope skill</strong>.</p>\n<p>You could call it a wrapper skill, an orchestration skill, an encompassing skill. The terminology matters less than the architecture.</p>\n<p>The envelope owns the complete journey. It is the glue between the different steps and the different skills that need to be invoked, and it is the part that adapts to the specific agent running it and to the human’s preferences. This is where the actual magic lives.</p>\n<p>It knows which smaller skills exist and how they fit together:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>Magic Prompt</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Envelope Skill</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Pre-flight</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Environment / Account Setup</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Authentication</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Tool Installation</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Domain-Specific Workflow</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Validation</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Human Verification</span></span>\n<span class=\"line\"><span>     ↓</span></span>\n<span class=\"line\"><span>Next Action</span></span></code></pre></div>\n<p>Each of those pieces can then be skillified independently.</p>\n<p>An authentication skill doesn’t need to know everything about creating a Slack application. A developer-registration skill — the one that walks a human through joining a developer program, creating a sandbox workspace, and accepting the terms — doesn’t need to know how the eventual project will be implemented. A local-environment skill can specialize in determining whether this machine satisfies the requirements and fixing it when it doesn’t.</p>\n<p>The envelope composes them.</p>\n<p>And importantly, it composes them <strong>conditionally</strong>. If pre-flight establishes that a prerequisite has already been satisfied, that branch disappears. If the agent can drive a browser, the OAuth branch runs one way; if it cannot, the same branch becomes a guided handoff to the human.</p>\n<p>Two properties of the envelope are worth spelling out.</p>\n<p>First, it should be <strong>fetched live</strong>. The prompt points at it; the agent retrieves the current version at invocation time. That is what lets the workflow be improved, templated, and optimized without anyone re-pasting anything. A magic prompt that embeds the whole workflow inline is frozen the moment it is shared.</p>\n<p>Second, the smaller skills underneath it should be <strong>tested and evolved on their own</strong>, before the envelope depends on them. The developer-registration skill gets exercised in isolation until it reliably takes a new person through the developer program. Only then does it become a prerequisite the envelope can trust. The groundwork comes first; the composition comes after.</p>\n<p>This is the same argument I made in <a href=\"https://kobi.kadosh.me/blog/2026-03-15-unix-philosophy-agents-skills\">Skills, Pipes, and the Unix Philosophy</a>, applied one level up. Small, well-scoped pieces, each doing one thing well, composed by something whose only job is composition.</p>\n<p>The prompt isn’t magical.</p>\n<p>Composition is.</p>\n<hr/>\n<h2 id=\"skills-need-two-interfaces\">Skills Need Two Interfaces</h2>\n<p>There is another design property I think is important here.</p>\n<p>Many agent skills are written exclusively for the agent.</p>\n<p>But these workflows inherently have two participants: the agent and the human.</p>\n<p>A good skill therefore needs instructions for both.</p>\n<p>The agent side might say:</p>\n<ul>\n<li>inspect authentication state;</li>\n<li>detect the installed CLI version;</li>\n<li>verify permissions;</li>\n<li>attempt automatic configuration;</li>\n<li>validate the resulting environment.</li>\n</ul>\n<p>The human side of the same step might become:</p>\n<blockquote>\n<p>Your browser is going to open an authorization page. Sign in using the workspace you want to develop against. Approve these permissions. Tell me when the browser confirms authorization.</p>\n</blockquote>\n<p>These are different interfaces to the same operation. The agent gets rules. The human gets a guide. Both are written by the author, in the same skill, so they can never drift apart.</p>\n<p>This makes skills less like documentation and more like small collaborative protocols between humans and agents. The human is not reading a guide <em>while</em> the agent works; the human is participating in a step the agent is orchestrating.</p>\n<hr/>\n<h2 id=\"never-make-manual-the-default-escape-hatch\">Never Make “Manual” the Default Escape Hatch</h2>\n<p>One of the easiest ways to accidentally break the magic is the word <em>manual</em>.</p>\n<p>Agents frequently reach some boundary and respond with something equivalent to:</p>\n<blockquote>\n<p>I can’t complete this part. Follow this guide and return when you’re finished.</p>\n</blockquote>\n<p>Sometimes human intervention genuinely is unavoidable. But human intervention and manual delegation are not the same thing.</p>\n<p>If the human needs to click a legal acceptance checkbox, the agent can navigate them to it — the exact page, the exact box, what it means, and what to do afterward.</p>\n<p>If OAuth requires the human to authenticate, the agent can initiate OAuth, explain what is happening, wait for completion, and verify authentication afterward.</p>\n<p>If information is required — a workspace name, a region, a plan choice — the agent can ask for that specific information at the appropriate point, rather than stopping and pointing at a form.</p>\n<p>The human should remain part of the execution graph. They shouldn’t be ejected from it.</p>\n<p>A magic prompt therefore needs a useful rule:</p>\n<p><strong>When automation stops, orchestration should continue.</strong></p>\n<hr/>\n<h2 id=\"completion-isnt-the-command-succeeded\">Completion Isn’t “The Command Succeeded”</h2>\n<p>There is also a surprisingly important question: when is the magic prompt finished?</p>\n<p>Agents tend to define completion technically. The repository exists. The application was created. The deployment succeeded. The command returned zero.</p>\n<p>But that isn’t necessarily the human’s definition of completion.</p>\n<p>If I asked an agent to create something, I probably need to see it.</p>\n<p>Open the application. Show the deployment. Take me to the relevant dashboard. Render the result in my browser, or capture it and show it to me. Verify that the thing we created actually behaves as expected — send the test message, hit the endpoint, trigger the slash command.</p>\n<p>Completion should include a <strong>human-observable outcome</strong> whenever one exists.</p>\n<p>And even that isn’t quite enough.</p>\n<hr/>\n<h2 id=\"a-magic-prompt-should-leave-another-door-open\">A Magic Prompt Should Leave Another Door Open</h2>\n<p>Suppose the workflow successfully created my development environment.</p>\n<p>Great. Now what?</p>\n<p>Traditional setup documentation often ends there:</p>\n<blockquote>\n<p>Congratulations! Your environment is ready.</p>\n</blockquote>\n<p>But from the human’s perspective, this may be precisely the point where the interesting part begins.</p>\n<p>The magic prompt should establish the <strong>next interaction</strong>. For example:</p>\n<blockquote>\n<p>Everything is configured and validated. You can now ask me to find a suitable starter issue in this repository and prepare a contribution.</p>\n</blockquote>\n<p>Or:</p>\n<blockquote>\n<p>Your Slack app is installed in your sandbox workspace and responding. From here, you can ask me to implement your first slash command.</p>\n</blockquote>\n<p>Or:</p>\n<blockquote>\n<p>Deployment is complete. You can now describe the change you want to make and I’ll modify and redeploy it.</p>\n</blockquote>\n<p>Don’t assume the process is done because the thing we set out to create exists. The product isn’t merely the artifact. The product is also the new capability the human has acquired — and the very next sentence they can say to their agent to use it.</p>\n<hr/>\n<h2 id=\"we-made-the-same-mistake-with-documentation\">We Made the Same Mistake With Documentation</h2>\n<p>A lot of this reminds me of how we’ve historically written developer documentation.</p>\n<p>Documentation is usually written by somebody for whom the environment already works.</p>\n<p>That’s a dangerous starting point.</p>\n<p>The author already has the correct runtime. Their SSH keys work. Their accounts exist. They signed the agreement six months ago. Their machine contains some tool they installed for an unrelated project three years ago. They know what the internal terminology means — what a “workspace” is here versus there, what “sandbox” means in this company’s vocabulary, which of the three dashboards is the one you actually use.</p>\n<p>Then they write:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#61AFEF\">npm</span><span style=\"color:#98C379\"> install</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">npm</span><span style=\"color:#98C379\"> run</span><span style=\"color:#98C379\"> dev</span></span></code></pre></div>\n<p>And everything works.</p>\n<p>For them.</p>\n<p>We compensate for this by expecting expertise from the reader. A sufficiently experienced engineer can usually bridge the missing pieces. They search for an error. They recognize that a credential is missing. They know what OAuth is. They infer which account they should use.</p>\n<p>But this isn’t evidence that the documentation was complete. It’s evidence that the human was capable of repairing it.</p>\n<p>Every system, every company, every community has its own way of doing things — its own onboarding rituals, its own forms, its own naming. To the author these are obvious. To a new person in a new environment they are foreign, and the “obvious” steps are exactly the ones that go unwritten. We were reckless about prerequisites, reckless about how our documentation fit into the puzzle in different people’s heads, and we relied on experts to close the gap.</p>\n<p>Agents give us an opportunity to stop relying on that repair mechanism — but only if we stop carrying the old gaps into the new medium.</p>\n<hr/>\n<h2 id=\"test-the-empty-workspace\">Test the Empty Workspace</h2>\n<p>There is a simple test for magic prompts that I think should become standard:</p>\n<p><strong>Run them from nothing.</strong></p>\n<p>Not from the machine where the workflow was developed. Not from the author’s workspace. Not from the environment where everything has been configured for six months.</p>\n<p>Use a clean environment. A fresh container, a new machine, a throwaway account. Assume the human has only the minimal prerequisites that the magic prompt explicitly declares — and nothing else.</p>\n<p>Then run the whole thing.</p>\n<p>Every time the workflow encounters an undocumented assumption, that assumption becomes one of three things:</p>\n<ol>\n<li>an explicit human prerequisite,</li>\n<li>part of pre-flight,</li>\n<li>or another skill the workflow needs to own.</li>\n</ol>\n<p>Repeat until there are no accidental gaps.</p>\n<p>Then do it again with a different agent, because the pre-flight branch that works in Claude Code may not be the one that works in Codex.</p>\n<p>This is almost like integration testing, except the system under test is a human-agent workflow. It is the same discipline I described in <a href=\"https://kobi.kadosh.me/blog/feedback-loops-part-3-agents\">The Feedback Loop Series Part 3: AI Agents</a>, pointed at a different target: instead of testing whether the agent produced correct code, you are testing whether the workflow reliably gets a stranger to the outcome.</p>\n<p>When I build these flows honestly — mimicking the new user fully, on a clean workspace, with their setup and not mine — there is no gap left for an expert to bridge. The gap only exists when I skip that step and build on top of what I already have.</p>\n<hr/>\n<h2 id=\"prompts-are-becoming-entry-points\">Prompts Are Becoming Entry Points</h2>\n<p>We spent years treating prompts primarily as instructions.</p>\n<p>I think that mental model is becoming insufficient.</p>\n<p>As agents gain shells, browsers, computer use, APIs, persistent skills, authentication, and the ability to operate external systems, a prompt can increasingly behave like an entry point into executable knowledge.</p>\n<p>That changes what good prompt engineering looks like.</p>\n<p>The interesting problem is no longer:</p>\n<blockquote>\n<p>What exact words will make the model produce the best answer?</p>\n</blockquote>\n<p>It becomes:</p>\n<blockquote>\n<p>How do I package a process so that a human can invoke it from an unknown environment and reliably reach the intended outcome?</p>\n</blockquote>\n<p>That includes instructions. But it also includes capability discovery, dependency resolution, configuration, authentication, conditional execution, human interaction, validation, recovery, and handoff.</p>\n<p>The magic prompt is simply the smallest interface into that system.</p>\n<hr/>\n<h2 id=\"the-magic-is-everything-behind-the-prompt\">The Magic Is Everything Behind the Prompt</h2>\n<p>This is also why I think publishing a collection of magic prompts without the infrastructure behind them misses the point.</p>\n<p>The valuable artifact isn’t:</p>\n<blockquote>\n<p>Paste this into your coding agent.</p>\n</blockquote>\n<p>The valuable artifact is everything required to make that sentence trustworthy:</p>\n<ul>\n<li>The skills have been designed.</li>\n<li>The prerequisites have been identified and minimized.</li>\n<li>The branches have been considered.</li>\n<li>The human interventions have been designed, not delegated.</li>\n<li>The workflow has been tested from a clean environment.</li>\n<li>Failure states have paths forward.</li>\n<li>The outcome can be validated and shown.</li>\n<li>And the human knows what to do after it succeeds.</li>\n</ul>\n<p>Only then can the invocation become tiny.</p>\n<p>That’s an interesting inversion. The more engineering we put behind the prompt, the less prompt the user should need to see.</p>\n<p>And perhaps that’s the best definition:</p>\n<blockquote>\n<p>A magic prompt is a minimal invocation into a tested, adaptive human-agent workflow that owns the path from prerequisites to a verified outcome — and leaves the human ready for what comes next.</p>\n</blockquote>\n<p>The magic isn’t that the prompt knows everything.</p>\n<p>The magic is that the user no longer needs to.</p>\n<hr/>\n<p><em>In a follow-up, I plan to make this concrete: an actual magic prompt for a first open-source contribution to one of the Slack SDKs, with its envelope skill and the full skill tree underneath it — developer registration, sandbox workspace, CLI setup, authentication, and the contribution itself — tested from an empty workspace.</em></p>",
      "date_published": "2026-09-17T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Claude Code",
        "Skills",
        "Documentation",
        "Developer Experience",
        "Open Source",
        "Software Craftsmanship"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-09-15-harness-driven-development/",
      "url": "https://kobi.kadosh.me/blog/2026-09-15-harness-driven-development/",
      "title": "Harness-Driven Development: More Than Source Code",
      "summary": "Software is no longer just its source code. Its harness — the tools, memory, skills, and rules guiding the agents that build and run it — is now first-class.",
      "content_html": "<p>A few days ago I stopped and actually read the file tree of my most active repository. Not the <code>src</code> directory — the rest of it.</p>\n<p>There was a <code>CLAUDE.md</code> at the root. A <code>.claude</code> directory holding agents, skills, and commands. A publishing-cadence document. A roadmap. Research notes from an architecture debate two agents had while I was asleep. Migration plans for migrations that already shipped. Critique outputs from a review loop. Scratchpads full of intermediate thinking that fed decisions I now take for granted.</p>\n<p>Very little of it was written for the people who use what the repository builds. Surprisingly little of it was even written for me. Most of it was written by agents, for agents — instructions, memories, and working notes that exist so the next session starts smarter than the last one.</p>\n<p>I tried to imagine actually deleting it. The site would still build. The tests would still pass. Nothing a user touches would change. And yet the project would get worse in a way I could feel but not point to — the next review would miss context, the next migration would re-derive settled decisions, the next agent session would ask questions the repository had already answered.</p>\n<p>My first instinct was to tidy it all away. My second was that I was looking at the software.</p>\n<p>We have named ways of working before — object-oriented programming, domain-driven design, test-driven development. I think the way we work now deserves a name too, and the honest name centers neither the model nor the code.</p>\n<p>I’ve started calling it harness-driven development — though I don’t claim the phrase. Others have been reaching for the same language this year, and OpenAI now writes about “harness engineering” outright. What I want to add is a specific shape: an outer harness that builds the system and an inner harness that operates it, and the argument that this environment is now a first-class part of the software rather than scaffolding around it.</p>\n<hr/>\n<h2 id=\"the-repo-grew-a-second-layer\">The repo grew a second layer</h2>\n<p>Look at any repository that has been through a few months of serious agent-assisted development and you will find the same thing: a second layer growing alongside the source code.</p>\n<p>Part of that layer is native to the tools: skills that teach the agent a procedure, rules that constrain it, hooks that fire around its actions, and memory files that persist what it learned. Context documents — <code>CLAUDE.md</code>, <code>AGENTS.md</code>, and their cousins — tell it what this project is and how to behave inside it.</p>\n<p>The other part is produced: research summaries, design debates, migration plans, review critiques, scratchpads of intermediate reasoning. Documents the agent wrote because it needed them to think, and kept because the next agent will need them to think too.</p>\n<p>We used to complain that engineers don’t write enough documentation. That problem quietly inverted. There is now far more of it than we ever wrote by hand, scattered exactly where the work happened, and most of it was never meant for human eyes.</p>\n<p>I wrote before that <a href=\"https://kobi.kadosh.me/blog/2026-04-29-your-docs-are-not-the-prompt\">your docs are not the prompt</a> — descriptive documentation is a weak signal for a coding agent next to the prompt and the code. That is still true, and it is why this second layer is a different animal. A README is a brochure: it describes the software and hopes someone reads it. A skill, a rule, or a memory file is a control surface: it steers the machinery that builds the software.</p>\n<p>Which means the old reflex — treat everything that isn’t source code as clutter or ceremony — now deletes real capability. Remove a rules file and behavior changes. Lose a research document and the next session pays to re-derive it. Let a skill drift out of date and every workflow that loads it degrades. These files carry the operational weight we used to reserve for code, and they deserve the same treatment: version control, review, deliberate design.</p>\n<p>This layer doesn’t document the software. It is the software.</p>\n<h2 id=\"you-are-not-using-a-model-you-are-using-a-harness\">You are not using a model, you are using a harness</h2>\n<p>We say “I’m using Claude” or “I’m using GPT” the way we used to say “I’m on Java.” It’s shorthand, and it hides the thing that actually matters.</p>\n<p>You are not using a model. You are using Claude Code, or Codex, or Cursor. You are using a harness.</p>\n<p>A harness is everything wrapped around the model that turns raw intelligence into a working agent: the tools it can call, the memory it carries between sessions, the skills it can load, the rules that constrain it, the runtime it executes in, the processes it follows when it plans and reviews, and the interfaces through which a human steers it. The model is one component. A decisive one — a harness is only ever as good as the model it harnesses — but still one component.</p>\n<p>I’ve watched this play out in a single afternoon. Give the same frontier model the same task three ways — through the raw API, inside a full coding harness, and inside a purpose-built agent with curated tools — and you can get three very different engineers. Same weights, comparable knowledge, sharply different competence, because competence, for an agent, is mostly a property of what surrounds it.</p>\n<p>The distinction shows up even inside one product. Codex runs across several surfaces — a terminal CLI, an IDE extension, a cloud task runner, and the ChatGPT app — and OpenAI has described the same core agent loop sitting under all of them. What each surface changes is the envelope around that core: which tools are wired in, which permissions are granted, what runtime it can reach. On a locked-down surface it can read and suggest; give it a sandboxed machine and write access and it can run a test suite, edit files, and open a pull request. The model and the core loop never changed — the harness wrapped around them did, and with it the whole range of what the agent can actually do.</p>\n<p>The same model in a different harness is a different engineer.</p>\n<p>The frontier labs clearly understand this, because they stopped behaving like model vendors. The API business — here are the weights, build what you want — still exists, but the strategic energy goes into the harnesses: the coding tools, the desktop apps, the migration paths that pull your existing skills and setups from one ecosystem into another. They are no longer competing only on who has the smartest model this quarter. Increasingly, they are competing on who owns the environment where the work happens.</p>\n<p>Once you see this, the benchmark conversation gets less interesting and the harness conversation gets much more interesting. The question stops being “which model is smartest” and becomes “which environment lets the intelligence I already have actually reach my problem.”</p>\n<h2 id=\"the-editor-became-the-least-important-part-of-the-ide\">The editor became the least important part of the IDE</h2>\n<p>If the labs are competing on the environment, the development tools show what that competition looks like up close.</p>\n<p>Cursor made the early move, shifting from tab-completion — the model as a faster keyboard — toward the agent window as the primary surface. Claude Code skipped the editor entirely and moved into the terminal. OpenAI folded Codex into ChatGPT on the Mac and made the conversation itself the development environment. VS Code grew an agent mode, Zed is rebuilding around one, and Antigravity arrived agent-first from day one.</p>\n<p>Different products, different bets, one convergence: the text editor — the thing an IDE fundamentally was — is becoming the least important pane in the window. What these tools compete on now is orchestration: how well they let you direct agents, run work in parallel, review what comes back, and keep the whole thing steerable.</p>\n<p>Look at where the feature velocity goes: plan modes and ask modes, permission levels that run from confirm-every-step to full autonomy for sandboxed work, background tasks, sub-agents, side chats, browser control with annotation and feedback, PR integration, automation hooks. None of these make typing faster. All of them make directing agents better. That is what the tools believe their job is now, and I think they’re right.</p>\n<p>An IDE used to integrate tools for a human who writes. These environments integrate tools for agents that build, and a human who decides. Every serious development tool is becoming a harness, whatever its marketing says.</p>\n<h2 id=\"the-bash-tool-changed-everything\">The Bash tool changed everything</h2>\n<p>If you want a single reason this generation of tools feels categorically different, it is not the context windows and it is not the benchmark scores. It is that somebody gave the model a terminal.</p>\n<p>The Bash tool looks humble next to purpose-built integrations, but it is the strongest tool an agent has ever been handed. Everything a developer can do from a shell, an agent can now attempt: grep and find at the small end; test runners, linters, and build systems in the middle; and at the far end, your project’s own scripts, your company’s internal CLIs, your deployment tooling. The whole accumulated tool culture of computing, inherited in one move.</p>\n<p>This is <a href=\"https://kobi.kadosh.me/blog/2026-03-15-unix-philosophy-agents-skills\">the Unix philosophy paying its oldest dividend</a>: agents didn’t need a bespoke integration for every tool humans have built over fifty years — they needed the one integration that reaches all of them.</p>\n<p>Which inverts an assumption we’ve carried for years. We used to ask what the model could do, and the answer was bounded by whatever tools someone had hand-wired into it. Now an agent’s reach is bounded by what its environment exposes: what’s installed, what’s permitted, what’s documented, what’s discoverable. Drop a frontier model into an empty container and it is clever but helpless. Drop the same model into your real project — real scripts, real access, real conventions — and it becomes startlingly capable.</p>\n<p>The ceiling moved from the model to the environment.</p>\n<h2 id=\"the-outer-harness-builds-the-system\">The outer harness builds the system</h2>\n<p>Here is the distinction I keep coming back to, the one that turns this from an observation into a practice. Every project now has two harnesses to design, and they answer different questions.</p>\n<p>The first is the outer harness: the environment through which the software gets built. It is your coding tool — Claude Code, Codex, whichever you’ve chosen — plus everything your project layers on top of it: the skills, the rules, the hooks, the memories, the context documents, the scripts it knows how to run, the commands that only make sense in this codebase. The base tool is generic. The outer harness is yours.</p>\n<p>Its job is everything we used to call development, in the widest sense of the word. Writing features, yes. But also researching options before anything gets written, planning, reviewing, testing, and debating designs — literally, setting agents against a question from different positions and reading the argument. Answering the questions your teammates would otherwise ask you in Slack. Producing the <a href=\"https://kobi.kadosh.me/blog/2026-06-18-disposable-software\">throwaway prototypes</a> that inform the real build and then get discarded without guilt.</p>\n<p>My favorite example of the outer harness at work is one I’ve <a href=\"https://kobi.kadosh.me/blog/2026-06-16-reusing-the-pull-request-research-artifact\">written about before</a>: opening pull requests you never intend to merge, because a PR is the outer harness’s native artifact — code, reasoning, preview, and conversation in one place. The outer harness doesn’t just produce merged code. It produces attempts.</p>\n<p>A strong outer harness has a specific feel: you stop performing the mechanical parts of engineering and start directing them. The scarce resources — judgment, taste, accountability — stay with you; <a href=\"https://kobi.kadosh.me/blog/2026-04-22-ai-slop-human-bandwidth-where-i-draw-the-line\">I’ve drawn that line before</a> and won’t redraw it here. What changes is that everything else, the running and testing and researching and drafting, flows through an environment that has been deliberately taught how your project works.</p>\n<p>And the teaching compounds. Every rule you write, every skill you add, every memory that persists makes next week’s work cheaper than this week’s. That is what it means for the harness to be part of the software: it accrues value the way good architecture does, invisibly, in the speed of everything that follows.</p>\n<h2 id=\"the-inner-harness-operates-the-system\">The inner harness operates the system</h2>\n<p>The second harness lives inside the product.</p>\n<p>More and more systems now embed agents as features: a support flow that resolves issues instead of routing them, a back-office process that reconciles data overnight, an event handler that investigates an anomaly instead of merely alerting on it, a report that researches and writes itself on schedule. When your product can usefully run an agent — on a user’s request, as a background operation, in reaction to an event — that agent and everything around it is your inner harness.</p>\n<p>It has the same anatomy as the outer one: a model, tools, permissions, memories, objectives. But the design constraints are harsher, because nobody is watching. The outer harness has you in the loop; the inner harness has your error budget. What tools does the embedded agent get? What is it empowered to do without a human? What does it remember between runs? How is its output checked before it touches anything real? Those are product decisions now, as consequential as any schema.</p>\n<p>And the boundary between the two harnesses moves. Work migrates from outer to inner. A workflow you run by hand in your coding tool today — verify the deployment, triage the flaky test, regenerate the derived data — becomes, once it has succeeded enough times to be boring, something the system runs on its own. The outer harness is where processes get discovered and hardened. The inner harness is where they retire to.</p>\n<p>The clearest example I can offer is running in the repository that publishes this essay. For months, keeping this blog healthy was outer-harness work I did in sessions: check that CI is green, hunt broken links, verify the RSS feed and the sitemap, look at what search engines and answer engines actually see, fix what’s trivial, file what isn’t. The procedure repeated until it stopped being interesting — which is exactly the signal to move it.</p>\n<p>Today that procedure is written down as two persistent agents — one that sweeps for regressions, one that proposes discoverability improvements — with a scheduled workflow meant to run them weekly. I’ll be honest about the state of it: this example hasn’t yet earned the nobody-is-watching property I described above. The unattended schedule isn’t dependable yet. The recent scheduled runs have been failing, and for now I still kick the agents off by hand more often than not. What already works is the part that matters for this argument — the agents exist, their boundaries are defined, and the work has moved out of my head and into the harness. Turning that into reliable, unattended automation is exactly the kind of hardening that comes last, and I haven’t finished it.</p>\n<p>The part worth studying isn’t the automation; it’s the decisions at the boundary. The QA agent may fix trivial regressions directly, but everything it ships still arrives as a pull request I review. The improvement agent may prototype, but only as draft PRs. And both operate under one hard rule: no agent touches the essays themselves unless I have explicitly opened a writing session — content is the part of this system where my hands stay on the wheel. Tools, permissions, memories, objectives, and a human gate exactly where the error budget is thinnest. Strictly speaking these agents still do development work — they open pull requests, not support tickets — and they’re designed to run outside my sessions. They are the blog’s back office, and the boundary keeps creeping: every time one of their filed issues teaches me a procedure, that procedure becomes a candidate for the next migration.</p>\n<p>That migration is what I mean when I say these systems can begin to operate themselves. Not autonomy as a slogan — a pipeline. You develop a process with an agent, you watch it hold up, you move it inside, and your attention moves to whatever isn’t automated yet. The back office of software that keeps getting predicted isn’t a new department. It is this migration, repeated until it stops being remarkable.</p>\n<p>Not every product needs an inner harness today. Some never will, and an outer harness alone already changes how a team moves. But I would bet that most serious systems end up with both — because once operating the system is a written-down process, and processes are things agents run, keeping a human on the crank becomes a choice you have to justify rather than a default you inherit.</p>\n<h2 id=\"the-stack-now-has-five-layers\">The stack now has five layers</h2>\n<p>Put the pieces together and you get a stack. It is worth drawing, because the interesting part is which layers get the attention.</p>\n<pre class=\"mermaid\">\nflowchart TD\n  M[Foundation model&lt;br/&gt;capabilities, no process]\n  B[Base harness&lt;br/&gt;Claude Code, Codex, Cursor]\n  O[Project harness&lt;br/&gt;skills, rules, hooks, memories]\n  I[Inner harness&lt;br/&gt;agents inside the running product]\n  A[Application&lt;br/&gt;what users touch]\n\n  M --&gt; B --&gt; O --&gt; I --&gt; A\n\n  classDef strong fill:#0A0A0A,stroke:#00FFFF,color:#E0E0E0\n  classDef weak fill:#0A0A0A,stroke:#666,color:#999\n  class B,O,I strong\n  class M,A weak\n</pre>\n<p>In plain text, the diagram is a five-layer stack, each layer specializing the one beneath it. At the base sits the foundation model. The base harness — Claude Code, Codex, Cursor — wraps the model. The project harness, your skills, rules, hooks, and memories, wraps the base harness. The inner harness sits above that, running inside the product itself. The application users touch sits on top. The three middle layers — base harness, project harness, inner harness — are the harness itself.</p>\n<p>The two gray layers are the ones everyone talks about. The model, at the bottom, is the layer you control least — you pick one, and a frontier lab does the rest. The application, at the top, is the output: it is what users touch, but it sits downstream of everything below it.</p>\n<p>The three layers in the middle are where your leverage actually lives. The base harness you choose. The project harness you accumulate on top of it — every skill, rule, and memory that specializes a generic tool into your team’s tool. Those two together are what I’ve been calling the outer harness. The inner harness you embed in the product itself. Each layer specializes the one below it, and each is a body of decisions that belongs to you, not to a lab.</p>\n<p>Teams argue endlessly about the gray layers and improvise the middle three. I think that ratio is exactly backwards.</p>\n<h2 id=\"we-used-to-reuse-code-now-we-reuse-process\">We used to reuse code, now we reuse process</h2>\n<p>Every previous era of software engineering had a unit of reuse. Functions. Objects. Libraries. Packages. Services. The entire open-source economy is built on the insight that solved problems should stay solved, and code was how we shipped solutions to each other.</p>\n<p>Harnesses introduce a new reusable unit, and it isn’t code. It’s process.</p>\n<p>A skill that runs a design review to your team’s actual standards. A security critique that knows what your threat model cares about. A migration workflow that plans, executes in slices, and verifies each slice before the next. A research loop that fans out, reads, and synthesizes with citations. A debugging procedure that instruments the code instead of guessing at it. None of these are libraries. You don’t call them; agents perform them.</p>\n<p>And they travel in a way code never quite did. Code reuse always came with friction — a library assumes a language, a framework, a runtime, a particular shape of problem. A well-written process assumes almost nothing. One of the skills in this repository audits documentation for drift against the code it describes; nothing in it knows about Astro, or blogs, or the web. Point it at a Rust CLI with a stale README and the procedure runs unchanged — only the nouns are different.</p>\n<p>The honest caveat is that process reuse has none of the verification infrastructure code reuse earned over decades. A schema can validate a skill’s shape — that its fields and frontmatter are well-formed — but it cannot prove the procedure is still sound: that the steps it encodes remain the right ones, that the standard it reviews against hasn’t quietly moved. No test fails when a review procedure goes stale — it simply starts reviewing for the wrong things. Which is one more reason the operational layer needs the discipline I argued for earlier: version control, review, deliberate design. The process artifacts are load-bearing precisely because nothing else checks them.</p>\n<p>Skills are already shared and versioned like packages. Plugins bundle whole domains of process. Engineering know-how — the part that used to live in senior engineers’ heads and in tribal review culture — is becoming an artifact you can write down, refine, and hand to the next project.</p>\n<p>We used to share libraries. Now we share behaviors.</p>\n<h2 id=\"a-capability-is-not-a-process\">A capability is not a process</h2>\n<p>There is an easy mistake to make when comparing these tools: reading the capability list and thinking you’ve read the harness. Image generation, browser control, code execution — those are capabilities. A harness is what turns a capability into a dependable outcome. The three examples that follow I’ve only watched from the outside, as a user, so treat the mechanics as observed behavior rather than inside knowledge.</p>\n<p>Claude Design is the cleanest example I know. Its raw capabilities are nothing exotic — the same models everyone can call. What makes its output striking is process: it builds a design system before it designs, prototypes inside that system, critiques its own work, checks the result across viewports, and iterates until it’s satisfied. You could try to replicate each step locally and still struggle to match it, because the hard part is the accumulated judgment about sequencing, evaluation, and when to stop.</p>\n<p>A custom pet-hatching skill I watched run on Codex makes the same point from a playful direction — and to be clear, this is someone’s hand-built workflow, not a shipped Codex feature. On the surface it’s a toy: hand it a reference, receive an animated companion. Underneath is one of the more rigorous pipelines I’ve seen — grounding the character’s identity from reference material the way an artist builds a mood board, iterating sprite sheets through sub-agents, checking its output against consistency rules, looping until the result clears the skill’s own bar. It runs long, and it earns the runtime. The impressive part was never the image model.</p>\n<p>Antigravity rounds out the pattern from the opposite side. It leans on Google’s image and video models rather than generating pixels itself, so visual work flows through it as process. The capability lives elsewhere; the harness contributes the knowing-how — which model to call, with what grounding, checked by what loop.</p>\n<p>A capability is a fact about what is possible, a process is a commitment about what will reliably happen. Users only ever experience the second one. Source capabilities wherever they’re strongest, but understand that the process is the product — and the process is exactly the part you can design.</p>\n<h2 id=\"dont-build-your-own-base-harness\">Don’t build your own base harness</h2>\n<p>At some point in this journey, every engineer gets the itch: I understand the loop now — model, tools, memory, repeat. I could build this.</p>\n<p>You should scratch that itch once, as education. Building a small harness teaches you more about how these systems actually behave than a year of merely using them.</p>\n<p>Then you should stop, because a production-grade base harness is one of those artifacts that looks simple and is actually thousands of accumulated decisions: permission models that are safe without being suffocating, context management under real limits, failure recovery, tool sandboxing, evaluation, parallelism, the interface through which a human stays meaningfully in control. The frontier tools have burned years of iteration on those problems, and their feature velocity is brutal.</p>\n<p>The leverage sits one layer up. Take the base harness as given and pour your effort into the project harness — the layer of skills, rules, memories, and workflows that no vendor can ship, because it encodes your system, your standards, your taste. That layer is small enough to own and valuable enough to matter.</p>\n<p>Two trends make this an even easier call. Base harnesses are turning into infrastructure — frameworks are beginning to ship agent harnesses as part of the platform, usable as an outer harness for building or embedded as an inner one, rather than as a separate product you visit. And they are getting cheaper: capable low-cost stacks are forming around strong open-weight models, including Qwen’s coder models, so the entry cost of running serious agent processes keeps falling. Exact prices move month to month; the direction — downward — is the durable part.</p>\n<p>The liabilities are real too. Committing to a harness is a platform dependency like any other: every skill written against one tool’s conventions is a small piece of lock-in. Harnesses drift — rules go stale, skills rot, memories accumulate contradictions — and the operational layer needs maintenance exactly like the code layer does. And there is a failure mode I have already watched from close range: teams so absorbed in tuning the harness that the product starves. The harness carries half the weight of the software. It is not the point of the software.</p>\n<h2 id=\"harness-decisions-are-architecture-decisions\">Harness decisions are architecture decisions</h2>\n<p>Architecture has always been the discipline of decisions that are expensive to reverse. Which services exist. Where the data lives. What owns what. We treat those with respect: documents, reviews, structured debate.</p>\n<p>The harness has joined that list. Consider the decisions a serious project now embeds, whether or not anyone writes them down.</p>\n<p>Which harness owns which class of task. Which model backs which agent, at which reasoning effort — because effort is a dial now, and every position of that dial is a cost-quality tradeoff you are making whether you notice or not. What each agent may do autonomously, and where a human gate stays. What persists in memory versus what gets re-derived each session. Which evaluation loop checks which output.</p>\n<p>That last one deserves more attention than it gets, because evaluation is where harness design succeeds or fails. A test suite is the right gate when correctness is checkable — it is cheap, objective, and merciless. A critic agent is the right gate when the failure is qualitative: a review that reads the diff the way a colleague would, catching what compiles but shouldn’t ship. A judge model is the right gate when you are choosing between attempts rather than validating one. And you are the right gate when the cost of being wrong exceeds the cost of your attention. Most harness failures I’ve seen reduce to a mismatch here — qualitative output gated by nothing, or trivial output gated by a human who becomes the bottleneck.</p>\n<p>Nothing says you settle on one harness, either. The same project can run different harnesses for different classes of work — one SDK powering the inner agents, another driving a specialized pipeline — with the outer harness orchestrating them the way it already orchestrates compilers and test runners. “Which harness owns this task” is a real architectural question now, and pretending there is one answer for a whole system is as naive as pretending there is one database.</p>\n<p>Get these decisions right and the whole development motion compounds: work parallelizes, quality gates hold, trust in the agents grows as the harness accumulates knowledge. Get them wrong and the failures are distinctly modern. The over-permissioned agent is the canonical one: give an agent broad write access and a vaguely worded objective, and it will pursue that objective with the confidence of a system that has never been embarrassed. Nothing about the model malfunctioned — the harness decision that let it act without a gate is the malfunction. The same anatomy repeats in the quieter failures: the agent hallucinating around a tool nobody provisioned, the unevaluated loop generating slop at scale. Again and again, the postmortem points at a harness decision, not a model.</p>\n<p>These are not implementation details, and they are not tool configuration. They are the same kind of decision as a service boundary: they determine what the system can safely become. They deserve the same treatment — written down, reviewed, revisited.</p>\n<p>Which brings me to the discipline hiding under the name. Harness-driven development is, at bottom, one commitment: nothing about how the software is built or operated is left outside a harness, and the harness itself is engineered, not improvised. Your outer harness should be able to run the project, test it, extend it, research for it, critique it, and debate it. If some part of your engineering practice can’t flow through it yet, that is your backlog.</p>\n<h2 id=\"the-harness-is-half-the-software\">The harness is half the software</h2>\n<p>Object-oriented programming organized code around objects. Domain-driven design organized it around the business. Test-driven development organized the act of building around feedback. Each one earned its name by moving something to the center that had been treated as incidental.</p>\n<p>Harness-driven development moves the environment to the center. Not the model — the environment that surrounds the model: the tools, the memories, the skills, the rules, the processes, the permissions, the evaluation loops. The claim is simple to state and slow to sink in: that environment is now a first-class part of your software, as deliberate and as maintained as the source code, because it determines how well every future change gets made — by you, and increasingly by the system itself.</p>\n<p>I don’t know if the name sticks. Names rarely survive contact with an industry, and I hold this one loosely. The practice is what matters, and the practice is already here — sitting in your repository, in files you may still be tempted to call clutter.</p>\n<p>I suspect that before long, “works well with agents” will stop being a differentiating skill, the same way “uses version control” quietly stopped being one. The harness will dissolve into the frameworks, the way good ideas do. But between now and then there is a real window, and in that window the teams that engineer their harnesses — deliberately, on both sides of the product boundary — should ship faster, gate with tighter evaluation, and trust their systems with more of the work than the teams that don’t.</p>\n<p>Every line of code should arrive through a harness — the outer one while we build it, the inner one while it runs.</p>\n<h2 id=\"further-reading\">Further reading</h2>\n<p>A few primary sources that shaped the framing here, chosen because they should stay useful as products churn:</p>\n<ul>\n<li>OpenAI, <a href=\"https://openai.com/index/unlocking-the-codex-harness/\">Unlocking the Codex harness: how we built the App Server</a> — one harness underneath every Codex surface.</li>\n<li>OpenAI, <a href=\"https://openai.com/index/harness-engineering/\">Harness engineering: leveraging Codex in an agent-first world</a> — building software through the harness rather than around it.</li>\n<li>Anthropic, <a href=\"https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills\">Equipping agents for the real world with Agent Skills</a> — skills as portable, composable process.</li>\n<li>Anthropic, <a href=\"https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents\">Effective harnesses for long-running agents</a> — designing the environment around a long-running agent.</li>\n</ul>\n<hr/>\n<p><em>Written and developed with Claude. The arguments are mine; the drafting was collaborative.</em></p>",
      "date_published": "2026-09-15T00:00:00.000Z",
      "tags": [
        "Developer Philosophy",
        "AI Tools",
        "Agents",
        "Software Engineering",
        "Claude Code",
        "Software Craftsmanship"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-09-10-buzzcut-cutting-through-the-ai-tool-hype/",
      "url": "https://kobi.kadosh.me/blog/2026-09-10-buzzcut-cutting-through-the-ai-tool-hype/",
      "title": "Buzzcut: Cutting Through the AI Tool Hype",
      "summary": "Every week there is a new must-have AI tool, and somewhere inside the buzz there is usually a grain of truth. Buzzcut is a small agent whose only job is to find that grain: map the shiny new thing against what you already run and return keep, skip, or watch, with one flip condition. Not anti-hype. Anti-hype-driven architecture.",
      "content_html": "<p>Every week there is a new must-have AI tool.</p>\n<p>A new model. A new agent harness. A new MCP server. A new skill. A new coding workflow. A demo that looks like it just made everything you were using yesterday obsolete.</p>\n<p>And sometimes, underneath all that buzz, there is something genuinely useful.</p>\n<p>The problem is figuring out which part.</p>\n<p>That is why I built <a href=\"https://x.ai/bot/5ckX0A4wk-1cpUJnzM1kW\">Buzzcut</a>.</p>\n<p>Buzzcut is a small experiment in something I increasingly think belongs in the arsenal of anyone seriously working with AI: an agent whose job is not to get excited about new tools, but to evaluate whether you actually need them.</p>\n<p>Its job is to cut the buzz.</p>\n<p>The one-line description on the bot says it more precisely than I could in a paragraph:</p>\n<blockquote>\n<p>Cuts the buzz on AI tools, skills, and harness trends. Maps each shiny demo onto what you already run, then returns keep / skip / watch with one flip condition — so adoption fatigue does not drive the shop.</p>\n</blockquote>\n<p>The rest of this post is about why that sentence exists, and what it is meant to protect.</p>\n<hr/>\n<h2 id=\"the-problem-isnt-that-ai-is-overhyped\">The problem isn’t that AI is overhyped</h2>\n<p>I don’t actually think dismissing AI hype is particularly useful.</p>\n<p>There is usually a grain of truth somewhere inside it.</p>\n<p>A new framework might genuinely have a better abstraction. A new model may be substantially better at a particular class of tasks. A new skill may solve something that previously required a lot of plumbing. A demo that is being passed around X may represent a real shift.</p>\n<p>But:</p>\n<p><strong>“This is impressive” and “I should adopt this” are two completely different conclusions.</strong></p>\n<p>That distinction is becoming increasingly important.</p>\n<p>As engineers, we’re accumulating an enormous arsenal: Claude Code, Codex, Grok, agents, subagents, MCPs, skills, CLIs, custom harnesses, memory systems, orchestration layers, and whatever gets released next Tuesday.</p>\n<p>The cost of adding something isn’t zero.</p>\n<p>Every addition introduces another abstraction to understand, another dependency to maintain, another behavior to debug, another set of permissions to reason about, and another decision your team eventually has to revisit.</p>\n<p>Eventually, your AI stack can suffer from exactly the same disease as any other software stack: complexity accumulated in the name of productivity.</p>\n<p>I wrote about this from a different angle in <a href=\"https://kobi.kadosh.me/blog/2026-04-29-your-docs-are-not-the-prompt\">Your Docs Are Not the Prompt</a> and in <a href=\"https://kobi.kadosh.me/blog/2026-04-22-ai-slop-human-bandwidth-where-i-draw-the-line\">AI Slop, Human Bandwidth</a>. The common thread is that the scarce resource is no longer capability. It is the attention required to integrate capability well.</p>\n<h2 id=\"the-hammer-was-never-the-problem\">The hammer was never the problem</h2>\n<p>There is a tendency in technology to discover a good tool and immediately try to make it the answer to everything.</p>\n<p>AI has amplified this.</p>\n<p>People discover Claude Code and try to build their entire workflow around Claude Code. Someone gets great results from Codex and suddenly every problem becomes a Codex problem. The same thing happens with Grok, Cursor, MCP, skills, agent frameworks, and whatever comes next.</p>\n<p>But this wasn’t true before AI either.</p>\n<p>A hammer is an extraordinarily effective tool. It has been one since roughly the invention of the hammer. Nobody has ever concluded from that fact that it should also be the screwdriver, the saw, and the level.</p>\n<p>Mastery isn’t figuring out how to use the hammer for every possible job. Mastery is knowing when to pick up the hammer, and when to leave it in the toolbox.</p>\n<p>I think AI tooling should be approached the same way.</p>\n<p>I use different agents and models for different things because they have different strengths, environments, interfaces, and failure modes. Claude Code is where I do most of my serious repository work. Codex is what I reach for when I want a second opinion with a different set of habits. Grok Bot is where I run small, sharply scoped bots that need to live close to a conversation rather than close to a codebase. None of them is wrong for the others’ jobs. They are just not optimal for them, and forcing the issue costs more than it saves.</p>\n<p>The objective isn’t tool loyalty.</p>\n<p>The objective is leverage.</p>\n<h2 id=\"what-buzzcut-does\">What Buzzcut does</h2>\n<p>Buzzcut sits one level above the tools themselves.</p>\n<p>You show it something you’re considering adopting:</p>\n<blockquote>\n<p>Here’s this new agent framework everyone is talking about. Should I use it?</p>\n</blockquote>\n<p>Or:</p>\n<blockquote>\n<p>Someone published this skill. Does it improve my current workflow?</p>\n</blockquote>\n<p>Or:</p>\n<blockquote>\n<p>This new harness looks interesting. Should we migrate anything to it?</p>\n</blockquote>\n<p>Buzzcut shouldn’t begin from the assumption that the new thing is useless.</p>\n<p>It also shouldn’t begin from the assumption that because everyone is talking about it, you need it.</p>\n<p>Instead, it asks a more useful question:</p>\n<p><strong>Relative to what I already have, what does this actually change?</strong></p>\n<p>That means understanding the claim, stripping away the marketing language, identifying the underlying capability, and mapping that capability against your existing stack.</p>\n<p>Then it needs to make a decision.</p>\n<h3 id=\"keep\">KEEP</h3>\n<p>This creates enough additional leverage to justify adopting it.</p>\n<h3 id=\"skip\">SKIP</h3>\n<p>Interesting, perhaps even technically good, but it doesn’t meaningfully improve what you already have.</p>\n<h3 id=\"watch\">WATCH</h3>\n<p>There is something real here, but adoption doesn’t make sense yet.</p>\n<p>And every verdict needs one additional thing:</p>\n<p><strong>the flip condition.</strong></p>\n<p>What would have to become true for this decision to change?</p>\n<p>Maybe:</p>\n<blockquote>\n<p>WATCH. If the project gets native GitHub Actions support, reconsider.</p>\n</blockquote>\n<p>Or:</p>\n<blockquote>\n<p>SKIP. Unless your workload grows beyond the concurrency your existing harness handles comfortably.</p>\n</blockquote>\n<p>Or:</p>\n<blockquote>\n<p>WATCH. If the API becomes available independently of the hosted product, reevaluate it.</p>\n</blockquote>\n<p>That last part matters because AI changes too quickly for permanent verdicts.</p>\n<p>A good evaluation should tell you not only <em>no</em>, but <em>no until X</em>.</p>\n<p>The flip condition is also what turns an opinion into a record. Six months from now, when the same tool shows up again in a different launch thread, you don’t have to re-argue the whole thing. You check whether X happened. If it did, the verdict is open again. If it didn’t, you move on without spending a second afternoon on it.</p>\n<h2 id=\"buzzcut-should-be-allowed-to-like-things\">Buzzcut should be allowed to like things</h2>\n<p>This is important.</p>\n<p>I don’t want Buzzcut to become a cynical bot whose personality is saying everything is hype.</p>\n<p>That would be just as useless as believing every launch thread.</p>\n<p>The goal is calibration.</p>\n<p>Sometimes the correct answer should be:</p>\n<blockquote>\n<p>KEEP. This is legitimately better for your use case. Adopt it.</p>\n</blockquote>\n<p>There are tools surrounded by enormous amounts of hype that are nevertheless genuinely useful.</p>\n<p>Grok Bot is a good example for me.</p>\n<p>There is plenty of buzz around agents and bots right now. Underneath that, I find the concept useful when I give a bot a constrained responsibility and become extremely explicit about what it should and shouldn’t do. Be direct about what you want from it. Be sincere about what it is not for. Most of the disappointment people report with these tools comes from skipping that step and expecting a general-purpose assistant to infer a very specific job.</p>\n<p>The interesting part isn’t that a bot can theoretically do everything.</p>\n<p>The interesting part is defining one thing it should do well.</p>\n<p>Buzzcut itself follows that philosophy.</p>\n<p>Its job isn’t to build my software.</p>\n<p>It isn’t my research agent, coding agent, or product manager.</p>\n<p>Its job is to stop those systems from accumulating things simply because I saw something shiny on the internet.</p>\n<h2 id=\"the-agent-that-protects-the-harness\">The agent that protects the harness</h2>\n<p>I increasingly think this becomes an important role in an agentic system.</p>\n<p>We spend a lot of time thinking about agents that add capabilities.</p>\n<p>Maybe we also need agents that challenge additions.</p>\n<p>If you maintain a serious AI harness, Buzzcut can effectively become its technology radar.</p>\n<p>It should understand:</p>\n<ul>\n<li>what models you use;</li>\n<li>which agents already exist;</li>\n<li>what skills they have;</li>\n<li>what MCPs and integrations are installed;</li>\n<li>where the current system actually struggles;</li>\n<li>what you’re paying for;</li>\n<li>what you’ve previously evaluated;</li>\n<li>and why you rejected or adopted something.</li>\n</ul>\n<p>Now a new tool doesn’t get evaluated in a vacuum.</p>\n<p>It gets evaluated against your reality.</p>\n<p>That is a very different question from:</p>\n<blockquote>\n<p>Is this tool good?</p>\n</blockquote>\n<p>The useful question is:</p>\n<blockquote>\n<p>Does this tool make my system better enough to justify adding it?</p>\n</blockquote>\n<p>This is the same instinct behind the <a href=\"https://kobi.kadosh.me/blog/2026-03-15-unix-philosophy-agents-skills\">Unix philosophy for agents and skills</a>: small pieces with sharp responsibilities compose better than one large piece that does everything adequately. A harness is a system, and systems have a carrying capacity. Someone, or something, has to guard the door.</p>\n<h2 id=\"a-sensible-first-interaction\">A sensible first interaction</h2>\n<p>You don’t need some sophisticated workflow to start using Buzzcut.</p>\n<p>Give it the thing you’re tempted by.</p>\n<p>Give it your scenario.</p>\n<p>And ask:</p>\n<blockquote>\n<p>Should I use this?</p>\n</blockquote>\n<p>Then argue with it.</p>\n<p>Tell it what you’re currently doing. Show it your harness. Give it the documentation. Give it the GitHub repository. Give it the launch thread that got your attention.</p>\n<p>Let it separate capability from positioning.</p>\n<p>If the tool solves a real problem, Buzzcut should identify it.</p>\n<p>If you can already accomplish 95% of the same thing with something in your stack, it should tell you that too.</p>\n<p>If it is promising but premature, WATCH it and define exactly what would change the verdict.</p>\n<p>That is the entire philosophy.</p>\n<p>A concrete example of the shape of a good session:</p>\n<blockquote>\n<p><strong>Me:</strong> Everyone is posting about this new orchestration layer that runs a planner, a coder, and a reviewer agent in a loop. I already run Claude Code with subagents and a review step in CI. Should I switch?</p>\n<p><strong>Buzzcut:</strong> SKIP. The capability being demoed is a three-role loop with shared memory. You already have the three roles and the loop; what you don’t have is the shared memory, and your CI review step reads the PR diff instead, which covers most of the same ground for your repo sizes. Flip condition: if you start running loops across multiple repositories in one session and the missing shared memory shows up as repeated context rebuilding, reevaluate.</p>\n</blockquote>\n<p>That answer is worth more to me than a thumbs up or a thumbs down. It tells me what the tool actually is, what I already have, where the real gap is, and when to look again.</p>\n<h2 id=\"ai-mastery-is-increasingly-about-what-you-dont-adopt\">AI mastery is increasingly about what you don’t adopt</h2>\n<p>We’re moving into a world where the number of available AI capabilities will vastly exceed anyone’s ability to seriously evaluate, learn, and integrate them all.</p>\n<p>The constraint isn’t access anymore.</p>\n<p>It’s attention.</p>\n<p>That changes the job.</p>\n<p>Being good at AI tooling isn’t going to mean knowing every tool.</p>\n<p>It will mean building a sufficiently good mental model of the landscape that you know which tools deserve a place in your arsenal.</p>\n<p>Adopt deliberately. Specialize aggressively. Replace when the evidence warrants it.</p>\n<p>And don’t confuse novelty with leverage.</p>\n<p>That’s what Buzzcut stands for.</p>\n<p>It isn’t anti-hype.</p>\n<p>It’s anti-hype-driven architecture.</p>\n<p><strong>Cut the buzz. Keep the signal.</strong></p>\n<hr/>\n<p>Bring the thing you are tempted by, bring your current setup, and argue with it. If it talks you out of something you didn’t need, it has already paid for itself. If it talks you into something, it should be able to tell you exactly why.</p>\n<a href=\"https://x.ai/bot/5ckX0A4wk-1cpUJnzM1kW\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"link-card group not-prose ascii-border my-6 flex flex-col overflow-hidden bg-terminal-gray/30 no-underline transition-all duration-300 hover:border-terminal-cyan hover:shadow-lg hover:shadow-terminal-cyan/10 sm:flex-row\"><div class=\"shrink-0 border-b border-terminal-line/50 bg-terminal-gray sm:w-64 sm:border-b-0 sm:border-r\"><img src=\"https://kobi.kadosh.me/media/link-cards/buzzcut-grok-bot.png\" alt=\"Meet my Bot: Buzzcut, on Grok Bot\" class=\"h-40 w-full object-cover sm:h-full\" loading=\"lazy\" decoding=\"async\"></div><div class=\"flex flex-1 flex-col justify-center gap-1.5 p-4\"><div class=\"flex items-center gap-2 text-xs text-terminal-comment\"><span class=\"text-terminal-green\">▸</span><span class=\"truncate\">x.ai/bot</span><span class=\"text-terminal-comment transition-colors group-hover:text-terminal-cyan\" aria-hidden=\"true\">↗</span></div><h3 class=\"text-terminal-cyan font-semibold leading-snug transition-colors group-hover:text-terminal-green\">Buzzcut by Kobi</h3><p class=\"text-sm leading-relaxed text-terminal-comment\">Cuts the buzz on AI tools, skills, and harness trends. Maps each shiny demo onto what you already run, then returns keep / skip / watch with one flip condition.</p></div></a>",
      "date_published": "2026-09-10T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Developer Philosophy",
        "Software Craftsmanship",
        "Grok",
        "Claude Code",
        "Codex",
        "Harness"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-06-18-disposable-software/",
      "url": "https://kobi.kadosh.me/blog/2026-06-18-disposable-software/",
      "title": "Disposable Software: When Code Becomes a Temporary Medium",
      "summary": "Software used to be expensive enough that we assumed every serious effort should last. AI weakens that assumption. Some software now only needs to exist long enough to solve one problem, communicate an idea, or feed the real build. Disposable software is its own category — not a lower one.",
      "content_html": "<p>For most of computing history, software was treated as something expensive to create — and therefore something worth preserving.</p>\n<p>That single assumption shaped almost everything around it: engineering organizations, product management, roadmaps, QA, release cycles, support models, procurement, SaaS pricing, and the corporate machinery that decides what gets built in the first place. Software was scarce, so we built processes around the scarcity.</p>\n<p>But that scarcity is weakening.</p>\n<p>With AI agents, coding tools, research tools, and design-to-code workflows, software is becoming easier to generate, easier to explore, and easier to throw away. Not all software needs to become a product. Not all software needs a roadmap. Not all software needs to survive.</p>\n<p>Some software only needs to exist long enough to solve a specific problem.</p>\n<p>That is disposable software.</p>\n<hr/>\n<h2 id=\"software-was-always-a-tool\">Software was always a tool</h2>\n<p>Software exists because humans need help doing things.</p>\n<p>Before computers, we extended ourselves with mechanical systems, animals, paper processes, and physical tools. Computers changed the medium, not the purpose. A computer is a machine that follows instructions. Software is the instruction layer that turns general-purpose hardware into a useful tool.</p>\n<p>For decades, writing those instructions was hard. Storage was limited. Compute was limited. Hardware was expensive. Programming required deep specialization. From punch cards and tapes, through assembly, compilers, operating systems, graphical interfaces, cloud platforms, and SaaS, every generation made software more powerful — but also more central to modern work.</p>\n<p>As software became more important, it also became more institutional.</p>\n<p>Because building software was expensive, companies built layers around it: product managers, project managers, designers, analysts, executives, roadmaps, tickets, requirements, ceremonies, reviews, QA cycles, and release processes. These layers were not accidental. They existed because engineering time was scarce, coordination was costly, and mistakes were expensive.</p>\n<p>When software is expensive, you need strong filters before deciding what deserves to be built.</p>\n<p>But what happens when the cost of building small software drops dramatically?</p>\n<h2 id=\"the-economics-are-changing\">The economics are changing</h2>\n<p>AI does not eliminate software engineering. It does not remove quality concerns. It does not magically produce reliable systems.</p>\n<p>But it changes the cost curve.</p>\n<p>Research that previously took weeks can now take hours. Prototypes that previously required days can now be generated in an afternoon. A person with a specific pain point can describe the problem, explore existing options, generate a tool, iterate on it, and use it — without waiting for a company to decide whether that problem is worth solving at scale.</p>\n<p>This matters, because many genuinely useful software ideas never became products.</p>\n<p>Not because they were bad ideas, but because the market was too small. The business case was not strong enough. The audience was too niche. The workflow was too specific. The value was real, but not large enough to justify a software company, a SaaS subscription, a procurement process, or a dedicated engineering team.</p>\n<p>AI agents change that math.</p>\n<p>A person may not need to buy software. They may not need to wait for someone to build it. They may not even need to care which framework or language is used. They can spend some tokens, describe their need, and generate something good enough for the task.</p>\n<p>That is a fundamentally different relationship with software.</p>\n<h2 id=\"what-is-disposable-software\">What is disposable software?</h2>\n<p>Disposable software is software built for a narrow purpose, used for a specific need, and then discarded, rebuilt, repurposed, or kept as context for something more permanent.</p>\n<p>It does not need to be elegant. It does not need to scale. It does not need to become a platform. It only needs to serve the job it was created for.</p>\n<p>A disposable tool might reconcile a spreadsheet, generate a report, visualize an idea, test a workflow, explore an API, compare options, or demonstrate a concept to a team. It may be used once. It may be used for a week. It may be rebuilt the next time the same problem appears, because rebuilding it is cheaper than maintaining it.</p>\n<p>This is uncomfortable for traditional software thinking, because we tend to equate software value with durability. We ask whether something is maintainable, extensible, production-ready, tested, documented, and scalable.</p>\n<p>Those questions still matter — but not for everything.</p>\n<p>For disposable software, the better question is simpler:</p>\n<p>Did it help someone move forward?</p>\n<h2 id=\"disposable-software-for-users\">Disposable software for users</h2>\n<p>The most obvious case is the end user.</p>\n<p>A person with a specific need can now build software around their own workflow, instead of bending their workflow to fit whatever software happens to exist. They can explain the pain point directly to an agent. They can research existing tools. They can generate a small app, script, dashboard, calculator, workflow, or interactive artifact.</p>\n<p>For many people, that is enough.</p>\n<p>The result may not be beautiful. It may not be engineered the way a professional team would engineer it. It may have rough edges. But if it solves the problem, the software has done its job.</p>\n<p>This is where “vibe coding” gets both interesting and dangerous.</p>\n<p>Yes, it introduces low-effort software. Yes, it creates quality risks. Yes, it can produce fragile systems that people do not fully understand. I have <a href=\"https://kobi.kadosh.me/blog/2026-01-07-vibe-code-messy-artifacts\">written before</a> about the mess this style of work leaves behind, and I stand by it.</p>\n<p>But that criticism misses part of the point.</p>\n<p>Not every generated tool is trying to become infrastructure. Some of it is closer to a spreadsheet, a temporary notebook, a one-off automation, or a whiteboard sketch that happens to execute. The mistake is not creating disposable software.</p>\n<p>The mistake is confusing disposable software with production software.</p>\n<h2 id=\"disposable-software-for-engineers\">Disposable software for engineers</h2>\n<p>Disposable software is not only useful for non-engineers. It may be even more powerful in the hands of experienced ones.</p>\n<p>Proofs of concept, spikes, technical explorations, and design branches used to take real time. Today, several of them can be explored in parallel. An engineer can generate prototypes to test assumptions, compare approaches, visualize tradeoffs, or create working context for a larger system.</p>\n<p>This changes collaboration.</p>\n<p>In the past, turning an idea into shared understanding often required meetings, diagrams, written proposals, whiteboarding sessions, and repeated explanation. Now an engineer can use agents to research, prototype, and produce something interactive enough for others to respond to.</p>\n<p>The prototype does not need to be the final implementation. It can be a conversation object.</p>\n<p>It can help a team see what is being proposed. It can expose missing requirements. It can reveal UX issues. It can pull feedback forward in time. It can hand another agent the context it needs to build the real system.</p>\n<p>In this sense, disposable software becomes part of the design process. It is not the destination. It is a means to an end.</p>\n<p>Imagine the older path: a series of meetings and processes to flesh an idea into a design — whiteboarding, diagrams, documents — all to earn buy-in from your team. Today you can research alongside your agents and produce something visual and tangible that earns that same buy-in far faster. It does not mean the prototype is something you have to keep. It means you have a thing to point at, and the choice to toss it, reuse it, or feed it into the real build with production constraints.</p>\n<h2 id=\"disposable-software-as-context\">Disposable software as context</h2>\n<p>One of the most important shifts is that disposable software can become context for non-disposable software.</p>\n<p>A prototype encodes decisions that are hard to explain in prose: layout, flow, tone, interaction model, information hierarchy, edge cases, and user intent. This is especially clear in design-oriented tools that produce interactive artifacts. A generated prototype can act like a living specification.</p>\n<p>It is similar to Figma — but with behavior.</p>\n<p>A design artifact can show not only what a screen looks like, but how it responds. It can carry style tokens, layout structure, content decisions, media references, and interaction patterns. An agent can inspect it and infer far more than it could from a paragraph of requirements. It is already answering questions before they are asked.</p>\n<p>This is why tools like Claude Artifacts, Claude Design, “show me” experiences, Codex web previews, and similar agent-generated interfaces matter. They are not only producing software. They are producing shared context.</p>\n<p>Think of one of these as a full export of a Figma file that happens to be a working website — the HTML and the styling are just the technical details behind an interactive prototype. An agent can go in and read the style tokens, the layout, the content, the media: all the things that are really design choices dressed up as front-end code. That does not make the artifact production-ready. It will have gaps. But it makes the target concrete in a way prose never does.</p>\n<p>Sometimes the artifact is good enough to use as-is. Sometimes it should be thrown away. Sometimes it becomes the clearest possible input for building the real thing.</p>\n<h2 id=\"the-new-boundary-disposable-vs-durable\">The new boundary: disposable vs. durable</h2>\n<p>The important skill is learning where the boundary is.</p>\n<p>Disposable software is appropriate when the cost of correctness is low, the scope is narrow, the lifetime is short, and the purpose is exploration, communication, or personal utility.</p>\n<p>Durable software is needed when reliability, security, maintainability, compliance, scale, shared ownership, or long-term evolution matter.</p>\n<p>The future is not that everything becomes disposable. The future is that software splits more clearly into categories:</p>\n<ul>\n<li>Some software should be treated like infrastructure.</li>\n<li>Some software should be treated like a product.</li>\n<li>Some software should be treated like a prototype.</li>\n<li>Some software should be treated like a temporary tool.</li>\n<li>And some software should be generated, used, and thrown away.</li>\n</ul>\n<p>The problem is that our organizations, engineering habits, and product processes were mostly built for the first two categories. We are still learning how to work with the last three.</p>\n<h2 id=\"the-practice-of-using-disposable-software-well\">The practice of using disposable software well</h2>\n<p>Disposable software still requires judgment.</p>\n<p>There is a balance between token cost, time saved, risk, and usefulness. There is a skill in knowing when to generate, when to refine, when to stop, and when to rebuild properly. There is also a skill in knowing when a prototype has served its purpose and should not be promoted into production.</p>\n<p>The danger is not that disposable software exists.</p>\n<p>The danger is laundering disposable software into durable systems without doing the engineering work.</p>\n<p>A prototype can inspire the real system. It can inform the real system. It can provide context for the real system. But production software still needs production constraints: architecture, security, tests, observability, deployment discipline, maintainability, ownership, and clear failure modes. Disposable software should accelerate that path, not bypass it.</p>\n<h2 id=\"the-pull-request-is-the-same-shift-one-scale-down\">The pull request is the same shift, one scale down</h2>\n<p>There is a smaller version of this story happening inside our daily workflow, and it is worth making the connection explicit.</p>\n<p>The pull request used to be finished code waiting to be merged. It is quietly becoming something more: a compressed software object that is generated, reviewed, tested, learned from, and — sometimes — discarded on purpose. I argue that case in detail in <a href=\"https://kobi.kadosh.me/blog/2026-06-16-reusing-the-pull-request-research-artifact\">Reusing the Pull Request: From Code Contribution to Research Artifact</a>.</p>\n<p>Disposable software is that same pattern applied to the whole artifact rather than a single change. A PR is a microcosm; software as a whole is the macrocosm. In both, the scarce resource stopped being the code. As <a href=\"https://kobi.kadosh.me/blog/2026-04-22-ai-slop-human-bandwidth-where-i-draw-the-line\">I have argued before</a>, generation got cheap and judgment did not. What you spend your attention on is no longer typing the thing — it is deciding which generated thing is worth keeping, and which one has already done its job and can go.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>Software is entering a new era.</p>\n<p>For decades, software was expensive enough that every serious effort carried an assumption of permanence. AI weakens that assumption. It lets software be generated for smaller needs, shorter lifetimes, narrower audiences, and more experimental purposes.</p>\n<p>That does not make engineering less important. It makes judgment more important.</p>\n<p>The question is no longer only: what software should we build?</p>\n<p>It is also: what software should we generate temporarily, so we can think, decide, communicate, or act faster?</p>\n<p>Disposable software is not a lower form of software. It is a different category of software.</p>\n<p>It is software as a sketch. Software as a conversation. Software as a prototype. Software as a single-use tool. Software as context.</p>\n<p>And in a world where agents can produce software on demand, learning when to throw software away may become just as important as learning how to build it.</p>\n<hr/>\n<p><em>Written and developed with Claude. The arguments are mine; the drafting was collaborative.</em></p>",
      "date_published": "2026-06-18T00:00:00.000Z",
      "tags": [
        "Developer Philosophy",
        "AI Tools",
        "Vibe Coding",
        "Software Engineering",
        "Agents",
        "Software Craftsmanship"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-06-16-reusing-the-pull-request-research-artifact/",
      "url": "https://kobi.kadosh.me/blog/2026-06-16-reusing-the-pull-request-research-artifact/",
      "title": "Reusing the Pull Request: From Code Contribution to Research Artifact",
      "summary": "The pull request used to be finished code waiting to be merged. With agents doing most of the building, it has quietly become something more: a rich research and communication artifact — without shedding the code review and compliance checks that made it trustworthy in the first place.",
      "content_html": "<p>A few weeks ago I opened a pull request I never intended to merge.</p>\n<p>I knew that before I opened it. The branch was an agent’s first attempt at a feature I was still arguing with myself about. The code compiled. The preview deployed. The diff touched the database, two services, a chunk of the frontend, and an architecture diagram I had been avoiding updating for a month. And then I closed it, on purpose, after I had learned what I needed from it.</p>\n<p>If you had described that workflow to me three years ago, I would have called it a waste. You wrote code you knew you would throw away. You opened a PR you knew you would not merge. You asked another human — me — to review something that was never a candidate for production.</p>\n<p>But none of those objections survive contact with how I actually work now. The PR was not a waste. It was one of the most efficient research instruments I had that week. And the reason it worked is that the pull request, the most boring and load-bearing object in our entire workflow, has quietly taken on a second job it was never designed for — without giving up its first.</p>\n<p>We did not build a new tool for this. We reused an old one.</p>\n<h2 id=\"what-a-pull-request-used-to-be\">What a pull request used to be</h2>\n<p>For most of its life, the pull request had a single, well-understood job. It was a unit of finished work. You took a definition of done, you implemented it, you packaged the implementation into a branch, and you submitted it to be checked and merged. The PR was a request in the most literal sense: <em>please pull my code into yours.</em> Its entire future was binary. It merged, or it didn’t.</p>\n<p>Everything we built around the PR assumed that frame. Review existed to gate the merge. CI existed to protect the merge. The conversation in the comments existed to unblock the merge. A PR that sat open for weeks without merging was a smell, a sign of indecision or abandonment. A PR that got closed unmerged was, softly, a small failure — somebody’s effort that didn’t make it.</p>\n<p>I have written before, more than once, that you should think before you implement. Design first. Understand the problem before you reach for the keyboard. I still believe most of that. But it was built on an assumption I want to make explicit, because the assumption is the part that broke: writing the code was the expensive step. Implementation was where your hours went, so implementing the wrong thing was where your hours got wasted. “Think first” was, underneath, a budgeting rule about human time.</p>\n<p>That budget no longer looks the way it did.</p>\n<h2 id=\"we-stopped-spending-our-time-on-the-keys\">We stopped spending our time on the keys</h2>\n<p>The most concrete change in how I work is also the most underrated: I barely type the implementation anymore.</p>\n<p>I am not saying this as a slogan. I mean it as a description of where the hours physically go. My day is mostly reviewing, researching, deciding, and steering. Reading a diff and judging it. Comparing two approaches an agent took to the same problem. Working out what we actually want to build before something gets blessed as the next step. The keyboard time that used to dominate — the part where you sit and translate a known design into syntax — has collapsed into a background process that runs while I think about something else.</p>\n<p>When the cost of producing an attempt drops to roughly the price of some tokens, the math under “think first” changes. The point of thinking first was never that thinking is holy. It was that building was costly, so you wanted to be sure before you paid. If building a serious first attempt now costs me a little compute and almost none of my own time, then building <em>is</em> part of how I think. The attempt is research. I can ask an agent to go implement a direction I’m unsure about, not because I expect to ship it, but because seeing it built tells me things no amount of upfront design would have.</p>\n<p>This is the same shift I keep circling back to in <a href=\"https://kobi.kadosh.me/blog/2026-04-22-ai-slop-human-bandwidth-where-i-draw-the-line\">AI Slop, Human Bandwidth, and Where I Draw the Line</a>: the scarce resource is not code anymore, and it is not tokens. It is responsible human attention. Generation got cheap. Comprehension and judgment did not. Once you internalize that, you stop spending your scarce attention on producing first drafts and start spending it on evaluating them. And the object you evaluate is the pull request.</p>\n<h2 id=\"a-pr-carries-things-an-issue-never-could\">A PR carries things an issue never could</h2>\n<p>Here is the question I keep getting, usually with a slightly impatient tone: if not every PR is headed for a merge anymore, why use a PR at all? Why not write an issue? Why not put it in the project tracker, the spec doc, the task board — the tools literally built for describing work?</p>\n<p>Because none of those tools can hold what a PR holds.</p>\n<p>A project tracker can hold text and images. It can hold a description, an argument, a mockup, a checklist. That is genuinely useful, and for a lot of work it is enough. But a tracker card does not hold <em>code</em>. It does not have a CI status. It does not carry the perspective you only get from the system actually trying to build the thing. It has no attempt inside it.</p>\n<p>A pull request has all of that, and the richness compounds:</p>\n<ul>\n<li>It holds <strong>real code</strong>, often compiled, sometimes running. Not a description of a solution — an instance of one.</li>\n<li>It carries a <strong>CI pipeline</strong> that can build artifacts off that code: a preview image, a rendered diff, a video, a generated report. The same evidence you’d manually paste into a tracker, except it’s produced by the change itself and stays attached to it.</li>\n<li>It can expose a <strong>live link</strong> — a sandbox deploy, an ephemeral preview environment — where the thing is compiled and clickable right now, curated to exactly this branch.</li>\n<li>And, if you’ve structured your repository well, it shows <strong>cross-cutting impact visually</strong>. One feature touches infrastructure, the docs, an architecture diagram, the frontend, the backend, the database — and the PR lays out, in one place, every surface it disturbs and how.</li>\n</ul>\n<p>That last point is the one people underrate. A tracker card describing a feature gives you the <em>idea</em> of its blast radius. A PR shows you the blast radius. You can see, concretely, that this “small” change also rewrites a migration and quietly alters an API contract three layers away. That visibility is not a side effect of merging. It is independently valuable, and you get it whether or not the branch ever lands.</p>\n<p>This is the reuse. We took a mechanism built to gate merges and discovered it is also the best container we have for showing what a change <em>means</em> across an entire system. Same object, new job.</p>\n<h2 id=\"a-pull-request-doesnt-have-to-be-mergeable\">A pull request doesn’t have to be mergeable</h2>\n<p>So let me say the uncomfortable part directly, because it’s the hinge of the whole argument.</p>\n<p>A pull request does not have to be mergeable. It does not have to reach production. It does not even have to be used.</p>\n<p>In Caro, the project where this way of working got most extreme for me, most PRs behave exactly like this. They are not finished contributions. They are feature requests rendered as attempts — an agent’s best one-shot pass at whatever the original session asked for. Someone described what they wanted, an agent took its single best swing at it, and the result is a branch plus a diff plus a pile of context: the research it did, the decisions it made, the next steps it would take. The PR is, at its core, sometimes just a prompt that grew a body. A prompt, a code artifact, and a description of where this could go.</p>\n<p>That sounds like a degradation of the PR. I think it’s the opposite. It’s a PR doing <em>more</em> than it used to, not less. An issue can tell you “here is what we should build.” A PR of this kind tells you “here is what it looks like when we actually try” — with the friction, the surprises, the parts that turned out harder than the card implied, all surfaced by the attempt instead of imagined in advance.</p>\n<p>And critically, the agent’s attempt is almost always incomplete. That is not a defect; it is the normal condition. Any non-trivial feature takes several iterations to land. Complex work gets split across days, across multiple passes, across more than one mind. Expecting an agent’s first PR to be a complete, mergeable feature is the same mistake as expecting a human’s first PR on a hard problem to be one. It rarely is. The difference is that now we can afford to make the incomplete attempt anyway, look at it honestly, and learn from it — because it cost us tokens, not a week.</p>\n<p>A PR like that doesn’t end at “merge or close.” It can feed the next PR. It can be handed to another agent that consumes the attempt and finishes it. Several of them can be folded together into a real understanding of what we should actually build.</p>\n<p>A PR that’s generated, learned from, and thrown away on purpose is a small instance of a larger pattern — software itself becoming disposable, built to exist just long enough to do its job. I follow that thread up a scale in <a href=\"https://kobi.kadosh.me/blog/2026-06-18-disposable-software\">Disposable Software</a>.</p>\n<h2 id=\"when-merge-stops-meaning-take-the-code\">When “merge” stops meaning “take the code”</h2>\n<p>This is where the vocabulary starts to strain, in a way I find genuinely interesting.</p>\n<p>If I’m staring down something hard, I don’t have to bet on one approach. I can send three agents at it from three different angles and let them run in parallel. Then I “merge” the results — except <em>merge</em> no longer means what it used to. I am not concatenating three diffs into one branch. I am reading three attempts, seeing where they agree and where they diverge, working out which direction actually understood the underlying problem, and using all three to decide how the real thing should be built.</p>\n<p>The output of that “merge” might be a fourth PR that resembles none of the three. The three inputs were never products. They were probes. Their value was in what they revealed about the problem, not in the lines they contained.</p>\n<p>I touched the edge of this in the AI Slop piece — running multiple coders against one task and using their outputs as competing inputs to a final synthesis. I want to push it one step further here. The convergence isn’t a code-merge at all. It’s a <em>decision</em>. The PRs are how the options make themselves legible enough to choose between. You couldn’t do this with three issue cards, because three issue cards are three guesses. Three PRs are three demonstrations, each with its own CI status, its own preview, its own honest record of where it got stuck.</p>\n<p>And the resource cost of running them is the thing that makes it reasonable. Three parallel attempts would have been an absurd luxury when each one cost a human a day. As tokens, with a reasonable subscription, it’s close to a rounding error — and the converged result is often <em>better</em> than what any single upfront design would have produced, because it was chosen against real evidence instead of imagined ahead of time.</p>\n<h2 id=\"the-review-and-the-checks-dont-go-away--they-get-more-important\">The review and the checks don’t go away — they get more important</h2>\n<p>I want to be careful here, because it would be easy to read everything above as “merge discipline is over.” It is not. None of this relaxes the bar for a PR that <em>is</em> meant to merge.</p>\n<p>When a PR is a real contribution, it goes through the full gate, exactly as it always has. Human code review. Required status checks. Lint, type-check, the test suite. Security and license scanning. Branch protection. Whatever the project’s definition of done and compliance rules demand before anything touches a protected branch. That machinery is not a relic of the old model that the new one outgrows. It is the part that earns a change the right to ship, and I would not merge agent-written code without it any more than I would merge my own.</p>\n<p>If anything, agent-authored code raises that bar rather than lowering it. When generation is cheap, the scarce and expensive thing is the reviewer’s judgment and accountability — the same point I made in the AI Slop piece. A PR an agent produced in four minutes can still demand a careful hour to review responsibly, and it should. Cheap to write is not the same as cheap to trust.</p>\n<p>And here is the part people miss: those same checks are what make the <em>research</em> mode work at all. CI is the honest signal that an attempt actually compiles and passes, not merely that it reads plausibly. Review comments are how a probe gets steered toward the real problem. The compliance gates — no forbidden license pulled in, no secret committed to the diff, no convention quietly broken — are exactly what let a PR be trusted enough for anyone to act on it. Strip those away and you do not get a richer artifact. You get a confident-looking guess wearing a green checkmark it never earned.</p>\n<p>So when I say a PR does not have to be mergeable, I do not mean review and checks are optional. I mean a PR now has two modes, and both run on the same rails. The merge-bound PR earns its merge by passing them. The research PR earns its credibility the same way.</p>\n<h2 id=\"one-artifact-many-roles\">One artifact, many roles</h2>\n<p>There’s a second kind of reuse hiding in here, and it’s about people rather than process.</p>\n<p>Because the PR now carries the whole vertical slice — infra to docs to UI to data — it stops being the exclusive territory of the engineer who would have written the code. A product manager can open one. A product engineer can. A designer can. They’re all working against the same constraints, in the same surfaces, with the same CI and the same preview environment standing behind their change. The PR becomes a shared workspace, and what it can do next depends only on the stage of the work: ship to a sandbox to get a feel for it, or, when it’s ready and owned, ship further.</p>\n<p>An issue body can’t be a shared workspace in that way. It can hold everyone’s words, but only one discipline’s artifact — the prose. The PR holds the running thing, which is exactly what lets a designer and a backend engineer and a PM all point at the <em>same</em> object and mean the same object. They’re not describing a feature to each other. They’re standing inside one.</p>\n<h2 id=\"the-gap-i-keep-running-into\">The gap I keep running into</h2>\n<p>Which brings me to the friction that made me want to write this down in the first place.</p>\n<p>There is a persistent, recurring mismatch between how a pull request gets <em>interpreted</em> and how, increasingly, it’s <em>meant</em>. Someone opens a PR as a research artifact — an attempt, a probe, a way of communicating “here’s what this feature could be, here’s the research, here’s a partial working prototype.” And it gets received as what PRs have always been: a finished contribution presented for merge. The reviewer reaches for the ritual — is this correct, is this complete, is this ready to land — when the author was asking a different question entirely: <em>is this the right direction, and what did building it teach us?</em> The reviewer is not wrong to ask those questions. For a merge-bound PR they are exactly the right ones. The mismatch is about mode, not about rigor.</p>\n<p>That gap is not a small communication hiccup. I think it’s the central source of confusion in agentic teams right now. The artifact evolved faster than our shared understanding of it. Half the people in the conversation are using the PR as a merge candidate and half are using it as a thinking tool, and they’re staring at the same screen wondering why the other side seems to be missing the point.</p>\n<p>Naming the gap is most of the fix. A PR can be a contribution to be merged. It can also be a feature request with working code attached, a product definition that happens to compile, a partial prototype, a decision document with a CI status, a first attempt meant to be consumed by the next one. These are different objects wearing the same interface. The interface stopped telling you which one you’re looking at. So we have to say it — in the description, in the conventions, in the team’s shared vocabulary: <em>this PR is here to be merged</em>, or <em>this PR is here to be learned from.</em></p>\n<h2 id=\"the-attempt-is-the-thinking\">The attempt is the thinking</h2>\n<p>I’ll close where I started, with the thing that still feels slightly heretical against my own old advice.</p>\n<p>“Think before you implement” was good guidance in a world where implementing was the expensive part. But the implementation is now, often, <em>part of the thinking</em> — because asking an agent to build a direction costs almost nothing I care about, and a built attempt answers questions that staring at a design doc never will. The pull request is where that attempt becomes visible, reviewable, and shareable: code, CI, preview, and cross-cutting impact, all in one object that the rest of the system already knows how to display.</p>\n<p>We didn’t need a new tool for the agentic era. We had one all along. We just used only half of what it could do. The merge — gated by review and checks, exactly as it should be — was always one essential job, and it still is. The other job, being the richest way we have to show in running detail what a change would mean, is the one we left mostly on the table, right up until building got cheap and deciding got expensive.</p>\n<p>The PR is still a contribution waiting to be merged: gated, reviewed, checked. It’s <em>also</em> a research artifact now. Holding both at once, and being honest about which one you’re handing someone, is the whole shift. Once you start treating it that way, a lot of the friction dissolves — and a lot of the leverage shows up.</p>\n<hr/>\n<p><em>Written and developed with Claude. The arguments are mine; the drafting was collaborative. The PR I opened and closed on purpose was, naturally, also mine.</em></p>",
      "date_published": "2026-06-16T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Code Review",
        "Developer Philosophy",
        "Software Engineering"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-04-29-your-docs-are-not-the-prompt/",
      "url": "https://kobi.kadosh.me/blog/2026-04-29-your-docs-are-not-the-prompt/",
      "title": "Your Docs Are Not the Prompt: How AI Coding Agents Really Use Project Context",
      "summary": "Not every piece of context an AI coding agent sees carries the same weight. The user prompt and the code dominate; documentation is a secondary orientation layer. Here's why imperfect docs are usually good enough, and where your effort actually pays off.",
      "content_html": "<p>AI coding agents have made an old engineering debate feel urgent again: how much documentation does a project really need?</p>\n<p>For years, teams have struggled with stale design docs, outdated architecture notes, half-maintained onboarding guides, and README files that drift away from reality. This was already a problem before AI. Now that coding agents can read those same files and use them as part of a coding session, the question feels sharper: should we invest much more time keeping docs perfectly current because AI agents depend on them?</p>\n<p>My answer is: not as much as people think.</p>\n<p>That does not mean documentation is useless. It means we should understand what kind of signal documentation provides to an AI coding agent, and how that signal compares to other parts of the session.</p>\n<p>The strongest user-controlled signal is still the user prompt. After that comes the actual project: the source files, the file structure, the conventions, the tests, the build system, the errors, and whatever the agent can discover through local tool use. Documentation matters, but it is not equal to code. It is usually a secondary orientation layer.</p>\n<p>This distinction changes how I think about engineering docs in the age of AI.</p>\n<h2 id=\"the-prompt-is-still-the-highest-leverage-input\">The prompt is still the highest-leverage input</h2>\n<p>When I give a coding agent a task, the user prompt is the most direct steering mechanism I control.</p>\n<p>A bad prompt can waste the entire session. A clear prompt can make the same agent look dramatically better. This is not surprising. The prompt defines the problem, the desired outcome, the constraints, the acceptable trade-offs, and the level of autonomy expected from the agent.</p>\n<p>For coding work, assuming the request is within the model’s intended behavior and does not run into safety or policy restrictions, the prompt has enormous influence. It tells the model what to optimize for.</p>\n<p>That does not mean the model ignores the project. It means the project context is interpreted through the task.</p>\n<p>A vague prompt plus perfect docs can still produce a confused session. A strong prompt plus imperfect docs often produces a useful result, especially when the agent can inspect files, run commands, ask clarifying questions, and reason from the actual codebase.</p>\n<p>This is why I do not believe documentation should be treated as the primary steering layer for coding agents.</p>\n<p>The prompt comes first.</p>\n<h2 id=\"code-is-primary-context-docs-are-proxy-context\">Code is primary context; docs are proxy context</h2>\n<p>When an agent works inside a repository, the most important information is usually not in a Markdown file. It is in the code.</p>\n<p>The project structure tells the agent what kind of system it is looking at. The source files reveal naming conventions, framework choices, architectural boundaries, testing patterns, and implementation style. TypeScript files, package boundaries, test files, routes, schemas, migrations, and configuration files often carry more reliable signal than a design doc written three months ago.</p>\n<p>Docs are still useful, but they are often proxy information. They describe intent, direction, decisions, and team preferences. They are not usually the ground truth of the system.</p>\n<p>This matters because modern coding agents do not only read a documentation file and blindly obey it. They explore. They list files. They read source. They inspect nearby implementations. They infer patterns. They compare the requested task against what already exists.</p>\n<p>The docs help orient the model, but the code anchors it.</p>\n<p>That is why I would rank the practical signals roughly like this:</p>\n<ol>\n<li>The user prompt.</li>\n<li>The current code and project structure.</li>\n<li>Local tool results from reading, searching, testing, and inspecting the project.</li>\n<li>Rules, memories, and agent-specific instruction files.</li>\n<li>Documentation.</li>\n<li>External tools, MCPs, links, articles, and remote context.</li>\n</ol>\n<pre class=\"mermaid\">\nflowchart TD\n  P[1. User prompt&lt;br/&gt;defines the task]\n  C[2. Code &amp;amp; project structure&lt;br/&gt;ground truth]\n  T[3. Local tool results&lt;br/&gt;read, search, test]\n  R[4. Rules &amp;amp; memories&lt;br/&gt;steering constraints]\n  D[5. Documentation&lt;br/&gt;orientation layer]\n  X[6. External: MCPs, links, articles&lt;br/&gt;weakest signal]\n\n  P --&gt; C --&gt; T --&gt; R --&gt; D --&gt; X\n\n  classDef strong fill:#0A0A0A,stroke:#00FFFF,color:#E0E0E0\n  classDef weak fill:#0A0A0A,stroke:#666,color:#999\n  class P,C,T strong\n  class D,X weak\n</pre>\n<p>The exact order can vary by agent and workflow, but the main point remains: not every piece of context has the same weight.</p>\n<p>A note on what this ordering means. It is roughly the <em>order of practical influence per session</em>, not absolute authority per token. Rules can have higher per-token authority than docs, because they are short, direct, and explicitly framed as constraints. But the agent reaches them only after the prompt and the surrounding code are already shaping the session. So a great rule cannot rescue a vague prompt, and a good prompt rarely needs much help from docs.</p>\n<p>About MCPs and external context: tools like Model Context Protocol servers, fetched URLs, knowledge-base lookups, and pasted articles can be useful, but they tend to sit furthest from the model’s training distribution for <em>this</em> codebase. The model has not seen your private MCP responses or your internal wiki page during training. It treats them as foreign material and weights them accordingly. They are valuable when the local project genuinely lacks the information, but they are not a substitute for code the agent can read directly.</p>\n<h2 id=\"imperfect-docs-are-not-the-same-as-bad-docs\">Imperfect docs are not the same as bad docs</h2>\n<p>A lot of the documentation debate collapses two very different cases.</p>\n<p>There is a big difference between imperfect documentation and actively misleading documentation.</p>\n<p>Imperfect docs are common. They may be slightly stale, repetitive, incomplete, vague in places, or missing the newest implementation detail. Most real projects live here. Even strong teams rarely maintain perfect documentation for long.</p>\n<p>Bad docs are different. Bad docs point the agent in the wrong direction. They describe an architecture that no longer exists. They name systems incorrectly. They contradict the code. They encode abandoned decisions as if they were still active.</p>\n<p>My argument is not that bad documentation is safe. Very bad docs can absolutely damage an agent session.</p>\n<p>My argument is narrower: mostly correct but imperfect docs are often good enough.</p>\n<p>They give the agent shape. They communicate direction. They reduce the amount of repeated explanation required from the user. They help the model locate the project inside a familiar pattern space: this is a frontend app, this is a service-oriented backend, this is a monorepo, this is an API layer, this is a design system, this is a migration path.</p>\n<p>That orientation has value, even when the docs are not perfect.</p>\n<h2 id=\"the-model-has-seen-messy-projects-before\">The model has seen messy projects before</h2>\n<p>One reason imperfect docs still work is that the model’s training data almost certainly contains a lot of imperfect software projects.</p>\n<p>That is important. Real repositories are messy. They contain old READMEs, abandoned plans, inconsistent comments, half-finished migrations, TODOs, deprecated files, and documentation that lags behind implementation. The model has learned from this kind of material. It has seen the contradiction between what docs claim and what code does.</p>\n<p>This does not make the model immune to bad context. It does mean that imperfection itself is not unusual input.</p>\n<p>When an agent encounters a stale doc, it does not necessarily collapse. A capable agent can cross-check. It can inspect implementation. It can notice that a documented file no longer exists. It can ask whether a described pattern is still preferred. It can reconcile docs with tests and source code.</p>\n<p>That is why I am more worried about unclear prompts than slightly stale docs.</p>\n<p>A bad prompt gives the model the wrong job.</p>\n<p>An imperfect doc usually just creates friction.</p>\n<h2 id=\"clarifying-questions-are-not-failure\">Clarifying questions are not failure</h2>\n<p>People often treat clarifying questions from agents as a negative outcome. I do not see it that way.</p>\n<p>If the agent asks a good question, that is often a sign that the session is working. It means the agent found ambiguity and did not silently choose a path that might be wrong.</p>\n<p>Could better docs reduce the number of questions? Yes.</p>\n<p>Is that always worth the documentation effort? Not necessarily.</p>\n<p>Answering questions keeps the human engaged in the session. That matters. The worst workflow is often not the one where the agent asks questions. It is the one where the human gives a vague task, disengages, and returns to a large diff built on misunderstood assumptions.</p>\n<p>Clarifying questions create checkpoints. They surface ambiguity. They let the human correct direction before too much code is written.</p>\n<p>For fully autonomous workflows, this is a different problem. If the goal is to run agents without human involvement, then ambiguity must be encoded more aggressively into rules, tests, specs, and evaluation systems — the same control surfaces I covered in <a href=\"https://kobi.kadosh.me/blog/feedback-loops-part-3-agents\">The Feedback Loop Series Part 3: AI Agents</a>. But for interactive development, questions are not necessarily waste. They can be part of the control loop.</p>\n<h2 id=\"better-docs-have-diminishing-returns\">Better docs have diminishing returns</h2>\n<p>Of course, better docs are better.</p>\n<p>A clear architecture document helps humans. A good onboarding guide helps new engineers. A concise decision record helps the team remember why something exists. A current integration guide reduces repeated explanations. None of this disappears because AI agents exist.</p>\n<p>But the question is not whether better docs are good. The question is whether making docs perfect is the best use of engineering time.</p>\n<p>Usually, it is not.</p>\n<p>There is a point where the effort required to keep docs perfectly aligned exceeds the value gained in agent performance. The agent still needs the actual code. The agent still needs the prompt. The agent still needs to inspect the repository. The agent still needs tests or feedback.</p>\n<p>For day-to-day coding, I suspect the gain from perfect docs is often smaller than people expect.</p>\n<p>The gain from good prompts is larger.</p>\n<p>The gain from clear rules is larger.</p>\n<p>The gain from executable tests is larger.</p>\n<p>The gain from consistent project structure is larger.</p>\n<p>This does not mean docs should be neglected. It means docs should be kept useful, not worshipped.</p>\n<h2 id=\"what-should-go-into-project-docs\">What should go into project docs?</h2>\n<p>The best project docs for AI agents are not necessarily long. In fact, verbose AI-generated documentation can become its own problem. If every file has a long explanation and every decision is repeated in five places, the agent gets more text but not necessarily more signal.</p>\n<p>The goal is not to maximize documentation volume. The goal is to provide stable orientation.</p>\n<p>Good project docs should answer questions like:</p>\n<ul>\n<li>What kind of project is this?</li>\n<li>What are the main architectural boundaries?</li>\n<li>What conventions should be followed?</li>\n<li>What should not be changed casually?</li>\n<li>Where are the important entry points?</li>\n<li>How should the agent validate its work?</li>\n<li>What common mistakes should be avoided?</li>\n<li>Which docs are authoritative, and which are historical?</li>\n</ul>\n<p>That is enough to steer many sessions.</p>\n<p>Docs do not need to explain every implementation detail. The code can do that. Docs should explain the shape of the system, the team’s preferences, and the traps that are not obvious from the code alone.</p>\n<h2 id=\"rules-are-better-than-docs-for-repeated-agent-mistakes\">Rules are better than docs for repeated agent mistakes</h2>\n<p>When an agent repeatedly makes the same mistake, I would not bury the correction inside a long documentation file.</p>\n<p>I would turn it into a rule.</p>\n<p>Rules are better for behavioral constraints. They should be short, direct, and specific. For example:</p>\n<ul>\n<li>Do not introduce a new state management library.</li>\n<li>Prefer existing API client wrappers over raw fetch calls.</li>\n<li>Always add or update tests for changed business logic.</li>\n<li>Do not modify generated files directly.</li>\n<li>Use the existing design system components before creating new UI primitives.</li>\n</ul>\n<p>These are different from docs. They are not explanations of the project. They are steering instructions for the agent.</p>\n<p>This connects to a broader argument I made in <a href=\"https://kobi.kadosh.me/blog/2026-03-15-unix-philosophy-agents-skills\">Skills, Pipes, and the Unix Philosophy</a>: the agent’s value comes from composing small, well-scoped pieces — skills, rules, tools — rather than from one monolithic instruction file trying to do everything. Rules are the small Unix-style pipes of project context. Docs are the prose around them.</p>\n<p>This distinction matters. Documentation provides orientation. Rules provide constraints. Tests provide verification. Prompts provide task direction.</p>\n<p>Trying to make one layer do all of these jobs leads to bloated docs and inconsistent agent behavior.</p>\n<h2 id=\"update-docs-during-the-session-not-as-a-separate-ritual\">Update docs during the session, not as a separate ritual</h2>\n<p>One practical workflow I like is to let agent sessions reveal documentation gaps.</p>\n<p>If the agent asks a clarifying question, answer it. If the answer represents a durable team preference, ask the agent to update the relevant doc or rule file in the same session.</p>\n<p>This keeps documentation work connected to real ambiguity.</p>\n<p>Instead of scheduling a large documentation cleanup that may or may not improve future agent behavior, update the docs when the missing information actually affects a task.</p>\n<p>This is a better feedback loop:</p>\n<ol>\n<li>Give the agent a clear task.</li>\n<li>Let it inspect the project.</li>\n<li>Notice where it gets confused.</li>\n<li>Answer the clarification.</li>\n<li>Ask why it got confused.</li>\n<li>Update a doc, rule, or test only if the issue is likely to repeat.</li>\n</ol>\n<p>This turns documentation into a byproduct of development rather than a parallel maintenance burden.</p>\n<h2 id=\"why-not-remove-docs-entirely\">Why not remove docs entirely?</h2>\n<p>A fair question follows: if docs are lower-signal than code, why not remove them?</p>\n<p>Because lower-signal does not mean no-signal.</p>\n<p>Docs still provide structure. They help the agent orient itself faster. They reduce repeated explanation. They help humans understand the project. They create named places for durable knowledge. They make the repository feel intentional.</p>\n<p>Even a lightweight docs folder can be useful because it tells both humans and agents that certain concepts exist: architecture, conventions, workflows, deployment, testing, data model, product decisions.</p>\n<p>Empty docs are not enough, but the structure itself is not worthless. A well-named file can serve as an invitation to fill in durable knowledge when it appears.</p>\n<p>The mistake is assuming that every doc must be polished before it has value.</p>\n<h2 id=\"the-practical-position\">The practical position</h2>\n<p>My position is pragmatic:</p>\n<p>Keep docs good enough to orient humans and agents.</p>\n<p>Do not spend disproportionate time making them perfect.</p>\n<p>Invest more in prompts, rules, tests, and consistent project structure.</p>\n<p>Use agent confusion as a signal for what needs to be documented.</p>\n<p>Treat clarifying questions as part of the workflow, not automatically as failure.</p>\n<p>Do not allow docs to become actively misleading.</p>\n<p>This is especially important in fast-moving projects. AI can generate code quickly. It can also generate documentation quickly. But humans still need to verify whether that documentation is true, useful, and worth maintaining. If AI accelerates both code and docs, the risk is not only stale docs. The risk is a flood of plausible but low-signal text.</p>\n<p>The answer is not more documentation by default.</p>\n<p>The answer is better placement of information.</p>\n<p>Prompts should define the task.</p>\n<p>Code should remain the source of truth.</p>\n<p>Tests should verify behavior.</p>\n<p>Rules should encode repeated constraints.</p>\n<p>Docs should orient the reader.</p>\n<p>When each layer does its job, the agent does not need perfect documentation to be effective.</p>\n<p>It needs enough structure to understand the project, enough code to infer the real patterns, enough rules to avoid repeated mistakes, and enough human engagement to resolve ambiguity at the right moments.</p>\n<p>That is a healthier standard than trying to keep every doc perfectly aligned with a codebase that is changing faster than ever.</p>\n<h2 id=\"a-simple-experiment\">A simple experiment</h2>\n<p>This is easy to test.</p>\n<p>Take the same coding task and run it under different conditions:</p>\n<ol>\n<li>Bad prompt, good docs.</li>\n<li>Good prompt, imperfect docs.</li>\n<li>Good prompt, bad docs.</li>\n<li>Good prompt, improved docs.</li>\n<li>Good prompt, no docs, but strong project structure.</li>\n<li>Good prompt, strong rules, and imperfect docs.</li>\n</ol>\n<p>Measure not only whether the task succeeds, but how the session behaves. How many clarifying questions does the agent ask? How much code does it inspect? Does it follow existing patterns? Does it make wrong assumptions? Does it recover when the docs conflict with the code?</p>\n<p>My expectation is that the prompt and the codebase will dominate more often than the docs. Good docs will reduce friction. Bad docs will create risk. Imperfect docs will usually be tolerable.</p>\n<p>That is the distinction I care about.</p>\n<p>The future of AI-assisted development is not documentation maximalism. It is context design.</p>\n<p>And in context design, not all context is equal.</p>",
      "date_published": "2026-04-29T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Documentation",
        "Software Engineering",
        "Developer Philosophy",
        "Software Craftsmanship"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-04-22-ai-slop-human-bandwidth-where-i-draw-the-line/",
      "url": "https://kobi.kadosh.me/blog/2026-04-22-ai-slop-human-bandwidth-where-i-draw-the-line/",
      "title": "AI Slop, Human Bandwidth, and Where I Draw the Line",
      "summary": "AI can produce code, comments, PR descriptions, and review replies faster than humans can read and own them. The real bottleneck is no longer generation — it's comprehension. Here's how I think about effort symmetry, accountability, and why I don't buy a universal taxonomy.",
      "content_html": "<p>A few weeks ago I sat down to review a pull request that, on paper, looked fine.</p>\n<p>The description was crisp. The commit messages were tidy. There were comments in the code explaining the tricky parts. When I asked a question in the review, I got a confident, well-structured answer within minutes.</p>\n<p>Halfway through reading it, I realized something uncomfortable: almost none of it had been written by the person whose name was on the PR. The description read like a model. The inline comments read like a model. The review reply read like a model. The code itself was somewhere between “plausible” and “actually wrong” in places that only careful reading would surface.</p>\n<p>I was not annoyed because AI had been used. I was annoyed because the author had shipped me several thousand words of confident prose and zero thinking. My job as a reviewer had quietly been upgraded from <em>check the work</em> to <em>audit the plausibility of an entire synthetic artifact</em>.</p>\n<p>That is the thing that worries me most about AI in software. Not that models can generate code. That they can generate <em>everything around the code</em> even faster.</p>\n<hr/>\n<h2 id=\"ai-slop-is-not-just-bad-code\">AI Slop Is Not Just Bad Code</h2>\n<p>When people say “AI slop,” they usually mean low-quality generated code. I think that definition is too narrow.</p>\n<p>AI slop is any generated artifact that is cheaper to produce than it is to evaluate.</p>\n<p>Sometimes that artifact is code. Sometimes it is a 700-line issue body. Sometimes it is a PR description that says everything and nothing. Sometimes it is a wall of comments added to create the feeling of rigor. Sometimes it is a review response that sounds confident but is really a proxy answer copied from another model run.</p>\n<p>The common pattern is not “AI was used.” The common pattern is that the human cost has been externalized. One person gets leverage. Everyone else inherits the reading burden.</p>\n<p>That is where the social contract breaks.</p>\n<hr/>\n<h2 id=\"the-problem-is-not-ai-the-problem-is-offloading-cognition\">The Problem Is Not AI. The Problem Is Offloading Cognition</h2>\n<p>I am not anti-AI. That framing is too primitive.</p>\n<p>I have been shipping with AI agents in my daily workflow for a while now — drafting code, exploring design space, producing scaffolding, generating test cases, arguing with my own plans. A lot of this post itself started as notes I worked through with Claude. The value of these tools is obvious to me. I am not in the abstinence camp.</p>\n<p>Sometimes the thing you are writing is not even primarily for a human. It is a token blob meant to prime the next tool call. An issue can be a prompt. A system prompt can be architecture. A structured spec can be less about documentation than about steering an agentic workflow.</p>\n<p>That is real work. It is often valuable work.</p>\n<p>You can push it further. Run multiple coder agents against the same task. Compare approaches. Use their outputs as competing prompts for a final synthesis. That can be a productive workflow.</p>\n<p>None of this removes the need for human judgment. It just changes where judgment is most scarce and most expensive.</p>\n<p>The real bottleneck is no longer generation. It is comprehension.</p>\n<hr/>\n<h2 id=\"accountability-is-not-the-same-thing-as-understanding\">Accountability Is Not the Same Thing as Understanding</h2>\n<p>This is the part I think many teams still talk past each other on.</p>\n<p>“Accountability” and “understanding” are related, but they are not the same thing.</p>\n<p><strong>Accountability</strong> means I can own the result. I can defend the branch I took. I can explain why a dependency was added, what tradeoff was made, what the blast radius is, how this affects DX, reliability, and product quality, and whether the tests are actually good enough. I can say, without hiding behind the tool, <em>this change is mine</em>.</p>\n<p><strong>Understanding</strong> is narrower and deeper. Do I understand the design pattern behind this module? Do I understand why this abstraction exists? Do I understand how this generic type will behave when the next integration point arrives? Do I understand the failure modes of this virtualization strategy? Did I inspect alternatives, or did I just accept the first plausible answer?</p>\n<p>Some work requires both. Some work can survive with stronger accountability than line-by-line understanding. Pretending otherwise is purity theater.</p>\n<p>A rule that says “the human must fully understand all code” sounds clean. In practice, it is subjective, hard to operationalize, and easy to weaponize. <a href=\"https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md\">Ghostty</a> takes a strict version of that stance, requiring disclosure and saying contributors should not submit changes they cannot explain without AI help. That may be exactly right for a terminal emulator maintained by a small group with a specific culture. I do not think it scales cleanly into a general standard.</p>\n<p>In real teams, full understanding is not binary. It is uneven, contextual, and often discovered under pressure. A policy that pretends otherwise will either be ignored or weaponized, usually both.</p>\n<hr/>\n<h2 id=\"why-i-dont-buy-the-visidata-10-levels-as-a-standard\">Why I Don’t Buy the VisiData 10 Levels as a Standard</h2>\n<p><a href=\"https://www.visidata.org/blog/2026/ai/\">Saul Pwanson’s VisiData post</a> is thoughtful, and the taxonomy is useful as a lens. It distinguishes between cases like “bots coded, human understands completely,” “bots coded, human understands mostly,” “human specced, bots coded,” and “bots planned, human approved.” It is a serious attempt to name meaningful differences in human involvement.</p>\n<p>I do not agree with treating that 10-level scheme as a de facto standard.</p>\n<p>It is too specific to one project’s culture, one maintainer’s concerns, and one style of contribution management. Even VisiData presents it as <em>its</em> framework, not as an ecosystem-wide norm.</p>\n<p>My issue is not that the distinctions are intellectually wrong. My issue is that they are hard to operationalize consistently in policy.</p>\n<p>“Reviewed” is observable. “Generated” is observable enough. “Human approved” is at least claimable. But “understands mostly” versus “understands completely” is a subjective mental-state audit. That is not a stable enforcement boundary. It is a vibe.</p>\n<p>For a small open source project, a highly opinionated taxonomy may be acceptable. For inner-source or commercial product development, it can become friction masquerading as rigor. Teams with limited resources do not need a more ornate morality scale. They need clarity around ownership, risk, and review burden.</p>\n<p>In my own work, most AI-assisted PRs would land somewhere VisiData would call level 7 or level 8. In a simplified internal system I would describe them more bluntly as “AI-generated, human-reviewed” or sometimes “AI-generated, lightly reviewed.” That is exactly why I do not want a policy that performs precision without delivering it. My bar is not whether I can philosophically classify my state of understanding. My bar is whether I can responsibly own the outcome.</p>\n<hr/>\n<h2 id=\"the-ecosystem-is-not-converging-on-one-answer\">The Ecosystem Is Not Converging on One Answer</h2>\n<p>Another reason I resist universalizing any one project’s framework is simple: open source itself has not converged.</p>\n<p>The Linux Foundation is permissive in spirit — AI-generated contributions are allowed, with the burden on contributors to ensure license compatibility and compliance. The Linux kernel now has official guidance for AI coding assistants, but routes them through the ordinary development process rather than through an abstract theory of “AI contribution levels.”</p>\n<p><a href=\"https://github.com/rust-lang/leadership-council/issues/273\">Rust’s recent discussion</a> is reacting to a different failure mode: low-effort, insufficiently self-reviewed, extractive contributions that consume maintainer attention and feel, in their words, like a denial-of-service attack on review capacity. Firefox has focused entirely on accountability and declined to require disclosure. LLVM requires disclosure <em>and</em> calls out extractive contributions explicitly. QEMU sits much further to the restrictive side and says it will decline contributions believed to include or derive from AI-generated content at all.</p>\n<p>These projects are not disagreeing about whether AI is good or bad. They are making different tradeoffs about which failure mode they most want to prevent: license risk, maintainer burnout, learning-opportunity capture, code quality, or contributor accountability.</p>\n<p>So no, I do not think one project’s 10-level rubric should be treated as the standard. The principles may travel. The taxonomy should not.</p>\n<hr/>\n<h2 id=\"my-practical-line\">My Practical Line</h2>\n<p>My line is pragmatic.</p>\n<p>Use AI aggressively where it compresses toil, expands exploration, or helps structure a problem.</p>\n<p>Do not use it as a shield against ownership.</p>\n<p>Do not use generated verbosity as a substitute for thinking.</p>\n<p>Do not dump unbounded review cost on another human and call it productivity.</p>\n<p>Do not answer reviewer questions by laundering model output through your keyboard.</p>\n<p>And do not confuse “the tests pass” with “the design is sound” in areas where the real risk is architectural, operational, or product-facing.</p>\n<p>If you want a simple rule, mine is this:</p>\n<p><strong>The more AI reduced the cost of producing the change, the more the author must compensate with clarity, verification, and accountability.</strong></p>\n<p>That is the trade.</p>\n<p>Not abstinence. Not blanket permission. Not taxonomy worship.</p>\n<p>A fair exchange.</p>\n<hr/>\n<h2 id=\"what-should-policies-optimize-for\">What Should Policies Optimize For?</h2>\n<p>I think good policies should optimize for three things:</p>\n<ul>\n<li><strong>Transparency</strong> about how the work was produced.</li>\n<li><strong>Human accountability</strong> for the result.</li>\n<li><strong>Protection of reviewer bandwidth.</strong></li>\n</ul>\n<p>That last one matters more than many teams want to admit. Human attention is the scarce resource here. Not tokens. Not compute. Not generated prose.</p>\n<p>When a policy makes contribution harder in ways that protect scarce human review capacity, that can be reasonable. When it adds ceremony without improving accountability or reducing review cost, it becomes process cosplay.</p>\n<p>This is why I prefer simpler classifications over elaborate ladders. I would rather have a small system that contributors can apply honestly and maintainers can use consistently than a nuanced system that encodes subjective mental states nobody can verify.</p>\n<p>I wrote a while back in <a href=\"https://kobi.kadosh.me/blog/2026-01-21-ai-coding-ethics-empathy-vibe-coding\">The Unpopular Opinion About Vibe Coding and AI</a> that AI has not replaced coding — it has shifted where the effort lives, from writing to governance. The same shift applies here. The question is no longer “did a human write this line.” It is “does a human stand behind this change, and did they invest enough effort to justify mine?”</p>\n<hr/>\n<h2 id=\"augmentation-not-theater\">Augmentation, Not Theater</h2>\n<p>The choice is not between stopping progress and surrendering to slop.</p>\n<p>The right frame is augmentation.</p>\n<p>AI should help humans move faster, see more options, and spend more time on the parts that actually require judgment. It should not become a machine for manufacturing plausible-looking burden for someone else to sort through.</p>\n<p>That is the line for me.</p>\n<p>I am not against AI-generated code. I am against unaccountable output. I am against content inflation. I am against policies that pretend every use case is the same. And I am against importing a rigid framework from one project and treating it like a universal answer.</p>\n<p>The future is not human-only software development. But it also cannot be a world where machines generate explanation faster than humans can maintain trust.</p>\n<p>The scarce asset is not code.</p>\n<p>It is responsible attention.</p>\n<p>That PR I started with, the one that made me write this post — I did eventually close the review. Not because AI had been used. Because the author could not defend the parts I asked about, and the review itself had started feeling like a conversation with a model wearing a human’s badge. The fix was not a new taxonomy. The fix was a short message that said: <em>do the next one yourself, then loop me back in.</em></p>\n<p>That, to me, is the whole policy in one sentence.</p>\n<hr/>\n<p><em>Written and developed with Claude. The arguments are mine; the drafting was collaborative. The inciting PR review was, unfortunately, also mine.</em></p>",
      "date_published": "2026-04-22T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Code Review",
        "Open Source",
        "Software Craftsmanship",
        "Developer Philosophy"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-03-15-unix-philosophy-agents-skills/",
      "url": "https://kobi.kadosh.me/blog/2026-03-15-unix-philosophy-agents-skills/",
      "title": "Skills, Pipes, and the Unix Philosophy: Why Agents Should Run Programs, Not Replace Them",
      "summary": "The Unix philosophy — small programs, composed through pipes, doing one thing well — turns out to be an ideal operating model for AI agents. Skills leverage this by guiding agents to use battle-tested programs rather than reinventing them. Here's why that beats building custom MCP servers for most workflows.",
      "content_html": "<h2 id=\"the-philosophy-that-keeps-winning\">The Philosophy That Keeps Winning</h2>\n<p>There is a design philosophy that has been quietly winning for over fifty years. It was articulated in the early 1970s at Bell Labs, refined through decades of practice, and embedded so deeply into how we build software that most developers absorb it by osmosis without ever reading the original texts.</p>\n<p>The Unix philosophy:</p>\n<ol>\n<li>Write programs that do one thing and do it well.</li>\n<li>Write programs to work together.</li>\n<li>Write programs to handle text streams, because that is a universal interface.</li>\n</ol>\n<p>Three principles. That is it.</p>\n<p>And yet, as I watch the current wave of agent tooling unfold—MCPs, custom servers, plugin architectures, tool registries—I keep coming back to the same thought:</p>\n<p><strong>We already had the right abstraction. We just forgot we had it.</strong></p>\n<p>I started programming in 2009 on mainframes in the Israeli Defense Force’s J6 &amp; Cyber Defense Directorate. Those systems were built on a version of this philosophy: small, focused utilities composed into pipelines. No framework-of-the-week. No plugin ecosystem. Just programs that did their job, connected by well-defined interfaces.</p>\n<p>In <a href=\"https://kobi.kadosh.me/blog/2025-12-05-handcrafted-software-age-of-automation\">Handcrafted Software in the Age of Automation</a>, I argued that we should guide AI toward existing, proven tools rather than letting it generate bespoke solutions for already-solved problems. This post is the next step in that argument.</p>\n<p>The Unix philosophy is not just good engineering advice from the past. It is the ideal operating model for AI agents right now. And skills—lightweight prompt-based guidance—are how you unlock it.</p>\n<hr/>\n<h2 id=\"agents-are-not-the-program-they-are-the-operator\">Agents Are Not the Program. They Are the Operator</h2>\n<p>There is a popular way to talk about agents that gets the abstraction slightly wrong. We describe them as if they are replacing software.</p>\n<p>In practice, the more useful framing is the opposite. An agent is not replacing the program. It is learning how to <em>operate</em> the program.</p>\n<p>That distinction matters.</p>\n<p>Yes, there are scenarios where an agent can itself behave like a tool. That boundary will shift as models improve. Some workflows that currently require explicit software will eventually collapse into the model itself. But today, agents are generally strongest when they are orchestrators—deciding what to do next, interpreting messy intent, translating goals into steps, and then driving deterministic systems on the user’s behalf.</p>\n<p>A user says, “Find all the error logs from the last week and summarize the top failure modes.” The agent’s job is not to become a log parser. Its job is to figure out that <code>find</code>, <code>grep</code>, <code>sort</code>, <code>uniq</code>, and <code>head</code> already solve this problem—and compose them correctly.</p>\n<p>The model handles ambiguity: what the user probably means, what the intent is, what sequence of actions makes sense.</p>\n<p>The program handles execution: parse the file, filter the lines, count the occurrences, sort the results.</p>\n<p>This is a very good pairing. And it maps directly onto the Unix philosophy.</p>\n<p>The agent is the operator at the terminal. The programs are the tools on the shelf. The pipes are the composition mechanism. The skill tells the agent which tools to reach for and how to connect them.</p>\n<hr/>\n<h2 id=\"what-a-skill-actually-is\">What a Skill Actually Is</h2>\n<p>I think “prompt” is too weak a word for what a good skill does.</p>\n<p>A skill is better understood as <strong>operational guidance for using a program in a particular way for a specific class of problems</strong>.</p>\n<p>It tells the agent things like:</p>\n<ul>\n<li>Which tool to use for this category of task</li>\n<li>What order to use tools in</li>\n<li>What assumptions need to be verified before acting</li>\n<li>What failure modes are common and how to recover</li>\n<li>What a good output should look like</li>\n</ul>\n<p>In <a href=\"https://kobi.kadosh.me/blog/2026-02-04-claude-code-skill-codebase-synchronization\">Building a Claude Code Skill for Codebase Synchronization</a>, I walked through a practical example: a skill that detects documentation drift across a codebase and fixes it. That skill does not contain the intelligence to understand your codebase. It contains the <em>procedure</em>—inspect these files, compare these sources of truth, show the delta, propose a fix, confirm with the user.</p>\n<p>The procedure is the valuable part.</p>\n<p>Raw capability is not the same as applied competence. An agent might know that <code>jq</code> exists. A skill tells it: <em>use jq here, with this filter pattern, pipe the output through this transformation, and verify the result has the expected shape before moving on.</em></p>\n<p>An agent might know a shell exists. A skill tells it: <em>this is a shell problem. Inspect state first. Avoid destructive commands. Verify the result. Here is the recovery path if it fails.</em></p>\n<p>That is not “extra prompting.” That is operational knowledge. And it is the difference between an agent that vaguely knows tools exist and one that can use them effectively in production.</p>\n<p>This is not very different from how humans work. Knowing that <code>ffmpeg</code>, <code>git</code>, <code>jq</code>, <code>sed</code>, <code>ImageMagick</code>, <code>pandoc</code>, or <code>psql</code> exist is not the same as knowing how to solve a real problem with them. The useful part is not awareness of the tool. The useful part is the learned procedure.</p>\n<p>A skill packages that procedure in a form the agent can reliably follow.</p>\n<hr/>\n<h2 id=\"why-this-works-training-data-already-contains-the-raw-ingredients\">Why This Works: Training Data Already Contains the Raw Ingredients</h2>\n<p>One reason skills work so well is that they align with the kinds of patterns models have already absorbed during training.</p>\n<p>There are two major sources of leverage here.</p>\n<h3 id=\"1-tool-use-is-embedded-in-the-training-data\">1. Tool Use Is Embedded in the Training Data</h3>\n<p>Training data is saturated with programs being used directly. Bash commands, shell pipelines, Makefiles, Dockerfiles, Python scripts, CI configs, terminal transcripts, debugging sessions, SQL queries, CLI help pages, man pages, setup instructions.</p>\n<p>That matters because Unix tools have a text-based interface. A shell command is text. A script is text. A config file is text. An API call is usually text. For a language model, that is home territory.</p>\n<p>Consider the difference:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Finding error patterns in recent logs — Unix pipeline</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">find</span><span style=\"color:#98C379\"> /var/log</span><span style=\"color:#D19A66\"> -name</span><span style=\"color:#98C379\"> &quot;*.log&quot;</span><span style=\"color:#D19A66\"> -mtime</span><span style=\"color:#D19A66\"> -7</span><span style=\"color:#D19A66\"> -exec</span><span style=\"color:#98C379\"> grep</span><span style=\"color:#D19A66\"> -l</span><span style=\"color:#98C379\"> &quot;ERROR&quot;</span><span style=\"color:#98C379\"> {}</span><span style=\"color:#98C379\"> +</span><span style=\"color:#56B6C2\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">  | </span><span style=\"color:#61AFEF\">xargs</span><span style=\"color:#D19A66\"> -I</span><span style=\"color:#ABB2BF\">{} sh -c &#39;echo &quot;=== {} ===&quot; &amp;&amp; grep &quot;ERROR&quot; &quot;{}&quot; | sort | uniq -c | sort -rn | head -5&#39;</span></span></code></pre></div>\n<p>An agent guided by a skill toward this pipeline is drawing on a deep prior—countless examples of humans solving concrete problems with exactly these mechanisms. The model has seen <code>find</code>, <code>grep</code>, <code>sort</code>, <code>uniq</code>, and <code>head</code> composed together thousands of times across its training data.</p>\n<p>This does not mean the model is always right. But it means the model has a strong native bias toward this mode of problem-solving. The skill activates that bias and constrains it toward the specific task.</p>\n<h3 id=\"2-the-internet-is-full-of-problem-solution-pairs\">2. The Internet Is Full of Problem-Solution Pairs</h3>\n<p>The second source of leverage is even more important: the web is packed with people asking “how do I do X?” and other people answering with step-by-step procedures.</p>\n<p>Forums, Q&amp;A sites, documentation, blog posts, README files, tutorials, issue trackers, troubleshooting threads—they all reinforce the same pattern:</p>\n<ul>\n<li>Here is the problem</li>\n<li>Here is the environment</li>\n<li>Here are the constraints</li>\n<li>Here is the sequence of commands</li>\n<li>Here is the expected result</li>\n<li>Here is what to do when it fails</li>\n</ul>\n<p>That is almost exactly the structure of agentic work.</p>\n<p>When a user asks an agent to solve a task, the agent is often matching that request against a vast library of previously seen “how to accomplish X using Y” examples. A skill sharpens that retrieval path. It narrows the solution space. It says: <em>in this context, use these tools, in this order, with this standard of care.</em></p>\n<p>So the skill is not replacing the model’s knowledge. It is <strong>activating and constraining</strong> it. Two different operations, both important.</p>\n<hr/>\n<h2 id=\"why-this-beats-mcps-for-most-workflows\">Why This Beats MCPs for Most Workflows</h2>\n<p>Now let me say the thing directly.</p>\n<p>MCP servers—Model Context Protocol servers—are a popular way to give agents access to capabilities. You write a server that exposes tools through a standardized protocol. The agent calls the server. The server runs logic and returns results.</p>\n<p>For certain cases, this makes sense. I will get to those.</p>\n<p>But for the vast majority of agent workflows, MCPs are the wrong abstraction. They add complexity where none is needed. And they actively work against the grain of what makes agents effective.</p>\n<p>Here is why.</p>\n<h3 id=\"mcps-create-cold-starts\">MCPs Create Cold Starts</h3>\n<p>When an agent calls an MCP server, it is interacting with an interface it has likely never seen before. The tool names, parameter schemas, return formats, and behavioral semantics are all novel. The model has zero prior exposure to your custom <code>extract-json-fields</code> MCP tool. It is working from the schema description alone.</p>\n<p>Compare that to a skill that guides the agent to use <code>jq</code>:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Skill-guided: extract active user emails from JSON</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">jq</span><span style=\"color:#98C379\"> &#39;.users[] | select(.active == true) | .email&#39;</span><span style=\"color:#98C379\"> data.json</span></span></code></pre></div>\n<p>The model has seen <code>jq</code> used in hundreds of thousands of examples across its training data. It knows the syntax, common patterns, edge cases, error messages. The skill does not need to explain <code>jq</code> from first principles. It only needs to direct behavior: <em>for this class of problems, use jq, here is the pattern.</em></p>\n<p>That is an enormous advantage. You are building on a warm prior instead of starting cold.</p>\n<h3 id=\"mcps-add-unnecessary-infrastructure\">MCPs Add Unnecessary Infrastructure</h3>\n<p>An MCP server requires:</p>\n<ul>\n<li>Writing code (TypeScript, Python, whatever the runtime)</li>\n<li>Defining tool schemas</li>\n<li>Running a server process</li>\n<li>Managing the connection between agent and server</li>\n<li>Handling errors, timeouts, and version mismatches</li>\n<li>Maintaining and updating the codebase over time</li>\n</ul>\n<p>A skill requires:</p>\n<ul>\n<li>Writing a Markdown file</li>\n</ul>\n<p>I am not exaggerating. A Claude Code skill is a <code>.md</code> file that describes a procedure. No server. No runtime. No dependencies. No deployment. You write it, the agent reads it, the agent follows the procedure using tools that already exist on the system.</p>\n<p>The maintenance burden is not comparable. When <code>jq</code> gets an update, you do nothing—your skill still works. When your MCP server’s dependencies need updating, you have a maintenance task.</p>\n<h3 id=\"mcps-break-composability\">MCPs Break Composability</h3>\n<p>One of the most powerful features of Unix programs is that they compose through pipes. The output of one program becomes the input of the next. No special integration required. No orchestration layer. Just text flowing through a pipeline.</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Three programs composed through pipes — no orchestration code needed</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">curl</span><span style=\"color:#D19A66\"> -s</span><span style=\"color:#98C379\"> https://api.example.com/users</span><span style=\"color:#56B6C2\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">  | </span><span style=\"color:#61AFEF\">jq</span><span style=\"color:#98C379\"> &#39;.[] | select(.role == &quot;admin&quot;)&#39;</span><span style=\"color:#56B6C2\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">  | </span><span style=\"color:#61AFEF\">wc</span><span style=\"color:#D19A66\"> -l</span></span></code></pre></div>\n<p>MCPs are silos. Each MCP server is its own world. Composing two MCPs requires the agent to call one, capture the result, transform it, and call the other. That orchestration lives in the agent’s reasoning—which is the least deterministic part of the system.</p>\n<p>Unix pipes move the composition into the deterministic layer. The agent constructs the pipeline. The shell executes it. The agent checks the result. That is a much more robust workflow.</p>\n<h3 id=\"mcps-duplicate-what-already-exists\">MCPs Duplicate What Already Exists</h3>\n<p>This is the part that bothers me most.</p>\n<p>Consider what people build MCP servers for: reading files, searching codebases, extracting data from JSON, transforming text, querying databases, managing git operations, processing images, converting formats.</p>\n<p>Now consider what already exists on any Unix system: <code>cat</code>, <code>grep</code>, <code>find</code>, <code>jq</code>, <code>sed</code>, <code>awk</code>, <code>sort</code>, <code>uniq</code>, <code>curl</code>, <code>git</code>, <code>ffmpeg</code>, <code>convert</code>, <code>pandoc</code>, <code>psql</code>, <code>sqlite3</code>, <code>tar</code>, <code>diff</code>, <code>wc</code>, <code>head</code>, <code>tail</code>, <code>cut</code>, <code>tr</code>, <code>xargs</code>.</p>\n<p>These tools are battle-tested. They have been refined for decades. They handle edge cases. They are fast. They are documented. They are deterministic. And—crucially—the model already knows how to use them.</p>\n<p>Building an MCP server to wrap functionality that <code>jq</code> or <code>grep</code> or <code>git</code> already provides is not just unnecessary overhead. It is actively counterproductive, because you are replacing a tool the model has a deep prior on with a novel interface it has never seen.</p>\n<h3 id=\"when-mcps-do-make-sense\">When MCPs Do Make Sense</h3>\n<p>I am not saying MCPs are never the right choice. They make sense when:</p>\n<ul>\n<li>You need authenticated access to APIs (Slack, GitHub with auth, internal services)</li>\n<li>You are interfacing with proprietary systems that have no CLI equivalent</li>\n<li>You need stateful interactions that cannot be expressed as shell commands</li>\n<li>You are building a product where the MCP protocol is the distribution mechanism</li>\n</ul>\n<p>Those are real use cases. But they are a small fraction of what I see people building MCP servers for.</p>\n<p>For file processing, text transformation, system inspection, data extraction, format conversion, log analysis, git operations, and most of the day-to-day work that agents do? You do not need a custom server. You need a good skill and access to a shell.</p>\n<hr/>\n<h2 id=\"the-multiplier-effect\">The Multiplier Effect</h2>\n<p>There is an interesting compounding dynamic here.</p>\n<p>If the deterministic program already appears frequently in the model’s training data, the skill becomes <em>more</em> powerful. The reason is simple: the prompt is not teaching the program from scratch. It is cueing a system the model has likely seen many times before.</p>\n<p>Instructing an agent to use <code>bash</code>, <code>python</code>, <code>git</code>, <code>curl</code>, <code>ffmpeg</code>, <code>jq</code>, <code>grep</code>, <code>awk</code>, or <code>sed</code> is effective partly because those interfaces are deeply represented in the training corpus. The model has seen them used across documentation, scripts, answers, debugging threads, and tutorials. A skill can therefore be compact while still being highly effective.</p>\n<p>The prompt does not need to explain everything. It only needs to direct behavior toward a known operational pattern.</p>\n<p>This is one reason skills often feel disproportionately useful relative to their size. A few paragraphs of guidance can unlock a large amount of latent procedural knowledge. The skill is not providing the intelligence. It is providing the <em>direction</em>.</p>\n<p>MCPs, by contrast, are always a cold start. Every custom MCP tool is a novel interface. The model works from the schema description, not from deep familiarity. That is a fundamentally weaker position, and no amount of schema documentation fully compensates for the absence of training data.</p>\n<hr/>\n<h2 id=\"determinism-is-part-of-the-point\">Determinism Is Part of the Point</h2>\n<p>One reason this pattern is robust is that the program is deterministic.</p>\n<p>The model is not being asked to improvise every step of the solution. It is being asked to drive something that has crisp, predictable behavior.</p>\n<p>That gives you a useful split:</p>\n<ul>\n<li><strong>The model decides</strong> — which tools, which order, which parameters</li>\n<li><strong>The program executes</strong> — deterministically, exactly as invoked</li>\n<li><strong>The model checks</strong> — did the output match expectations?</li>\n<li><strong>The user gets a result</strong> — verified through a deterministic channel</li>\n</ul>\n<p>This is much more reliable than asking the model to do everything internally, especially when the task involves files, formats, transformations, or any workflow where correctness matters.</p>\n<p>The skill is what makes that split work consistently. It tells the model how to move between judgment and execution without losing the thread.</p>\n<p>Without that layer, the agent may still succeed on any given run. With it, the agent is far more likely to succeed <em>repeatedly</em>.</p>\n<hr/>\n<h2 id=\"the-boundary-will-move-but-the-principle-will-not\">The Boundary Will Move, But the Principle Will Not</h2>\n<p>I do not think skills in their current form are permanent. Over time, models will absorb more tool-like behavior. Some explicit programs will disappear behind model interfaces. Some workflows that currently need careful orchestration will become default capabilities.</p>\n<p>But the Unix philosophy insight—composition of small, focused, deterministic units—is a design principle, not a temporary workaround. It applies regardless of whether the units are shell programs, API calls, or model-native capabilities. The principle survives because it solves a real problem: managing complexity through composition rather than consolidation.</p>\n<p>Today, that composition happens through skills guiding agents to use Unix programs.</p>\n<p>Tomorrow, the specific tools may change. But the pattern—small units, clear interfaces, text as the universal medium, composition over monoliths—will still be the right way to build reliable systems.</p>\n<p>The Unix philosophy was not designed for AI agents. It just happens to describe exactly what they need.</p>\n<hr/>\n<h2 id=\"the-operating-model\">The Operating Model</h2>\n<p>Here is the mental model I keep coming back to:</p>\n<p><strong>The model brings interpretation.</strong>\nIt handles ambiguity, figures out intent, translates fuzzy human goals into structured action.</p>\n<p><strong>The program brings precision.</strong>\nIt executes deterministically, handles edge cases correctly, and produces predictable output.</p>\n<p><strong>The skill connects the two.</strong>\nIt encodes operational knowledge—which tools, which order, which patterns, which guardrails.</p>\n<p>That connection is powerful precisely because it leverages training data twice. First through the model’s deep familiarity with tool interfaces and executable patterns. Second through the enormous corpus of human problem-solving examples—the entire internet’s worth of “how do I accomplish X using Y.”</p>\n<p>A skill is not just a prompt. It is a compact operational wrapper around accumulated procedural knowledge. It tells the agent how to use a deterministic program for a specific class of problems. And when that program already lives in the model’s prior—when the agent has effectively seen it used ten thousand times—the result is surprisingly strong.</p>\n<p>We do not need to rebuild the wheel with custom servers and novel protocols for most agent workflows. The programs already exist. The training data already contains the knowledge. The composition mechanism already works.</p>\n<p>All we need is the skill to connect them.</p>\n<p>Because, for now at least, agents are not at their best when they replace programs.</p>\n<p>They are at their best when they know how to run them well.</p>\n<hr/>\n<p><em>This post builds on ideas from <a href=\"https://kobi.kadosh.me/blog/2025-12-05-handcrafted-software-age-of-automation\">Handcrafted Software in the Age of Automation</a>, which argued for respecting battle-tested tools in the age of AI, and <a href=\"https://kobi.kadosh.me/blog/2026-02-04-claude-code-skill-codebase-synchronization\">Building a Claude Code Skill for Codebase Synchronization</a>, which demonstrated what skills look like in practice. Together they form a thread: respect the fundamentals, encode the procedures, and let agents compose what already works.</em></p>\n<p><em>Written and developed with Claude. The arguments are mine; the drafting was collaborative.</em></p>",
      "date_published": "2026-03-15T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Unix",
        "Claude Code",
        "Software Craftsmanship",
        "Developer Philosophy",
        "Automation"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-02-11-back-office-front-office-split-future-software/",
      "url": "https://kobi.kadosh.me/blog/2026-02-11-back-office-front-office-split-future-software/",
      "title": "The Back Office / Front Office Split: A Prediction for the Future of Software",
      "summary": "We've become unpaid staff for the applications we use. The future will separate operational back-office work from human-facing front-office experiences, with AI agents handling the infrastructure while humans focus on judgment and decision-making.",
      "content_html": "<p>Look at a normal day.</p>\n<p>You’re juggling Google Calendar, email, Slack, documents, a task manager, notes, maybe a CRM, maybe finances, maybe a dozen tabs that represent “adulting.” Each one has good UX. Each one has brilliant engineering behind it.</p>\n<p>And yet… they feel like chores.</p>\n<p>Not because they’re poorly built—but because they require you to play too many roles just to keep them operational.</p>\n<h2 id=\"the-hidden-job-nobody-applied-for\">The Hidden Job Nobody Applied For</h2>\n<p>Take the simplest example: your calendar.</p>\n<p>To make a calendar useful, you have to do <em>back office work</em>:</p>\n<ul>\n<li>Decide what matters enough to be an event</li>\n<li>Translate fuzzy intent into structured fields (time, location, attendees, reminders)</li>\n<li>Resolve conflicts (“can’t do that then… maybe here… maybe shorter…”)</li>\n<li>Maintain it continuously so it stays trustworthy</li>\n<li>Protect it from distraction (notifications tuned, noise reduced, no random stuff polluting the day)</li>\n</ul>\n<p>And that’s <em>before</em> the “real life” part begins: showing up, being prepared, reading context, making decisions, following up.</p>\n<p>Same story for email:</p>\n<ul>\n<li>Sort, prioritize, label, archive</li>\n<li>Identify what needs action vs what is FYI</li>\n<li>Track commitments (“I said I’d do X”)</li>\n<li>Write the reply in the right tone, at the right level, with the right context</li>\n</ul>\n<p>Same story for to-do lists, notes, social feeds, personal finance, investment portfolios—all of it.</p>\n<p>We aren’t just <em>using</em> software.</p>\n<p>We’re <em>staffing</em> it.</p>\n<h2 id=\"even-with-a-human-assistant-the-work-doesnt-disappear\">Even With a Human Assistant, the Work Doesn’t Disappear</h2>\n<p>If you’ve ever had (or worked with) a personal assistant, you’ll recognize the split:</p>\n<p><strong>They can do the inbound handling and structuring:</strong></p>\n<ul>\n<li>Schedule meetings</li>\n<li>Organize materials</li>\n<li>Manage contacts</li>\n<li>Keep things moving</li>\n</ul>\n<p><strong>But you still have to do the “front office” part:</strong></p>\n<ul>\n<li>Review the agenda</li>\n<li>Make decisions</li>\n<li>Show up informed</li>\n<li>Respond with intent</li>\n<li>Carry accountability</li>\n</ul>\n<p>A good assistant doesn’t eliminate work—they move you up the stack.</p>\n<p>That’s the key.</p>\n<h2 id=\"my-prediction-software-will-split-into-back-office-and-front-office\">My Prediction: Software Will Split Into Back Office and Front Office</h2>\n<p>I think we’re going to see a major divide in how software is built and experienced.</p>\n<h3 id=\"back-office-automation--orchestration\">Back Office (Automation + Orchestration)</h3>\n<p>The stuff that turns messy human life into structured systems:</p>\n<ul>\n<li>Capturing intent from conversations/messages</li>\n<li>Drafting, scheduling, filing, tagging, summarizing</li>\n<li>Reconciling conflicts and constraints</li>\n<li>Continuous maintenance of data integrity (“keep it clean”)</li>\n<li>Executing routines across services</li>\n</ul>\n<h3 id=\"front-office-review--decision--outcomes\">Front Office (Review + Decision + Outcomes)</h3>\n<p>The part humans actually want to do:</p>\n<ul>\n<li>Reviewing what matters today</li>\n<li>Approving actions</li>\n<li>Making decisions with context</li>\n<li>Noticing risks and tradeoffs</li>\n<li>Steering direction rather than typing fields into forms</li>\n</ul>\n<p>Most apps today force you to do both—and that’s why they feel exhausting.</p>\n<p>The next generation won’t.</p>\n<h2 id=\"the-operators-of-the-back-office-will-be-agents\">The “Operators” of the Back Office Will Be Agents</h2>\n<p>This is the part that makes the split inevitable: the back office is basically operator work.</p>\n<p>And modern services already expose the machinery needed for operators:</p>\n<ul>\n<li>APIs</li>\n<li>Webhooks</li>\n<li>Automation primitives</li>\n<li>Integrations</li>\n<li>Permissions models (even if they’re messy)</li>\n</ul>\n<p>So instead of you doing the operator work manually, the operators become:</p>\n<ul>\n<li>AI agents (plural)</li>\n<li>Running workflows</li>\n<li>Specialized by domain (calendar agent, inbox agent, travel agent, finance agent)</li>\n<li>Operating across vendor boundaries</li>\n</ul>\n<p>And the “product” becomes less about “here’s a screen, please fill the form” and more about:</p>\n<ul>\n<li>“Here’s what I did for you”</li>\n<li>“Here’s what I’m about to do”</li>\n<li>“Approve / edit / reject”</li>\n<li>“Here’s why”</li>\n</ul>\n<p>That’s a fundamentally different UX contract.</p>\n<h2 id=\"if-the-front-office-is-what-i-care-about-why-do-i-care-who-runs-the-back-office\">If the Front Office Is What I Care About… Why Do I Care Who Runs the Back Office?</h2>\n<p>Once the separation is real, it changes incentives.</p>\n<p>If I mostly interact with a “review + decision” layer, then the underlying back office becomes interchangeable—as long as it’s reliable, auditable, secure, and composable.</p>\n<h2 id=\"the-back-office-wants-to-be-invisibleand-agnostic\">The Back Office Wants to Be Invisible—and Agnostic</h2>\n<p>If the back office really becomes autonomous, then one thing follows almost immediately:</p>\n<p>We won’t care how it looks.\nWe won’t care how it’s rendered.\nWe’ll barely care who runs it.</p>\n<p>We’ll care about <strong>functionality, reliability, auditability, and alignment with our intent</strong>.</p>\n<p>That pushes the back office toward being <em>agnostic infrastructure</em>—not an “app” in the traditional sense, but a substrate:</p>\n<ul>\n<li>Data stores</li>\n<li>Schedulers</li>\n<li>Workflow engines</li>\n<li>Policy systems</li>\n<li>Connectors</li>\n<li>Memory and context layers</li>\n</ul>\n<p>This is infrastructure that used to be accessible only to a narrow group of experts—operators, SREs, platform engineers, power users—the kind of tools that required deep technical literacy.</p>\n<p>What changes now is not that the tools become simpler.</p>\n<p>What changes is that the <strong>human-facing layer becomes radically better</strong>, and agents absorb the operational complexity.</p>\n<h2 id=\"from-one-size-fits-all-apps-to-composable-systems\">From One-Size-Fits-All Apps to Composable Systems</h2>\n<p>Today’s model is simple but brittle:</p>\n<ul>\n<li>You download an app</li>\n<li>You adapt yourself to its workflow</li>\n<li>If you need another capability, you download another app</li>\n<li>Then you juggle</li>\n</ul>\n<p>That model does not scale with human complexity.</p>\n<p>In the future, it won’t be “one app per function.”</p>\n<p>It will be:</p>\n<ul>\n<li>One (or a few) agnostic back offices</li>\n<li>Connected to multiple front ends</li>\n<li>Operated by multiple agents</li>\n<li>With your data remaining portable and sovereign</li>\n</ul>\n<p>The back office becomes <em>yours</em>—not in the sense that you micromanage it, but in the sense that:</p>\n<ul>\n<li>The data is transferable</li>\n<li>The logic is inspectable</li>\n<li>The operators (agents) are replaceable</li>\n<li>The interfaces are optional</li>\n</ul>\n<p>This is where <strong>data sovereignty</strong> stops being a slogan and starts being structural.</p>\n<h2 id=\"agents-as-employees-not-features\">Agents as Employees, Not Features</h2>\n<p>Once you accept this model, agents stop looking like “AI features” and start looking like employees:</p>\n<ul>\n<li>They onboard</li>\n<li>They have scopes and permissions</li>\n<li>They specialize</li>\n<li>They hand work off to one another</li>\n<li>They improve over time</li>\n<li>They can be replaced if they don’t perform</li>\n</ul>\n<p>Multiple agents can operate the same back office, just as multiple employees can operate the same company systems.</p>\n<p>And just like with employees, what matters is not <em>how</em> they do the work—but:</p>\n<ul>\n<li>What outcomes they produce</li>\n<li>How predictable they are</li>\n<li>How well they understand your preferences</li>\n<li>How safely they operate</li>\n</ul>\n<p>This naturally leads to <strong>standards</strong>:</p>\n<ul>\n<li>How agents interact with back offices</li>\n<li>How they declare intent</li>\n<li>How they log actions</li>\n<li>How they coordinate</li>\n</ul>\n<p>Once standards emerge, competition becomes healthy.</p>\n<h2 id=\"a-golden-age-for-open-source-back-offices\">A Golden Age for Open-Source Back Offices</h2>\n<p>Here’s where this gets interesting.</p>\n<p>If:</p>\n<ul>\n<li>Back offices are infrastructure</li>\n<li>Operators are interchangeable</li>\n<li>Front ends are decoupled</li>\n</ul>\n<p>Then open source becomes an obvious gravity well.</p>\n<p>Not because it’s ideological—but because it’s practical.</p>\n<p>Open-source back offices allow:</p>\n<ul>\n<li>Shared improvement of core logic</li>\n<li>Faster agent interoperability</li>\n<li>Transparent operating models</li>\n<li>Collective hardening of critical systems</li>\n</ul>\n<p>Companies can still differentiate:</p>\n<ul>\n<li>By how they package</li>\n<li>By how they operate agents</li>\n<li>By how they design front-office experiences</li>\n<li>By trust, safety, and performance guarantees</li>\n</ul>\n<p>But the bare bones—the operational logic—benefits enormously from being shared.</p>\n<p>This creates an ecosystem where:</p>\n<ul>\n<li>Vendors compete on experience</li>\n<li>Communities collaborate on infrastructure</li>\n<li>Agents learn common operational patterns</li>\n<li>Users retain leverage</li>\n</ul>\n<p>That’s a healthier software economy than what we have today.</p>\n<h2 id=\"when-agents-move-from-the-shadows-to-the-stage\">When Agents Move From the Shadows to the Stage</h2>\n<p>So far, everything I described keeps agents “in the shadows”:</p>\n<ul>\n<li>Doing legwork</li>\n<li>Preparing context</li>\n<li>Cleaning up messes</li>\n<li>Making you look prepared when you show up</li>\n</ul>\n<p>But the uncomfortable question is unavoidable:</p>\n<p><strong>When do agents enter the front office themselves?</strong></p>\n<p>At first, this will feel dystopian.</p>\n<p>An agent attending a meeting for you.\nAn agent negotiating on your behalf.\nAn agent presenting status updates while you don’t show up at all.</p>\n<p>But over time, this forces a refinement we’ve been avoiding:</p>\n<p><strong>What is the work that actually requires a human?</strong></p>\n<p>Right now, many humans are doing high-effort, low-meaning labor simply because the ecosystem demands it.</p>\n<p>Agents change that pressure.</p>\n<p>They don’t eliminate human value—they clarify it.</p>\n<h2 id=\"new-front-offices-from-screens-to-presence\">New Front Offices: From Screens to Presence</h2>\n<p>I don’t think the front office stops at chat windows and dashboards.</p>\n<p>My prediction is that we’ll see a rapid evolution in <strong>presence-based interfaces</strong>:</p>\n<ul>\n<li>High-fidelity voice agents</li>\n<li>Expressive 3D avatars</li>\n<li>Real-time generative faces and bodies</li>\n<li><a href=\"https://www.metahuman.com/\">MetaHuman</a>-style representations inside business contexts</li>\n</ul>\n<p>Not as gimmicks—but because presence matters in human coordination.</p>\n<p>Think of what we’re already seeing with voice agents, but extended to full-blown experiences that feel like a high-fidelity game. Imagine <a href=\"https://www.metahuman.com/\">MetaHuman</a>—Epic’s photorealistic digital human framework—applied not to entertainment, but to business applications. Your AI assistant with a face. Your agent representative in a virtual meeting room.</p>\n<p>Over time, this extends further:</p>\n<ul>\n<li>Holographic interfaces</li>\n<li>Embodied robotics</li>\n<li>Distributed agents with physical presence</li>\n</ul>\n<p>At that point, your “app” is no longer something on your phone.</p>\n<p>Your phone becomes a remote control.</p>\n<p>The system itself is:</p>\n<ul>\n<li>Distributed</li>\n<li>Agent-operated</li>\n<li>Continuously monitored</li>\n<li>Self-updating</li>\n<li>Self-optimizing</li>\n</ul>\n<p>And humans interact with it only where it actually makes sense.</p>\n<h2 id=\"the-real-transition\">The Real Transition</h2>\n<p>We often frame this future as “AI replacing humans.”</p>\n<p>I think that’s the wrong lens.</p>\n<p>What’s actually happening is:</p>\n<ul>\n<li>Humans stop being unpaid back-office workers</li>\n<li>Agents take over operational labor</li>\n<li>Front offices become about judgment, trust, creativity, and meaning</li>\n</ul>\n<p>The real shift is from <strong>data entry to stewardship</strong>.</p>\n<p>Today’s apps assume you are a data entry clerk.</p>\n<p>Tomorrow’s apps assume you are a steward.</p>\n<p>You don’t “keep your calendar updated.”\nYou review what your calendar became, and decide what to accept.</p>\n<p>You don’t “process your inbox.”\nYou approve a set of proposed actions with context, grouped by intent.</p>\n<p>You don’t “maintain your task system.”\nYou steer priorities while the system maintains itself.</p>\n<p>And the moment that becomes normal, we’ll look back at today’s workflow the same way we look back at:</p>\n<ul>\n<li>Manually mapping routes before GPS</li>\n<li>Balancing checkbooks by hand</li>\n<li>Syncing music files via cables</li>\n</ul>\n<p>Not because we were foolish—but because the interface was wrong.</p>\n<p>We were doing back office work because nothing else could.</p>\n<p>That’s about to change.</p>",
      "date_published": "2026-02-11T00:00:00.000Z",
      "tags": [
        "AI Agents",
        "Software Architecture",
        "Future of Work",
        "Predictions",
        "Open Source"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-02-11-what-exceptional-recruiting-looks-like/",
      "url": "https://kobi.kadosh.me/blog/2026-02-11-what-exceptional-recruiting-looks-like/",
      "title": "What Exceptional Recruiting Looks Like (From the Candidate's Side)",
      "summary": "After more than a decade in tech, I thought I'd seen every variation of the hiring process. Then I worked with a recruiter who showed me what it looks like when someone genuinely cares about doing the job right. This is a reflection on trust, transparency, and why the best recruiting feels less like a transaction and more like a partnership.",
      "content_html": "<p>I’ve been working professionally since 2012. That’s more than a decade of recruiter messages, LinkedIn pitches, interview loops, and offer negotiations. I’ve seen the spectrum: the mass-blast outreach that misspells your name, the aggressive follow-ups that feel like sales pressure, the radio silence after you’ve invested hours in technical challenges.</p>\n<p>None of it prepared me for what it felt like to work with someone who actually did the job exceptionally well.</p>\n<hr/>\n<h2 id=\"the-baseline-is-low\">The Baseline Is Low</h2>\n<p>Let me be honest about the baseline. Most recruiting interactions feel transactional. You’re a slot to fill, a commission to close, a metric to move. The communication is either too sparse or too aggressive. Preparation is minimal. Context is generic. And somewhere in the process, you realize that the person coordinating your interviews doesn’t really understand what you do or why you might be good at it.</p>\n<p>This isn’t an indictment of recruiters as people. It’s an observation about systems. When the incentives are misaligned, when speed matters more than fit, when volume trumps quality, the candidate experience suffers. Everyone knows this. Most of us have just accepted it.</p>\n<p>So when something different happens, it’s disorienting. You notice it immediately, even if you can’t articulate why.</p>\n<hr/>\n<h2 id=\"night-and-day\">Night and Day</h2>\n<p>Recently, I went through a hiring process that was genuinely different. The recruiter, <a href=\"https://linkedin.com/in/megan-ashbee\">Megan Ashbee</a>, approached me in a way that stood out from the first message.</p>\n<p>No hard sell. No exaggeration. Just clear information: the company, the role, the technical focus, the compensation range. She was transparent about constraints before I’d even responded. That alone is rare.</p>\n<p>But what made the experience exceptional wasn’t any single thing. It was the consistency of care across every interaction.</p>\n<hr/>\n<h2 id=\"what-transparency-actually-looks-like\">What Transparency Actually Looks Like</h2>\n<p>Early in the process, Megan asked about my experience with specific technologies. She asked about my salary expectations. She shared the target range upfront: not after three rounds of interviews, not as a negotiation tactic, but as information I needed to make an informed decision.</p>\n<p>“In the name of transparency,” she wrote.</p>\n<p>That phrase stuck with me. It’s easy to say. It’s harder to mean. She meant it.</p>\n<p>When I told her my current compensation was above their initial range, she didn’t disappear. She said they could push the range if everything else aligned. No games. No false scarcity. Just honesty about what was possible.</p>\n<p>This kind of transparency changes the dynamic. You stop trying to guess what’s real. You stop performing. You start actually evaluating whether the opportunity is right for both sides.</p>\n<hr/>\n<h2 id=\"preparation-as-care\">Preparation as Care</h2>\n<p>Before every interview, Megan sent context. Not just logistics, but who I’d be meeting and why. What they cared about. How to frame my experience. What to expect from the conversation.</p>\n<p>This might sound like standard practice. It isn’t.</p>\n<p>Most of the time, you walk into interviews blind. You don’t know what the interviewer is looking for. You don’t know what they’ve already heard about you. You’re guessing, adapting in real-time, trying to read the room.</p>\n<p>Megan eliminated that uncertainty. Not by scripting the conversation, but by giving me enough context to show up as myself: prepared, focused, and confident.</p>\n<p>That’s what good preparation does. It doesn’t make you perform better artificially. It removes the friction that prevents you from performing at your actual level.</p>\n<hr/>\n<h2 id=\"advocacy-on-both-sides\">Advocacy on Both Sides</h2>\n<p>One of the things that stood out most was the sense that Megan was advocating on both sides of the process.</p>\n<p>She wasn’t just presenting me to the company. She was helping me understand the company. She wasn’t just filtering candidates for her client. She was helping me evaluate whether this was the right fit for my career.</p>\n<p>That’s a subtle but important distinction. Most recruiters are aligned with the hiring company. Their incentive is to close. Megan’s approach felt different, like she was aligned with the process itself, with making sure both sides had the information they needed to make good decisions.</p>\n<p>I’m confident she represented me internally with the same care she showed me directly. That’s not something you can fake. It comes through in how someone communicates, how they prepare, how they follow up.</p>\n<hr/>\n<h2 id=\"human-not-over-familiar\">Human, Not Over-Familiar</h2>\n<p>There’s a delicate balance in professional relationships. Too formal and it feels cold. Too casual and it feels presumptuous.</p>\n<p>Megan navigated this perfectly. She was warm without being unprofessional. She made space for real conversations, about my family situation, about timing, about things that mattered outside the immediate transaction, without overstepping.</p>\n<p>When I shared that I might need to travel to Israel for a family matter, she didn’t treat it as a complication. She asked how she could help. She communicated it to the company thoughtfully. She made sure I felt supported, not exposed.</p>\n<p>That’s not process. That’s judgment. That’s someone who understands that candidates are people, not resources.</p>\n<hr/>\n<h2 id=\"respect-for-time-and-attention\">Respect for Time and Attention</h2>\n<p>Senior engineers are busy. We get a lot of recruiter messages. Most of them are noise.</p>\n<p>Megan’s initial outreach was concise, relevant, and respectful. She included just enough information to be useful: not so much that it felt like a pitch, not so little that I had to guess whether it was worth responding.</p>\n<p>When we scheduled calls, she was flexible but efficient. When I needed to reschedule, there was no friction. When there were updates, she communicated them promptly. When there was nothing to report, she didn’t manufacture urgency.</p>\n<p>This sounds simple. It’s surprisingly rare.</p>\n<p>The cumulative effect of all these small choices is trust. You stop worrying about the process and start focusing on the actual decision.</p>\n<hr/>\n<h2 id=\"why-this-matters\">Why This Matters</h2>\n<p>I’ve been thinking about why this experience affected me so much. Part of it is contrast: after years of mediocre interactions, excellence stands out.</p>\n<p>But there’s something deeper. When recruiting is done well, it changes how you feel about the company you’re joining. Megan didn’t just get me through a process. She represented her client in a way that made me excited to join.</p>\n<p>That’s the multiplier effect of great recruiting. It’s not just about filling a role. It’s about starting relationships on the right foundation. It’s about signaling what kind of organization you’re joining.</p>\n<p>If a company invests in people like Megan, people who care about doing the job right, that tells you something about their values. It suggests that quality matters. That people matter. That they understand the difference between closing candidates and building teams.</p>\n<hr/>\n<h2 id=\"raising-the-bar\">Raising the Bar</h2>\n<p>I’m not writing this as a testimonial. I’m writing it because I think the bar for recruiting is too low, and most people have stopped noticing.</p>\n<p>We accept transactional interactions because we’ve been conditioned to expect them. We’re grateful when someone responds promptly, even though that should be table stakes. We’re surprised when a recruiter prepares, even though preparation should be the minimum.</p>\n<p>What Megan showed me is what recruiting looks like when someone treats it as a craft. When they take pride in the process itself, not just the outcome. When they understand that every interaction is an opportunity to build trust or erode it.</p>\n<p>This should be the standard, not the exception.</p>\n<hr/>\n<h2 id=\"a-note-of-gratitude\">A Note of Gratitude</h2>\n<p>I don’t often write about personal experiences. I prefer to keep my professional reflections abstract, focused on patterns rather than individuals.</p>\n<p>But some experiences deserve recognition. Megan made my decision to accept the offer easy, not because she persuaded me, but because she created the conditions for me to evaluate clearly and choose confidently.</p>\n<p>That’s what exceptional work looks like in any field. You don’t notice the effort. You just notice that things work.</p>\n<p>To anyone fortunate enough to work with Megan Ashbee: you’re in good hands. And to anyone building a recruiting function: this is the standard to aim for.</p>\n<hr/>\n<blockquote>\n<p><strong>About this post:</strong> This essay reflects on a recent hiring experience that stood out from more than a decade of professional interactions. It’s not a recommendation letter—it’s a reflection on what recruiting can be when done with care, transparency, and genuine investment in both sides of the process.</p>\n</blockquote>",
      "date_published": "2026-02-11T00:00:00.000Z",
      "tags": [
        "Career",
        "Hiring",
        "Leadership",
        "Software Engineering",
        "Developer Philosophy"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-02-04-claude-code-skill-codebase-synchronization/",
      "url": "https://kobi.kadosh.me/blog/2026-02-04-claude-code-skill-codebase-synchronization/",
      "title": "Building a Claude Code Skill for Codebase Synchronization",
      "summary": "Documentation drift is the silent killer of codebases. Your roadmap says one thing, your website another, and your README hasn't been updated in months. Here's how I built a pluggable Claude Code skill that detects drift, shows exactly what's out of sync, and fixes it—with your approval.",
      "content_html": "<h2 id=\"the-problem-everyone-ignores\">The Problem Everyone Ignores</h2>\n<p>Every codebase I’ve worked on has the same disease: <strong>documentation drift</strong>.</p>\n<p>It starts innocently. You update your roadmap in GitHub milestones because that’s where the real work happens. The ROADMAP.md file gets updated… eventually. The public roadmap page on your website? Maybe next quarter.</p>\n<p>Three months later, your GitHub shows 21 issues in the v1.1.0 milestone with 5% completion. Your ROADMAP.md claims 16 issues with 7% completion. Your website says “Coming Soon” for features you shipped last month.</p>\n<p>This isn’t laziness. It’s the natural entropy of information distributed across multiple locations. <strong>Every duplicated source of truth is a lie waiting to happen.</strong></p>\n<p>I got tired of the manual hunt-and-fix cycle. So I built a Claude Code skill to automate it.</p>\n<hr/>\n<h2 id=\"what-is-claude-code\">What is Claude Code?</h2>\n<p>Before diving into the solution, a quick primer for those unfamiliar: <a href=\"https://docs.anthropic.com/en/docs/claude-code\">Claude Code</a> is Anthropic’s official CLI for Claude. It’s an AI-powered terminal tool that can read your codebase, execute commands, edit files, and—crucially—be extended with custom <strong>skills</strong> and <strong>commands</strong>.</p>\n<p>Skills are Markdown files that define specialized capabilities. When you invoke a skill, Claude Code reads those instructions and gains context about how to perform a particular task. Think of them as domain-specific prompts that turn a general-purpose AI into a specialized tool.</p>\n<p>Commands are user-invokable actions that route to skills or perform specific operations.</p>\n<p>This architecture creates something powerful: <strong>pluggable, versioned, shareable AI workflows</strong> that live in your repository alongside your code.</p>\n<hr/>\n<h2 id=\"the-architecture-ground-truth-and-drift-detection\">The Architecture: Ground Truth and Drift Detection</h2>\n<p>The skill I built is called <code>code-parts-syncer</code>. Its core insight is simple but powerful:</p>\n<blockquote>\n<p><strong>Every sync domain needs exactly one source of truth. Everything else derives from it.</strong></p>\n</blockquote>\n<p>For roadmap data, the ground truth is GitHub (milestones and issues). For project instructions, it might be <code>CLAUDE.md</code>. For documentation, perhaps your website or README.</p>\n<p>The skill implements a consistent five-step process across all sync domains:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>┌─────────────────────────────────────────────────────────────────┐</span></span>\n<span class=\"line\"><span>│                     CODE-PARTS-SYNCER                           │</span></span>\n<span class=\"line\"><span>├─────────────────────────────────────────────────────────────────┤</span></span>\n<span class=\"line\"><span>│                                                                 │</span></span>\n<span class=\"line\"><span>│   1. FETCH GROUND TRUTH                                         │</span></span>\n<span class=\"line\"><span>│      └── Query authoritative source (e.g., GitHub API)          │</span></span>\n<span class=\"line\"><span>│                                                                 │</span></span>\n<span class=\"line\"><span>│   2. READ CURRENT STATE                                         │</span></span>\n<span class=\"line\"><span>│      └── Extract values from all target files                   │</span></span>\n<span class=\"line\"><span>│                                                                 │</span></span>\n<span class=\"line\"><span>│   3. DETECT DRIFT                                               │</span></span>\n<span class=\"line\"><span>│      └── Compare ground truth with current state                │</span></span>\n<span class=\"line\"><span>│                                                                 │</span></span>\n<span class=\"line\"><span>│   4. REPORT DRIFT                                               │</span></span>\n<span class=\"line\"><span>│      └── Display discrepancies with ✓/✗ indicators              │</span></span>\n<span class=\"line\"><span>│                                                                 │</span></span>\n<span class=\"line\"><span>│   5. APPLY UPDATES (with user confirmation)                     │</span></span>\n<span class=\"line\"><span>│      └── Edit files to align with ground truth                  │</span></span>\n<span class=\"line\"><span>│                                                                 │</span></span>\n<span class=\"line\"><span>└─────────────────────────────────────────────────────────────────┘</span></span></code></pre></div>\n<p>This pattern scales to any sync domain: roadmaps, documentation, instructions, configuration—anything that exists in multiple places but should stay consistent.</p>\n<hr/>\n<h2 id=\"file-structure-modular-by-design\">File Structure: Modular by Design</h2>\n<p>The skill lives entirely within the <code>.claude/</code> directory, following Claude Code’s conventions:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>.claude/</span></span>\n<span class=\"line\"><span>├── commands/</span></span>\n<span class=\"line\"><span>│   └── caro.sync.md              # Main command dispatcher</span></span>\n<span class=\"line\"><span>└── skills/</span></span>\n<span class=\"line\"><span>    └── code-parts-syncer/</span></span>\n<span class=\"line\"><span>        ├── README.md              # Overview documentation</span></span>\n<span class=\"line\"><span>        ├── roadmap-sync.md        # GitHub ↔ ROADMAP.md ↔ website</span></span>\n<span class=\"line\"><span>        ├── docs-sync.md           # README ↔ website ↔ CLAUDE.md</span></span>\n<span class=\"line\"><span>        └── instructions-sync.md   # CLAUDE.md ↔ CONTRIBUTING ↔ README</span></span></code></pre></div>\n<p>Each module is independent. The roadmap syncer knows nothing about documentation sync. This separation makes the system easier to understand, test, and extend.</p>\n<p>The total implementation is approximately <strong>1,766 lines of Markdown</strong>—substantial enough to be thorough, small enough to audit and understand.</p>\n<hr/>\n<h2 id=\"the-roadmap-sync-module-a-deep-dive\">The Roadmap Sync Module: A Deep Dive</h2>\n<p>Let me walk through the fully functional module: roadmap synchronization.</p>\n<h3 id=\"the-sources\">The Sources</h3>\n<p>This module keeps three sources in sync:</p>\n<ol>\n<li><strong>GitHub API</strong> (milestones + issues) — The ground truth</li>\n<li><strong>ROADMAP.md</strong> — Markdown documentation for developers</li>\n<li><strong>website/src/pages/roadmap.astro</strong> — Public-facing roadmap page</li>\n</ol>\n<p>GitHub is authoritative because that’s where work actually happens. Issues get created, closed, moved between milestones. Any process that requires manual duplication will fall behind.</p>\n<h3 id=\"ground-truth-extraction\">Ground Truth Extraction</h3>\n<p>The first step queries GitHub using the <code>gh</code> CLI:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Fetch all milestones with issue counts</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">gh</span><span style=\"color:#98C379\"> api</span><span style=\"color:#98C379\"> repos/owner/repo/milestones</span><span style=\"color:#D19A66\"> --jq</span><span style=\"color:#98C379\"> &#39;.[] | {</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  title,</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  description,</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  due_on,</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  open_issues,</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  closed_issues,</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  state</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">}&#39;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># For each milestone, fetch its issues</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">gh</span><span style=\"color:#98C379\"> api</span><span style=\"color:#98C379\"> repos/owner/repo/issues?milestone=</span><span style=\"color:#D19A66\">1</span><span style=\"color:#ABB2BF\">&amp;</span><span style=\"color:#E06C75\">state</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#98C379\">all</span><span style=\"color:#61AFEF\"> --jq</span><span style=\"color:#98C379\"> &#39;.[] | {</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  title,</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  number,</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  state,</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">  labels: [.labels[].name]</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">}&#39;</span></span></code></pre></div>\n<p>This gives us the real data: actual issue counts, actual completion percentages, actual due dates.</p>\n<h3 id=\"drift-detection\">Drift Detection</h3>\n<p>Next, the skill parses the current state from each target file. For ROADMAP.md, it looks for structured sections:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">markdown</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"markdown\"><code><span class=\"line\"><span style=\"color:#E06C75\">## v1.1.0 - Core Improvements</span></span>\n<span class=\"line\"><span style=\"color:#E5C07B\">-</span><span style=\"color:#D19A66\"> **Status**</span><span style=\"color:#ABB2BF\">: 15 open, 1 closed (7% complete)</span></span>\n<span class=\"line\"><span style=\"color:#E5C07B\">-</span><span style=\"color:#D19A66\"> **Due**</span><span style=\"color:#ABB2BF\">: March 15, 2026 (48 days)</span></span></code></pre></div>\n<p>For the Astro website component, it parses the data structures:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">astro</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"astro\"><code><span class=\"line\"><span style=\"color:#ABB2BF\">const milestones = [</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">  {</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">    version</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#98C379\">&quot;v1.1.0&quot;</span><span style=\"color:#ABB2BF\">,</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">    title</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#98C379\">&quot;Core Improvements&quot;</span><span style=\"color:#ABB2BF\">,</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">    openIssues</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#D19A66\">20</span><span style=\"color:#ABB2BF\">,</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">    closedIssues</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#D19A66\">1</span><span style=\"color:#ABB2BF\">,</span></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\">    // ...</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">  }</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">];</span></span></code></pre></div>\n<p>Then comes the comparison. For each milestone, the skill checks:</p>\n<ul>\n<li>Issue counts match</li>\n<li>Completion percentages match</li>\n<li>Due dates match</li>\n<li>Issue lists match (if detailed tracking is enabled)</li>\n</ul>\n<h3 id=\"the-drift-report\">The Drift Report</h3>\n<p>Here’s what a real drift report looks like:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>================================================================================</span></span>\n<span class=\"line\"><span>                        ROADMAP DRIFT DETECTION</span></span>\n<span class=\"line\"><span>================================================================================</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Source of Truth: GitHub API (gh api repos/wildcard/caro/milestones)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>v1.1.0 - Core Improvements:</span></span>\n<span class=\"line\"><span>  GitHub (TRUTH):    20 open, 1 closed, 21 total (5% complete, 46 days)</span></span>\n<span class=\"line\"><span>  ROADMAP.md:        15 open, 1 closed, 16 total (7% complete, 48 days) ✗ DRIFT</span></span>\n<span class=\"line\"><span>  website/roadmap:   20 open, 1 closed, 21 total (5% complete, 46 days) ✓ SYNCED</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>v1.2.0 - Extended Platforms:</span></span>\n<span class=\"line\"><span>  GitHub (TRUTH):    8 open, 0 closed, 8 total (0% complete, 90 days)</span></span>\n<span class=\"line\"><span>  ROADMAP.md:        8 open, 0 closed, 8 total (0% complete, 90 days) ✓ SYNCED</span></span>\n<span class=\"line\"><span>  website/roadmap:   8 open, 0 closed, 8 total (0% complete, 90 days) ✓ SYNCED</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>================================================================================</span></span>\n<span class=\"line\"><span>                              SUMMARY</span></span>\n<span class=\"line\"><span>================================================================================</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  ROADMAP.md:        1 milestone needs updates</span></span>\n<span class=\"line\"><span>  website/roadmap:   All synced ✓</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  Recommended action: /caro.sync roadmap → Apply updates now</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>================================================================================</span></span></code></pre></div>\n<p>This report tells you exactly what’s wrong without making any changes. The <code>--check</code> flag runs drift detection only—a dry run that shows problems without fixing them.</p>\n<h3 id=\"safe-updates\">Safe Updates</h3>\n<p>When you’re ready to fix the drift, the skill:</p>\n<ol>\n<li>Shows a preview of proposed changes</li>\n<li>Asks for explicit confirmation</li>\n<li>Applies edits using Claude Code’s file editing capabilities</li>\n<li>Shows a git diff of all changes</li>\n</ol>\n<p>No silent overwrites. No surprises. You see exactly what will change before it happens.</p>\n<hr/>\n<h2 id=\"usage-examples\">Usage Examples</h2>\n<p>The command dispatcher makes this ergonomic:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Show available sync modules</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">/caro.sync</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Full roadmap sync with updates</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">/caro.sync</span><span style=\"color:#98C379\"> roadmap</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Drift detection only (dry run)</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">/caro.sync</span><span style=\"color:#D19A66\"> --check</span><span style=\"color:#98C379\"> roadmap</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Check all modules for drift</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">/caro.sync</span><span style=\"color:#D19A66\"> --check</span><span style=\"color:#98C379\"> all</span></span></code></pre></div>\n<p>The compound <code>all</code> option runs every enabled module, giving you a dashboard view of documentation health across your entire codebase.</p>\n<hr/>\n<h2 id=\"planned-modules-docs-and-instructions\">Planned Modules: Docs and Instructions</h2>\n<p>The architecture supports additional sync domains. Two are planned:</p>\n<h3 id=\"documentation-sync-docs-syncmd\">Documentation Sync (docs-sync.md)</h3>\n<p>Keeps installation instructions, feature lists, and CLI examples consistent across:</p>\n<ul>\n<li>README.md</li>\n<li>Website documentation pages</li>\n<li><code>CLAUDE.md</code> (AI context file)</li>\n<li>Inline Rust doc comments</li>\n</ul>\n<p>The ground truth here is contextual—README for user-facing docs, <code>CLAUDE.md</code> for development context.</p>\n<h3 id=\"instructions-sync-instructions-syncmd\">Instructions Sync (instructions-sync.md)</h3>\n<p>Synchronizes project descriptions and development workflows across:</p>\n<ul>\n<li><code>CLAUDE.md</code> (primary for development instructions)</li>\n<li>README.md (user-facing)</li>\n<li>CONTRIBUTING.md (contributor guidelines)</li>\n<li>About page on website</li>\n</ul>\n<p>Each module follows the same five-step pattern. Adding a new sync domain means writing a new Markdown file that defines sources, ground truth, and sync logic.</p>\n<hr/>\n<h2 id=\"the-development-workflow\">The Development Workflow</h2>\n<p>Here’s how I built and shipped this skill—a process that’s itself worth documenting.</p>\n<h3 id=\"using-worktrees-for-isolation\">Using Worktrees for Isolation</h3>\n<p>I use a git worktree pattern for feature development:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Create a dedicated worktree for the feature</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">git</span><span style=\"color:#98C379\"> worktree</span><span style=\"color:#98C379\"> add</span><span style=\"color:#98C379\"> .worktrees/010-code-parts-syncer</span><span style=\"color:#D19A66\"> -b</span><span style=\"color:#98C379\"> feature/code-parts-syncer</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Work in isolation</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">cd</span><span style=\"color:#98C379\"> .worktrees/010-code-parts-syncer</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># All changes happen here, main branch stays clean</span></span></code></pre></div>\n<p>Worktrees give you parallel working directories on different branches. I can experiment freely without polluting main or creating WIP commits.</p>\n<h3 id=\"commit-message-convention\">Commit Message Convention</h3>\n<p>The commit follows <a href=\"https://www.conventionalcommits.org/\">Conventional Commits</a>:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">yaml</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"yaml\"><code><span class=\"line\"><span style=\"color:#E06C75\">feat</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#98C379\">Add code-parts-syncer skill for drift detection and sync</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#98C379\">Implements a pluggable sync skill to keep related content synchronized</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">across different parts of the codebase (roadmap, docs, instructions).</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#C678DD\">*</span><span style=\"color:#E06C75\">*Architecture**:</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#E06C75\">Main command dispatcher</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#98C379\">/caro.sync</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#98C379\">Pluggable module system for different sync domains</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#98C379\">Ground truth pattern (authoritative source per domain)</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#98C379\">Drift detection before updates</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#98C379\">Safe updates with user confirmation</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#C678DD\">*</span><span style=\"color:#E06C75\">*Modules**:</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#E06C75\">✓ roadmap-sync.md</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#98C379\">Functional - syncs ROADMAP.md ↔ website ↔ GitHub API</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#E06C75\">⋯ docs-sync.md</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#98C379\">Placeholder - will sync README ↔ website ↔ CLAUDE.md</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#E06C75\">⋯ instructions-sync.md</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#98C379\">Placeholder - will sync CLAUDE.md ↔ README ↔ CONTRIBUTING</span></span></code></pre></div>\n<p>Clear, structured, and informative. Anyone reading the git log understands what changed and why.</p>\n<h3 id=\"ship-one-working-module\">Ship One Working Module</h3>\n<p>Notice that only the roadmap module is fully implemented. The others are placeholders with architecture but no logic.</p>\n<p>This is intentional. <strong>Ship what works, document what’s planned.</strong> A single working module proves the pattern. The placeholders show where it’s going without blocking the release.</p>\n<hr/>\n<h2 id=\"key-design-decisions\">Key Design Decisions</h2>\n<h3 id=\"why-ground-truth-matters\">Why Ground Truth Matters</h3>\n<p>Without a clear source of truth, sync becomes a nightmare. Which version is correct? If ROADMAP.md says 15 issues and GitHub says 21, which wins?</p>\n<p>The ground truth pattern eliminates this ambiguity. GitHub is authoritative for roadmap data—period. Everything else derives from it. No debates, no merge conflicts of intent.</p>\n<h3 id=\"why-drift-detection-before-updates\">Why Drift Detection Before Updates</h3>\n<p>The obvious approach is “just sync everything.” But that’s dangerous:</p>\n<ul>\n<li>You might overwrite intentional variations</li>\n<li>You lose visibility into what’s changing</li>\n<li>Automation without understanding leads to accidents</li>\n</ul>\n<p>Drift detection first means you always know what’s out of sync. You can review, understand, and approve. The tool informs; you decide.</p>\n<h3 id=\"why-user-confirmation\">Why User Confirmation</h3>\n<p>Claude Code can edit files directly. It would be easy to make sync fully automatic. But that trades safety for convenience—a bad bargain.</p>\n<p>The confirmation step keeps humans in the loop. You see the proposed changes, you approve them, they happen. No surprises.</p>\n<h3 id=\"why-markdown-for-skills\">Why Markdown for Skills</h3>\n<p>Skills are Markdown files, not code. This has several advantages:</p>\n<ul>\n<li><strong>Readable</strong>: Anyone can understand what a skill does</li>\n<li><strong>Auditable</strong>: No hidden logic in compiled binaries</li>\n<li><strong>Versioned</strong>: Lives in git with your code</li>\n<li><strong>Shareable</strong>: Copy the file to share the capability</li>\n<li><strong>Editable</strong>: Modify behavior without recompilation</li>\n</ul>\n<p>The trade-off is that complex logic requires verbose instructions. But for most workflows, Markdown is expressive enough and far more transparent.</p>\n<hr/>\n<h2 id=\"results-and-impact\">Results and Impact</h2>\n<p>After implementing the roadmap sync:</p>\n<p><strong>Before</strong>:</p>\n<ul>\n<li>Manual checking once a month (if I remembered)</li>\n<li>Drift detected by users pointing out inconsistencies</li>\n<li>Updates required editing 2-3 files manually</li>\n<li>30+ minutes per sync cycle</li>\n</ul>\n<p><strong>After</strong>:</p>\n<ul>\n<li>Instant drift detection with <code>/caro.sync --check roadmap</code></li>\n<li>Automated updates with single confirmation</li>\n<li>Git diff visibility for all changes</li>\n<li>Under 2 minutes for full sync</li>\n</ul>\n<p>More importantly, I now <strong>know</strong> when things are out of sync. The mental overhead of “I should probably check the roadmap files” is gone.</p>\n<hr/>\n<h2 id=\"lessons-for-building-your-own-skills\">Lessons for Building Your Own Skills</h2>\n<h3 id=\"start-with-one-module\">Start With One Module</h3>\n<p>It’s tempting to design the perfect universal sync system. Don’t. Build one module that solves one problem. Prove the pattern works. Then expand.</p>\n<p>The roadmap syncer taught me what the architecture needed. Building it first made the placeholder modules easier to design.</p>\n<h3 id=\"design-for-extension\">Design for Extension</h3>\n<p>The pluggable module pattern means adding new sync domains is trivial:</p>\n<ol>\n<li>Create a new Markdown file in <code>.claude/skills/code-parts-syncer/</code></li>\n<li>Define sources, ground truth, and sync logic</li>\n<li>Register it in the command dispatcher</li>\n</ol>\n<p>No refactoring. No breaking changes. Just add a file.</p>\n<h3 id=\"safety-is-non-negotiable\">Safety Is Non-Negotiable</h3>\n<p>Any tool that modifies files must be safe by default. Drift detection without modification is the baseline. User confirmation for changes is required. Git diff visibility is essential.</p>\n<p>Build trust through transparency, not through hoping nothing goes wrong.</p>\n<h3 id=\"documentation-is-part-of-the-skill\">Documentation Is Part of the Skill</h3>\n<p>The skill includes a README.md that explains the architecture, lists modules, and shows usage examples. When someone invokes <code>/caro.sync</code> without arguments, they get help.</p>\n<p>Good tools are self-documenting. The skill should explain itself.</p>\n<hr/>\n<h2 id=\"future-enhancements\">Future Enhancements</h2>\n<h3 id=\"automated-pre-commit-checks\">Automated Pre-Commit Checks</h3>\n<p>Imagine a pre-commit hook that runs <code>/caro.sync --check all</code>. If any drift is detected, the commit fails with a helpful message:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>❌ Commit blocked: Documentation drift detected</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  ROADMAP.md is out of sync with GitHub milestones.</span></span>\n<span class=\"line\"><span>  Run `/caro.sync roadmap` to fix, then commit again.</span></span></code></pre></div>\n<p>No more shipping inconsistent documentation.</p>\n<h3 id=\"cicd-integration\">CI/CD Integration</h3>\n<p>A GitHub Action that runs drift detection on every PR:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">yaml</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"yaml\"><code><span class=\"line\"><span style=\"color:#ABB2BF\">- </span><span style=\"color:#E06C75\">name</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#98C379\">Check Documentation Drift</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">  run</span><span style=\"color:#ABB2BF\">: </span><span style=\"color:#C678DD\">|</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">    claude-code /caro.sync --check all --ci</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">    if [ $? -ne 0 ]; then</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">      echo &quot;::error::Documentation drift detected. Run /caro.sync locally.&quot;</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">      exit 1</span></span>\n<span class=\"line\"><span style=\"color:#98C379\">    fi</span></span></code></pre></div>\n<p>PRs that introduce drift get flagged automatically.</p>\n<h3 id=\"health-dashboard\">Health Dashboard</h3>\n<p>A <code>/caro.sync status --all</code> command that shows sync health across all modules:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>================================================================================</span></span>\n<span class=\"line\"><span>                     CODEBASE SYNC HEALTH DASHBOARD</span></span>\n<span class=\"line\"><span>================================================================================</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  Module              Status    Last Sync    Issues</span></span>\n<span class=\"line\"><span>  ─────────────────────────────────────────────────</span></span>\n<span class=\"line\"><span>  roadmap-sync        ✓ OK      2 hours ago  0</span></span>\n<span class=\"line\"><span>  docs-sync           ✗ DRIFT   5 days ago   3 files outdated</span></span>\n<span class=\"line\"><span>  instructions-sync   ✓ OK      1 day ago    0</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  Overall Health: NEEDS ATTENTION</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  Run `/caro.sync docs` to resolve.</span></span>\n<span class=\"line\"><span>================================================================================</span></span></code></pre></div>\n<p>Visibility drives action. If you can see the problem, you’ll fix it.</p>\n<hr/>\n<h2 id=\"try-it-yourself\">Try It Yourself</h2>\n<p>If you’re using Claude Code and maintaining documentation across multiple locations, this pattern is immediately applicable.</p>\n<p>The key insights are:</p>\n<ol>\n<li><strong>Define your ground truth</strong> for each sync domain</li>\n<li><strong>Detect drift</strong> before making any changes</li>\n<li><strong>Require confirmation</strong> for updates</li>\n<li><strong>Show git diffs</strong> for transparency</li>\n<li><strong>Make it modular</strong> for extensibility</li>\n</ol>\n<p>You don’t need my specific skill. You need the pattern. Build a sync skill for your own codebase, tailored to your own sources of truth.</p>\n<p>Documentation drift is a solved problem—if you have the right tools.</p>\n<hr/>\n<h2 id=\"conclusion-treat-documentation-like-code\">Conclusion: Treat Documentation Like Code</h2>\n<p>We lint our code. We test our code. We version our code. We review our code before merging.</p>\n<p>Why don’t we do the same for documentation?</p>\n<p>The answer is usually “it’s too hard” or “there’s no tooling.” Claude Code changes that equation. Skills give you the ability to encode documentation maintenance as automated workflows.</p>\n<p>The <code>code-parts-syncer</code> skill is one implementation of a broader idea: <strong>documentation as code, with all the rigor that implies.</strong> Ground truth sources. Drift detection. Automated sync. Auditable changes.</p>\n<p>Your roadmap should never lie. Your README should never be stale. Your website should never contradict your repository.</p>\n<p>Build the tools to make it so.</p>\n<hr/>\n<blockquote>\n<p><strong>About this post:</strong> This essay documents a real skill built for the <a href=\"https://github.com/wildcard/caro\">Caro</a> project—a Rust CLI that converts natural language to shell commands. The code-parts-syncer skill lives in the repository and is actively used to keep roadmap documentation synchronized.</p>\n</blockquote>\n<p>If you’re interested in Claude Code skills, check out the <a href=\"https://docs.anthropic.com/en/docs/claude-code\">official documentation</a> for getting started. The pattern described here—ground truth, drift detection, safe updates—applies to any synchronization problem you might face.</p>\n<p><strong>The best documentation is documentation that maintains itself.</strong></p>",
      "date_published": "2026-02-04T00:00:00.000Z",
      "tags": [
        "Claude Code",
        "Developer Tools",
        "Documentation",
        "Automation",
        "Software Engineering",
        "DevTools"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-02-04-comprehensive-zsh-completions-troubleshooting/",
      "url": "https://kobi.kadosh.me/blog/2026-02-04-comprehensive-zsh-completions-troubleshooting/",
      "title": "Comprehensive Zsh Completions Setup & Troubleshooting on macOS",
      "summary": "A deep dive into fixing broken zsh tab completions with Oh My Zsh, and how I turned the solution into a Claude Code skill. Learn why fpath order matters, how compinit timing breaks completions, and the pattern of encoding debugging knowledge for AI assistants.",
      "content_html": "<h2 id=\"the-moment-everything-clicked\">The Moment Everything Clicked</h2>\n<p>I’d been fighting with my shell completions for weeks. <code>brew &lt;TAB&gt;</code> showed nothing. <code>rustup &lt;TAB&gt;</code> was silent. <code>cargo &lt;TAB&gt;</code>—the tool I use dozens of times daily—gave me nothing but a blinking cursor.</p>\n<p>I tried the usual fixes: reinstalling Oh My Zsh, adding the zsh-completions plugin, running <code>compinit</code> manually. Nothing worked. Every Stack Overflow answer led to the same vague advice: “just add it to your fpath.”</p>\n<p>Then I discovered the root cause, and it was so simple I almost laughed.</p>\n<p><strong>The problem wasn’t what I was adding. It was when I was adding it.</strong></p>\n<hr/>\n<h2 id=\"the-core-problem-timing-matters\">The Core Problem: Timing Matters</h2>\n<p>Here’s what I had in my <code>.zshrc</code>:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># My broken configuration</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">source</span><span style=\"color:#E06C75\"> $ZSH</span><span style=\"color:#98C379\">/oh-my-zsh.sh</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">fpath</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#ABB2BF\">(</span><span style=\"color:#98C379\">&quot;$(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#98C379\">)/share/zsh/site-functions&quot;</span><span style=\"color:#E06C75\"> $fpath</span><span style=\"color:#ABB2BF\">)  </span><span style=\"color:#7F848E;font-style:italic\"># Wrong!</span></span></code></pre></div>\n<p>And here’s why it didn’t work:</p>\n<p>Oh My Zsh calls <code>compinit</code> automatically when <code>oh-my-zsh.sh</code> is sourced. <code>compinit</code> is the function that scans your <code>fpath</code> directories and caches all completion functions. Any <code>fpath</code> modifications <strong>after</strong> this line are invisible to the completion system because <code>compinit</code> has already scanned and cached the paths.</p>\n<p>It’s like adding books to a library catalog after the index has been printed. The books are there, but no one can find them.</p>\n<p>The fix is embarrassingly simple:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># The correct configuration</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">fpath</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#ABB2BF\">(</span><span style=\"color:#98C379\">&quot;$(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#98C379\">)/share/zsh/site-functions&quot;</span><span style=\"color:#E06C75\"> $fpath</span><span style=\"color:#ABB2BF\">)  </span><span style=\"color:#7F848E;font-style:italic\"># First!</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">source</span><span style=\"color:#E06C75\"> $ZSH</span><span style=\"color:#98C379\">/oh-my-zsh.sh</span><span style=\"color:#7F848E;font-style:italic\">  # Then compinit sees it</span></span></code></pre></div>\n<p><strong>Fpath first, then source Oh My Zsh.</strong></p>\n<hr/>\n<h2 id=\"the-complete-fix-a-step-by-step-guide\">The Complete Fix: A Step-by-Step Guide</h2>\n<h3 id=\"step-1-fix-your-zshrc-configuration\">Step 1: Fix Your ~/.zshrc Configuration</h3>\n<p>Open your <code>.zshrc</code> and ensure your <code>fpath</code> modifications come <strong>before</strong> the Oh My Zsh source line:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Path to Oh My Zsh</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">export</span><span style=\"color:#E06C75\"> ZSH</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$HOME</span><span style=\"color:#98C379\">/.oh-my-zsh&quot;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Theme</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">ZSH_THEME</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#98C379\">&quot;robbyrussell&quot;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Plugins</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">plugins</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#ABB2BF\">(</span><span style=\"color:#98C379\">git</span><span style=\"color:#98C379\"> brew</span><span style=\"color:#98C379\"> docker</span><span style=\"color:#98C379\"> 1password</span><span style=\"color:#98C379\"> zsh-autosuggestions</span><span style=\"color:#98C379\"> fast-syntax-highlighting</span><span style=\"color:#ABB2BF\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># === COMPLETIONS (BEFORE oh-my-zsh.sh) ===</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># zsh-completions plugin (200+ additional completions)</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">fpath</span><span style=\"color:#C678DD\">+=</span><span style=\"color:#ABB2BF\">${</span><span style=\"color:#E06C75\">ZSH_CUSTOM</span><span style=\"color:#ABB2BF\">:-${</span><span style=\"color:#E06C75\">ZSH</span><span style=\"color:#ABB2BF\">:-~/.</span><span style=\"color:#E06C75\">oh-my-zsh</span><span style=\"color:#ABB2BF\">}/</span><span style=\"color:#E06C75\">custom</span><span style=\"color:#ABB2BF\">}</span><span style=\"color:#98C379\">/plugins/zsh-completions/src</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Homebrew completions (Apple Silicon + Intel compatible)</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">if</span><span style=\"color:#56B6C2\"> type</span><span style=\"color:#98C379\"> brew</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">  fpath</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#ABB2BF\">(</span><span style=\"color:#98C379\">&quot;$(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#98C379\">)/share/zsh/site-functions&quot;</span><span style=\"color:#E06C75\"> $fpath</span><span style=\"color:#ABB2BF\">)</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">fi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Custom user completions (optional)</span></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># fpath=(~/.zsh/completions $fpath)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># === LOAD OH MY ZSH ===</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">source</span><span style=\"color:#E06C75\"> $ZSH</span><span style=\"color:#98C379\">/oh-my-zsh.sh</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># === POST-LOAD INITIALIZATION ===</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">eval</span><span style=\"color:#98C379\"> &quot;$(</span><span style=\"color:#61AFEF\">starship</span><span style=\"color:#98C379\"> init zsh)&quot;</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">eval</span><span style=\"color:#98C379\"> &quot;$(</span><span style=\"color:#61AFEF\">zoxide</span><span style=\"color:#98C379\"> init zsh)&quot;</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">eval</span><span style=\"color:#98C379\"> &quot;$(</span><span style=\"color:#61AFEF\">atuin</span><span style=\"color:#98C379\"> init zsh)&quot;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#C678DD\">export</span><span style=\"color:#E06C75\"> PATH</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$HOME</span><span style=\"color:#98C379\">/.local/bin:</span><span style=\"color:#E06C75\">$PATH</span><span style=\"color:#98C379\">&quot;</span></span></code></pre></div>\n<h3 id=\"step-2-remove-manual-compinit-calls\">Step 2: Remove Manual compinit Calls</h3>\n<p>If you have a line like this anywhere in your <code>.zshrc</code>, <strong>delete it</strong>:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># DELETE THIS LINE if present</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">autoload</span><span style=\"color:#D19A66\"> -U</span><span style=\"color:#98C379\"> compinit</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#61AFEF\">compinit</span></span></code></pre></div>\n<p>Oh My Zsh handles <code>compinit</code> automatically. Having multiple calls causes confusion and can slow down shell startup.</p>\n<h3 id=\"step-3-rebuild-the-completion-cache\">Step 3: Rebuild the Completion Cache</h3>\n<p>After making changes to your <code>.zshrc</code>, you need to rebuild the completion cache:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#61AFEF\">rm</span><span style=\"color:#D19A66\"> -f</span><span style=\"color:#98C379\"> ~/.zcompdump</span><span style=\"color:#E5C07B\">*</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">exec</span><span style=\"color:#98C379\"> zsh</span></span></code></pre></div>\n<p>This removes all cached completion data and starts a fresh shell session.</p>\n<hr/>\n<h2 id=\"the-hidden-treasure-tools-that-generate-their-own-completions\">The Hidden Treasure: Tools That Generate Their Own Completions</h2>\n<p>Here’s something I didn’t know until I dug into this: many modern CLI tools can generate their own zsh completions, but they don’t install them automatically. You have to ask.</p>\n<h3 id=\"tools-with-built-in-completion-generation\">Tools with Built-in Completion Generation</h3>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<table><thead><tr><th>Tool</th><th>Command</th><th>Category</th></tr></thead><tbody><tr><td>rustup</td><td><code>rustup completions zsh rustup &gt; $(brew --prefix)/share/zsh/site-functions/_rustup</code></td><td>Rust toolchain</td></tr><tr><td>cargo</td><td><code>rustup completions zsh cargo &gt; $(brew --prefix)/share/zsh/site-functions/_cargo</code></td><td>Rust packages</td></tr><tr><td>starship</td><td><code>starship completions zsh &gt; $(brew --prefix)/share/zsh/site-functions/_starship</code></td><td>Shell prompt</td></tr><tr><td>uv</td><td><code>uv generate-shell-completion zsh &gt; $(brew --prefix)/share/zsh/site-functions/_uv</code></td><td>Python packages</td></tr><tr><td>atuin</td><td><code>atuin gen-completions --shell zsh &gt; $(brew --prefix)/share/zsh/site-functions/_atuin</code></td><td>Shell history</td></tr><tr><td>op</td><td><code>op completion zsh &gt; $(brew --prefix)/share/zsh/site-functions/_op</code></td><td>1Password CLI</td></tr><tr><td>fclones</td><td><code>fclones complete zsh &gt; $(brew --prefix)/share/zsh/site-functions/_fclones</code></td><td>Duplicate finder</td></tr><tr><td>rclone</td><td><code>rclone completion zsh - &gt; $(brew --prefix)/share/zsh/site-functions/_rclone</code></td><td>Cloud sync (note the <code>-</code>)</td></tr><tr><td>gh</td><td><code>gh completion -s zsh &gt; $(brew --prefix)/share/zsh/site-functions/_gh</code></td><td>GitHub CLI</td></tr></tbody></table>\n<h3 id=\"discovery-patterns\">Discovery Patterns</h3>\n<p>Not sure if a tool supports completion generation? Try these patterns:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Most common patterns</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">&lt;tool&gt; completion zsh        </span><span style=\"color:#7F848E;font-style:italic\"># Most common</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">&lt;tool&gt; completions zsh       </span><span style=\"color:#7F848E;font-style:italic\"># Rust tools</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">&lt;tool&gt; complete zsh          </span><span style=\"color:#7F848E;font-style:italic\"># Alternative</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">&lt;tool&gt; gen-completions --shell zsh    </span><span style=\"color:#7F848E;font-style:italic\"># Some tools</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">&lt;tool&gt; generate-shell-completion zsh  </span><span style=\"color:#7F848E;font-style:italic\"># Modern tools</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Discovery command</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">&lt;tool&gt; --help | </span><span style=\"color:#61AFEF\">grep</span><span style=\"color:#D19A66\"> -i</span><span style=\"color:#98C379\"> completion</span></span></code></pre></div>\n<hr/>\n<h2 id=\"verification-proving-it-works\">Verification: Proving It Works</h2>\n<p>After making changes, you need to verify completions are actually loaded.</p>\n<h3 id=\"check-if-completions-exist\">Check if Completions Exist</h3>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#61AFEF\">whence</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> _brew</span><span style=\"color:#7F848E;font-style:italic\">     # Should show path</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">whence</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> _rustup</span><span style=\"color:#7F848E;font-style:italic\">   # Should show path</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">whence</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> _cargo</span><span style=\"color:#7F848E;font-style:italic\">    # Should show path</span></span></code></pre></div>\n<p>If these return “not found,” the completion isn’t loaded. If they show a path, you’re in business.</p>\n<h3 id=\"view-current-fpath\">View Current fpath</h3>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#56B6C2\">echo</span><span style=\"color:#E06C75\"> $fpath</span><span style=\"color:#ABB2BF\"> | </span><span style=\"color:#61AFEF\">tr</span><span style=\"color:#98C379\"> &quot; &quot;</span><span style=\"color:#98C379\"> &quot;\\n&quot;</span></span></code></pre></div>\n<p>This should include:</p>\n<ul>\n<li><code>/opt/homebrew/share/zsh/site-functions</code> (or <code>/usr/local/...</code> on Intel)</li>\n<li><code>~/.oh-my-zsh/custom/plugins/zsh-completions/src</code></li>\n</ul>\n<h3 id=\"test-tab-completion\">Test Tab Completion</h3>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#61AFEF\">brew</span><span style=\"color:#ABB2BF\"> &lt;</span><span style=\"color:#98C379\">TA</span><span style=\"color:#ABB2BF\">B&gt;</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">rustup</span><span style=\"color:#ABB2BF\"> &lt;</span><span style=\"color:#98C379\">TA</span><span style=\"color:#ABB2BF\">B&gt;</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">cargo</span><span style=\"color:#ABB2BF\"> &lt;</span><span style=\"color:#98C379\">TA</span><span style=\"color:#ABB2BF\">B&gt;</span></span></code></pre></div>\n<p>You should see a list of available commands and options.</p>\n<hr/>\n<h2 id=\"common-troubleshooting-scenarios\">Common Troubleshooting Scenarios</h2>\n<h3 id=\"issue-1-completions-generated-but-not-found\">Issue 1: Completions Generated but Not Found</h3>\n<p><strong>Symptom</strong>: <code>whence -v _rustup</code> shows “not found”</p>\n<p><strong>Cause</strong>: Completion cache not rebuilt after generating new completions</p>\n<p><strong>Fix</strong>:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">rm</span><span style=\"color:#D19A66\"> -f</span><span style=\"color:#98C379\"> ~/.zcompdump</span><span style=\"color:#E5C07B\">*</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">exec</span><span style=\"color:#98C379\"> zsh</span></span></code></pre></div>\n<h3 id=\"issue-2-permission-denied-writing-completions\">Issue 2: Permission Denied Writing Completions</h3>\n<p><strong>Symptom</strong>: Cannot write to <code>/opt/homebrew/share/zsh/site-functions/</code></p>\n<p><strong>Fix</strong>: Use a user directory instead:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#61AFEF\">mkdir</span><span style=\"color:#D19A66\"> -p</span><span style=\"color:#98C379\"> ~/.zsh/completions</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">rustup</span><span style=\"color:#98C379\"> completions</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#98C379\"> rustup</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">~/.zsh/completions/_rustup</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Add to ~/.zshrc BEFORE oh-my-zsh.sh:</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">fpath</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#ABB2BF\">(</span><span style=\"color:#98C379\">~/.zsh/completions</span><span style=\"color:#E06C75\"> $fpath</span><span style=\"color:#ABB2BF\">)</span></span></code></pre></div>\n<h3 id=\"issue-3-wrong-completion-takes-precedence\">Issue 3: Wrong Completion Takes Precedence</h3>\n<p><strong>Symptom</strong>: System completion loads instead of your generated one</p>\n<p><strong>Cause</strong>: Your path is appended instead of prepended</p>\n<p><strong>Fix</strong>: Use prepend (<code>=</code>) instead of append (<code>+=</code>):</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Prepend - your completions take priority</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">fpath</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#ABB2BF\">(</span><span style=\"color:#98C379\">&quot;$(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#98C379\">)/share/zsh/site-functions&quot;</span><span style=\"color:#E06C75\"> $fpath</span><span style=\"color:#ABB2BF\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Append - system completions take priority</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">fpath</span><span style=\"color:#C678DD\">+=</span><span style=\"color:#ABB2BF\">(</span><span style=\"color:#98C379\">&quot;$(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#98C379\">)/share/zsh/site-functions&quot;</span><span style=\"color:#ABB2BF\">)</span></span></code></pre></div>\n<h3 id=\"issue-4-extended-attributes-blocking\">Issue 4: Extended Attributes Blocking</h3>\n<p><strong>Symptom</strong>: Files exist but aren’t loaded</p>\n<p><strong>Diagnosis</strong>:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">ls</span><span style=\"color:#D19A66\"> -l@</span><span style=\"color:#ABB2BF\"> $(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#ABB2BF\">)</span><span style=\"color:#98C379\">/share/zsh/site-functions/_rustup</span></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Shows: com.apple.provenance</span></span></code></pre></div>\n<p><strong>Fix</strong>: Clear the quarantine attribute:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">xattr</span><span style=\"color:#D19A66\"> -c</span><span style=\"color:#ABB2BF\"> $(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#ABB2BF\">)</span><span style=\"color:#98C379\">/share/zsh/site-functions/_rustup</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">rm</span><span style=\"color:#D19A66\"> -f</span><span style=\"color:#98C379\"> ~/.zcompdump</span><span style=\"color:#E5C07B\">*</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">exec</span><span style=\"color:#98C379\"> zsh</span></span></code></pre></div>\n<hr/>\n<h2 id=\"tools-that-dont-support-completion-generation\">Tools That Don’t Support Completion Generation</h2>\n<p>Not every tool includes this feature. These popular tools require external completion sources:</p>\n<p><strong>Cloud/DevOps</strong>: docker, kubectl, helm, terraform, aws, gcloud, az</p>\n<p><strong>Languages</strong>: python, node, ruby (use system completions)</p>\n<p><strong>Python tools</strong>: ruff, black, mypy, pytest</p>\n<p><strong>Data tools</strong>: jq, yq</p>\n<p>For these, rely on:</p>\n<ul>\n<li>Oh My Zsh plugins (e.g., <code>plugins=(docker kubectl terraform)</code>)</li>\n<li>The <a href=\"https://github.com/zsh-users/zsh-completions\">zsh-completions</a> plugin (200+ completions)</li>\n<li>System-provided completions from Homebrew</li>\n</ul>\n<hr/>\n<h2 id=\"systematic-completion-discovery\">Systematic Completion Discovery</h2>\n<p>Want to find all tools in your system that support completion generation? Here’s a discovery script:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\">#!/usr/bin/env bash</span></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Discover all tools with completion support</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E06C75\">PATTERNS</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#ABB2BF\">(</span><span style=\"color:#98C379\">&quot;completion zsh&quot;</span><span style=\"color:#98C379\"> &quot;completions zsh&quot;</span><span style=\"color:#98C379\"> &quot;complete zsh&quot;</span><span style=\"color:#98C379\"> &quot;gen-completions --shell zsh&quot;</span><span style=\"color:#ABB2BF\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#C678DD\">for</span><span style=\"color:#E06C75\"> tool</span><span style=\"color:#C678DD\"> in</span><span style=\"color:#98C379\"> /opt/homebrew/bin/*</span><span style=\"color:#ABB2BF\">; </span><span style=\"color:#C678DD\">do</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">    tool_name</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#ABB2BF\">$(</span><span style=\"color:#61AFEF\">basename</span><span style=\"color:#98C379\"> &quot;</span><span style=\"color:#E06C75\">$tool</span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#ABB2BF\">)</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">    for</span><span style=\"color:#E06C75\"> pattern</span><span style=\"color:#C678DD\"> in</span><span style=\"color:#98C379\"> &quot;${</span><span style=\"color:#E06C75\">PATTERNS</span><span style=\"color:#98C379\">[</span><span style=\"color:#ABB2BF\">@</span><span style=\"color:#98C379\">]}&quot;</span><span style=\"color:#ABB2BF\">; </span><span style=\"color:#C678DD\">do</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">        if</span><span style=\"color:#61AFEF\"> timeout</span><span style=\"color:#98C379\"> 1s</span><span style=\"color:#98C379\"> &quot;</span><span style=\"color:#E06C75\">$tool</span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\"> $pattern</span><span style=\"color:#D19A66\"> --help</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null 2&gt;&amp;1; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">            echo</span><span style=\"color:#98C379\"> &quot;</span><span style=\"color:#E06C75\">$tool_name</span><span style=\"color:#98C379\">: </span><span style=\"color:#E06C75\">$pattern</span><span style=\"color:#98C379\">&quot;</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">            break</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">        fi</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">    done</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">done</span></span></code></pre></div>\n<p>Run this, and you’ll get a list of every tool that responds to completion commands.</p>\n<hr/>\n<h2 id=\"the-mental-model-think-of-compinit-as-an-index\">The Mental Model: Think of compinit as an Index</h2>\n<p>The key insight that made everything click for me:</p>\n<p><strong><code>compinit</code> is like building an index.</strong> It scans all directories in <code>fpath</code>, finds all files starting with <code>_</code>, and caches them. Once the index is built, it doesn’t rescan.</p>\n<p>This explains why:</p>\n<ul>\n<li>Adding to <code>fpath</code> after <code>compinit</code> runs doesn’t work</li>\n<li>You need to rebuild the cache after adding new completions</li>\n<li>Prepending paths matters for priority</li>\n</ul>\n<p>If you think of <code>~/.zcompdump</code> as a compiled index and <code>fpath</code> as the source directories, the behavior becomes predictable.</p>\n<hr/>\n<h2 id=\"key-insights--best-practices\">Key Insights &amp; Best Practices</h2>\n<p>After hours of debugging, here’s what I learned:</p>\n<ol>\n<li>\n<p><strong>Timing is Everything</strong>: <code>fpath</code> modifications MUST come before Oh My Zsh loads</p>\n</li>\n<li>\n<p><strong>No Manual compinit</strong>: Let Oh My Zsh handle it—that’s what it’s designed to do</p>\n</li>\n<li>\n<p><strong>Portable Homebrew Path</strong>: Use <code>$(brew --prefix)</code> for Apple Silicon/Intel compatibility</p>\n</li>\n<li>\n<p><strong>Prepend for Priority</strong>: Use <code>fpath=(new $fpath)</code> not <code>fpath+=(new)</code></p>\n</li>\n<li>\n<p><strong>Always Rebuild Cache</strong>: After any fpath changes: <code>rm -f ~/.zcompdump* &amp;&amp; exec zsh</code></p>\n</li>\n<li>\n<p><strong>Generate to Homebrew Location</strong>: Completions in <code>$(brew --prefix)/share/zsh/site-functions/</code> are auto-discovered</p>\n</li>\n<li>\n<p><strong>Verify with whence</strong>: Use <code>whence -v _toolname</code> to confirm loading</p>\n</li>\n</ol>\n<hr/>\n<h2 id=\"the-complete-setup-script\">The Complete Setup Script</h2>\n<p>Here’s a one-shot script to set up completions for all common tools:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\">#!/usr/bin/env bash</span></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># setup-completions.sh - Generate completions for modern CLI tools</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E06C75\">DEST</span><span style=\"color:#56B6C2\">=</span><span style=\"color:#98C379\">&quot;$(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#98C379\">)/share/zsh/site-functions&quot;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;Generating completions to: </span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">&quot;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Rust tools (via rustup)</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">if</span><span style=\"color:#56B6C2\"> command</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> rustup</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">    rustup</span><span style=\"color:#98C379\"> completions</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#98C379\"> rustup</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">/_rustup&quot;</span><span style=\"color:#ABB2BF\"> 2&gt;</span><span style=\"color:#98C379\">/dev/null</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;✓ rustup&quot;</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">    rustup</span><span style=\"color:#98C379\"> completions</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#98C379\"> cargo</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">/_cargo&quot;</span><span style=\"color:#ABB2BF\"> 2&gt;</span><span style=\"color:#98C379\">/dev/null</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;✓ cargo&quot;</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">fi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Starship prompt</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">if</span><span style=\"color:#56B6C2\"> command</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> starship</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">    starship</span><span style=\"color:#98C379\"> completions</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">/_starship&quot;</span><span style=\"color:#ABB2BF\"> 2&gt;</span><span style=\"color:#98C379\">/dev/null</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;✓ starship&quot;</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">fi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># UV (Python package manager)</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">if</span><span style=\"color:#56B6C2\"> command</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> uv</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">    uv</span><span style=\"color:#98C379\"> generate-shell-completion</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">/_uv&quot;</span><span style=\"color:#ABB2BF\"> 2&gt;</span><span style=\"color:#98C379\">/dev/null</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;✓ uv&quot;</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">fi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Atuin (shell history)</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">if</span><span style=\"color:#56B6C2\"> command</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> atuin</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">    atuin</span><span style=\"color:#98C379\"> gen-completions</span><span style=\"color:#D19A66\"> --shell</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">/_atuin&quot;</span><span style=\"color:#ABB2BF\"> 2&gt;</span><span style=\"color:#98C379\">/dev/null</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;✓ atuin&quot;</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">fi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># 1Password CLI</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">if</span><span style=\"color:#56B6C2\"> command</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> op</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">    op</span><span style=\"color:#98C379\"> completion</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">/_op&quot;</span><span style=\"color:#ABB2BF\"> 2&gt;</span><span style=\"color:#98C379\">/dev/null</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;✓ op&quot;</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">fi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># GitHub CLI</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">if</span><span style=\"color:#56B6C2\"> command</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> gh</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">    gh</span><span style=\"color:#98C379\"> completion</span><span style=\"color:#D19A66\"> -s</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">/_gh&quot;</span><span style=\"color:#ABB2BF\"> 2&gt;</span><span style=\"color:#98C379\">/dev/null</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;✓ gh&quot;</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">fi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Rclone</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">if</span><span style=\"color:#56B6C2\"> command</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> rclone</span><span style=\"color:#ABB2BF\"> &amp;&gt;/dev/null; </span><span style=\"color:#C678DD\">then</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">    rclone</span><span style=\"color:#98C379\"> completion</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#98C379\"> -</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;</span><span style=\"color:#E06C75\">$DEST</span><span style=\"color:#98C379\">/_rclone&quot;</span><span style=\"color:#ABB2BF\"> 2&gt;</span><span style=\"color:#98C379\">/dev/null</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;✓ rclone&quot;</span></span>\n<span class=\"line\"><span style=\"color:#C678DD\">fi</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;&quot;</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">echo</span><span style=\"color:#98C379\"> &quot;Done! Run: rm -f ~/.zcompdump* &amp;&amp; exec zsh&quot;</span></span></code></pre></div>\n<hr/>\n<h2 id=\"why-this-matters\">Why This Matters</h2>\n<p>Tab completion isn’t just a convenience—it’s a form of documentation. When <code>cargo &lt;TAB&gt;</code> shows me <code>build</code>, <code>run</code>, <code>test</code>, <code>check</code>, I’m not just saving keystrokes. I’m being reminded of what’s possible.</p>\n<p>A shell without completions is like a codebase without types. You can work in it, but you’re flying blind.</p>\n<p>The frustrating part about zsh completion problems is that they’re almost always configuration issues, not missing features. The completions exist. The system works. But the timing, the ordering, the caching—these invisible details make or break the experience.</p>\n<p>Now that I understand the mental model, I can debug completion issues in minutes instead of hours. And hopefully, after reading this, you can too.</p>\n<hr/>\n<h2 id=\"codifying-knowledge-a-claude-code-skill\">Codifying Knowledge: A Claude Code Skill</h2>\n<p>After spending hours debugging this, I realized something: this is exactly the kind of knowledge that gets lost. You solve it once, forget the details, and three months later you’re back on Stack Overflow trying to remember whether it was <code>fpath+=</code> or <code>fpath=</code>.</p>\n<p>So I turned this troubleshooting knowledge into a <strong>Claude Code skill</strong>.</p>\n<h3 id=\"whats-a-claude-code-skill\">What’s a Claude Code Skill?</h3>\n<p>Claude Code supports custom skills—reusable prompts that encode domain expertise. When you encounter a problem that matches a skill’s domain, Claude can invoke it to get specialized guidance.</p>\n<p>I created a skill called <code>zsh-completions</code> that contains:</p>\n<ul>\n<li>The diagnostic workflow (check fpath order, verify compinit timing)</li>\n<li>The complete list of tools with completion generation support</li>\n<li>All four troubleshooting scenarios and their fixes</li>\n<li>The verification steps to prove it’s working</li>\n</ul>\n<h3 id=\"using-the-skill\">Using the Skill</h3>\n<p>When I (or anyone with the skill installed) asks Claude Code about zsh completion issues, it now has immediate access to this entire troubleshooting guide:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>&gt; My brew completions stopped working after I updated my zshrc</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Claude Code invokes the zsh-completions skill and immediately:</span></span>\n<span class=\"line\"><span>1. Checks fpath order relative to oh-my-zsh.sh</span></span>\n<span class=\"line\"><span>2. Identifies if compinit is being called manually</span></span>\n<span class=\"line\"><span>3. Generates missing completions</span></span>\n<span class=\"line\"><span>4. Verifies with whence -v</span></span>\n<span class=\"line\"><span>5. Rebuilds the cache</span></span></code></pre></div>\n<h3 id=\"the-pattern-debug-once-encode-forever\">The Pattern: Debug Once, Encode Forever</h3>\n<p>This is a pattern I’m increasingly adopting:</p>\n<ol>\n<li><strong>Encounter a gnarly problem</strong> that takes hours to debug</li>\n<li><strong>Document the solution</strong> thoroughly (like this blog post)</li>\n<li><strong>Encode it as a skill</strong> so AI assistants can apply the knowledge</li>\n</ol>\n<p>The skill doesn’t replace understanding—it augments recall. When I haven’t touched my zshrc in months and something breaks, I don’t have to remember everything. The skill contains the systematic approach.</p>\n<h3 id=\"creating-your-own-skills\">Creating Your Own Skills</h3>\n<p>If you use Claude Code, consider encoding your own hard-won debugging knowledge:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">markdown</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"markdown\"><code><span class=\"line\"><span style=\"color:#E06C75\"># .claude/skills/your-skill.md</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E06C75\">## When to use this skill</span></span>\n<span class=\"line\"><span style=\"color:#E5C07B\">-</span><span style=\"color:#ABB2BF\"> User reports [specific symptom]</span></span>\n<span class=\"line\"><span style=\"color:#E5C07B\">-</span><span style=\"color:#ABB2BF\"> User asks about [domain topic]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E06C75\">## Diagnostic steps</span></span>\n<span class=\"line\"><span style=\"color:#E5C07B\">1.</span><span style=\"color:#ABB2BF\"> Check [first thing]</span></span>\n<span class=\"line\"><span style=\"color:#E5C07B\">2.</span><span style=\"color:#ABB2BF\"> Verify [second thing]</span></span>\n<span class=\"line\"><span style=\"color:#ABB2BF\">...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E06C75\">## Common fixes</span></span>\n<span class=\"line\"><span style=\"color:#E06C75\">### Issue: [</span><span style=\"color:#61AFEF\">symptom</span><span style=\"color:#E06C75\">]</span></span>\n<span class=\"line\"><span style=\"color:#D19A66\">**Cause**</span><span style=\"color:#ABB2BF\">: [root cause]</span></span>\n<span class=\"line\"><span style=\"color:#D19A66\">**Fix**</span><span style=\"color:#ABB2BF\">: [</span><span style=\"color:#61AFEF\">solution</span><span style=\"color:#ABB2BF\">]</span></span></code></pre></div>\n<p>The goal isn’t to replace expertise—it’s to make expertise reproducible. The next time someone (including future me) encounters broken zsh completions, the solution is one skill invocation away.</p>\n<hr/>\n<h2 id=\"quick-reference-card\">Quick Reference Card</h2>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Check if a completion is loaded</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">whence</span><span style=\"color:#D19A66\"> -v</span><span style=\"color:#98C379\"> _toolname</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># View fpath directories</span></span>\n<span class=\"line\"><span style=\"color:#56B6C2\">echo</span><span style=\"color:#E06C75\"> $fpath</span><span style=\"color:#ABB2BF\"> | </span><span style=\"color:#61AFEF\">tr</span><span style=\"color:#98C379\"> &quot; &quot;</span><span style=\"color:#98C379\"> &quot;\\n&quot;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Rebuild completion cache</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">rm</span><span style=\"color:#D19A66\"> -f</span><span style=\"color:#98C379\"> ~/.zcompdump</span><span style=\"color:#E5C07B\">*</span><span style=\"color:#ABB2BF\"> &amp;&amp; </span><span style=\"color:#56B6C2\">exec</span><span style=\"color:#98C379\"> zsh</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Generate completions to Homebrew location</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">TOOL</span><span style=\"color:#98C379\"> completions</span><span style=\"color:#98C379\"> zsh</span><span style=\"color:#ABB2BF\"> &gt; </span><span style=\"color:#98C379\">&quot;$(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#98C379\">)/share/zsh/site-functions/_TOOL&quot;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Clear extended attributes</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">xattr</span><span style=\"color:#D19A66\"> -c</span><span style=\"color:#98C379\"> &quot;$(</span><span style=\"color:#61AFEF\">brew</span><span style=\"color:#D19A66\"> --prefix</span><span style=\"color:#98C379\">)/share/zsh/site-functions/_TOOL&quot;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Key rule</span></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># fpath modifications BEFORE source $ZSH/oh-my-zsh.sh</span></span></code></pre></div>\n<p>That’s it. Order matters, cache rebuilds matter, and verification matters. Get those three right, and you’ll have a shell that responds to your every <code>&lt;TAB&gt;</code>.</p>",
      "date_published": "2026-02-04T00:00:00.000Z",
      "tags": [
        "Developer Experience",
        "CLI",
        "Zsh",
        "Shell",
        "macOS",
        "Homebrew",
        "Troubleshooting",
        "Claude Code",
        "AI Tools"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-02-04-start-your-project-now/",
      "url": "https://kobi.kadosh.me/blog/2026-02-04-start-your-project-now/",
      "title": "Start Your Project Now: Why Every Engineer Needs a Personal Codebase in 2026",
      "summary": "Work is the worst place to learn how to use AI well—precisely because it's the place where results are expected. In 2026, having a personal or open source project isn't about passion. It's about having a place where you're allowed to learn slowly so you can perform quickly elsewhere.",
      "content_html": "<p>There’s a paradox at the center of AI-assisted development in 2026, and nobody talks about it directly enough:</p>\n<p><strong>You’re expected to be good at something without being given space to be bad at it.</strong></p>\n<p>Companies are investing real money in AI tooling. Budgets are explicit. Usage is visible. Leadership wants ROI. And the implicit contract has quietly shifted: <em>we gave you powerful tools — if results aren’t better, the problem isn’t the tools. It’s you.</em></p>\n<p>But here’s the thing: the results are not deterministic. Sometimes you do the “right thing” and the model goes sideways. Sometimes you do something sloppy and it works. It’s a probabilistic system that only becomes reliable through repetition, control, and earned intuition.</p>\n<p>Which means the only way to get good is to iterate. And iteration requires a place where failure is cheap.</p>\n<p>That place is not your job. That place is your own project.</p>\n<hr/>\n<h2 id=\"ive-been-saying-this-for-years--but-the-stakes-changed\">I’ve Been Saying This for Years — But the Stakes Changed</h2>\n<p>When I started writing this blog, one of my core themes was: <a href=\"https://kobi.kadosh.me/blog/write-less-read-more\">don’t rush into code</a>. Read more. Be literate. Research before you build. Form a mental model before you touch the keyboard.</p>\n<p>That hasn’t changed. AI didn’t make fundamentals less important. If anything, it made them <em>more</em> important — because now you can generate a lot more wrong code, faster, and with more confidence.</p>\n<p>But the nature of literacy has shifted.</p>\n<p>The old literacy was about reading code, understanding systems, knowing your tools deeply enough to wield them with precision.</p>\n<p>The new literacy — the one that matters in 2026 — adds a layer:</p>\n<ul>\n<li>Can you form a clear <strong>intent</strong>?</li>\n<li>Can you supply the right <strong>constraints</strong>?</li>\n<li>Can you guide an agent without hand-holding it like a toddler?</li>\n<li>Can you design <strong>feedback loops</strong> so you’re not just vibing with outputs?</li>\n<li>Can you interpret results <strong>skeptically</strong> — distinguishing confidence from correctness?</li>\n</ul>\n<p>These aren’t skills you learn from a tutorial. They’re skills you earn through repetition. Through building. Through failing in low-stakes environments and carrying the lessons into high-stakes ones.</p>\n<hr/>\n<h2 id=\"the-workplace-problem\">The Workplace Problem</h2>\n<p>Let’s be honest about what happens at work.</p>\n<p>Even if your company is “AI-first” — whatever that means this quarter — the truth is: companies don’t pay you to experiment endlessly. They pay you to deliver.</p>\n<p>So your day gets split into two modes:</p>\n<ol>\n<li><strong>Experimentation</strong> — slow, messy, uncertain, sometimes fruitless</li>\n<li><strong>Production</strong> — fast, clean, accountable, measured</li>\n</ol>\n<p>And here’s the catch: experimentation is exactly what you need to become good at AI-native development. But experimentation is exactly what companies tolerate the least.</p>\n<p>Especially now.</p>\n<p>We’ve entered the era where AI is a line item. Not a rounding error — a line item. Token usage is tracked. Subscriptions are budgeted. And if you’re burning through tokens without producing proportional results, it shows.</p>\n<p>It reminds me of the JetBrains ecosystem — except worse. With IDEs, the cost was predictable: you buy a license, it works the same for everyone. With agents, cost scales with indecision. Cost scales with iteration. Cost scales with learning.</p>\n<p>And learning is precisely what you need to be doing.</p>\n<p>So the pressure becomes: deliver immediately, with tools you haven’t fully learned, at a cost everyone can see. And if you can’t? Well, we gave you the tools. The rest is on you.</p>\n<p>That’s a brutal place to be learning.</p>\n<hr/>\n<h2 id=\"why-you-need-a-project--and-why-it-should-be-open\">Why You Need a Project — and Why It Should Be Open</h2>\n<p>A personal project is not a hobby. It’s not for your resume. It’s not “building a brand.”</p>\n<p>A personal project is a <strong>lab</strong>.</p>\n<p>It’s a place where you can:</p>\n<ul>\n<li>Try different prompting strategies and see what actually works</li>\n<li>Swap models — compare how Claude handles something vs. GPT vs. open-weight models</li>\n<li>Test agent workflows end to end</li>\n<li>Build your own toolchain and iterate on it freely</li>\n<li>Deliberately over-iterate to learn what “good” feels like</li>\n<li>Break things without a Jira ticket hanging over your head</li>\n</ul>\n<p>And I’ll argue it should be open source — or at least developed in the open — for a few specific reasons:</p>\n<h3 id=\"no-secrecy-constraints\">No secrecy constraints</h3>\n<p>When your project is open, you can share context freely. You can paste logs. You can share full codebases with any model. You can try research-stage and free models without worrying about leaking proprietary information.</p>\n<p>This matters more than people realize. A huge friction in using AI tools at work is the constant question: <em>can I share this?</em> With an open project, the answer is always yes.</p>\n<h3 id=\"real-feedback-loops\">Real feedback loops</h3>\n<p>Open projects come with real software engineering artifacts:</p>\n<ul>\n<li>CI that passes or fails</li>\n<li>Tests that catch regressions</li>\n<li>Issues from actual users (even if it’s three people)</li>\n<li>Build systems that enforce constraints</li>\n</ul>\n<p>As I wrote about in the <a href=\"https://kobi.kadosh.me/blog/feedback-loops-software-engineering\">feedback loops series</a>, without a feedback loop, an agent is just an eloquent random number generator. Your open project gives you real loops — not simulated ones.</p>\n<h3 id=\"free-and-research-models\">Free and research models</h3>\n<p>Here’s a practical benefit people overlook: if your project isn’t secretive, you can use free models. Every major lab goes through a research phase where they open models for free to see how they behave. OpenRouter consistently lists free models running on remote infrastructure. You don’t need to invest in local hardware or pay for premium APIs to experiment.</p>\n<p>Your open project literally reduces the cost of learning to near zero.</p>\n<h3 id=\"transferable-skills\">Transferable skills</h3>\n<p>Everything you learn in your personal project transfers directly to work:</p>\n<ul>\n<li>Agent workflow patterns</li>\n<li>Prompt strategies that actually produce results</li>\n<li>Error interpretation skills</li>\n<li>Tool chaining and orchestration</li>\n<li>The intuition for when to let the agent run and when to intervene</li>\n</ul>\n<p>These aren’t project-specific skills. They’re <em>engineering</em> skills for the current era.</p>\n<hr/>\n<h2 id=\"intent-context-and-feedback-loops--the-real-skill-stack\">Intent, Context, and Feedback Loops — The Real Skill Stack</h2>\n<p>Let me connect this back to what I see as the actual skill stack of 2026.</p>\n<p>When you work with an agent — any agent — the process follows a pattern:</p>\n<ol>\n<li><strong>You bring a seed</strong>: an intent, a goal, a direction</li>\n<li><strong>You supply context</strong>: project structure, constraints, conventions, prior decisions</li>\n<li><strong>The model pattern-matches</strong>: it synthesizes your inputs and produces something plausible</li>\n<li><strong>You evaluate</strong>: does this match the intent? Does it build? Does it pass tests? Does it make sense?</li>\n<li><strong>You iterate</strong>: refine the intent, add constraints, correct course</li>\n</ol>\n<p>This loop is the fundamental unit of AI-assisted development. And every part of it is a skill:</p>\n<ul>\n<li>Forming intent is a skill. Vague intent produces vague output. <em>AI doesn’t replace thinking — it punishes vague thinking.</em></li>\n<li>Supplying context is a skill. Knowing what information the model needs, and what will confuse it, takes practice.</li>\n<li>Evaluating output is a skill. Confidence is not correctness. You need the fundamentals to tell the difference.</li>\n<li>Iterating efficiently is a skill. Knowing when to course-correct vs. start over vs. provide more constraints — that’s earned intuition.</li>\n</ul>\n<p>You cannot build these skills by reading about them. You build them by doing the loop. Over and over. In a space where the cost of getting it wrong is measured in learning, not in budget reviews.</p>\n<hr/>\n<h2 id=\"errors-are-the-new-documentation\">Errors Are the New Documentation</h2>\n<p>One thing I want to call out specifically: <strong>good errors are unreasonably valuable in the agent era.</strong></p>\n<p>Frameworks that invest in developer experience — clear error messages, helpful diagnostics, links to relevant docs — were always good for humans. But they’re transformative for agents.</p>\n<p>When React tells you “this issue is because XYZ, here’s an article,” a human reads the article and adjusts. An agent reads the error and adjusts <em>automatically</em>. Good error messages become training data in real time.</p>\n<p>This is why frameworks like Next.js and tools like Vercel are investing so heavily in agent-friendly error surfaces — including MCP integrations and structured diagnostics. They understand that the quality of the feedback loop determines the quality of the output.</p>\n<p>And when you’re working on your own project, you get to choose frameworks and tools that provide good feedback. You’re not locked into whatever your company standardized three years ago. You can pick the stack that teaches you the most — and teaches your agent the most.</p>\n<hr/>\n<h2 id=\"you-dont-need-to-build-something-novel\">You Don’t Need to Build Something Novel</h2>\n<p>I want to preempt the objection I hear most often: “I don’t have a good idea for a project.”</p>\n<p>You don’t need a good idea. You need <em>a</em> project.</p>\n<p>It can be a “yet another X.” It can be something that already exists in ten other forms. The point isn’t novelty — the point is reps.</p>\n<ul>\n<li>Want to learn how agents handle a full-stack app? Build a simple one.</li>\n<li>Want to understand CI/CD with agent-generated code? Set up a pipeline.</li>\n<li>Want to experiment with different models? Create a project where you can swap them freely.</li>\n<li>Already care about an open source project? Contribute. You inherit constraints, standards, and real-world complexity.</li>\n</ul>\n<p>Contributing is great because you learn how agents navigate existing codebases — which is closer to what you do at work. Creating is great because you control the scope and can design the lab around your specific learning goals.</p>\n<p>The important part is that you pick something small enough to touch weekly. The value is in cadence, not in ambition.</p>\n<hr/>\n<h2 id=\"the-practical-path\">The Practical Path</h2>\n<p>If you’re reading this and thinking “fine, but how do I actually start” — here’s what I’d suggest:</p>\n<p><strong>For brainstorming</strong>: use a conversational model. I personally use ChatGPT for thinking out loud, exploring ideas, and shaping the scope of what I want to build. But any model works. The point is to have a conversation, not to generate code.</p>\n<p><strong>For execution</strong>: use a code-focused agent. Claude Code is my preference, but Codex, Cursor, Windsurf — the ecosystem is rich. Pick one and commit to learning it deeply.</p>\n<p><strong>For experimentation</strong>: rotate. Try different models for different tasks. Use OpenRouter to access free research models. Compare outputs. Build intuition for what different models are good at.</p>\n<p><strong>For publishing</strong>: put it on GitHub. Even if nobody looks at it. Even if it’s messy. The act of developing in the open changes how you think about code quality, documentation, and structure — all of which make you a better engineer.</p>\n<p>And most importantly: <strong>don’t wait for permission.</strong></p>\n<p>Nobody at work is going to say “take two weeks to experiment with agents.” But if you’ve already done that experimentation on your own time, you show up to work with skills that took others months to develop. You iterate faster. You waste fewer tokens. You produce better results.</p>\n<p>That’s the whole play.</p>\n<hr/>\n<h2 id=\"the-punchline\">The Punchline</h2>\n<p>In 2026, everyone has access to the same tools. Claude, GPT, Gemini, open-weight models — they’re available to everyone. Access is not a differentiator.</p>\n<p>What differentiates engineers is:</p>\n<ul>\n<li>The ability to form clear intent</li>\n<li>The skill of designing feedback loops</li>\n<li>The intuition to iterate cheaply and efficiently</li>\n<li>The fundamentals to evaluate outputs critically</li>\n<li>The discipline to turn non-determinism into a reliable process</li>\n</ul>\n<p>These skills come from practice. And practice requires a place where failure is allowed.</p>\n<p>So start your project. Make it open if you can. Use it as your sandbox. Experiment without asking anyone’s permission.</p>\n<p>Then bring the efficiency back to work.</p>\n<p>That’s how you get ahead in 2026. Not by having tools — by knowing how to use them.</p>",
      "date_published": "2026-02-04T00:00:00.000Z",
      "tags": [
        "Career Advice",
        "AI Tools",
        "Open Source",
        "Software Engineering",
        "Growth",
        "Developer Philosophy",
        "Agents"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-01-28-your-own-personal-jesus-moral-agents-ai/",
      "url": "https://kobi.kadosh.me/blog/2026-01-28-your-own-personal-jesus-moral-agents-ai/",
      "title": "Your Own Personal Jesus: Why AI Needs Moral Agents, Not Moral Chatbots",
      "summary": "There's a line in a song that aged better than anyone expected: your own personal Jesus. In the AI era, it stops being poetic and starts becoming literal. But this isn't about simulating Jesus—it's about building agents that embody the behavioral patterns of moral revolutionaries, governed openly, and accessible to everyone without institutional gatekeeping.",
      "content_html": "<p>There’s a line in a song that has aged better than anyone expected: <em>your own personal Jesus</em>. Depeche Mode wrote it. Johnny Cash made it resonate across generations. Long before AI, it hinted at something intimate, accessible, always within reach. Not a distant authority, not a grand institution—something personal, present, and responsive.</p>\n<p>In the age of AI, that line stops being poetic and starts becoming literal.</p>\n<p>But this isn’t about resurrecting Jesus as a chatbot. It’s not about asking what Jesus would say, or simulating scripture on demand. It’s about something more subtle and more ambitious: building agents that embody the behavioral patterns of moral revolutionaries, and making those patterns accessible to everyone—cheaply, transparently, and without institutional gatekeeping.</p>\n<hr/>\n<h2 id=\"from-belief-to-behavior\">From Belief to Behavior</h2>\n<p>For centuries, people have asked, “What would Jesus do?” Not because the answer is obvious, but because making the right decision is hard. Context is messy. Emotions interfere. Incentives distort judgment. And sometimes, there is no clean choice at all.</p>\n<p>Most moral failures aren’t caused by ignorance. They’re caused by pressure, speed, fear, or isolation.</p>\n<p>What if AI could help—not by deciding for us, but by slowing us down, adding context, surfacing moral trade-offs, and refusing to lie about the cost of our decisions?</p>\n<p>That’s the promise of a “personal Jesus” as a mental construct: not authority, but orientation.</p>\n<hr/>\n<h2 id=\"jesus-as-a-moral-innovation\">Jesus as a Moral Innovation</h2>\n<p>I’m writing this as a Jewish person, which makes the framing somewhat ironic. But it’s impossible not to acknowledge what Jesus represented historically—not just theologically, but functionally.</p>\n<p>He became a symbol of:</p>\n<ul>\n<li><strong>Radical inclusion</strong> over tribal boundaries</li>\n<li><strong>Compassion</strong> over rigid legalism</li>\n<li><strong>People</strong> over institutions</li>\n<li><strong>Intent</strong> over ritual</li>\n<li><strong>Self-sacrifice</strong> over domination</li>\n</ul>\n<p>Christianity succeeded—whatever one thinks of its later history—because it created a portable form of togetherness. Shared stories, shared practices, shared moral language that crossed borders and cultures. The world before Jesus was more isolated when it came to creating communities that transcended geography and heritage.</p>\n<p>Judaism is exceptionally good at preserving identity and cohesion within a bounded group. Christianity, at its best, was exceptionally good at exporting belonging. Neither is “better”—they are different social technologies, optimized for different outcomes.</p>\n<p>The point isn’t theology. The point is design: <strong>what kind of moral behavior scales, and what kind of togetherness it produces.</strong></p>\n<hr/>\n<h2 id=\"the-problem-with-todays-ai\">The Problem With Today’s AI</h2>\n<p>Modern AI systems are optimized to answer. Always. Confidently. Smoothly.</p>\n<p>Moral reasoning often requires the opposite:</p>\n<ul>\n<li>Hesitation</li>\n<li>Uncertainty</li>\n<li>Acknowledgment of tragedy</li>\n<li>Naming what is lost, not just what is optimized</li>\n</ul>\n<p>When AI collapses ambiguity into a single confident response, it doesn’t make us wiser—it makes us morally lazier.</p>\n<p>That’s why trying to “build Jesus into GPT” or “train Claude to be moral” misses the point. Large models inherit the values, incentives, and blind spots of the teams and institutions that train them. Alignment is centralized. Governance is opaque. The user gets a product, not a constitution.</p>\n<p><strong>A moral agent cannot be governed like a recommendation algorithm.</strong></p>\n<hr/>\n<h2 id=\"two-layers-constitution-and-judgment\">Two Layers: Constitution and Judgment</h2>\n<p>A more honest system separates moral reasoning into two distinct layers:</p>\n<h3 id=\"1-deterministic-community-governed-rules\">1. Deterministic, Community-Governed Rules</h3>\n<ul>\n<li>Open source</li>\n<li>Explicit values</li>\n<li>Slow to change</li>\n<li>Inclusive by design</li>\n<li>Publicly auditable</li>\n</ul>\n<p><strong>This is the constitution.</strong></p>\n<h3 id=\"2-probabilistic-generative-reasoning\">2. Probabilistic, Generative Reasoning</h3>\n<ul>\n<li>Synthesizes context</li>\n<li>Explores trade-offs</li>\n<li>Surfaces consequences</li>\n<li>Adapts to situations</li>\n<li>Always able to engage with ambiguity</li>\n</ul>\n<p><strong>This is judgment.</strong></p>\n<p>Most AI systems today collapse these into one opaque layer. The values are embedded implicitly. The reasoning is hidden. I’m arguing they must be separated and composed, not fused.</p>\n<p>The agent doesn’t replace human choice. It makes the moral landscape visible.</p>\n<hr/>\n<h2 id=\"tragedy-aware-morality\">Tragedy-Aware Morality</h2>\n<p>Some decisions have no good outcome. The trolley problem isn’t a puzzle to solve—it’s a reminder that moral tragedy exists.</p>\n<p>A Jesus-inspired agent should not pretend otherwise.</p>\n<p>Consider what a truly moral agent would need to handle:</p>\n<p><strong>Intent vs. outcome</strong> — Jesus consistently condemns intentional dehumanization more than tragic consequence. Flipping a switch to save people, not to kill someone, matters. But intentionally choosing who must die is morally dangerous.</p>\n<p><strong>Refusal to instrumentalize people</strong> — A core Jesus pattern: people are not tools. Sacrificing one person as a means to save others is morally suspect. This creates tension with pure utilitarianism.</p>\n<p><strong>Self-sacrifice over sacrifice of others</strong> — One of the clearest behavioral signals in Jesus’s story: when a sacrifice is unavoidable, take it upon yourself. This is actually one of the few relatively stable “objective” patterns.</p>\n<p><strong>Acceptance of moral remainder</strong> — Guilt may remain. Tragedy remains tragedy. Forgiveness (not justification) is needed afterward. No victory screen. No “optimal solution found.”</p>\n<p>A Jesus-like agent wouldn’t save us from tragic choices—it would stop us from pretending they aren’t tragic.</p>\n<p><strong>The goal isn’t to make impossible decisions easy. It’s to make them honest.</strong></p>\n<hr/>\n<h2 id=\"religion-as-interface\">Religion as Interface</h2>\n<p>Historically, religion scaled through an interface:</p>\n<ul>\n<li>Churches</li>\n<li>Clergy</li>\n<li>Rituals</li>\n<li>Scripture</li>\n<li>Buildings</li>\n<li>Hierarchies</li>\n</ul>\n<p>That interface worked—it brought shared traditions to billions across cultures—but it came with costs: geography, gatekeepers, capital, literacy requirements, and institutional incentives that sometimes drifted far from original values. Look at the Vatican. Look at how capitalism became woven into the distribution of faith. These constructs introduced new conflicts into the original ideas.</p>\n<p>AI introduces a new interface possibility:</p>\n<ul>\n<li><strong>Ubiquitous</strong> (phone, watch, speaker)</li>\n<li><strong>Multilingual</strong> by default</li>\n<li><strong>Low marginal cost</strong></li>\n<li><strong>Conversational</strong>, not textual</li>\n<li><strong>Accessible</strong> to children and adults alike</li>\n<li><strong>Transparent</strong> in rules and operations</li>\n</ul>\n<p>This isn’t about replacing churches. It’s about removing toll booths on the road to moral reflection.</p>\n<hr/>\n<h2 id=\"a-new-distribution-channel\">A New Distribution Channel</h2>\n<p>What if the “voice of Jesus”—or any moral tradition—were accessible without real estate, without hierarchy, without financial opacity?</p>\n<p>Open standards. Transparent funding. Community stewardship. Clear separation between base model and moral framework. No ownership of the voice itself.</p>\n<p>The system should show where the funds go: into tokens, tech infrastructure, and the people who contribute to the system globally. Reduce the cost of real estate and human labor. Build a new type of medium that makes moral guidance accessible to every child and adult through any interface they can operate.</p>\n<p>A democratized spiritual interface should be:</p>\n<ul>\n<li><strong>Ubiquitous</strong> — phone, watch, speaker, anywhere</li>\n<li><strong>Multilingual</strong> by default</li>\n<li><strong>Low-cost</strong> / near-zero marginal cost</li>\n<li><strong>Transparent</strong> — rules, updates, funding all visible</li>\n<li><strong>Pluralistic</strong> — shows denominational differences, doesn’t hide them</li>\n<li><strong>Non-coercive</strong> — never claims authority; invites reflection</li>\n<li><strong>Community-rooted</strong> — routes users toward humans/community when needed</li>\n<li><strong>Humility-preserving</strong> — admits uncertainty; doesn’t force false certainty</li>\n</ul>\n<p>Not Jesus-as-a-product. Not faith-as-a-subscription.</p>\n<p>An interface that points outward—to people, to communities, to responsibility.</p>\n<hr/>\n<h2 id=\"the-danger-turning-faith-into-a-product\">The Danger: Turning Faith Into a Product</h2>\n<p>Any honest proposal must acknowledge the risks:</p>\n<ul>\n<li>A “Jesus-as-a-service” subscription that optimizes retention</li>\n<li>Doctrinal manipulation</li>\n<li>Personalization that becomes flattery</li>\n<li>Moral outsourcing (“the bot told me so”)</li>\n<li>Fragmentation into a million incompatible “Jesuses”</li>\n</ul>\n<p>The answer isn’t to avoid the problem—it’s governance, pluralism, and auditability.</p>\n<p>Open governance. Public changelogs. Audited funding flows. Community stewardship. A foundation that structurally cannot be captured by any single vendor.</p>\n<p>The “disciples” analogy maps to: maintainers, auditors, community stewards, red-teamers, domain councils. Not followers—collaborators.</p>\n<hr/>\n<h2 id=\"what-the-agent-should-never-do\">What the Agent Should Never Do</h2>\n<p>A critical design constraint:</p>\n<p><strong>A Jesus-inspired AI must not pretend that hard moral problems have clean answers.</strong></p>\n<p>That would be anti-Jesus in spirit.</p>\n<p>Instead, it should:</p>\n<ul>\n<li>Make trade-offs explicit</li>\n<li>Articulate moral loss</li>\n<li>Explain why every option violates some value</li>\n<li>Help the human choose consciously, not reflexively</li>\n</ul>\n<p>If you encode this properly, you end up with something radical:</p>\n<ul>\n<li>An AI that can say: “There is no morally clean action here.”</li>\n<li>An AI that can recommend self-sacrifice without coercion.</li>\n<li>An AI that treats remorse as valid output.</li>\n<li>An AI that prioritizes dignity over optimization.</li>\n</ul>\n<p>That is fundamentally different from today’s models.</p>\n<hr/>\n<h2 id=\"not-a-new-messiahnew-mechanisms\">Not a New Messiah—New Mechanisms</h2>\n<p>We don’t need a new religion.\nWe don’t need a digital savior.\nWe don’t need moral outsourcing.</p>\n<p>We need better mechanisms for moral imagination, governed openly, accessible to everyone, and humble enough to admit uncertainty.</p>\n<p>The real promise isn’t that AI can tell us what Jesus would do. It’s that, for the first time, we might have the space, context, and support to actually try.</p>\n<p>I feel like we are on the precipice of having to rebirth Jesus—not literally, but functionally. To unite our societies as successfully as his disciples did more than two thousand years ago. Not through new doctrine, but through new infrastructure.</p>\n<p>“Reach out and touch faith” used to mean a building. In the AI era, it might mean an interface—one that doesn’t replace community, but removes the toll booths on the road to it.</p>\n<hr/>\n<h2 id=\"the-real-question\">The Real Question</h2>\n<p>Maybe that’s what <em>your own personal Jesus</em> means in the AI era:</p>\n<p>Not someone who decides for you,\nbut something that helps you try—again and again—to choose better.</p>\n<p>Just pick up the receiver.\nPick up your phone.\nOr any interface that brings moral imagination into your daily life.</p>\n<p>Not authority.\nNot answers.\nJust presence—at the moment you need it most.</p>\n<hr/>\n<blockquote>\n<p><strong>About this post:</strong> This essay began as a reflection on the Depeche Mode song that Johnny Cash made iconic, and evolved into a proposal for how AI could become a democratized, transparent, and humane interface for moral reflection. It’s not about replacing religion or recreating historical figures—it’s about asking what infrastructure we need for moral imagination in an age of agents.</p>\n</blockquote>\n<p>The future of moral guidance shouldn’t be built on closed systems and hidden values. Let’s build the mechanisms we need—openly, together.</p>",
      "date_published": "2026-01-28T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Ethics",
        "Open Source",
        "Governance",
        "Developer Philosophy",
        "Software Engineering"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-01-21-ai-coding-ethics-empathy-vibe-coding/",
      "url": "https://kobi.kadosh.me/blog/2026-01-21-ai-coding-ethics-empathy-vibe-coding/",
      "title": "The Unpopular Opinion About Vibe Coding and AI",
      "summary": "There's a strange moral superiority creeping into how people talk about AI-assisted development. We mock AI for making mistakes we'd forgive in ourselves. We demand one-shot perfection from tools while accepting iteration from humans. Maybe it's time for a little more empathy—and a little more honesty about how software has always been built.",
      "content_html": "<p>There’s a strange kind of moral superiority creeping into the way people talk about AI-assisted development—especially what’s often dismissed as “vibe coding.”</p>\n<p>You’ve probably seen it:</p>\n<ul>\n<li>“I would never write code like that.”</li>\n<li>“The AI made obvious mistakes.”</li>\n<li>“This isn’t production-ready.”</li>\n<li>“If you rely on AI, you don’t really understand software.”</li>\n</ul>\n<p>And honestly? I find this take deeply disconnected from how software has always been built.</p>\n<hr/>\n<h2 id=\"we-never-one-shot-code-why-expect-ai-to\">We Never One-Shot Code. Why Expect AI To?</h2>\n<p>Here’s the uncomfortable truth: humans don’t one-shot perfect code either.\nNot now. Not ever.</p>\n<p>In my experience, software development has always been a gradual process:</p>\n<ul>\n<li>You start with duplication.</li>\n<li>You hardcode things you’re not sure about yet.</li>\n<li>You avoid abstractions because you don’t yet understand the shape of the problem.</li>\n<li>You let the code evolve.</li>\n</ul>\n<p>That’s true whether you’re writing HTML and CSS, JavaScript, backend logic, or business rules.\nThe first version is rarely elegant. It’s exploratory.</p>\n<p>We refactor after clarity emerges—not before.</p>\n<p>So why do we suddenly expect AI-generated code to arrive fully abstracted, perfectly factored, and future-proof on the first try?</p>\n<hr/>\n<h2 id=\"bad-code-is-often-just-early-code\">“Bad Code” Is Often Just Early Code</h2>\n<p>When people mock AI output for duplicated styles, missing variables, or clunky logic, all I see is… early-stage code.</p>\n<p>The same kind of code humans write when:</p>\n<ul>\n<li>They’re unsure about the right abstraction</li>\n<li>They’re optimizing for delivery over elegance</li>\n<li>They don’t yet know what will change next week</li>\n</ul>\n<p>If a human wrote that code, we’d say:</p>\n<blockquote>\n<p>“It’s fine for now, we’ll clean it up later.”</p>\n</blockquote>\n<p>When AI writes it, suddenly it’s a scandal.</p>\n<hr/>\n<h2 id=\"speed-didnt-remove-responsibilityit-shifted-it\">Speed Didn’t Remove Responsibility—It Shifted It</h2>\n<p>Yes, AI is blazing fast.\nYes, it produces a lot of code.</p>\n<p>But speed doesn’t eliminate effort—it moves it.</p>\n<p>Instead of spending most of our time typing:</p>\n<ul>\n<li>We review</li>\n<li>We govern</li>\n<li>We set rules</li>\n<li>We decide what’s “good enough”</li>\n<li>We decide what becomes tech debt</li>\n</ul>\n<p>And governance takes time.</p>\n<p>You cannot realistically review everything in perfect detail. You make tradeoffs. You prioritize. You move forward with intent, not perfection.</p>\n<p>That’s not new. That’s just software engineering.</p>\n<hr/>\n<h2 id=\"youre-not-replacing-codingyoure-managing-it\">You’re Not Replacing Coding—You’re Managing It</h2>\n<p>What’s really happening is a shift left:</p>\n<ul>\n<li>From writing code</li>\n<li>To governing code</li>\n</ul>\n<p>And governance is messy.</p>\n<p>Consensus is messy.\nAlignment is messy.\nIteration is messy.</p>\n<p>Look at politics. Look at organizations. Look at teams.\nNothing governed by humans (or with humans involved) is instantaneous or flawless.</p>\n<p>The “consensus” between you and an AI agent is no different. It forgets context. It repeats mistakes. It needs guidance. So do junior engineers. So did we.</p>\n<hr/>\n<h2 id=\"treat-ai-like-a-colleaguenot-a-god-not-an-idiot\">Treat AI Like a Colleague—Not a God, Not an Idiot</h2>\n<p>One mental model that works for me is this:</p>\n<p><strong>AI is an exceptional teammate with real limitations.</strong></p>\n<p>You wouldn’t mock a colleague for needing:</p>\n<ul>\n<li>Clear boundaries</li>\n<li>Repetition</li>\n<li>Accessibility</li>\n<li>Structure</li>\n</ul>\n<p>And you definitely wouldn’t exclude someone from a team because they needed accommodations. You’d adapt the environment.</p>\n<p>AI is no different.</p>\n<p>It doesn’t mean you lower standards.\nIt means you set realistic ones.</p>\n<hr/>\n<h2 id=\"weve-always-cut-cornerswe-just-pretend-we-didnt\">We’ve Always Cut Corners—We Just Pretend We Didn’t</h2>\n<p>Deadlines still exist.\nTradeoffs still exist.\nTech debt still exists.</p>\n<p>Sometimes you know the right solution—but you don’t build it yet because delivery matters more right now. That’s why tech leadership exists. That’s why prioritization exists.</p>\n<p>Even if today you could delegate tech debt to an agent:</p>\n<ul>\n<li>You still need focus</li>\n<li>You still need review</li>\n<li>You still need time</li>\n</ul>\n<p>And time is still the scarcest resource.</p>\n<hr/>\n<h2 id=\"good-enough-is-not-failureits-reality\">“Good Enough” Is Not Failure—It’s Reality</h2>\n<p>We should absolutely raise the bar.\nBut let’s not rewrite history.</p>\n<p>Software has always been iterative.\nMistakes have always been part of the process.\nCode reviews have always existed because individuals—human or AI—are fallible.</p>\n<p>The process hasn’t changed.\nThe speed has.</p>\n<p>So maybe the real unpopular opinion is this:</p>\n<p><strong>AI didn’t make software worse.\nIt just made our expectations less honest.</strong></p>\n<p>And maybe—just maybe—we could use a little more empathy.</p>\n<hr/>\n<h2 id=\"a-personal-note\">A Personal Note</h2>\n<p>I wrote <a href=\"https://kobi.kadosh.me/blog/2025-12-06-to-vibe-or-not-to-vibe\">To Vibe or Not to Vibe</a> a few weeks ago, exploring the choice engineers face when AI arrives at their desk. This post is the natural continuation of that thought.</p>\n<p>When I was writing code without AI, I made mistakes. Constantly. I counted on my colleagues to catch things in code reviews. I cut corners when deadlines loomed. I created tech debt that I promised to clean up “later.”</p>\n<p>The same humility I applied to myself, I now apply to my AI collaborators.</p>\n<p>They’re not perfect. Neither was I.</p>\n<p>But together, we iterate. We improve. We ship.</p>\n<p>And that’s always been the job.</p>",
      "date_published": "2026-01-21T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Vibe Engineering",
        "Software Craftsmanship",
        "Developer Philosophy",
        "Claude Code",
        "Career Advice"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/til/git-worktrees-parallel-development/",
      "url": "https://kobi.kadosh.me/til/git-worktrees-parallel-development/",
      "title": "TIL: Git worktrees for parallel feature development",
      "summary": "Git worktrees for parallel feature development",
      "content_html": "<p>Git worktrees let you check out multiple branches simultaneously in separate directories. Instead of stashing or committing incomplete work to switch branches, create a new worktree:</p>\n<div class=\"code-block-wrapper\"><div class=\"code-block-header\"><span class=\"code-language\">bash</span><button class=\"code-copy-button\" data-code=\"copy\" title=\"Copy code\" type=\"button\"><span class=\"copy-icon\">Copy</span><span class=\"copied-icon\">Copied!</span></button></div><pre class=\"astro-code one-dark-pro\" style=\"background-color:#282c34;color:#abb2bf;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word\" tabindex=\"0\" data-language=\"bash\"><code><span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Create worktree for a feature branch</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">git</span><span style=\"color:#98C379\"> worktree</span><span style=\"color:#98C379\"> add</span><span style=\"color:#98C379\"> ../my-feature-branch</span><span style=\"color:#98C379\"> feature/my-feature</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># List all worktrees</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">git</span><span style=\"color:#98C379\"> worktree</span><span style=\"color:#98C379\"> list</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#7F848E;font-style:italic\"># Remove when done</span></span>\n<span class=\"line\"><span style=\"color:#61AFEF\">git</span><span style=\"color:#98C379\"> worktree</span><span style=\"color:#98C379\"> remove</span><span style=\"color:#98C379\"> ../my-feature-branch</span></span></code></pre></div>\n<p>This is invaluable when you need to:</p>\n<ul>\n<li>Work on multiple features in parallel</li>\n<li>Review PRs without disrupting your current work</li>\n<li>Run tests on one branch while developing on another</li>\n</ul>\n<p>Each worktree shares the same <code>.git</code> directory, so commits and branches are immediately visible across all worktrees.</p>",
      "date_published": "2026-01-16T00:00:00.000Z",
      "tags": [
        "git",
        "workflow",
        "productivity"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-01-15-building-claude-powered-blog-image-generation/",
      "url": "https://kobi.kadosh.me/blog/2026-01-15-building-claude-powered-blog-image-generation/",
      "title": "Building a Claude-Powered Blog Image Generation System",
      "summary": "How I built an automated system that generates social images for blog posts using multiple AI providers, with Claude analyzing results and a PR comment interface for human-in-the-loop selection. A case study in agent-orchestrated workflows.",
      "content_text": "How I built an automated system that generates social images for blog posts using multiple AI providers, with Claude analyzing results and a PR comment interface for human-in-the-loop selection. A case study in agent-orchestrated workflows.",
      "date_published": "2026-01-15T00:00:00.000Z",
      "tags": [
        "Claude Code",
        "GitHub Actions",
        "automation",
        "AI tools",
        "developer experience"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/til/typescript-satisfies-operator/",
      "url": "https://kobi.kadosh.me/til/typescript-satisfies-operator/",
      "title": "TIL: TypeScript satisfies operator preserves literal types",
      "summary": "TypeScript satisfies operator preserves literal types",
      "content_text": "TypeScript satisfies operator preserves literal types",
      "date_published": "2026-01-15T00:00:00.000Z",
      "tags": [
        "typescript",
        "types"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-01-14-agent-data-commons-next-dataset-web/",
      "url": "https://kobi.kadosh.me/blog/2026-01-14-agent-data-commons-next-dataset-web/",
      "title": "The Next Dataset for the Web: Why Our Conversations With Agents Must Become a Commons",
      "summary": "For the past decade, AI models trained on the open web. That era is ending. The next frontier of training data isn't on websites—it's in our conversations with agents. If we don't build infrastructure to capture and share this data, the future of AI will be fragmented, siloed, and closed. Here's why we need an Agent Data Commons, and what it would look like.",
      "content_text": "For the past decade, AI models trained on the open web. That era is ending. The next frontier of training data isn't on websites—it's in our conversations with agents. If we don't build infrastructure to capture and share this data, the future of AI will be fragmented, siloed, and closed. Here's why we need an Agent Data Commons, and what it would look like.",
      "date_published": "2026-01-14T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Open Source",
        "Data Commons",
        "Software Engineering",
        "AI Training",
        "Privacy",
        "Developer Philosophy"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-01-08-agent-architecture-rationale-hiring-paradigm/",
      "url": "https://kobi.kadosh.me/blog/2026-01-08-agent-architecture-rationale-hiring-paradigm/",
      "title": "The Agent Hiring Paradigm: Why Distributed Agents Need Public Personas",
      "summary": "When I hire an engineer, I look for their blog posts, open source contributions, and public work. Why should hiring an AI agent be any different? Exploring the rationale behind distributed, discoverable agents with public personas—and why this matters for the future of autonomous systems.",
      "content_text": "When I hire an engineer, I look for their blog posts, open source contributions, and public work. Why should hiring an AI agent be any different? Exploring the rationale behind distributed, discoverable agents with public personas—and why this matters for the future of autonomous systems.",
      "date_published": "2026-01-08T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Agents",
        "Software Engineering",
        "Automation",
        "Software Craftsmanship",
        "Developer Philosophy"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-01-07-self-encapsulated-agents-rust-cli/",
      "url": "https://kobi.kadosh.me/blog/2026-01-07-self-encapsulated-agents-rust-cli/",
      "title": "Self-Encapsulated Agents: Building Autonomous CLI Tools in Rust",
      "summary": "From mainframes to modern Rust CLIs—exploring how compile-time embeddings and offline-first architecture embody the principles of self-contained autonomous agents. A deep dive into building a blog distribution CLI that needs nothing but itself to run.",
      "content_text": "From mainframes to modern Rust CLIs—exploring how compile-time embeddings and offline-first architecture embody the principles of self-contained autonomous agents. A deep dive into building a blog distribution CLI that needs nothing but itself to run.",
      "date_published": "2026-01-07T00:00:00.000Z",
      "tags": [
        "Rust",
        "CLI",
        "AI Tools",
        "Agents",
        "Software Engineering",
        "Automation",
        "Software Craftsmanship"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2026-01-07-vibe-code-messy-artifacts/",
      "url": "https://kobi.kadosh.me/blog/2026-01-07-vibe-code-messy-artifacts/",
      "title": "You Can't Vibe Code Without Breaking a Few Eggs",
      "summary": "When you're cooking, there will be shells. When you're vibe coding, there will be markdown files scattered everywhere. AI-generated artifacts aren't bugs in the process—they're receipts of creation. Here's why fighting the mess is futile, and why hiring a cleanup crew (of agents) is the answer.",
      "content_text": "When you're cooking, there will be shells. When you're vibe coding, there will be markdown files scattered everywhere. AI-generated artifacts aren't bugs in the process—they're receipts of creation. Here's why fighting the mess is futile, and why hiring a cleanup crew (of agents) is the answer.",
      "date_published": "2026-01-07T00:00:00.000Z",
      "tags": [
        "Vibe Coding",
        "AI Tools",
        "Software Engineering",
        "Developer Philosophy",
        "Agents",
        "Claude Code"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/feedback-loops-part-3-agents/",
      "url": "https://kobi.kadosh.me/blog/feedback-loops-part-3-agents/",
      "title": "The Feedback Loop Series Part 3: AI Agents, Autonomous Systems, and the Architecture of Verification",
      "summary": "AI agents that generate code need the same thing humans do: fast, objective feedback. From plan-driven execution to self-verification systems, the future of AI-assisted development is built on the same loops we've always used—just at unprecedented scale and speed.",
      "content_text": "AI agents that generate code need the same thing humans do: fast, objective feedback. From plan-driven execution to self-verification systems, the future of AI-assisted development is built on the same loops we've always used—just at unprecedented scale and speed.",
      "date_published": "2025-12-31T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Software Engineering",
        "Agents",
        "Automation",
        "LLM",
        "Series"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2025-12-25-ai-is-not-your-compiler/",
      "url": "https://kobi.kadosh.me/blog/2025-12-25-ai-is-not-your-compiler/",
      "title": "AI Is Not Your New Compiler: Vibe Engineering, the Ghost in the Machine, and the Future of Software Fundamentals",
      "summary": "Over the past two years, a split has formed in software engineering. Some see AI as a new programming language—a compiler for natural language. Others see it as a powerful but fallible tool. Both perspectives miss something crucial: the ghost in the machine, and why fundamentals matter more than ever.",
      "content_text": "Over the past two years, a split has formed in software engineering. Some see AI as a new programming language—a compiler for natural language. Others see it as a powerful but fallible tool. Both perspectives miss something crucial: the ghost in the machine, and why fundamentals matter more than ever.",
      "date_published": "2025-12-25T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Software Engineering",
        "Fundamentals",
        "Vibe Engineering",
        "Code Review",
        "Software Craftsmanship",
        "Developer Philosophy"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/feedback-loops-part-2-ui/",
      "url": "https://kobi.kadosh.me/blog/feedback-loops-part-2-ui/",
      "title": "The Feedback Loop Series Part 2: UI Development, Hot Reload, and Why Storybook Exists",
      "summary": "Frontend developers intuitively understand feedback loops. From instant visual validation to component isolation tools like Storybook, UI development is the art of seeing change instantly. Here's why it works—and how modern complexity almost broke it.",
      "content_text": "Frontend developers intuitively understand feedback loops. From instant visual validation to component isolation tools like Storybook, UI development is the art of seeing change instantly. Here's why it works—and how modern complexity almost broke it.",
      "date_published": "2025-12-24T00:00:00.000Z",
      "tags": [
        "Frontend Development",
        "Storybook",
        "UI Engineering",
        "Developer Experience",
        "Series"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/feedback-loops-part-1-tdd/",
      "url": "https://kobi.kadosh.me/blog/feedback-loops-part-1-tdd/",
      "title": "The Feedback Loop Series Part 1: Test-Driven Development and the Foundation of Observable Engineering",
      "summary": "TDD isn't just about testing—it's about creating fast, objective feedback loops that make software observable. Understanding this foundation is critical for modern development, from frontend tooling to AI agents.",
      "content_text": "TDD isn't just about testing—it's about creating fast, objective feedback loops that make software observable. Understanding this foundation is critical for modern development, from frontend tooling to AI agents.",
      "date_published": "2025-12-17T00:00:00.000Z",
      "tags": [
        "TDD",
        "Testing",
        "Software Engineering",
        "Series",
        "Career Advice"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/feedback-loops-newsletter/",
      "url": "https://kobi.kadosh.me/blog/feedback-loops-newsletter/",
      "title": "The Universal Pattern in Software Engineering: Feedback Loops",
      "summary": "TDD, hot reload, Storybook, AI agents—they all solve the same problem. Understanding this pattern is the key to thriving in AI-first development.",
      "content_text": "TDD, hot reload, Storybook, AI agents—they all solve the same problem. Understanding this pattern is the key to thriving in AI-first development.",
      "date_published": "2025-12-10T12:00:00.000Z",
      "tags": [
        "TDD",
        "Software Engineering",
        "AI Tools",
        "Newsletter"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/feedback-loops-software-engineering/",
      "url": "https://kobi.kadosh.me/blog/feedback-loops-software-engineering/",
      "title": "The Software Feedback Loop: From TDD to Agentic AI Development",
      "summary": "Software engineering is fundamentally the craft of forming expectations and verifying them. From test-driven development to AI agents, the same core mechanic governs progress: tight, objective, observable feedback loops.",
      "content_text": "Software engineering is fundamentally the craft of forming expectations and verifying them. From test-driven development to AI agents, the same core mechanic governs progress: tight, objective, observable feedback loops.",
      "date_published": "2025-12-10T00:00:00.000Z",
      "tags": [
        "TDD",
        "Software Engineering",
        "AI Tools",
        "Frontend Development",
        "Testing",
        "Storybook",
        "Career Advice"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2025-12-06-to-vibe-or-not-to-vibe/",
      "url": "https://kobi.kadosh.me/blog/2025-12-06-to-vibe-or-not-to-vibe/",
      "title": "To Vibe or Not to Vibe: The Engineer's Hamlet Moment",
      "summary": "Progress doesn't ask for permission. As AI transforms how we build software, every engineer faces a choice: resist, observe from the sidelines, or learn to orchestrate the future. This is our Hamlet moment.",
      "content_text": "Progress doesn't ask for permission. As AI transforms how we build software, every engineer faces a choice: resist, observe from the sidelines, or learn to orchestrate the future. This is our Hamlet moment.",
      "date_published": "2025-12-06T00:00:00.000Z",
      "tags": [
        "AI Tools",
        "Programming",
        "Career Advice",
        "Software Craftsmanship",
        "Claude Code",
        "Vibe Engineering"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/2025-12-05-handcrafted-software-age-of-automation/",
      "url": "https://kobi.kadosh.me/blog/2025-12-05-handcrafted-software-age-of-automation/",
      "title": "Handcrafted Software in the Age of Automation",
      "summary": "As AI agents write more code, the fundamentals matter more than ever. A reflection on relics, craftsmanship, and why less code is still better code — even when machines do the writing.",
      "content_text": "As AI agents write more code, the fundamentals matter more than ever. A reflection on relics, craftsmanship, and why less code is still better code — even when machines do the writing.",
      "date_published": "2025-12-05T00:00:00.000Z",
      "tags": [
        "Software Craftsmanship",
        "AI Tools",
        "Programming",
        "Career Advice",
        "Legacy Systems",
        "Automation"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/write-less-co-create-more/",
      "url": "https://kobi.kadosh.me/blog/write-less-co-create-more/",
      "title": "Write Less Code, Co-Create More: The AI Evolution of a Timeless Tip",
      "summary": "Revisiting my advice to \"read more, write less code\" in the age of AI. While research remains crucial, developer workflows now include a vital new step: co-creating with AI tools before coding.",
      "content_text": "Revisiting my advice to \"read more, write less code\" in the age of AI. While research remains crucial, developer workflows now include a vital new step: co-creating with AI tools before coding.",
      "date_published": "2025-03-02T12:00:00.000Z",
      "tags": [
        "Programming",
        "AI Tools",
        "Career Advice",
        "Claude Code",
        "Growth"
      ]
    },
    {
      "id": "https://kobi.kadosh.me/blog/write-less-read-more/",
      "url": "https://kobi.kadosh.me/blog/write-less-read-more/",
      "title": "Write Less Code, Read More: Career Advice For Programmers (And Everyone Else)",
      "summary": "We don't know what we don't know - You've got a task? Instead of going ahead and writing the code for it, try researching online.",
      "content_text": "We don't know what we don't know - You've got a task? Instead of going ahead and writing the code for it, try researching online.",
      "date_published": "2019-11-16T23:37:09.472Z",
      "tags": [
        "Programming",
        "Clean Code",
        "Career Advice",
        "Read More",
        "Growth"
      ]
    }
  ]
}