<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>~/notes</title><description>Notes from the build process — software, music, the occasional photo.</description><link>https://xweng.dev/</link><language>en-us</language><item><title>The question that cracked the bug</title><link>https://xweng.dev/posts/the-question-that-cracked-the-bug/</link><guid isPermaLink="true">https://xweng.dev/posts/the-question-that-cracked-the-bug/</guid><description>A morning of debugging with an AI pair-programmer, and the simple question — does this match production? — that turned a passing test into a deterministic reproducer.</description><pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A user reported that our AI-generated email drafts kept adopting the wrong voice. Someone would get an email from their insurance agent, and the auto-drafted reply would be written &lt;em&gt;as if the user were the insurance agent&lt;/em&gt;. The user’s correction in the feedback log said everything:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“The sender is the provider, I’m the customer, please revise.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I spent a morning debugging this with Claude as my pair-programmer. The story of how we eventually found the fix is, I think, a case study in how engineers should actually work with AI — not as an oracle that hands you answers, but as a fast collaborator whose premises you must constantly check.&lt;/p&gt;
&lt;h2&gt;The AI’s first move: an impressive reproducer test&lt;/h2&gt;
&lt;p&gt;I asked Claude to investigate the draft-generation pipeline and build a test that reproduced the bug. It did so quickly and well. Within a few turns I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A nicely structured integration test loading the captured feedback fixture&lt;/li&gt;
&lt;li&gt;An LLM-as-judge wrapper scoring the generated draft for role correctness (customer vs. provider voice, with structured JSON output)&lt;/li&gt;
&lt;li&gt;A monkeypatch to bypass database lookups so the test could run without infrastructure&lt;/li&gt;
&lt;li&gt;A parallelized 5-sample loop to handle the stochasticity of temperature 0.7&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I ran it. All five samples came back as clean customer-voice drafts. Zero role confusions.&lt;/p&gt;
&lt;p&gt;The test passed.&lt;/p&gt;
&lt;h2&gt;The AI’s theories for why the bug “vanished”&lt;/h2&gt;
&lt;p&gt;When I asked Claude why a bug the user had clearly experienced wasn’t reproducing, it generated a plausible list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Temperature non-determinism&lt;/strong&gt; — at temp 0.7, the same inputs produce different outputs every time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model drift&lt;/strong&gt; — the fixture might have been captured with a different model version&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implicit hints in the email&lt;/strong&gt; — the subject starting with “RE:” and the sender’s email domain (obviously an insurance provider) gave the LLM enough clues to figure out the roles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unlucky sampling at capture time&lt;/strong&gt; — we might have caught a rare misbehaving run&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of those theories was &lt;em&gt;plausible&lt;/em&gt;. Some were probably even partially true. Claude proposed running 20 samples, lowering temperature, pinning to a specific model version, crafting a more ambiguous test input.&lt;/p&gt;
&lt;p&gt;I didn’t like any of it. It all felt like patching around the fact that we weren’t actually seeing the bug.&lt;/p&gt;
&lt;h2&gt;The pivot&lt;/h2&gt;
&lt;p&gt;I asked one question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Can you double-check the test you wrote? Is it reproducing what we have in production?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the kind of question that is easy for a human and apparently not natural for AI. Claude audited its own test and produced a table that, in retrospect, should have been the first thing it built:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Production pipeline&lt;/th&gt;
&lt;th&gt;The test&lt;/th&gt;
&lt;th&gt;Match?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sender&lt;/td&gt;
&lt;td&gt;agent’s display name + email&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subject&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;RE: About &amp;lt;redacted policy type&amp;gt; insurance&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model / temperature&lt;/td&gt;
&lt;td&gt;open-weight model @ 0.7&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Body content&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;body.content&lt;/code&gt; from the email provider’s JSON (&lt;strong&gt;raw HTML&lt;/strong&gt;, 2000 then 1000 chars)&lt;/td&gt;
&lt;td&gt;fixture’s &lt;code&gt;body_preview&lt;/code&gt; (&lt;strong&gt;254 chars of clean plain text&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There it was. The production pipeline was handing the LLM up to 1000 characters of Microsoft Word / Outlook HTML — &lt;code&gt;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;meta&amp;gt;&amp;lt;style&amp;gt;@font-face{...}&amp;lt;/style&amp;gt;&lt;/code&gt; — and never reaching the actual email body. The test was handing the LLM a short, clean plain-text snippet — a one-line “thanks, see attached certificate” style acknowledgment. Of course the LLM could infer roles correctly from that. It had actual text to reason about.&lt;/p&gt;
&lt;p&gt;I pointed Claude at the fixture’s &lt;code&gt;full_text&lt;/code&gt; field (the real HTML the user’s inbox had stored) and told it to feed &lt;em&gt;that&lt;/em&gt; to the draft generator.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5/5 samples immediately confused roles.&lt;/strong&gt; Every draft was written in the voice of an insurance agent offering coverage:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“I’ve reviewed the details you sent regarding worker compensation coverage, and I’d be happy to help clarify the policy provisions…”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“We can help you assess coverage needs based on your business size, industry, and the states you operate in…”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The bug was now deterministic.&lt;/p&gt;
&lt;h2&gt;The fix was embarrassingly simple&lt;/h2&gt;
&lt;p&gt;Once we could see it, the root cause was obvious. The production code was doing this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;full_body = email_data[&apos;body&apos;][&apos;content&apos;]    # raw HTML from Graph API
item = {
    &apos;body_preview&apos;: full_body[:2000],        # 2000 chars of HTML
    ...
}
# Then later, in the prompt builder:
content = f&quot;Content:\n{body_preview[:1000]}&quot;  # another slice, still HTML
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first 1000 characters of any Outlook-authored email is CSS boilerplate. The LLM never saw the actual message. When the “body” is just &lt;code&gt;&amp;lt;style&amp;gt;@font-face{font-family:Calibri}...&lt;/code&gt;, and the subject is something generic about a policy type, the model does what it’s designed to do: invent a plausible-sounding reply. Since its training data is full of “insurance agent offering coverage” content, that’s what it produced. Not role confusion — hallucination with a consistent direction.&lt;/p&gt;
&lt;p&gt;The fix was three lines: strip HTML &lt;em&gt;before&lt;/em&gt; slicing, bump the cap up (now that we were showing real content, not CSS), and remove the redundant second slice in the prompt builder. The test dropped to 0/5 confusions. A second fixture we hadn’t tested yet — a &lt;em&gt;different&lt;/em&gt; bug report, about the draft “not understanding the context” — also dropped to 0/5 once we ran it through the same fix. Same root cause, different symptom.&lt;/p&gt;
&lt;h2&gt;The lesson I keep relearning&lt;/h2&gt;
&lt;p&gt;AI pair-programmers are fast, thorough, and surprisingly good at following instructions. What they are &lt;em&gt;not&lt;/em&gt; good at, at least not yet, is questioning the premises of a task. Claude happily built a beautiful reproducer test, generated multiple theories for why it didn’t reproduce the bug, and proposed increasingly elaborate mitigations — all inside a test whose relationship to production it had never examined.&lt;/p&gt;
&lt;p&gt;The question “does this match production?” took me five seconds to ask. It was the highest-leverage thing I did all morning.&lt;/p&gt;
&lt;p&gt;There is a tempting narrative that AI tools will replace the junior engineer and let senior engineers do “more important work.” The reality I keep running into is different. AI tools &lt;em&gt;amplify&lt;/em&gt; whichever direction you point them. If you point them at a plausible-but-wrong premise, they will build an impressive edifice on top of it. Senior engineering judgment — the instinct to ask “wait, is this even the right question?” — doesn’t get less important. It gets &lt;em&gt;more&lt;/em&gt;, because the cost of building on the wrong premise goes from “a few hours” to “a full afternoon of beautifully-structured work that solved the wrong problem.”&lt;/p&gt;
&lt;p&gt;The practical takeaway for anyone working this way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When AI tells you a bug doesn’t reproduce, don’t accept the first explanation. Ask whether the reproduction path matches production first.&lt;/li&gt;
&lt;li&gt;When AI hands you a “maybe it’s this, maybe it’s that” list of theories, that’s often a signal that the premise is wrong, not that the bug is genuinely mysterious.&lt;/li&gt;
&lt;li&gt;The questions that move things forward are usually simple. You don’t need to out-think the AI. You need to out-frame it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The final commit on this branch is 36 lines of production-code changes — strip HTML, extract TO/CC, add an identity block to the prompt. It took me a morning to write because the first few hours were spent on the wrong reproducer. That’s the shape of this work now. The AI is fast; the expensive part is making sure we’re asking it the right question.&lt;/p&gt;
</content:encoded><category>ai</category><category>debugging</category></item><item><title>Stop over-engineering your AI agent</title><link>https://xweng.dev/posts/stop-over-engineering-your-ai-agent/</link><guid isPermaLink="true">https://xweng.dev/posts/stop-over-engineering-your-ai-agent/</guid><description>We built an AI agent that searches across emails, Slack, Jira. Most of our &apos;improvements&apos; were making things worse. The fix had two halves: subtract intelligence that was noise, then encode the knowledge models can&apos;t derive.</description><pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We built an AI agent that searches across a user’s emails, Slack, Jira, and other channels to answer questions like “find the Acme project update” or “list all release versions.” The search was broken in ways we didn’t expect — and the fixes were counterintuitive.&lt;/p&gt;
&lt;p&gt;Every instinct said &lt;strong&gt;add more intelligence&lt;/strong&gt;: smarter classification, better query rewriting, self-grading loops, bigger models. Every one of those instincts was wrong.&lt;/p&gt;
&lt;p&gt;The real path had two parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Get out of the AI’s way.&lt;/strong&gt; Most of our “improvements” were making things worse. Removing them fixed more than adding them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Teach the AI what it can’t derive.&lt;/strong&gt; Once we stripped the pipeline down, we found failures that no amount of model capability could fix. Those needed human knowledge encoded as part of the system.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This post is about both halves.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Part 1: Get out of the AI’s way&lt;/h2&gt;
&lt;h3&gt;Wall #1: dense-only search doesn’t find what users actually search for&lt;/h3&gt;
&lt;p&gt;Users typed a client name — returned zero results. They typed a short project code — zero results. Dense vector search is great at semantic similarity but terrible at exact entity matching. A 3-letter abbreviation doesn’t embed well. Proper nouns don’t cluster near related content. And “Acme Corp” as a cosine similarity vector doesn’t match the actual email about Acme Corp as reliably as you’d expect.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The instinct:&lt;/strong&gt; train better embeddings. Fine-tune the model. Add query expansion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually worked:&lt;/strong&gt; add a boring keyword search alongside the semantic one. Fuse the results. This is hybrid search with &lt;a href=&quot;https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf&quot;&gt;Reciprocal Rank Fusion (RRF)&lt;/a&gt;, and every serious production system uses it. Dense retrieval and sparse (keyword) retrieval solve different problems. You need both.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;query → dense search + keyword search (parallel) → RRF fusion → results
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. No training, no fine-tuning, no fancy rewriters. Just two searches merged by rank.&lt;/p&gt;
&lt;h3&gt;Wall #2: pre-search LLM calls were making results worse&lt;/h3&gt;
&lt;p&gt;Our original pipeline looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;query → LLM call #1: classify the query type (1-2s)
      → LLM call #2: rewrite into 2-3 sentences (1-2s)
      → search → results
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It felt intelligent. It was actually terrible.&lt;/p&gt;
&lt;p&gt;The classifier would guess the query type (“this is a notification query”) and that guess would control downstream filtering. When it guessed wrong — which happened constantly on short or ambiguous queries — the filter threw out valid results.&lt;/p&gt;
&lt;p&gt;The rewriter was worse. Give it a client name and it would helpfully expand the query into something like &lt;em&gt;“update on the project, including progress, milestones, and team members involved.”&lt;/em&gt; The original keyword now lives inside a padded sentence instead of being the focused term the user typed. The embedding drifts. Keyword matching weakens. Results get worse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The instinct:&lt;/strong&gt; make the classifier smarter. Make the rewriter better. Add fallbacks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually worked:&lt;/strong&gt; delete both. Pass the user’s original query directly to hybrid search.&lt;/p&gt;
&lt;p&gt;Latency dropped from ~4-6s to ~2-3s. Zero-result failures went away. Quality went up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; every LLM call before retrieval is a place where your pipeline can add noise. If it isn’t provably adding signal, it’s subtracting it.&lt;/p&gt;
&lt;h3&gt;Wall #3: your tool output is for the LLM, not for humans&lt;/h3&gt;
&lt;p&gt;Search results were formatted to be readable: full previews, metadata fields, relevance scores, conversation IDs, the works. Roughly 300 tokens per result. Ten results per search.&lt;/p&gt;
&lt;p&gt;The agent would make two searches and suddenly its context was full of ~6,000 tokens of formatted noise. By the third search call, the model would start narrating its intentions (“Let me search more specifically…”) instead of actually making tool calls. It had lost coherence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The instinct:&lt;/strong&gt; give the LLM a bigger context window. Upgrade the model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What actually worked:&lt;/strong&gt; cut tool output from ~300 tokens per result to ~50. Stop sending conversation IDs and relevance percentages to the LLM. Keep just what’s needed to reason about the next step.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Before (~300 tokens per result)
[1] OUTLOOK: [repo] Release v0.4.0 (PR #332)
    Date: 2026-03-11 10:25
    Relevance Score: 98%
    Participants: github-actions[bot] &amp;lt;notifications@...&amp;gt;
    Content Preview:
    Summary: Bump project version from 0.3.2 to 0.4.0...
    Outlook Conversation ID: AAQkADE1YTA2Y2Q4...

# After (~50 tokens per result)
[src:332] (outlook) Release v0.4.0 (PR #332) | 2026-03-11 | From: github-actions
    Bump project version from 0.3.2 to 0.4.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The 6x reduction is the difference between the agent finishing its job and running out of coherence mid-response. Design tool output for the reader that’s consuming it — which, in an agentic system, isn’t you.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That’s the “get out of the way” part. Three changes. All subtractions. All improved the system.&lt;/p&gt;
&lt;p&gt;Now here’s where it gets interesting.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Part 2: teach the AI what it can’t derive&lt;/h2&gt;
&lt;p&gt;After stripping the pipeline down, we tested a harder query: &lt;strong&gt;“Find Alex’s phone number.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The number existed in the system. It lived in the email signature of a message from Alex. The agent searched “Alex phone number” — found emails mentioning phone numbers but not the actual digits. It searched “Alex Chen phone” — same thing. It tried six or seven variations. It eventually gave up.&lt;/p&gt;
&lt;p&gt;The number was right there. The agent couldn’t find it.&lt;/p&gt;
&lt;p&gt;Here’s why: &lt;strong&gt;the document that contained the phone number never used the word “phone.”&lt;/strong&gt; It was an email signature — just a name, a title, and some digits. The user’s query vocabulary and the document’s vocabulary had nothing in common. No amount of keyword search, embedding similarity, or query rewriting can bridge that gap. It’s the fundamental limit of lexical + semantic retrieval.&lt;/p&gt;
&lt;p&gt;This is where a human would instantly succeed. A person asked “find Alex’s phone number” would think: &lt;em&gt;“Phone numbers are in email signatures. Let me pull up an email from Alex and read the bottom.”&lt;/em&gt; That’s a two-step reasoning chain that has nothing to do with the words “phone number.”&lt;/p&gt;
&lt;p&gt;The AI doesn’t know this. Not because it’s dumb — because it can’t derive from first principles that phone numbers live in signatures. That’s human knowledge about how email works.&lt;/p&gt;
&lt;h3&gt;The instinct: wait for smarter models&lt;/h3&gt;
&lt;p&gt;We almost did this. “Once models get better at multi-hop reasoning, they’ll figure it out.”&lt;/p&gt;
&lt;p&gt;The problem: there will always be a next failure mode. Chasing the model capability curve is a losing race. And even if the next model reasoned through this specific case, it would fail on the next thing you didn’t anticipate.&lt;/p&gt;
&lt;h3&gt;The fix: encode the knowledge, keep the agent&lt;/h3&gt;
&lt;p&gt;We did two things. First, a simple tool — &lt;code&gt;get_communication_detail(id)&lt;/code&gt; — that fetches the full body of a specific communication. The compact format hides most details from the agent. This tool lets the agent drill down when the preview isn’t enough. Detail on demand.&lt;/p&gt;
&lt;p&gt;Second — and more importantly — we defined the reasoning chain as a &lt;strong&gt;skill&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
name: find-contact-info
triggers:
  - phone number
  - contact info
  - how to reach
---

## Steps
1. Search for the person across all channels
2. Search for emails sent BY that person
3. Fetch full content of top result to read the signature
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A skill is a declarative definition of a proven workflow. The steps use the same tools the agent already has, but they’re sequenced in a way the agent couldn’t reliably derive on its own. When the user asks about contact info, the system can invoke this skill instead of hoping the agent reasons through it.&lt;/p&gt;
&lt;p&gt;The skill isn’t hardcoded logic — it’s encoded knowledge. The tools still do the searching. The LLM still fills in adaptive parameters at each step. But the &lt;em&gt;order of operations&lt;/em&gt; — the human insight that “to find contact info you should look at emails from the person” — lives in the skill definition.&lt;/p&gt;
&lt;h3&gt;Why this matters more than it sounds&lt;/h3&gt;
&lt;p&gt;Skills are a bet on a different model of AI development. Instead of “AI that figures everything out,” it’s &lt;strong&gt;“AI that accumulates learned workflows.”&lt;/strong&gt; When the system notices a user manually performing a pattern — search a person, fetch an email, extract contact info — it can propose turning that pattern into a skill. The user approves. The next time anyone hits that problem, the skill handles it.&lt;/p&gt;
&lt;p&gt;This is the loop that makes the agent get better over time without needing a better model underneath. The model’s job is to handle the long tail — open-ended reasoning, novel situations, conversations. The skills handle the repeatable patterns that humans have already solved.&lt;/p&gt;
&lt;p&gt;This is also why we resisted implementing the contact lookup as a hardcoded Python tool. A tool is opaque and one-off. A skill is portable, discoverable, and composable. Future you can read the skill definition and know exactly what it does. Future users can propose new skills for patterns the system missed.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The two-part rule&lt;/h2&gt;
&lt;p&gt;Looking back, every improvement fit into one of two categories:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Subtractions from the pipeline:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dense-only search → hybrid search (delete the assumption that semantic is enough)&lt;/li&gt;
&lt;li&gt;Pre-search LLM calls → direct query (delete intelligence that was noise)&lt;/li&gt;
&lt;li&gt;Verbose tool output → compact format (delete metadata the LLM can’t use)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Additions of human knowledge:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detail-on-demand tool (humans know “read the full thing” is sometimes the answer)&lt;/li&gt;
&lt;li&gt;Contact lookup skill (humans know signatures contain contact info)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The instinct at every turn was to do the opposite — add intelligence to the pipeline, hope the model figures out the chains. That instinct was wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; first, delete everything in your pipeline that isn’t provably helping. Then, for the failures that remain, ask whether the AI is &lt;em&gt;missing knowledge a human has&lt;/em&gt;. If yes, encode it — as a tool, a skill, or structured data. Don’t wait for a smarter model.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;A practical checklist&lt;/h2&gt;
&lt;p&gt;If you’re building an agentic search or RAG system, check these in order:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Subtractions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do you have both keyword and semantic retrieval fused together? (If no, add it.)&lt;/li&gt;
&lt;li&gt;Are there LLM calls before retrieval that classify or rewrite the query? (Consider removing.)&lt;/li&gt;
&lt;li&gt;Is your tool output designed for human readability or LLM consumption? (Cut everything the LLM doesn’t need.)&lt;/li&gt;
&lt;li&gt;Does your agent have enough context budget to iterate 3-5 times without losing coherence? (If no, reduce per-result tokens.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Additions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When your agent fails on a task, ask: is this a vocabulary mismatch or missing world knowledge? (Those need encoded knowledge, not smarter prompts.)&lt;/li&gt;
&lt;li&gt;Does your agent have a way to drill down from summaries to full content when needed? (Add a detail-fetch tool.)&lt;/li&gt;
&lt;li&gt;Can you extract multi-step patterns from user interactions and encode them as skills? (This is where the compounding returns are.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The instinct is always to make the AI smarter. The practice is to make the system around it smaller and more specific.&lt;/p&gt;
</content:encoded><category>ai</category><category>search</category><category>agents</category></item></channel></rss>