<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
  <title>Zhao Yu — Engineering Notes</title>
  <link>https://zhaoyu.io/blog</link>
  <description>Notes on edge performance, LLM streaming interfaces, agent reliability, and shipping at scale.</description>
  <language>en-us</language>
  <atom:link href="https://zhaoyu.io/rss.xml" rel="self" type="application/rss+xml" />
  <lastBuildDate>Sun, 30 Aug 2026 00:00:00 GMT</lastBuildDate>
  <item>
    <title>Your Checks Are Lying to You</title>
    <link>https://zhaoyu.io/blog/your-checks-are-lying-to-you</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/your-checks-are-lying-to-you</guid>
    <pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate>
    <description>Run this in any repo with a gating script: ./check.sh | head -40; echo $?. If the gate fails, you still see 0, because without pipefail, $? is head&apos;s exit status, not the gate&apos;s. I measured it on a real gate in my own stack: run directly, exit 1; piped through a pager for readability, exit 0. The ga</description>
    <category>Reliability</category>
    <category>Verification</category>
    <category>AI Engineering</category>
    <content:encoded><![CDATA[<p>Run this in any repo with a gating script: <code>./check.sh | head -40; echo $?</code>. If the gate fails, you still see <code>0</code>, because without <code>pipefail</code>, <code>$?</code> is <code>head</code>'s exit status, not the gate's. I measured it on a real gate in my own stack: run directly, exit 1; piped through a pager for readability, exit 0. The gate was correct. The call site threw its verdict away, and everything downstream recorded a pass.</p>
<p>I call this a fail-open check: a check whose “did not run” is indistinguishable from “passed.” Over one month I logged twenty-four named instances in one small stack of scripts, CI jobs, and agent pipelines. Not, I think, because the stack is unusually bad, but because I gave the class a name and started writing instances down.</p>
<p><h2>Green is ambiguous by construction</h2></p>
<p>Three from the same month:</p>
<p><ul><li>A wrapper around a nightly embedding job logged success unconditionally and discarded stderr. The job had been dying mid-batch, at exit 0, behind months of healthy-looking logs.</li><li>A style lint reported clean on its first run because a comment line broke the grep over its own pattern file. Zero banned patterns were matched against anything. The check had not passed; it had not run. The recorded outcome was identical either way.</li><li>A link checker printed every finding to stdout and exited 0. Three downstream consumers read the exit code. None read the text.</li></ul></p>
<p><strong>The absence of a result gets recorded as the presence of a lesser one.</strong> A checker has three honest outcomes: found problems, found nothing after actually looking, and could not look. Most tooling gives it two exit states, so “could not look” gets folded into whichever side the error handling happens to land on. When it lands on green, the result is strictly worse than no check at all: a fail-open check produces a false record that closes the question, and the record is what stops anyone from looking again. A missing check, by contrast, is a visible gap. Anyone who asks “what verifies this?” finds nothing and knows the question is open.</p>
<p><h2>The artifact is the camouflage</h2></p>
<p>These survive for months because the failure produces an artifact, and the artifact conceals it. A test suite in that same stack wrote its “real build” case to the shipped deliverable instead of a fixture, which makes it an unreviewed production change wearing a green checkmark. Run to confirm an unrelated change was safe, it passed, and in passing silently rebuilt the deliverable against different inputs, evicting real content. Nothing failed, nothing warned; the output was a plausible, well-formed, freshly-dated file.</p>
<p>A curated export, built on a cloud machine, sat in the repo vouching for its build script, but an artifact is not evidence its producer runs <em>here</em>: the script used <code>tomllib</code> (Python 3.11+) against a local 3.9.6 and had never once executed locally. Dead at import. Its own test suite failed 9 of 14 cases the same way, and nobody read past the “5 passed” to ask out of how many.</p>
<p>This is not a tooling quirk; it is how oversight fails generally, because headcount measures effort applied, not failures found. <a href='https://www.berkshirehathaway.com/letters/2008ltr.pdf' target='_blank' rel='noopener'>Buffett's 2008 shareholder letter</a> describes OFHEO, a regulator created to oversee exactly two companies, staffed with more than a hundred people with no other assignment. It published a glowing review of its own first decade and, on Buffett's telling, entirely missed that both companies had spent years misstating their earnings.</p>
<p>The ranking error underneath is universal: crashes and blank fields at the bad end of the severity scale, mostly-correct output at the good end. Ranked by expected damage, the order inverts. A crash routes immediately to a human who now knows something is wrong; the output that looks complete routes to acceptance, consuming exactly the attention budget that would have caught it.</p>
<p><h2>The sneakiest variant: the check that cannot see the defect</h2></p>
<p>The instances above fail by not running or not examining. The subtler ones run perfectly, against a scope that excludes the defect.</p>
<p>My own site's CI config carried <code>.github/**</code> in its <code>paths-ignore</code> list, alongside <code>**.md</code> and <code>LICENSE</code>. Reasonable on its face: editing a workflow file doesn't change the application. It meant the one change class able to delete a job, loosen a trigger, or drop a required check was the only class that merged with nothing run against it; a weakened gate and an intact one leave identical clean history, so nothing in the record would ever have surfaced it.</p>
<p>The same scope failure arises with nobody configuring it: a frontmatter parser whose regex read only the <em>first</em> item of every YAML block list computed a health metric over a universe 16% smaller than it claimed, and printed roughly 78% either way. A list API returned the first 20 items with <code>has_more: true</code>, then returned the identical page when handed its own continuation cursor. Nothing consumed the one field that contradicted the roster, and I nearly filed a report that four scheduled jobs had vanished. They were on page two.</p>
<p>And <code>git log --since='7 days ago' --diff-filter=A --name-only</code> on a fresh CI clone reported 1,618 files added that week; the true figure was 32. The clone was shallow, so its boundary commit appeared to add the entire repo at once.</p>
<p>The design-time test for all of these: <strong>if this control were wrong, what would tell me?</strong> If the honest answer is “the same green output I get when it is right,” you do not have a check so much as a green light wired to the wall.</p>
<p><h2>The field guide</h2></p>
<p>Rules that have held up, each earned by at least one instance above:</p>
<p><ol><li><strong>Three outcomes, never two.</strong> Passed / failed / could-not-run, and could-not-run must be loud. This applies per item, too: an input your checker cannot parse must land in <em>undetermined</em>, never in a benign bucket like “no findings.”</li><li><strong>Assert the condition, not the reaching of the line.</strong> Emit success only after verifying the thing you claim, and match the invariant's shape to what consumers depend on. An embedding backfill satisfied its count-shaped post-condition (pending reached zero) while writing the same vector for forty different documents. A count cannot see a content defect.</li><li><strong>Every emitted marker needs a named consumer.</strong> A <code>has_more</code> flag, a truncation marker, a warning line: a signal nobody reads is worse than no signal, because it looks like coverage.</li><li><strong>Test the check against known-dirty input, from the position it actually runs in.</strong> Both checks I wrote to catch this class missed their own motivating cases on first run; only fixtures with known answers caught it. And a detector validated on a laptop can still be unrunnable at its scheduled call site; testing the detector is not testing the detection.</li><li><strong>A control's scope must include the control.</strong> Whatever decides what gets checked (a path filter, a sampling rule, a pagination default) is the best-hidden place for this defect.</li><li><strong>Spend verification on the fix.</strong> The patch that closes a fail-open is itself fresh, unverified check code, written under pressure with attention on the old defect. In my log, remediation builds are where new instances concentrate.</li></ol></p>
<p>“So write more checks” is the wrong response. The fix for this class is a contract on the checks you have, not a headcount of new detectors, and every new detector is new surface for the same defect. “We have a runbook for this” is worse: a written policy with no enforcing mechanism is not a missing check, it is a fail-open one, occupying the slot where verification would report while readers take its existence as evidence the boundary holds. Often the strongest move is not a better check at all: restructure so the bad output cannot be produced. <a href='https://lethain.com/agents-as-scaffolding/' target='_blank' rel='noopener'>Will Larson describes</a> catching an agent mis-forwarding alerts and, rather than adding an eval he knew would work, moving the filtering into a deterministic script so the agent never sees what it might mishandle. The check would have left the failure mode alive behind a gate; the restructure removed it from the system.</p>
<p>The same month produced two defects I deliberately did not log: they failed <em>closed</em>. Costly, but they never certified a falsehood, and logging them anyway would have blurred what the ledger measures.</p>
<p><strong>A clean report from a check that cannot say “I could not run” is not evidence of anything. Build checks that cannot fail quietly, or their green eventually becomes the thing that hides the failure they were hired to catch.</strong></p>]]></content:encoded>
  </item>
  <item>
    <title>Agents Re-Derive Judgment You Already Paid For</title>
    <link>https://zhaoyu.io/blog/agents-re-derive-judgment-you-already-paid-for</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/agents-re-derive-judgment-you-already-paid-for</guid>
    <pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate>
    <description>Every agent session starts from a blank context window, so it re-derives judgment you already paid for. You settle a tradeoff in March; in August an agent proposes the option you rejected, reasoning its way there with full confidence and none of the history. The usual diagnosis is that the model nee</description>
    <category>Agent Architecture</category>
    <category>AI Engineering</category>
    <category>Retrieval</category>
    <content:encoded><![CDATA[<p>Every agent session starts from a blank context window, so it re-derives judgment you already paid for. You settle a tradeoff in March; in August an agent proposes the option you rejected, reasoning its way there with full confidence and none of the history. The usual diagnosis is that the model needs memory, and the usual fix is a longer prompt: paste the standing decisions at the top and hope attention holds. Both miss the mechanism: the judgment that bears on a question is rarely lexically close to it. <strong>Risk parity distributing risk rather than capital and capacity-aware load balancing distributing work by headroom share no keywords and the same shape.</strong> BM25 cannot cross that gap, because it scores the words and the transferable part was never in the words.</p>
<p>So the vault of 1,700 claims stays in git, and OB1 projects it into Supabase behind MCP, so Claude, ChatGPT, and my phone all retrieve the same judgment. A claim is one assertable proposition, named by a filename that states it, hand-typed as claim, pattern, tension, or anti-pattern. <strong>Nothing derives that type, which is why it carries judgment</strong>: a thing is an anti-pattern because someone decided it was. Retrieval embeds the query, ranks with pgvector, then expands one hop along the wiki-links, so a claim reached by a <code>parallels</code> edge surfaces even when its similarity is low; 2,777 of the 8,281 edges cross domains, the ones worth the hop. On 15 August a reduce pass was about to generalize a second claim about legibility inverting felt value. Retrieval returned <code>prevented-loss-invisibility</code>, already established on an external source, and the output became an enrichment instead of a duplicate. <strong>The judgment you have to remember to look up is judgment you do not have.</strong></p>]]></content:encoded>
  </item>
  <item>
    <title>Reinforcement Anchors Beat Emphasis: Compressing a Production System Prompt</title>
    <link>https://zhaoyu.io/blog/reinforcement-anchors-beat-emphasis-in-system-prompts</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/reinforcement-anchors-beat-emphasis-in-system-prompts</guid>
    <pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate>
    <description>Production system prompts bloat by a predictable mechanism. The model does something wrong, so you add an instruction telling it not to. When that doesn&apos;t stick, you add a more forcefully worded one. NEVER do X. In capitals. With exclamation points. Salesforce found this same escalation across 20,00</description>
    <category>System Prompt Architecture</category>
    <category>LLM Mechanics</category>
    <category>AI Engineering</category>
    <content:encoded><![CDATA[<p>Production system prompts bloat by a predictable mechanism. The model does something wrong, so you add an instruction telling it not to. When that doesn't stick, you add a more forcefully worded one. <code>NEVER do X.</code> In capitals. With exclamation points. <a href='https://www.salesforce.com/news/stories/ai-lessons-building-enterprise-agents/' target='_blank' rel='noopener'>Salesforce found this same escalation</a> across 20,000 enterprise agent deployments, and found it does not work: <strong>an LLM does not process typographic emphasis the way a human reader does.</strong> Capitalization and punctuation are just more tokens, not a signal that reliably overrides competing considerations during generation. So the instruction fails again, another one gets appended, and the prompt accumulates. I took one production prompt from roughly 4,000 words to roughly 1,300 and it got <em>more</em> reliable, not less. That is only surprising if you believed the length was buying compliance in the first place.</p>
<p>What actually carries a constraint is position, not volume. Attention has a measurable front-and-back bias: <a href='https://arxiv.org/abs/2104.09864' target='_blank' rel='noopener'>RoPE</a>, the positional encoding most current models use, decays in a way that puts tokens far from both ends of the sequence into a systematically lower-attention zone, and <a href='https://arxiv.org/abs/2307.03172' target='_blank' rel='noopener'>retrieval accuracy for a fact placed mid-context drops by more than 20 points</a> compared to the same fact at the start or end. A constraint's <em>location</em> is load-bearing in a way its wording is not. So the rewrite was tiered rather than shortened: identity and non-negotiable constraints at the edges, task detail in the middle, and reinforcement anchors placed to survive attention decay across a long multi-turn conversation rather than only the first exchange. The other half of the compression was subtraction. Salesforce's corollary is that <strong>anything you can draw as a flowchart belongs in code, not in a prompt</strong>, because code executes identically every time and no wording does. A context window is an attention budget for the run, not a junk drawer for everything that once went wrong.</p>]]></content:encoded>
  </item>
  <item>
    <title>The Agent Run Is the New Unit of Work, and Reviewing It Is Management</title>
    <link>https://zhaoyu.io/blog/the-agent-run-is-the-new-unit-of-work</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/the-agent-run-is-the-new-unit-of-work</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>The genuinely new moment in AI-assisted engineering is not the chat answer; you watched that get produced and judged it in real time. It&apos;s when an agent comes back with finished work: it read the folder, edited the files, ran the commands, and declares itself done. You did not do the work and did no</description>
    <category>Agent Architecture</category>
    <category>Engineering Management</category>
    <category>AI Engineering</category>
    <content:encoded><![CDATA[<p>The genuinely new moment in AI-assisted engineering is not the chat answer; you watched that get produced and judged it in real time. It's when an agent comes back with <em>finished work</em>: it read the folder, edited the files, ran the commands, and declares itself done. You did not do the work and did not watch every step, so you cannot know which assumptions it made or which shortcut it took because the shortcut made the output look cleaner. The only question left is: <strong>is it real?</strong> The first time this happens it feels like magic. The tenth time it feels like management, because that is what it is: supervising labor you did not perform. I manage a direct team of ten and co-lead a rebuild across three teams, and the skills that job demands (scoping delegation, setting a review bar, calibrating trust per worker) are now individual-contributor skills too.</p>
<p>Management needs a unit of account, and session-level thinking is the wrong one. The right unit is the <strong>agent run</strong>: it begins at delegation, contains the tool calls, branches, and corrections, and ends in acceptance or rejection. That framing makes the work measurable: completion rate, correction rate, and whether your approval gates ever actually reject anything (a gate that always approves is not a control, it's theater). It also surfaces a free asset: every correction you make to agent output is a labeled evaluation you wrote by acting, the natural test set for the next run. This is the same discipline as my receipts rule: <code>done</code> without an attached artifact is self-attestation by the party most motivated to claim success. Getting the machine to do the work is the easy part now. <strong>Deciding the work is trustworthy is the job.</strong></p>]]></content:encoded>
  </item>
  <item>
    <title>Agents Degrade Quietly: Maintenance Is Where the Value Compounds</title>
    <link>https://zhaoyu.io/blog/agents-degrade-quietly-maintenance-is-where-value-compounds</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/agents-degrade-quietly-maintenance-is-where-value-compounds</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>Building an agent produces a visible artifact (there was nothing, now there is a working agent), so it reads as progress. Maintaining one produces no artifact; at best, nothing happens. So effort flows to building, and the felt value inverts the real value. A well-built agent nobody maintains degrad</description>
    <category>Agent Architecture</category>
    <category>Reliability</category>
    <category>Engineering Management</category>
    <content:encoded><![CDATA[<p>Building an agent produces a visible artifact (there was nothing, now there is a working agent), so it reads as progress. Maintaining one produces no artifact; at best, nothing happens. So effort flows to building, and the felt value inverts the real value. A well-built agent nobody maintains degrades on a schedule: its context sources go stale, its permissions drift wider than its job, its instructions calcify into a patch pile. A modestly-built agent someone reviews weekly <strong>compounds</strong>: each pass prunes a failure mode and sharpens the job. This is the oldest lesson in operations wearing a new costume: prevented loss is invisible, which is why nobody celebrates the on-call review that kept the incident from existing.</p>
<p>The corrective is ownership, and it decomposes into four responsibilities I now require for any agent near production work. <strong>Define the job narrowly</strong>: a vague agent is an unowned agent waiting to happen. <strong>Curate the diet</strong>: what it reads, which examples it learns from, including rejected outputs so it learns what <em>not</em> to do. <strong>Manage permissions proportional to stakes</strong>: draft-only and write access are different categories, and write access is earned inside a narrow job, not granted because a demo looked good. And <strong>run the review loop</strong>, where one-off failures get fixed at the output level but recurring failures get fixed at the system level. Team agents fail by tragedy of the commons (the pain is collective, the maintenance is nobody's job), so the owner follows the work. <strong>An agent is not a feature you ship. It is a service you operate.</strong></p>]]></content:encoded>
  </item>
  <item>
    <title>Spec Quality Is the Bottleneck Now, Not Implementation Speed</title>
    <link>https://zhaoyu.io/blog/spec-quality-is-the-bottleneck-not-implementation-speed</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/spec-quality-is-the-bottleneck-not-implementation-speed</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>The industry is measuring AI-assisted development with the wrong unit of analysis. Code-generation speed is the vanity metric; the METR result everyone cites (experienced developers who felt 20% faster while measuring slower) isn&apos;t evidence that AI doesn&apos;t work, it&apos;s evidence that implementation spe</description>
    <category>AI Engineering</category>
    <category>Agent Architecture</category>
    <category>Specification</category>
    <content:encoded><![CDATA[<p>The industry is measuring AI-assisted development with the wrong unit of analysis. Code-generation speed is the vanity metric; <a href='https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/' target='_blank' rel='noopener'>the METR result everyone cites</a> (experienced developers who <em>felt</em> 20% faster while measuring slower) isn't evidence that AI doesn't work, it's evidence that implementation speed was never the constraint. When agents can produce working code from any sufficiently precise description, the bottleneck moves upstream to the description itself. <a href='https://www.strongdm.com/blog/the-strongdm-software-factory-building-software-with-ai' target='_blank' rel='noopener'>StrongDM's autonomous pipeline</a> runs on nearly 6,000 lines of <a href='https://github.com/strongdm/attractor' target='_blank' rel='noopener'>public behavioral specification</a>, and that corpus, not the generated code, is the engineering artifact. <strong>The specification becomes the primary artifact; the codebase is a derivative</strong>, closer to a build output than to source.</p>
<p>Building production systems with Cursor and Claude Code has restructured where my own hours go. My leverage stopped correlating with how fast I can type and started correlating with how precisely I can state three things: the goal, the boundary, and what "done" has to prove. The human stays at the two endpoints, specification in and satisfaction judgment out, and everything between is increasingly the machine's. This also explains why AI amplifies experts instead of equalizing them: it equalizes execution speed, but execution was already cheap. What it amplifies is specification quality, and specification quality is a direct function of domain depth. If the agent keeps disappointing you, the uncomfortable first question is no longer about the model. It's whether you actually specified the thing you wanted.</p>]]></content:encoded>
  </item>
  <item>
    <title>Agent Failures Are Loop Failures, Not Intelligence Failures</title>
    <link>https://zhaoyu.io/blog/agent-failures-are-loop-failures</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/agent-failures-are-loop-failures</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>Every agent failure I&apos;ve debugged this year decomposes the same way. The agent didn&apos;t lack intelligence. The loop lacked definition. It wandered out of scope because no boundary was stated. It &quot;finished&quot; without finishing because nothing defined what done has to prove. Two agents double-executed the</description>
    <category>Agent Architecture</category>
    <category>Reliability</category>
    <category>Distributed Systems</category>
    <content:encoded><![CDATA[<p>Every agent failure I've debugged this year decomposes the same way. The agent didn't lack intelligence. The loop lacked definition. It wandered out of scope because no boundary was stated. It "finished" without finishing because nothing defined what done has to prove. Two agents double-executed the same task because nothing marked it claimed. These are Tuesday failures, and none of them are fixed by a smarter model, because <strong>smartness cannot supply a fact that was never specified</strong>. A run an agent can actually be held to has five parts: a goal, a boundary, tools, artifacts, and receipts. Miss one and you haven't delegated work. You've made a wish.</p>
<p>The good news: distributed systems solved these coordination problems decades ago, we just have to notice the mapping. A visible <code>CLAIMED</code> state on a task is a lease, revalidated when the worker returns. "Done" without an attached receipt is self-attestation by the party with the strongest incentive to declare success, so the receipt (the diff, the test run, the artifact link) is non-negotiable, the same way you require an acknowledgement instead of trusting a fire-and-forget write. And the issue tracker you already run is the natural control plane: it has owners, statuses, comments, links, and history built in. <strong>Reliability is engineered into the loop, not summoned from the model.</strong> Make the loop less ambiguous before you ask for a smarter agent.</p>]]></content:encoded>
  </item>
  <item>
    <title>Why I Made This Site Readable by Machines, Not Just Humans</title>
    <link>https://zhaoyu.io/blog/why-i-made-this-site-readable-by-machines-not-just-humans</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/why-i-made-this-site-readable-by-machines-not-just-humans</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>A site now has two kinds of reader, and only one of them renders a page. Crawlers, retrieval pipelines, and agents pull the raw document straight into a context window and answer out of it, so a site built only for a person scrolling and reading is serving half of its actual audience. I added the ot</description>
    <category>AI Engineering</category>
    <category>SEO</category>
    <category>Structured Data</category>
    <content:encoded><![CDATA[<p>A site now has two kinds of reader, and only one of them renders a page. Crawlers, retrieval pipelines, and agents pull the raw document straight into a context window and answer out of it, so a site built only for a person scrolling and reading is serving half of its actual audience. I added the other half: an <code>llms.txt</code> at the site root (a plain-text summary of the site and the work behind it, structured for a language model's context window rather than a browser's rendering engine), JSON-LD <code>Person</code> schema on every page, and real canonical URLs for these notes at <code>/blog/{slug}</code> instead of leaving them buried as anchors inside one long scrolling page. Same content, now individually addressable, cacheable, and citable.</p>
<p>The more interesting find while doing this wasn't a feature, it was a bug. My static-site adapter's SPA fallback page and the prerendered root route both wanted the filename <code>index.html</code>, and the fallback was winning the write, silently replacing the real homepage (title, description, Open Graph tags, all of it) with an empty shell at build time. Every crawler and every link preview had been getting nothing. The fix was a one-line rename, but the lesson generalizes: <strong>a static site's build output is not implied by its source, so verify what actually ships</strong>, especially at the config layer nobody re-reads after initial setup.</p>]]></content:encoded>
  </item>
  <item>
    <title>The Three Tiers of Using AI, and Why Only Two of Them Still Differentiate You</title>
    <link>https://zhaoyu.io/blog/the-three-tiers-of-using-ai-and-why-only-two-matter-now</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/the-three-tiers-of-using-ai-and-why-only-two-matter-now</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>There&apos;s a real difference between using AI as a faster typist (autocomplete, chat-assisted edits, &quot;fix this bug for me&quot;) and delegating a bounded unit of work to an agent that plans, executes across multiple files, and hands you a diff to review. The first tier is now table stakes; every engineer I </description>
    <category>AI Engineering</category>
    <category>Agent Architecture</category>
    <category>Career</category>
    <content:encoded><![CDATA[<p>There's a real difference between using AI as a faster typist (autocomplete, chat-assisted edits, "fix this bug for me") and delegating a bounded unit of work to an agent that plans, executes across multiple files, and hands you a diff to review. The first tier is now table stakes; every engineer I work with has a model open in a side pane, and it stopped being a differentiator the moment it became the default. The tiers above it, an agent working a scoped task end-to-end or several agents running in parallel with their own permission boundaries, are where the actual leverage still lives, because almost nobody has restructured how they delegate work to get there.</p>
<p>As the person who sets AI-adoption standards for my org, I spend almost none of my time on prompting technique. I spend it on the guardrails: what an agent can touch unsupervised, what requires a human review gate before it ships, and what "done" has to prove before I believe it. This site's agent-readable rewrite was built the same way: the notes, the structured data, and the build-output bug above were scoped to specific files, verified against the existing type-check, lint, test, and build gates before anything shipped, with the plan surfaced for review rather than pushed silently. <strong>"I use AI" stopped being the differentiator. Whether you can hand an agent a boundary and a review bar, instead of still typing every line yourself, is the one that's left.</strong></p>]]></content:encoded>
  </item>
  <item>
    <title>Building with AI: The Compound Advantage</title>
    <link>https://zhaoyu.io/blog/building-with-ai-the-compound-advantage</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/building-with-ai-the-compound-advantage</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>I built most of this site through Claude Code as a deliberate workflow: the components, the type errors that blocked deploys, and the notes themselves all went through the same loop of prompting and review. The obvious claim to make about that is speed, and the best available evidence does not suppo</description>
    <category>AI Engineering</category>
    <category>Productivity</category>
    <category>Meta</category>
    <content:encoded><![CDATA[<p>I built most of this site through Claude Code as a deliberate workflow: the components, the type errors that blocked deploys, and the notes themselves all went through the same loop of prompting and review. The obvious claim to make about that is speed, and the best available evidence does not support it. <a href='https://arxiv.org/abs/2507.09089' target='_blank' rel='noopener'>METR ran a randomized controlled trial on sixteen experienced open-source developers across 246 real tasks in their own repositories</a> and measured them 19% <em>slower</em> with AI tools than without. Those developers had forecast a 24% speedup before they started, and once the work was done they estimated they had been 20% faster.</p>
<p>What changed for me was the filter. When a first version costs little enough, an idea stops being killed at the worth-building stage and starts being killed by contact with something running, and the second filter is both later and far more informative, because a running version answers questions a plan cannot. The compounding lives in the attempt count rather than in any single task, which is exactly what a stopwatch on one task cannot see: it measures the task in front of it and says nothing about how many got tried. METR's own <a href='https://metr.org/blog/2026-02-24-uplift-update/' target='_blank' rel='noopener'>February 2026 update</a> reports that developers are likely more sped up now than in early 2025, while selection effects leave the size of that increase uncertain. I have no measured attempt count of my own to put against theirs. <strong>Count the things you tried, not the speed you felt.</strong></p>]]></content:encoded>
  </item>
  <item>
    <title>The Front End Is a Distributed System, Starting with the URL</title>
    <link>https://zhaoyu.io/blog/the-url-is-the-source-of-truth</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/the-url-is-the-source-of-truth</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>Filter a dashboard, refresh the page, and the filter is gone. Send the link to a colleague and they see a different view. The usual diagnosis is that the state was never persisted, and the usual fix is a client store, which handles the reload and misses the mechanism. Holding that filter in the URL </description>
    <category>Architecture</category>
    <category>Distributed Systems</category>
    <category>State Management</category>
    <content:encoded><![CDATA[<p>Filter a dashboard, refresh the page, and the filter is gone. Send the link to a colleague and they see a different view. The usual diagnosis is that the state was never persisted, and the usual fix is a client store, which handles the reload and misses the mechanism. Holding that filter in the URL is single-leader replication: <strong>one authoritative location owns the value, every view is a follower, and rendering is a read.</strong> Holding it in a store, plus the derived copies other components keep, is multi-leader replication. <em>Designing Data-Intensive Applications</em> takes up multi-leader setups inside its replication chapter and calls write conflicts their biggest problem, which is where the difficulty lands. You get concurrent writers with no total order between them, and no log to replay when the copies disagree.</p>
<p>The browser sits behind a network nobody controls, so a request that times out has not necessarily failed, and a payment the reader submits twice still has to land once. The IETF's httpapi working group took an <a href='https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/' target='_blank' rel='noopener'><code>Idempotency-Key</code> header draft</a> to revision 07 across four years and let it expire there, which says more about the problem than a finished standard would. What it encodes is that a client can repeat a request without the server applying the work twice. Rendering at the edge is a blast-radius decision before it is a performance one, and every time I have watched a region degrade during a market event, what mattered was that traffic routed around it instead of queueing behind it. None of this is new work. It is replication and failure domains, arriving in a codebase where nobody uses those words. <strong>You cannot reuse a solved problem you do not recognize.</strong></p>]]></content:encoded>
  </item>
  <item>
    <title>Decoupling State from Render in LLM Streaming</title>
    <link>https://zhaoyu.io/blog/decoupling-state-from-render-in-llm-streaming</link>
    <guid isPermaLink="true">https://zhaoyu.io/blog/decoupling-state-from-render-in-llm-streaming</guid>
    <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
    <description>The naive way to build a streaming AI interface is to pipe a Server-Sent Events stream straight into a React state setter: a chunk arrives, setState(prev =&gt; prev + chunk) fires, the component re-renders. At sub-50ms token intervals that is twenty or more reconciliation passes a second, each one walk</description>
    <category>React Performance</category>
    <category>HCI</category>
    <category>60fps</category>
    <content:encoded><![CDATA[<p>The naive way to build a streaming AI interface is to pipe a Server-Sent Events stream straight into a React state setter: a chunk arrives, <code>setState(prev => prev + chunk)</code> fires, the component re-renders. At sub-50ms token intervals that is twenty or more reconciliation passes a second, each one walking the tree to diff a string that grew by a few characters. The frame budget makes the arithmetic unforgiving. Sixty frames per second leaves 16ms per frame, and <a href='https://web.dev/articles/rail' target='_blank' rel='noopener'>the browser claims roughly 6ms of that for its own rendering work</a>, so application code has about 10ms to do everything else. A pass that overruns the budget does not render late. The frame is dropped.</p>
<p>The fix is to stop treating arrival and display as the same event. I buffer incoming chunks in a mutable <code>useRef</code>, which accepts writes synchronously without scheduling anything, then flush to state on a <code>requestAnimationFrame</code> tick, so the DOM is written at most once per frame and only when the browser is about to paint. Throughput and render frequency come apart: the stream arrives as fast as the model emits, and the interface still paints on a steady cadence. This separation is older than React and older than LLMs, since a game loop makes the same split between simulation and draw. <strong>When the producer is faster than the consumer, the answer is a buffer and a clock, not a faster consumer.</strong></p>]]></content:encoded>
  </item>
</channel>
</rss>
